Initial commit

This commit is contained in:
js6pak committed 2020-12-22 19:45:06 +01:00
commit 6e9cece706
44 files changed
+3673

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
[*]
end_of_line = lf
insert_final_newline = true
+7
View File
@@ -0,0 +1,7 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
.idea
work
+76
View File
@@ -0,0 +1,76 @@
<Project>
<ItemGroup>
<!-- Among Us -->
<Reference Include="Assembly-CSharp">
<HintPath>$(AmongUs)\BepInEx\unhollowed\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Hazel">
<HintPath>$(AmongUs)\BepInEx\unhollowed\Hazel.dll</HintPath>
<Private>False</Private>
</Reference>
<!-- BepInEx -->
<Reference Include="0Harmony">
<HintPath>$(AmongUs)\BepInEx\core\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="BepInEx">
<HintPath>$(AmongUs)\BepInEx\core\BepInEx.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="BepInEx.IL2CPP">
<HintPath>$(AmongUs)\BepInEx\core\BepInEx.IL2CPP.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="SemVer">
<HintPath>$(AmongUs)\BepInEx\core\SemVer.dll</HintPath>
<Private>False</Private>
</Reference>
<!-- IL2CPP Unhollower -->
<Reference Include="UnhollowerBaseLib">
<HintPath>$(AmongUs)\BepInEx\core\UnhollowerBaseLib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Il2Cppmscorlib">
<HintPath>$(AmongUs)\BepInEx\unhollowed\Il2Cppmscorlib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Il2CppSystem.Core">
<HintPath>$(AmongUs)\BepInEx\unhollowed\Il2CppSystem.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<!-- UnityEngine -->
<Reference Include="UnityEngine">
<HintPath>$(AmongUs)\BepInEx\unhollowed\UnityEngine.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>$(AmongUs)\BepInEx\unhollowed\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.InputLegacyModule">
<HintPath>$(AmongUs)\BepInEx\unhollowed\UnityEngine.InputLegacyModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.IMGUIModule">
<HintPath>$(AmongUs)\BepInEx\unhollowed\UnityEngine.IMGUIModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UI">
<HintPath>$(AmongUs)\BepInEx\unhollowed\UnityEngine.UI.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.UIModule">
<HintPath>$(AmongUs)\BepInEx\unhollowed\UnityEngine.UIModule.dll</HintPath>
</Reference>
</ItemGroup>
<Target Name="Copy" AfterTargets="Build">
<ItemGroup>
<OutputFiles Include="$(OutputPath)$(AssemblyName).dll" />
</ItemGroup>
<Copy SourceFiles="@(OutputFiles)" DestinationFolder="$(AmongUs)/BepInEx/plugins/" Condition="'$(Configuration)' == 'Debug'" />
</Target>
</Project>
+18
View File
@@ -0,0 +1,18 @@
# Reactor
Core (BepInEx) mod and API for Among Us.
Featuring amazing features like:
- modded handshake
- *partially* unstripped IMGUI and UI
- a lot of utility methods
## Reactor.OxygenFilter
Library for mapping output of Il2cppDumper, used in
[NuclearPowered/Il2CppAssemblyUnhollower](https://github.com/NuclearPowered/Il2CppAssemblyUnhollower)
## Reactor.Greenhouse
Standalone program which generates mappings for OxygenFilter
+97
View File
@@ -0,0 +1,97 @@
using System;
using BepInEx;
using BepInEx.IL2CPP;
using HarmonyLib;
using Reactor.Extensions;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
namespace Reactor.Debugger
{
[BepInPlugin(Id)]
[BepInProcess("Among Us.exe")]
[BepInDependency(ReactorPlugin.Id, "^0.2.0")]
[ReactorPluginSide(PluginSide.Client)]
public class DebuggerPlugin : BasePlugin
{
public const string Id = "gg.reactor.debugger";
public Harmony Harmony { get; } = new Harmony(Id);
public DebuggerComponent Component { get; private set; }
public override void Load()
{
ClassInjector.RegisterTypeInIl2Cpp<DebuggerComponent>();
var gameObject = new GameObject(nameof(DebuggerPlugin)).DontDestroy();
Component = gameObject.AddComponent<DebuggerComponent>();
Harmony.PatchAll();
Extensions.Extensions.once_sceneLoaded((_, _) =>
{
Dumping.Dump();
});
}
public class DebuggerComponent : MonoBehaviour
{
public bool DisableGameEnd { get; set; }
public DragWindow TestWindow { get; }
public DebuggerComponent(IntPtr ptr) : base(ptr)
{
TestWindow = new DragWindow(new Rect(20, 20, 120, 120), "Debugger", () =>
{
GUILayout.Label("Name: " + SaveManager.PlayerName, new Il2CppReferenceArray<GUILayoutOption>(0));
DisableGameEnd = GUILayout.Toggle(DisableGameEnd, "Disable game end", new Il2CppReferenceArray<GUILayoutOption>(0));
if (AmongUsClient.Instance.AmHost && ShipStatus.Instance && GUILayout.Button("Force game end", new Il2CppReferenceArray<GUILayoutOption>(0)))
{
ShipStatus.Instance.enabled = false;
ShipStatus.RpcEndGame(GameOverReason.ImpostorDisconnect, false);
}
if (PlayerControl.LocalPlayer)
{
var position = PlayerControl.LocalPlayer.transform.position;
GUILayout.Label($"x: {position.x}", new Il2CppReferenceArray<GUILayoutOption>(0));
GUILayout.Label($"y: {position.y}", new Il2CppReferenceArray<GUILayoutOption>(0));
}
})
{
Enabled = false
};
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.F1))
{
TestWindow.Enabled = !TestWindow.Enabled;
}
}
private void OnGUI()
{
TestWindow.OnGUI();
}
}
}
// [HarmonyPatch]
// public static class TracePatch
// {
// public static IEnumerable<MethodBase> TargetMethods()
// {
// return typeof(AmongUsClient).GetMethods(AccessTools.all).Where(x => x.ReturnType == typeof(Il2CppStructArray<byte>) && !x.GetParameters().Any());
// }
//
// public static void Postfix(MethodBase __originalMethod)
// {
// PluginSingleton<DebuggerPlugin>.Instance.Log.LogInfo(__originalMethod.FullDescription());
// }
// }
}
+71
View File
@@ -0,0 +1,71 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using BepInEx;
using UnityEngine;
namespace Reactor.Debugger
{
public static class Dumping
{
public static string DumpPath { get; } = Path.Combine(Paths.PluginPath, "dump");
public static void Dump()
{
Directory.CreateDirectory(DumpPath);
WritePalette();
WriteEnum("Hat", HatManager.Instance.AllHats.ToArray().Select(hat => hat.name));
WriteEnum("Pet", HatManager.Instance.AllPets.ToArray().Select(hat => hat.name));
WriteEnum("Skin", HatManager.Instance.AllSkins.ToArray().Select(hat => hat.name));
}
private static void WritePalette()
{
var builder = new StringBuilder();
builder.AppendLine("public static class Palette");
builder.AppendLine("{");
foreach (var propertyInfo in typeof(Palette).GetProperties())
{
if (propertyInfo.GetMethod == null || !propertyInfo.GetMethod.IsStatic)
continue;
var value = propertyInfo.GetValue(null);
if (value is Color f)
{
value = (Color32) f;
}
if (value is Color32 color)
{
builder.AppendLine($" public static readonly Color {propertyInfo.Name} = Color.FromArgb({(color.a != byte.MaxValue ? $"{color.a}, " : string.Empty)}{color.r}, {color.g}, {color.b});");
}
}
builder.AppendLine("}");
File.WriteAllText(Path.Combine(DumpPath, $"Palette.cs"), builder.ToString());
}
private static void WriteEnum(string name, IEnumerable<string> values)
{
var builder = new StringBuilder();
builder.AppendLine("public enum " + name);
builder.AppendLine("{");
foreach (var value in values)
{
builder.AppendLine(" " + value.Replace(" ", "").Replace("'", "") + ",");
}
builder.AppendLine("}");
File.WriteAllText(Path.Combine(DumpPath, $"{name}.cs"), builder.ToString());
}
}
}
+51
View File
@@ -0,0 +1,51 @@
using System.Linq;
using HarmonyLib;
namespace Reactor.Debugger.Patches
{
public static class Test
{
static Test()
{
GameOptionsData.MaxImpostors = GameOptionsData.RecommendedImpostors = Enumerable.Repeat((int) byte.MaxValue, byte.MaxValue).ToArray();
GameOptionsData.MinPlayers = Enumerable.Repeat(1, 4).ToArray();
}
[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))]
public static class UpdatePatch
{
public static void Prefix(GameStartManager __instance)
{
__instance.MinPlayers = 1;
}
}
[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))]
public static class CountdownPatch
{
public static void Prefix(GameStartManager __instance)
{
__instance.countDownTimer = 0;
}
}
[HarmonyPatch(typeof(StatsManager), nameof(StatsManager.AmBanned), MethodType.Getter)]
public static class AmBannedPatch
{
public static bool Prefix(ref bool __result)
{
return __result = false;
}
}
[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CheckEndCriteria))]
[HarmonyPriority(Priority.First)]
public static class CheckEndCriteriaPatch
{
public static bool Prefix()
{
return !PluginSingleton<DebuggerPlugin>.Instance.Component.DisableGameEnd;
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<Version>0.1.0</Version>
<Description>Debugging mod for Reactor</Description>
<Authors>NuclearPowered</Authors>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Reactor\Reactor.csproj" />
</ItemGroup>
<Import Project="../AmongUs.props" />
</Project>
+199
View File
@@ -0,0 +1,199 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using Mono.Cecil;
using Reactor.OxygenFilter;
namespace Reactor.Greenhouse
{
public static class Compiler
{
public static void Apply(this Mappings current, Mappings mappings)
{
static void ApplyType(List<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);
}
}
}
}
}
+28
View File
@@ -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?;
}
}
}
+138
View File
@@ -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;
}
}
}
+98
View File
@@ -0,0 +1,98 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Mono.Cecil;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Reactor.Greenhouse.Setup;
using Reactor.OxygenFilter;
namespace Reactor.Greenhouse
{
internal static class Program
{
private static Task<int> Main(string[] args)
{
var rootCommand = new RootCommand();
rootCommand.Handler = CommandHandler.Create(SearchAsync);
return rootCommand.InvokeAsync(args);
}
private static async Task SearchAsync()
{
var gameManager = new GameManager();
await gameManager.SetupAsync();
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ContractResolver = ShouldSerializeContractResolver.Instance,
};
Console.WriteLine($"Generating mappings from {gameManager.PreObfuscation.Name} ({gameManager.PreObfuscation.Version})");
using var old = ModuleDefinition.ReadModule(File.OpenRead(gameManager.PreObfuscation.Dll));
await GenerateAsync(gameManager.Steam, old);
await GenerateAsync(gameManager.Itch, old);
}
private static async Task GenerateAsync(Game game, ModuleDefinition old)
{
Console.WriteLine($"Compiling mappings for {game.Name} ({game.Version})");
using var moduleDef = ModuleDefinition.ReadModule(File.OpenRead(game.Dll));
var version = game.Version;
var postfix = game.Postfix;
var generated = Generator.Generate(old, moduleDef);
await File.WriteAllTextAsync(Path.Combine("work", version + postfix + ".generated.json"), JsonConvert.SerializeObject(generated, Formatting.Indented));
Apply(generated, Path.Combine("mappings", version + ".json"));
Apply(generated, Path.Combine("mappings", version + postfix + ".json"));
generated.Compile(moduleDef);
await File.WriteAllTextAsync(Path.Combine("work", version + postfix + ".json"), JsonConvert.SerializeObject(generated));
}
private static void Apply(Mappings generated, string file)
{
if (File.Exists(file))
{
var mappings = JsonConvert.DeserializeObject<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,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<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.6.0" />
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20478.1" />
<ProjectReference Include="..\Reactor.OxygenFilter\Reactor.OxygenFilter.csproj" />
<Reference Include="Il2CppDumper">
<HintPath>lib\Il2CppDumper.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+88
View File
@@ -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);
}
}
}
+70
View File
@@ -0,0 +1,70 @@
using System;
using System.IO;
using System.Threading.Tasks;
using DepotDownloader;
using Reactor.Greenhouse.Setup.Provider;
namespace Reactor.Greenhouse.Setup
{
public class GameManager
{
public string WorkPath { get; }
public Game PreObfuscation { get; }
public Game Steam { get; }
public Game Itch { get; }
public GameManager()
{
WorkPath = Path.GetFullPath("work");
PreObfuscation = new Game(new SteamProvider(true), "preobfuscation", Path.Combine(WorkPath, "preobfuscation"));
Steam = new Game(new SteamProvider(false), "steam", Path.Combine(WorkPath, "steam"));
Itch = new Game(new ItchProvider(), "itch", Path.Combine(WorkPath, "itch"));
}
public async Task SetupAsync()
{
var preObfuscation = PreObfuscation.Provider.IsUpdateNeeded();
var steam = Steam.Provider.IsUpdateNeeded();
var itch = Itch.Provider.IsUpdateNeeded();
if (preObfuscation || steam || itch)
{
ContentDownloader.ShutdownSteam3();
if (preObfuscation)
{
await PreObfuscation.DownloadAsync();
Console.WriteLine($"Downloaded {nameof(PreObfuscation)} ({PreObfuscation.Version})");
}
if (steam)
{
await Steam.DownloadAsync();
Console.WriteLine($"Downloaded {nameof(Steam)} ({Steam.Version})");
}
if (itch)
{
await Itch.DownloadAsync();
Console.WriteLine($"Downloaded {nameof(Itch)} ({Itch.Version})");
}
}
ContentDownloader.ShutdownSteam3();
PreObfuscation.UpdateVersion();
Steam.UpdateVersion();
Itch.UpdateVersion();
if (PreObfuscation.Version != "2020.9.9")
{
throw new ArgumentException("Pre obfuscation version is invalid");
}
PreObfuscation.Dump();
Steam.Dump();
Itch.Dump();
}
}
}
@@ -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,103 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;
using DepotDownloader;
using Newtonsoft.Json;
namespace Reactor.Greenhouse.Setup.Provider
{
public class ItchProvider : BaseProvider
{
private HttpClient HttpClient { get; } = new HttpClient(new HttpClientHandler
{
CookieContainer = new CookieContainer()
});
public string Username { get; set; }
public string Password { get; set; }
public override void Setup()
{
Console.Write("itch.io username: ");
Username = Console.ReadLine();
Console.Write("itch.io password: ");
Password = Util.ReadPassword();
Console.WriteLine();
}
public override async Task DownloadAsync()
{
var htmlParser = new HtmlParser();
var csrfResponse = await HttpClient.GetAsync("https://itch.io/login");
var csrfDocument = htmlParser.ParseDocument(await csrfResponse.Content.ReadAsStreamAsync());
var loginResponse = await HttpClient.PostAsync("https://itch.io/login", new FormUrlEncodedContent(new[]
{
new KeyValuePair<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,99 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using DepotDownloader;
using SteamKit2;
namespace Reactor.Greenhouse.Setup.Provider
{
public class SteamProvider : BaseProvider
{
private const uint AppId = 945360;
private const uint DepotId = 945361;
private const ulong PreObfuscationManifest = 3596575937380717449;
public bool IsPreObfuscation { get; }
public SteamProvider(bool isPreObfuscation)
{
IsPreObfuscation = isPreObfuscation;
}
public override bool IsUpdateNeeded()
{
DepotConfigStore.LoadFromFile(Path.Combine(Game.Path, ".DepotDownloader", "depot.config"));
if (DepotConfigStore.Instance.InstalledManifestIDs.TryGetValue(DepotId, out var installedManifest))
{
if (IsPreObfuscation)
{
if (installedManifest == PreObfuscationManifest)
{
return false;
}
}
else
{
if (ContentDownloader.steam3 == null)
{
ContentDownloader.InitializeSteam3();
}
ContentDownloader.steam3!.RequestAppInfo(AppId);
var depots = ContentDownloader.GetSteam3AppSection(AppId, EAppInfoSection.Depots);
if (installedManifest == depots[DepotId.ToString()]["manifests"][ContentDownloader.DEFAULT_BRANCH].AsUnsignedLong())
{
return false;
}
}
}
return true;
}
public override void Setup()
{
if (ContentDownloader.steam3 != null && ContentDownloader.steam3.bConnected)
{
return;
}
AccountSettingsStore.LoadFromFile("account.config");
ContentDownloader.Config.RememberPassword = true;
Console.Write("Steam username: ");
var username = Console.ReadLine();
string password = null;
if (!AccountSettingsStore.Instance.LoginKeys.ContainsKey(username))
{
Console.Write("Steam password: ");
password = ContentDownloader.Config.SuppliedPassword = Util.ReadPassword();
Console.WriteLine();
}
ContentDownloader.InitializeSteam3(username, password);
ContentDownloader.Config.UsingFileList = true;
ContentDownloader.Config.FilesToDownload = new List<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);
}
}
}
Binary file not shown.
+112
View File
@@ -0,0 +1,112 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://reactor.gg/schemas/mappings.schema.json",
"title": "Reactor.OxygenFilter - Mappings",
"type": "object",
"definitions": {
"member": {
"type": "object",
"properties": {
"original": {
"anyOf": [
{
"type": "object",
"properties": {
"index": {
"type": "number"
},
"signature": {
"type": "string"
},
"name": {
"type": "string"
},
"const": {
"type": "object",
"properties": {
"value": {
},
"type": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "string"
},
{
"type": "number"
}
]
},
"mapped": {
"type": "string"
},
"notes": {
"type": "string"
},
"additionalProperties": false
},
"required": [
"original"
],
"additionalProperties": false
},
"type": {
"type": "object",
"$ref": "#/definitions/member",
"properties": {
"nested": {
"type": "array",
"items": {
"$ref": "#/definitions/type"
}
},
"fields": {
"type": "array",
"items": {
"$ref": "#/definitions/member"
}
},
"properties": {
"type": "array",
"items": {
"$ref": "#/definitions/member"
}
},
"methods": {
"type": "array",
"items": {
"$ref": "#/definitions/member",
"properties": {
"parameters": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
},
"properties": {
"types": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/type"
},
"required": [
"original"
]
}
},
"required": [
"types"
]
}
+1
View File
@@ -0,0 +1 @@
2020.12.9.json
+809
View File
@@ -0,0 +1,809 @@
{
"$schema": "../mappings.schema.json",
"types": [
{
"original": "^",
"mapped": "Constants",
"methods": [
{
"original": {
"signature": "System.Byte[] ()"
},
"mapped": "GetBroadcastVersion"
}
],
"fields": [
{
"original": 0,
"mapped": "GamePlayPort"
},
{
"original": 1,
"mapped": "AnnouncementPort"
},
{
"original": 2,
"mapped": "ShipOnlyMask"
},
{
"original": 3,
"mapped": "ShipAndObjectsMask"
},
{
"original": 4,
"mapped": "ShipAndAllObjectsMask"
},
{
"original": 5,
"mapped": "NotShipMask"
},
{
"original": 6,
"mapped": "Usables"
},
{
"original": 7,
"mapped": "PlayersOnlyMask"
},
{
"original": 8,
"mapped": "ShadowMask"
},
{
"original": 9,
"mapped": "CompatVersions"
},
{
"original": 10,
"mapped": "Year"
},
{
"original": 11,
"mapped": "Month"
},
{
"original": 12,
"mapped": "Day"
},
{
"original": 13,
"mapped": "Revision"
},
{
"original": 14,
"mapped": "field_14"
},
{
"original": 15,
"mapped": "LocalNetAddress"
},
{
"original": 16,
"mapped": "InfinitySymbol"
}
]
},
{
"original": {},
"mapped": "SaveManager",
"fields": [
{
"original": {
"signature": "System.Collections.Generic.HashSet`1<System.String>",
"index": 23
},
"mapped": "purchases"
}
],
"properties": [
{
"mapped": "LastAnnouncement",
"original": 0
},
{
"mapped": "BoughtNoAds",
"original": 1
},
{
"mapped": "VSync",
"original": 2
},
{
"mapped": "CensorChat",
"original": 3
},
{
"mapped": "ShowAdsScreen",
"original": 4
},
{
"mapped": "ShowMinPlayerWarning",
"original": 6
},
{
"mapped": "ShowOnlineHelp",
"original": 7
},
{
"mapped": "SfxVolume",
"original": 8
},
{
"mapped": "MusicVolume",
"original": 9
},
{
"mapped": "TouchConfig",
"original": 10
},
{
"mapped": "JoystickSize",
"original": 11
},
{
"mapped": "PlayerName",
"original": 12
},
{
"mapped": "LastPet",
"original": 13
},
{
"mapped": "LastHat",
"original": 14
},
{
"mapped": "LastSkin",
"original": 15
},
{
"mapped": "LastLanguage",
"original": 16
},
{
"mapped": "BodyColor",
"original": 17
},
{
"mapped": "GameHostOptions",
"original": 18
},
{
"mapped": "GameSearchOptions",
"original": 19
}
]
},
{
"original": "^",
"mapped": "InnerNetClient",
"methods": [
{
"original": {
"signature": "System.Byte[] ()"
},
"mapped": "GetConnectionData"
},
{
"original": {
"signature": "System.Void ({{GameOverReason}},System.Boolean)"
},
"mapped": "OnGameEnd"
}
]
},
{
"original": "^",
"mapped": "GameOptionsData",
"fields": [
{
"original": 22,
"mapped": "isDefaults"
},
{
"original": 23,
"mapped": "ConfirmEjects"
},
{
"original": 24,
"mapped": "VisualTasks"
},
{
"original": 25,
"mapped": "TaskBarUpdate"
},
{
"original": 26,
"mapped": "AnonymousVotes"
},
{
"original": 27,
"mapped": "RecommendedKillCooldown"
},
{
"original": 28,
"mapped": "RecommendedImpostors"
},
{
"original": 29,
"mapped": "MaxImpostors"
},
{
"original": 30,
"mapped": "MinPlayers"
}
]
},
{
"original": {},
"mapped": "VersionShower",
"methods": [
{
"original": {
"name": "Start",
"signature": "System.Void ()"
}
}
],
"fields": [
{
"original": {
"index": 0,
"name": "text"
}
}
]
},
{
"original": {},
"mapped": "PingTracker",
"methods": [
{
"original": {
"name": "Update",
"signature": "System.Void ()"
}
}
],
"fields": [
{
"original": {
"index": 0,
"name": "text"
}
}
]
},
{
"original": "^",
"mapped": "GameStartManager",
"fields": [
{
"original": 3,
"mapped": "LastPlayerCount"
},
{
"original": 12,
"mapped": "startState"
},
{
"original": 13,
"mapped": "countDownTimer"
}
],
"nested": [
{
"original": 0,
"mapped": "StartingStates"
}
]
},
{
"original": "^",
"mapped": "GameData",
"nested": [
{
"original": 1,
"mapped": "PlayerInfo",
"fields": [
{
"original": 0,
"mapped": "PlayerId"
},
{
"original": 1,
"mapped": "PlayerName"
},
{
"original": 2,
"mapped": "ColorId"
},
{
"original": 3,
"mapped": "HatId"
},
{
"original": 4,
"mapped": "PetId"
},
{
"original": 5,
"mapped": "SkinId"
},
{
"original": 6,
"mapped": "Disconnected"
},
{
"original": 7,
"mapped": "Tasks"
},
{
"original": 8,
"mapped": "IsImpostor"
},
{
"original": 9,
"mapped": "IsDead"
},
{
"original": 10,
"mapped": "_object"
}
],
"properties": [
{
"original": 0,
"mapped": "Object"
}
]
}
]
},
{
"original": {},
"mapped": "RegionInfo",
"fields": [
{
"original": {
"index": 0,
"signature": "System.String"
},
"mapped": "Name"
},
{
"original": {
"index": 1,
"signature": "System.String"
},
"mapped": "PingServer"
},
{
"original": {
"index": 2,
"signature": "{{ServerInfo}}[]"
},
"mapped": "Servers"
}
]
},
{
"original": "^",
"mapped": "PlayerPhysics",
"fields": [
{
"original": {
"signature": "UnityEngine.Rigidbody2D"
},
"mapped": "body"
}
]
},
{
"original": "^",
"mapped": "PlayerControl",
"fields": [
{
"original": {
"signature": "UnityEngine.SpriteRenderer"
},
"mapped": "myRend"
}
]
},
{
"original": {},
"mapped": "MapBehaviour",
"methods": [
{
"original": {
"name": "ShowInfectedMap",
"signature": "System.Void ()"
}
}
]
},
{
"original": {},
"mapped": "IntroCutscene",
"nested": [
{
"original": 0,
"mapped": "CoBegin__d"
}
],
"fields": [
{
"original": {
"name": "IntroStinger",
"signature": "UnityEngine.AudioClip"
}
}
]
},
{
"original": {},
"mapped": "GameOptionsMenu",
"fields": [
{
"original": 2,
"mapped": "Children"
},
{
"original": {
"name": "ResetButton",
"signature": "UnityEngine.GameObject"
}
}
]
},
{
"original": {},
"mapped": "GameSettingMenu",
"fields": [
{
"original": {
"name": "HideForOnline",
"signature": "UnityEngine.Transform[]"
}
}
]
},
{
"original": "^",
"mapped": "AmongUsClient",
"nested": [
{
"original": 0,
"mapped": "CoStartGame__d"
}
]
},
{
"original": {},
"mapped": "Minigame",
"methods": [
{
"original": {
"name": "ForceClose",
"signature": "System.Void ()"
}
}
]
},
{
"original": {},
"mapped": "KeyboardJoystick",
"methods": [
{
"original": "Update"
},
{
"original": "get_Delta"
}
]
},
{
"original": {},
"mapped": "PlayerVoteArea",
"methods": [
{
"original": {
"name": "CoAnimateOverlay",
"signature": "System.Collections.IEnumerator ()"
}
}
]
},
{
"original": "^",
"mapped": "PlayerTab",
"fields": [
{
"original": {
"signature": "System.Collections.Generic.HashSet`1<System.Int32>"
},
"mapped": "AvailableColors"
}
],
"nested": [
{
"original": 0,
"mapped": "OnEnable__0"
}
]
},
{
"original": "^",
"mapped": "MeetingHud",
"nested": [
{
"original": 0,
"mapped": "VoteStates"
}
],
"fields": [
{
"original": 0,
"mapped": "ResultsTime"
},
{
"original": 1,
"mapped": "Depth"
},
{
"original": 7,
"mapped": "CounterOrigin"
},
{
"original": 8,
"mapped": "CounterOffsets"
},
{
"original": 10,
"mapped": "playerStates"
},
{
"original": 19,
"mapped": "state"
},
{
"original": 23,
"mapped": "exiledPlayer"
},
{
"original": 24,
"mapped": "wasTie"
},
{
"original": 27,
"mapped": "reporterId"
},
{
"original": 28,
"mapped": "amDead"
},
{
"original": 29,
"mapped": "resultsStartedAt"
},
{
"original": 30,
"mapped": "lastSecond"
}
]
},
{
"original": "^",
"mapped": "MessageExtensions",
"methods": [
{
"original": 0,
"mapped": "WriteNetObject"
},
{
"original": 1,
"mapped": "ReadNetObject"
}
]
},
{
"original": "^",
"mapped": "PhysicsHelpers",
"methods": [
{
"original": {
"signature": "System.Boolean (UnityEngine.Vector2,UnityEngine.Vector2,System.Single,System.Int32)"
},
"mapped": "AnyNonTriggersBetween"
}
]
},
{
"original": "^",
"mapped": "AspectPosition",
"nested": [
{
"original": 0,
"mapped": "EdgeAlignments"
}
]
},
{
"original": "^",
"mapped": "GameCode",
"methods": [
{
"original": 0,
"mapped": "IntToGameName"
},
{
"original": 1,
"mapped": "GameNameToInt"
},
{
"original": 2,
"mapped": "CreateGameId"
},
{
"original": 3,
"mapped": "IntToGameNameV2"
}
]
},
{
"original": "^",
"mapped": "Effects",
"methods": [
{
"original": {
"signature": "System.Collections.IEnumerator (System.Single,UnityEngine.Transform,System.Single,System.Single)"
},
"mapped": "Bloop"
}
]
},
{
"original": {},
"mapped": "StringOption",
"fields": [
{
"original": {
"name": "Values",
"signature": "StringNames[]"
}
}
]
},
{
"original": {},
"mapped": "PlainShipRoom",
"fields": [
{
"original": {
"index": 0,
"name": "RoomId"
}
},
{
"original": {
"index": 1,
"name": "survCamera"
}
},
{
"original": {
"index": 2,
"name": "roomArea"
}
}
]
},
{
"original": "^",
"mapped": "CooldownHelpers",
"methods": [
{
"original": {
"index": 0,
"signature": "System.Void (UnityEngine.SpriteRenderer)"
},
"mapped": "SetCooldownNormalizedUvs"
}
]
},
{
"original": {},
"mapped": "WinningPlayerData",
"fields": [
{
"original": {
"index": 0,
"signature": "System.String"
},
"mapped": "Name"
},
{
"original": {
"index": 1,
"signature": "System.Boolean"
},
"mapped": "IsDead"
},
{
"original": {
"index": 2,
"signature": "System.Boolean"
},
"mapped": "IsImpostor"
},
{
"original": {
"index": 3,
"signature": "System.Int32"
},
"mapped": "ColorId"
},
{
"original": {
"index": 4,
"signature": "System.UInt32"
},
"mapped": "SkinId"
},
{
"original": {
"index": 5,
"signature": "System.UInt32"
},
"mapped": "HatId"
},
{
"original": {
"index": 6,
"signature": "System.UInt32"
},
"mapped": "PetId"
},
{
"original": {
"index": 7,
"signature": "System.Boolean"
},
"mapped": "IsYou"
}
]
},
{
"original": {},
"mapped": "TempData",
"methods": [
{
"original": {
"index": 0,
"signature": "System.Boolean ({{GameOverReason}})"
},
"mapped": "DidHumansWin"
}
],
"fields": [
{
"original": 0,
"mapped": "LastDeathReason"
},
{
"original": 1,
"mapped": "EndReason"
},
{
"original": 2,
"mapped": "showAd"
},
{
"original": 3,
"mapped": "winners"
}
]
}
]
}
+134
View File
@@ -0,0 +1,134 @@
{
"$schema": "../mappings.schema.json",
"types": [
{
"original": "^",
"mapped": "MeetingHud",
"methods": [
{
"notes": "MeetingHud#CastVote",
"original": "EIGGEKBEOEA",
"mapped": "CheckForEndVoting"
},
{
"notes": "void (byte[], GameData.PlayerInfo, bool)",
"original": "KGIPOIOFBJH",
"mapped": "VotingComplete"
},
{
"notes": "MeetingHud#VotingComplete",
"original": "FBNKEAEKKJE",
"mapped": "PopulateResults"
},
{
"notes": "MeetingHud#VotingComplete",
"original": "ECLBANBAFCN",
"mapped": "SetupProceedButton"
}
]
},
{
"original": "^",
"mapped": "ShipStatus",
"methods": [
{
"notes": "ShipStatus#FixedUpdate",
"original": "ECNNLBKGIKE",
"mapped": "CheckEndCriteria"
},
{
"notes": "CheckEndCriteria",
"original": "PLBGOMIEONF",
"mapped": "RpcEndGame"
},
{
"original": "NJOJCFDGEAE",
"mapped": "SelectInfected"
}
]
},
{
"original": "^",
"mapped": "ServerManager",
"methods": [
{
"notes": "Last call in ServerManager#Awake",
"original": "BBIDOJKFGPM",
"mapped": "LoadServers"
}
]
},
{
"original": {},
"mapped": "StatsManager",
"properties": [
{
"notes": "Second call in *GameButton#OnClick",
"original": "IDEHLLDOODH",
"mapped": "AmBanned"
}
],
"fields": [
{
"original": {
"const": {
"type": "System.Byte",
"value": 3
}
},
"mapped": "StatsVersion"
},
{
"original": {
"const": {
"type": "System.Int32",
"value": 2
}
},
"mapped": "PointsUntilBanStarts"
},
{
"original": {
"const": {
"type": "System.Int32",
"value": 5
}
},
"mapped": "MinutesPerBanPoint"
}
]
},
{
"original": "^",
"mapped": "PlayerControl",
"methods": [
{
"notes": "Only method with [CallerCount(2)] and PlayerControl ()",
"original": "OAFBLFBAOJJ",
"mapped": "FindClosestTarget"
},
{
"notes": "first call in CmdReportDeadBody",
"original": "MKAIDPJOJFP",
"mapped": "ReportDeadBody"
},
{
"notes": "Only method with [CallerCount(1)] and void (byte[])",
"original": "OJPCECLPCCF",
"mapped": "SetInfected"
}
]
},
{
"original": "^",
"mapped": "EndGameManager",
"methods": [
{
"notes": "first call in EndGameManager#Start",
"original": "MFIBPNBCMKM",
"mapped": "SetEverythingUp"
}
]
}
]
}
+53
View File
@@ -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;
}
}
}
+199
View File
@@ -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)
{
}
}
}
+237
View File
@@ -0,0 +1,237 @@
using System;
using System.IO;
using System.Linq;
using Mono.Cecil;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Reactor.OxygenFilter
{
public class ObfuscationMapper
{
public ModuleDefinition ModuleDef { get; }
public Mappings Mappings { get; private set; }
public ObfuscationMapper(ModuleDefinition moduleDef)
{
ModuleDef = moduleDef;
}
public void Map()
{
var mappedAttribute = new TypeDefinition("", "MappedAttribute", TypeAttributes.Public, ModuleDef.ImportReference(typeof(Attribute)))
{
Attributes = TypeAttributes.Class & TypeAttributes.Public
};
ModuleDef.Types.Add(mappedAttribute);
var mappedAttributeCtor = new MethodReference(".ctor", ModuleDef.ImportReference(typeof(void)), mappedAttribute)
{
HasThis = true,
Parameters = { new ParameterDefinition(ModuleDef.ImportReference(typeof(string))) }
};
void MapMember(ICustomAttributeProvider attributes, string mapped)
{
attributes.CustomAttributes.Add(new CustomAttribute(mappedAttributeCtor)
{
ConstructorArguments = { new CustomAttributeArgument(ModuleDef.ImportReference(typeof(string)), mapped) }
});
}
// beebyte 1 iq?
foreach (var propertyDef in ModuleDef.Types.SelectMany(x => x.NestedTypes.Prepend(x)).SelectMany(x => x.Properties))
{
const string getPrefix = "get_";
const string setPrefix = "set_";
var getName = propertyDef.GetMethod != null && propertyDef.GetMethod.Name.StartsWith(getPrefix) ? propertyDef.GetMethod.Name.Substring(getPrefix.Length) : null;
var setName = propertyDef.SetMethod != null && propertyDef.SetMethod.Name.StartsWith(setPrefix) ? propertyDef.SetMethod.Name.Substring(setPrefix.Length) : null;
if (getName != null && setName != null && getName != setName)
{
throw new Exception($"{propertyDef.FullName} has 2 different accessor names");
}
if (getName != null || setName != null)
{
var name = getName ?? setName;
MapMember(propertyDef, name);
if (propertyDef.GetMethod != null)
MapMember(propertyDef.GetMethod, "get_" + name);
if (propertyDef.SetMethod != null)
MapMember(propertyDef.SetMethod, "set_" + name);
}
}
void MapType(MappedType type, TypeDefinition typeDef)
{
if (typeDef == null)
{
throw new NullReferenceException($"Type {type} was not found!");
}
if (type.Mapped != null)
{
MapMember(typeDef, type.Mapped);
}
foreach (var property in type.Properties)
{
var propertyDef = typeDef.Properties.SingleOrDefault(x => x.Name == property.Original.Name);
if (propertyDef == null)
{
throw new NullReferenceException($"Property {property} was not found in {type}!");
}
if (property.Mapped != null)
{
MapMember(propertyDef, property.Mapped);
if (propertyDef.GetMethod != null)
MapMember(propertyDef.GetMethod, "get_" + property.Mapped);
if (propertyDef.SetMethod != null)
MapMember(propertyDef.SetMethod, "set_" + property.Mapped);
}
}
foreach (var field in type.Fields)
{
var fieldDef = typeDef.Fields.SingleOrDefault(x => x.Name == field.Original.Name);
if (fieldDef == null)
{
throw new NullReferenceException($"Field {field} was not found in {type}!");
}
if (field.Mapped != null)
{
MapMember(fieldDef, field.Mapped);
}
}
foreach (var method in type.Methods)
{
var methodDef = typeDef.Methods
.SingleOrDefault(x => x.Name == method.Original.Name && (method.Original.Signature == null || x.GetSignature().ToString() == method.Original.Signature));
if (methodDef == null)
{
throw new NullReferenceException($"Method {method} was not found in {type}!");
}
foreach (var methodDef2 in ModuleDef.Types
.Where(x => x.BaseType == typeDef)
.Select(x => x.Methods.SingleOrDefault(m => m.Name == method.Original.Name && (method.Original.Signature == null || m.GetSignature().ToString() == method.Original.Signature)))
.Where(x => x != null)
.Prepend(methodDef)
)
{
if (method.Mapped != null)
{
MapMember(methodDef2, method.Mapped);
}
for (var i = 0; i < method.Parameters.Count; i++)
{
MapMember(methodDef2.Parameters.ElementAt(i), method.Parameters[i]);
}
}
}
foreach (var nested in type.Nested)
{
MapType(nested, typeDef.NestedTypes.SingleOrDefault(x => x.Name == nested.Original.Name));
}
}
foreach (var type in Mappings.Types)
{
MapType(type, ModuleDef.GetType(type.Original.Name));
}
foreach (var typeDef in ModuleDef.Types)
{
var i = 0;
foreach (var member in typeDef.Properties)
{
if (member.Name.IsObfuscated() && member.CustomAttributes.All(x => x.AttributeType != mappedAttribute))
{
MapMember(member, $"Property_{i}");
}
i++;
}
i = 0;
foreach (var member in typeDef.Fields)
{
if (member.Name.IsObfuscated() && member.CustomAttributes.All(x => x.AttributeType != mappedAttribute))
{
MapMember(member, $"Field_{i}");
}
i++;
}
i = 0;
foreach (var member in typeDef.Methods)
{
if (member.Name.IsObfuscated() && member.CustomAttributes.All(x => x.AttributeType != mappedAttribute))
{
MapMember(member, $"Method_{i}");
}
var j = 0;
foreach (var parameter in member.Parameters)
{
if (parameter.Name.IsObfuscated() && parameter.CustomAttributes.All(x => x.AttributeType != mappedAttribute))
{
MapMember(parameter, $"Parameter_{j}");
}
j++;
}
i++;
}
i = 0;
foreach (var member in typeDef.NestedTypes)
{
if (member.Name.IsObfuscated() && member.CustomAttributes.All(x => x.AttributeType != mappedAttribute))
{
MapMember(member, $"Nested_{i}");
}
i++;
}
}
}
public void LoadMappings(FileInfo file)
{
if (!file.Exists)
{
throw new FileNotFoundException();
}
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
Mappings = JsonConvert.DeserializeObject<Mappings>(File.ReadAllText(file.FullName));
}
}
}
+24
View File
@@ -0,0 +1,24 @@
using System.IO;
using Mono.Cecil;
namespace Reactor.OxygenFilter
{
public class OxygenFilter
{
public void Start(FileInfo mappingsFile, FileInfo dumpedDll, FileInfo outputDll)
{
using var inputStream = dumpedDll.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
using var outputStream = dumpedDll == outputDll ? inputStream : dumpedDll.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(dumpedDll.DirectoryName);
using var module = ModuleDefinition.ReadModule(inputStream, new ReaderParameters(ReadingMode.Immediate) { AssemblyResolver = resolver });
var mapper = new ObfuscationMapper(module);
mapper.LoadMappings(mappingsFile);
mapper.Map();
module.Dispose();
module.Write(outputStream);
}
}
}
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472;netstandard2.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
</Project>
+33
View File
@@ -0,0 +1,33 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactor", "Reactor\Reactor.csproj", "{91906039-9753-472B-ACC3-12C0561FFAD8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactor.OxygenFilter", "Reactor.OxygenFilter\Reactor.OxygenFilter.csproj", "{154C08E6-8F9F-43A3-86C3-A3A606316D2B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactor.Debugger", "Reactor.Debugger\Reactor.Debugger.csproj", "{68589B4D-9EF5-40DD-8D6D-10977ED07259}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactor.Greenhouse", "Reactor.Greenhouse\Reactor.Greenhouse.csproj", "{00BF8959-652F-46B8-AA0F-891B4DA1112B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{91906039-9753-472B-ACC3-12C0561FFAD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91906039-9753-472B-ACC3-12C0561FFAD8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91906039-9753-472B-ACC3-12C0561FFAD8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91906039-9753-472B-ACC3-12C0561FFAD8}.Release|Any CPU.Build.0 = Release|Any CPU
{154C08E6-8F9F-43A3-86C3-A3A606316D2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{154C08E6-8F9F-43A3-86C3-A3A606316D2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{154C08E6-8F9F-43A3-86C3-A3A606316D2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{154C08E6-8F9F-43A3-86C3-A3A606316D2B}.Release|Any CPU.Build.0 = Release|Any CPU
{68589B4D-9EF5-40DD-8D6D-10977ED07259}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68589B4D-9EF5-40DD-8D6D-10977ED07259}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68589B4D-9EF5-40DD-8D6D-10977ED07259}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68589B4D-9EF5-40DD-8D6D-10977ED07259}.Release|Any CPU.Build.0 = Release|Any CPU
{00BF8959-652F-46B8-AA0F-891B4DA1112B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{00BF8959-652F-46B8-AA0F-891B4DA1112B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{00BF8959-652F-46B8-AA0F-891B4DA1112B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{00BF8959-652F-46B8-AA0F-891B4DA1112B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Binary file not shown.
+81
View File
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
namespace Reactor.Extensions
{
/// <summary>
/// General utilities
/// </summary>
public static class Extensions
{
/// <summary>
/// Stops <paramref name="obj"/> from being destroyed
/// </summary>
/// <param name="obj">Object to stop from being destroyed</param>
/// <returns>Passed <paramref name="obj"/></returns>
public static T DontDestroy<T>(this T obj) where T : Object
{
obj.hideFlags |= HideFlags.HideAndDontSave;
Object.DontDestroyOnLoad(obj);
return obj;
}
/// <summary>
/// Adds a delegate to get one notification when a scene has loaded.
/// </summary>
public static void once_sceneLoaded(Action<Scene, LoadSceneMode> value)
{
UnityAction<Scene, LoadSceneMode> unityAction = null;
unityAction = (Action<Scene, LoadSceneMode>) ((scene, loadMode) =>
{
SceneManager.remove_sceneLoaded(unityAction);
value.Invoke(scene, loadMode);
});
SceneManager.add_sceneLoaded(unityAction);
}
/// <summary>
/// Returns random <typeparamref name="T"/> from <paramref name="input"/>
/// </summary>
public static T Random<T>(this IEnumerable<T> input)
{
var list = input as IList<T> ?? input.ToList();
return list.Count == 0 ? default : list[UnityEngine.Random.Range(0, list.Count)];
}
/// <summary>
/// Returns the color as a hexadecimal string in the format "RRGGBBAA".
/// </summary>
/// <param name="color">The color to be converted.</param>
/// <returns>Hexadecimal string representing the color.</returns>
/// <remarks>https://docs.unity3d.com/ScriptReference/ColorUtility.ToHtmlStringRGBA.html</remarks>
public static string ToHtmlStringRGBA(this Color32 color)
{
return $"{color.r:X2}{color.g:X2}{color.b:X2}{color.a:X2}";
}
/// <inheritdoc cref="ToHtmlStringRGBA(UnityEngine.Color32)"/>
public static string ToHtmlStringRGBA(this Color color)
{
return ((Color32) color).ToHtmlStringRGBA();
}
public static byte[] ReadFully(this Stream input)
{
using (var ms = new MemoryStream())
{
input.CopyTo(ms);
return ms.ToArray();
}
}
}
}
+128
View File
@@ -0,0 +1,128 @@
using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace Reactor.Extensions
{
/// <summary>
/// GUI utilities
/// </summary>
public static class GUIExtensions
{
/// <summary>
/// Shortcut for empty texture
/// </summary>
public static Texture2D CreateEmptyTexture(int width = 0, int height = 0)
{
return new Texture2D(width, height, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero);
}
/// <summary>
/// Clamp Rect to screen size
/// </summary>
public static Rect ClampScreen(this Rect rect)
{
rect.x = Mathf.Clamp(rect.x, 0, Screen.width - rect.width);
rect.y = Mathf.Clamp(rect.y, 0, Screen.height - rect.height);
return rect;
}
/// <summary>
/// Create <see cref="Sprite"/> from <paramref name="tex"/>
/// </summary>
public static Sprite CreateSprite(this Texture2D tex)
{
return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100f);
}
public static DefaultControls.Resources StandardResources { get; internal set; }
public static GameObject CreateCanvas()
{
var gameObject = new GameObject("Canvas");
var canvas = gameObject.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
var canvasScaler = gameObject.AddComponent<CanvasScaler>();
canvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand;
canvasScaler.referencePixelsPerUnit = 100f;
gameObject.AddComponent<GraphicRaycaster>();
return gameObject;
}
public static GameObject CreateEventSystem()
{
var gameObject = new GameObject("EventSystem");
gameObject.AddComponent<EventSystem>();
gameObject.AddComponent<StandaloneInputModule>();
gameObject.AddComponent<BaseInput>();
return gameObject;
}
public static void SetSize(this RectTransform rectTransform, float width, float height)
{
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width);
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
}
}
public class Window
{
private static int _lastWindowId;
public static int NextWindowId()
{
return _lastWindowId++;
}
public int Id { get; set; } = NextWindowId();
public bool Enabled { get; set; } = true;
public Rect Rect { get; set; }
public Action<int> Func { get; set; }
public string Title { get; set; }
public Window(Rect rect, string title, Action<int> func)
{
Rect = rect;
Title = title;
Func = func;
}
public Window(Rect rect, string title, Action func) : this(rect, title, id => func())
{
}
public void OnGUI()
{
if (Enabled)
{
Rect = GUI.Window(Id, Rect, Func, Title);
}
}
}
public class DragWindow : Window
{
public DragWindow(Rect rect, string title, Action<int> func) : base(rect, title, func)
{
Func = id =>
{
func(id);
GUI.DragWindow(new Rect(0, 0, 10000, 20));
Rect = Rect.ClampScreen();
};
}
public DragWindow(Rect rect, string title, Action func) : this(rect, title, id => func())
{
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using System.Linq;
using System.Reflection;
using BepInEx.IL2CPP;
using HarmonyLib;
using Hazel;
using UnhollowerBaseLib;
namespace Reactor.Patches
{
internal static class ModdedHandshake
{
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.GetConnectionData))]
public static class HandshakePatch
{
public static bool Prefix(out Il2CppStructArray<byte> __result)
{
var handshake = MessageWriter.Get(SendOption.Reliable);
var plugins = Preloader.Chainloader.Plugins;
if (plugins.Values
.Select(pluginInfo => pluginInfo.Instance.GetType().GetCustomAttribute<ReactorPluginSideAttribute>())
.Any(attribute => attribute == null || attribute.Side == PluginSide.Both))
{
handshake.Write(-1);
}
handshake.Write(Constants.GetBroadcastVersion());
handshake.Write(SaveManager.PlayerName);
handshake.WritePacked(plugins.Count);
foreach (var plugin in plugins)
{
handshake.Write(plugin.Key);
handshake.Write(plugin.Value.Metadata.Version.ToString());
}
__result = handshake.ToByteArray(false);
handshake.Recycle();
return false;
}
}
}
}
+55
View File
@@ -0,0 +1,55 @@
using System;
using System.Reflection;
using BepInEx;
using BepInEx.IL2CPP;
using UnityEngine.SceneManagement;
namespace Reactor.Patches
{
public static class ReactorVersionShower
{
public static TextRenderer Text { get; private set; }
public static event TextUpdatedHandler TextUpdated;
public delegate void TextUpdatedHandler(TextRenderer text);
internal static void Initialize()
{
SceneManager.add_sceneLoaded((Action<Scene, LoadSceneMode>) ((scene, loadMode) =>
{
var original = UnityEngine.Object.FindObjectOfType<VersionShower>();
if (!original)
return;
var gameObject = UnityEngine.Object.Instantiate(original.gameObject);
gameObject.name = "ReactorVersion";
var versionShower = gameObject.GetComponent<VersionShower>();
if (versionShower)
{
versionShower.enabled = false;
}
var aspectPosition = gameObject.GetComponent<AspectPosition>();
var position = aspectPosition.DistanceFromEdge;
position.y += 0.2f;
aspectPosition.DistanceFromEdge = position;
aspectPosition.AdjustPosition();
Text = gameObject.GetComponentInChildren<TextRenderer>();
UpdateText();
}));
}
public static void UpdateText()
{
Text.Text = "Reactor " + typeof(ReactorPlugin).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
Text.Text += "\nBepInEx: " + Paths.BepInExVersion;
Text.Text += "\nMods: " + Preloader.Chainloader.Plugins.Count;
TextUpdated?.Invoke(Text);
}
}
}
+35
View File
@@ -0,0 +1,35 @@
using System;
using System.IO;
using UnityEngine;
namespace Reactor.Patches
{
internal class RegionInfoWatcher : IDisposable
{
private FileSystemWatcher Watcher { get; }
public bool Reload { get; set; }
internal RegionInfoWatcher()
{
Watcher = new FileSystemWatcher(
string.IsNullOrEmpty(Application.persistentDataPath) ? Directory.GetCurrentDirectory() : Application.persistentDataPath,
"regionInfo.dat"
);
Watcher.Changed += (s, e) =>
{
if (new FileInfo(e.Name).Length > 0)
{
Reload = true;
}
};
Watcher.EnableRaisingEvents = true;
}
public void Dispose()
{
Watcher?.Dispose();
}
}
}
+34
View File
@@ -0,0 +1,34 @@
using System;
using System.Linq;
using BepInEx.IL2CPP;
namespace Reactor
{
public class PluginSingleton<T> where T : BasePlugin
{
private static T _instance;
public static T Instance
{
get
{
if (_instance == null)
{
_instance = Preloader.Chainloader.Plugins.Values.Select(x => x.Instance).OfType<T>().Single();
}
return _instance;
}
set
{
if (_instance != null)
{
throw new Exception($"Instance for {typeof(T).FullName} is already set");
}
_instance = value;
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<LangVersion>latest</LangVersion>
<Version>0.2.0</Version>
<Description>Core mod and API for Among Us</Description>
<Authors>NuclearPowered</Authors>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\**\*" />
</ItemGroup>
<Import Project="../AmongUs.props" />
</Project>
+61
View File
@@ -0,0 +1,61 @@
using System;
using BepInEx;
using BepInEx.IL2CPP;
using HarmonyLib;
using Reactor.Extensions;
using Reactor.Patches;
using Reactor.Unstrip;
using UnhollowerRuntimeLib;
using UnityEngine;
namespace Reactor
{
[BepInPlugin(Id)]
[BepInProcess("Among Us.exe")]
[ReactorPluginSide(PluginSide.Client)]
public class ReactorPlugin : BasePlugin
{
public const string Id = "gg.reactor.api";
public Harmony Harmony { get; } = new Harmony(Id);
private RegionInfoWatcher RegionInfoWatcher { get; } = new RegionInfoWatcher();
public override void Load()
{
ClassInjector.RegisterTypeInIl2Cpp<ReactorComponent>();
ClassInjector.RegisterTypeInIl2Cpp<Unstrip.Patches.GUIWordWrapSizer>();
var gameObject = new GameObject(nameof(ReactorPlugin)).DontDestroy();
gameObject.AddComponent<ReactorComponent>();
Harmony.PatchAll();
ReactorVersionShower.Initialize();
DefaultBundle.Load();
}
public override bool Unload()
{
RegionInfoWatcher?.Dispose();
return base.Unload();
}
public class ReactorComponent : MonoBehaviour
{
public ReactorComponent(IntPtr ptr) : base(ptr)
{
}
private void Update()
{
var watcher = PluginSingleton<ReactorPlugin>.Instance.RegionInfoWatcher;
if (watcher.Reload)
{
watcher.Reload = false;
ServerManager.Instance.LoadServers();
PluginSingleton<ReactorPlugin>.Instance.Log.LogInfo("Region file reloaded");
}
}
}
}
}
+44
View File
@@ -0,0 +1,44 @@
using System;
namespace Reactor
{
/// <summary>
/// This attribute specifies plugin's side
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ReactorPluginSideAttribute : Attribute
{
public PluginSide Side { get; }
public ReactorPluginSideAttribute(PluginSide side)
{
if (side == PluginSide.Server)
{
throw new ArgumentException("BepInEx plugin can't be server only!");
}
Side = side;
}
}
/// <summary>
/// Plugin side used in modded handshake
/// </summary>
public enum PluginSide
{
/// <summary>
/// Required by both sides, reject connection if missing on the other side
/// </summary>
Both,
/// <summary>
/// Required only by client
/// </summary>
Client,
/// <summary>
/// Required only by server
/// </summary>
Server
}
}
+63
View File
@@ -0,0 +1,63 @@
// https://github.com/sinai-dev/UnityExplorer/blob/master/src/Unstrip/AssetBundleUnstrip.cs
using System;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
namespace Reactor.Unstrip
{
public class AssetBundle
{
private delegate IntPtr d_LoadFromFile(IntPtr path, uint crc, ulong offset);
private static readonly d_LoadFromFile i_LoadFromFile = IL2CPP.ResolveICall<d_LoadFromFile>("UnityEngine.AssetBundle::LoadFromFile_Internal");
public static AssetBundle LoadFromFile(string path, uint crc = 0, ulong offset = 0)
{
return new AssetBundle(i_LoadFromFile.Invoke(IL2CPP.ManagedStringToIl2Cpp(path), crc, offset));
}
private delegate IntPtr d_LoadFromMemory(IntPtr binary, uint crc);
private static readonly d_LoadFromMemory i_LoadFromMemory = IL2CPP.ResolveICall<d_LoadFromMemory>("UnityEngine.AssetBundle::LoadFromMemory_Internal");
public static AssetBundle LoadFromMemory(byte[] binary, uint crc = 0)
{
return new AssetBundle(i_LoadFromMemory(((Il2CppStructArray<byte>) binary).Pointer, crc));
}
public IntPtr Pointer { get; }
public AssetBundle(IntPtr ptr)
{
Pointer = ptr;
}
private delegate IntPtr d_LoadAssetWithSubAssets_Internal(IntPtr __instance, IntPtr name, IntPtr type);
private static readonly d_LoadAssetWithSubAssets_Internal i_LoadAssetWithSubAssets_Internal = IL2CPP.ResolveICall<d_LoadAssetWithSubAssets_Internal>("UnityEngine.AssetBundle::LoadAssetWithSubAssets_Internal");
public UnityEngine.Object[] LoadAllAssets()
{
var ptr = i_LoadAssetWithSubAssets_Internal.Invoke(Pointer, IL2CPP.ManagedStringToIl2Cpp(string.Empty), Il2CppType.Of<UnityEngine.Object>().Pointer);
if (ptr == IntPtr.Zero)
{
return new UnityEngine.Object[0];
}
return new Il2CppReferenceArray<UnityEngine.Object>(ptr);
}
private delegate IntPtr d_LoadAsset_Internal(IntPtr __instance, IntPtr name, IntPtr type);
private static readonly d_LoadAsset_Internal i_LoadAsset_Internal = IL2CPP.ResolveICall<d_LoadAsset_Internal>("UnityEngine.AssetBundle::LoadAsset_Internal");
public T LoadAsset<T>(string name) where T : UnityEngine.Object
{
var ptr = i_LoadAsset_Internal.Invoke(Pointer, IL2CPP.ManagedStringToIl2Cpp(name), Il2CppType.Of<T>().Pointer);
return ptr == IntPtr.Zero ? null : new UnityEngine.Object(ptr).TryCast<T>();
}
}
}
+38
View File
@@ -0,0 +1,38 @@
using System;
using Reactor.Extensions;
using UnityEngine;
using UnityEngine.UI;
namespace Reactor.Unstrip
{
internal static class DefaultBundle
{
public static void Load()
{
var bundle = AssetBundle.LoadFromMemory(typeof(ReactorPlugin).Assembly.GetManifestResourceStream("Reactor.Assets.default.bundle").ReadFully());
var backupShader = bundle.LoadAsset<Shader>("UI-Default");
if (!Graphic.defaultGraphicMaterial.shader || Graphic.defaultGraphicMaterial.shader.name != "UI/Default")
{
Graphic.defaultGraphicMaterial.shader = backupShader;
}
var tex = new Texture2D(1, 1, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero);
tex.SetPixel(0, 0, Color.red);
var sprite = tex.CreateSprite();
GUIExtensions.StandardResources = new DefaultControls.Resources
{
background = sprite,
checkmark = sprite,
dropdown = sprite,
knob = sprite,
mask = sprite,
standard = sprite,
inputField = sprite
};
}
}
}
+21
View File
@@ -0,0 +1,21 @@
using System;
using UnhollowerBaseLib;
using UnityEngine;
namespace Reactor.Unstrip
{
public static class ImageConversion
{
private delegate bool d_LoadImage(IntPtr tex, IntPtr data, bool markNonReadable);
private static readonly d_LoadImage i_LoadImage = IL2CPP.ResolveICall<d_LoadImage>("UnityEngine.ImageConversion::LoadImage");
/// <summary>
/// Load image from <paramref name="data"/> to <paramref name="tex"/>
/// </summary>
public static bool LoadImage(this Texture2D tex, byte[] data, bool markNonReadable = false)
{
return i_LoadImage(tex.Pointer, ((Il2CppStructArray<byte>) data).Pointer, markNonReadable);
}
}
}
+213
View File
@@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
using HarmonyLib;
using UnhollowerBaseLib;
using UnityEngine;
using Object = Il2CppSystem.Object;
using Type = Il2CppSystem.Type;
namespace Reactor.Unstrip
{
internal static class Patches
{
[HarmonyPatch(typeof(GUIUtility), nameof(GUIUtility.HasKeyFocus))]
public static class HasKeyFocusPatch
{
public static bool Prefix(int controlID, ref bool __result)
{
__result = controlID == GUIUtility.keyboardControl;
return false;
}
}
[HarmonyPatch(typeof(GUILayoutGroup), nameof(GUILayoutGroup.Add))]
public static class AddPatch
{
public static bool Prefix(GUILayoutGroup __instance, GUILayoutEntry e)
{
__instance.entries.Add(e);
return false;
}
}
[HarmonyPatch(typeof(GUILayoutGroup), nameof(GUILayoutGroup.GetNext))]
public static class GetNextPatch
{
public static bool Prefix(GUILayoutGroup __instance, ref GUILayoutEntry __result)
{
if (__instance.m_Cursor < __instance.entries.Count)
{
__result = __instance.entries[__instance.m_Cursor];
__instance.m_Cursor++;
return false;
}
throw new ArgumentException(string.Concat("Getting control ", __instance.m_Cursor, "'s position in a group with only ", __instance.entries.Count, " controls when doing ", Event.current.rawType, "\nAborting"));
}
}
[HarmonyPatch(typeof(GUILayoutEntry), nameof(GUILayoutEntry.CalcWidth))]
public static class CalcWidthPatch
{
public static bool Prefix(GUILayoutEntry __instance)
{
var cast = __instance.TryCast<GUIWordWrapSizer>();
if (cast != null)
{
cast.CalcWidth();
return false;
}
return true;
}
}
[HarmonyPatch(typeof(GUILayoutEntry), nameof(GUILayoutEntry.CalcHeight))]
public static class CalcHeightPatch
{
public static bool Prefix(GUILayoutEntry __instance)
{
var cast = __instance.TryCast<GUIWordWrapSizer>();
if (cast != null)
{
cast.CalcHeight();
return false;
}
return true;
}
}
internal sealed class GUIWordWrapSizer : GUILayoutEntry
{
private readonly GUIContent m_Content;
private readonly float m_ForcedMinHeight;
private readonly float m_ForcedMaxHeight;
public GUIWordWrapSizer(IntPtr ptr) : base(ptr)
{
CalcWidth();
CalcHeight();
}
public GUIWordWrapSizer(GUIStyle style, GUIContent content, GUILayoutOption[] options)
: base(0.0f, 0.0f, 0.0f, 0.0f, style)
{
m_Content = new GUIContent(content.text, content.image, content.tooltip);
ApplyOptions(options);
m_ForcedMinHeight = minHeight;
m_ForcedMaxHeight = maxHeight;
CalcWidth();
CalcHeight();
}
public new void CalcWidth()
{
if (this.minWidth != 0.0 && this.maxWidth != 0.0)
return;
float minWidth;
float maxWidth;
style.CalcMinMaxWidth(m_Content, out minWidth, out maxWidth);
minWidth = Mathf.Ceil(minWidth);
maxWidth = Mathf.Ceil(maxWidth);
if (this.minWidth == 0.0)
this.minWidth = minWidth;
if (this.maxWidth == 0.0)
this.maxWidth = maxWidth;
}
public new void CalcHeight()
{
if (m_ForcedMinHeight != 0.0 && m_ForcedMaxHeight != 0.0)
return;
var num = style.CalcHeight(m_Content, rect.width);
if (m_ForcedMinHeight == 0.0)
minHeight = num;
else
minHeight = m_ForcedMinHeight;
if (m_ForcedMaxHeight == 0.0)
maxHeight = num;
else
maxHeight = m_ForcedMaxHeight;
}
}
[HarmonyPatch(typeof(GUILayoutUtility), nameof(GUILayoutUtility.DoGetRect), typeof(GUIContent), typeof(GUIStyle), typeof(Il2CppReferenceArray<GUILayoutOption>))]
public static class DoGetRectPatch
{
public static bool Prefix(GUIContent content, GUIStyle style, Il2CppReferenceArray<GUILayoutOption> options, ref Rect __result)
{
GUIUtility.CheckOnGUI();
var type = Event.current.type;
if (type != EventType.Layout)
{
if (type != EventType.Used)
{
var next = GUILayoutUtility.current.topLevel.GetNext();
__result = next.rect;
}
else
{
__result = GUILayoutUtility.kDummyRect;
}
}
else
{
var isHeightDependantOnWidth = style.isHeightDependantOnWidth;
if (isHeightDependantOnWidth)
{
GUILayoutUtility.current.topLevel.Add(new GUIWordWrapSizer(style, content, options));
}
else
{
var constraints = new Vector2(0f, 0f);
if (options != null)
{
foreach (var guilayoutOption in options)
{
var type2 = guilayoutOption.type;
if (type2 != GUILayoutOption.Type.maxWidth)
{
if (type2 == GUILayoutOption.Type.maxHeight)
{
constraints.y = guilayoutOption.value.Unbox<float>();
}
}
else
{
constraints.x = guilayoutOption.value.Unbox<float>();
}
}
}
var vector = style.CalcSizeWithConstraints(content, constraints);
vector.x = Mathf.Ceil(vector.x);
vector.y = Mathf.Ceil(vector.y);
GUILayoutUtility.current.topLevel.Add(new GUILayoutEntry(vector.x, vector.x, vector.y, vector.y, style));
}
__result = GUILayoutUtility.kDummyRect;
}
return false;
}
}
[HarmonyPatch(typeof(GUIUtility), nameof(GUIUtility.GetStateObject))]
public static class GetStateObjectPatch
{
private static Dictionary<int, Object> s_StateCache = new Dictionary<int, Object>();
public static bool Prefix(Type t, int controlID, out Object __result)
{
if (!s_StateCache.TryGetValue(controlID, out __result) || __result.GetIl2CppType() != t)
{
s_StateCache[controlID] = __result = t.GetConstructor(new Il2CppReferenceArray<Type>(0)).Invoke(null, new Il2CppReferenceArray<Object>(0));
}
return false;
}
}
}
}