backend refactor yay!!! 1.4.0

This commit is contained in:
2025-08-09 04:46:40 -04:00
parent 8d1bac8201
commit 808da23afa
21 changed files with 616 additions and 77 deletions

4
.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
[*.cs]
# CS8632: The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
dotnet_diagnostic.CS8632.severity = silent

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace undead_universal_patch_il2cpp.Core.AssetChanges;
public class RecChange
{
[JsonPropertyName("Key")]
public string RecKey { get; set; }
[JsonPropertyName("Value")]
public dynamic RecValue { get; set; }
}
public static class AGRoomChanges
{
public static string name = "AGRoomObjectChanges";
public static DediConfig<Dictionary<string, List<RecChange>>> config = new(name, "[]", null);
}

View File

@@ -1,23 +1,45 @@
using System;
using BepInEx.Configuration;
namespace undead_universal_patch_il2cpp
namespace undead_universal_patch_il2cpp.Core
{
public static class GenericConfig
{
public static ConfigEntry<bool> PatchDebug;
public static ConfigEntry<bool> LogAllRequests;
public static ConfigEntry<bool> CertificatePatch;
public static ConfigEntry<bool> HilePatch;
public static ConfigEntry<bool> SignalRHandshakeFix;
public static ConfigEntry<bool> ImageSignaturePatch;
public static ConfigEntry<bool> RegistrationPatch;
}
public static class GenericConfigDefaults
{
public static bool PatchDebug = false;
public static bool LogAllRequests = false;
public static bool CertificatePatch = false;
public static bool HilePatch = false;
public static bool SignalRHandshakeFix = false;
public static bool ImageSignaturePatch = false;
public static bool RegistrationPatch = false;
}
public static class AssetChangesConfig
{
public static ConfigEntry<bool> AGRoomChanges;
}
public static class AssetChangesConfigDefaults
{
public static bool AGRoomChanges = false;
}
public static class GameManagerConfig
{
public static ConfigEntry<bool> StaticGameTeamConfig;
public static ConfigEntry<bool> AnyGameFreeSpawn;
}
public static class GameManagerConfigDefaults
{
public static bool StaticGameTeamConfig;
public static bool AnyGameFreeSpawn;
}
public static class NameserverConfig
{

49
Core/DediConfig.cs Normal file
View File

@@ -0,0 +1,49 @@
using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
namespace undead_universal_patch_il2cpp.Core;
public class DediConfig<T>
{
private readonly string _default;
public readonly string _name;
public readonly string path;
private T? cached;
private bool loaded = false;
private readonly JsonSerializerOptions? _options;
public DediConfig(string name, string def, JsonSerializerOptions? options)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Name cannot be invalid");
_name = name;
path = $"BepInEx/config/Undead.{name}.json";
_default = def;
_options = options;
}
private string GetAlways() {
if (!File.Exists(path))
{
File.WriteAllText(path, _default);
return _default;
}
return File.ReadAllText(path);
}
public T Get()
{
if (loaded) return cached;
string data = GetAlways();
cached = JsonSerializer.Deserialize<T>(data, _options == null ? new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
AllowTrailingCommas = true
} : _options);
loaded = true;
return cached;
}
}

View File

@@ -8,7 +8,7 @@ using System.IO;
// this entire file could be better
namespace undead_universal_patch_il2cpp.Galvanic
namespace undead_universal_patch_il2cpp.Core
{
public class GalvanicAuth
{
@@ -16,20 +16,18 @@ namespace undead_universal_patch_il2cpp.Galvanic
{
PlayerPrefs.DeleteKey("GalvanicPublicKey");
PlayerPrefs.DeleteKey("GalvanicPrivateKey");
using (ECDsa ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256))
{
byte[] publicKeyBytes = ecdsa.ExportSubjectPublicKeyInfo();
string publicKeyBase64 = Convert.ToBase64String(publicKeyBytes);
using ECDsa ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
byte[] publicKeyBytes = ecdsa.ExportSubjectPublicKeyInfo();
string publicKeyBase64 = Convert.ToBase64String(publicKeyBytes);
byte[] privateKeyBytes = ecdsa.ExportPkcs8PrivateKey();
string privateKeyBase64 = Convert.ToBase64String(privateKeyBytes);
byte[] privateKeyBytes = ecdsa.ExportPkcs8PrivateKey();
string privateKeyBase64 = Convert.ToBase64String(privateKeyBytes);
PlayerPrefs.SetString("GalvanicPublicKey", publicKeyBase64);
PlayerPrefs.SetString("GalvanicPrivateKey", privateKeyBase64);
PlayerPrefs.Save();
PlayerPrefs.SetString("GalvanicPublicKey", publicKeyBase64);
PlayerPrefs.SetString("GalvanicPrivateKey", privateKeyBase64);
PlayerPrefs.Save();
Plugin.Log.LogInfo("Created new keypair for Galvanic authentication.");
}
Plugin.Log.LogInfo("Created new keypair for Galvanic authentication.");
}
public static string GetPubKey()
@@ -61,15 +59,13 @@ namespace undead_universal_patch_il2cpp.Galvanic
public static string SignPayload(string message)
{
using (ECDsa ecdsa = GetPrivKey())
{
if (ecdsa == null) return null;
using ECDsa ecdsa = GetPrivKey();
if (ecdsa == null) return null;
byte[] payloadBytes = Encoding.UTF8.GetBytes(message);
byte[] signature = ecdsa.SignData(payloadBytes, HashAlgorithmName.SHA256);
byte[] payloadBytes = Encoding.UTF8.GetBytes(message);
byte[] signature = ecdsa.SignData(payloadBytes, HashAlgorithmName.SHA256);
return Convert.ToBase64String(signature);
}
return Convert.ToBase64String(signature);
}
public static void Export()
{
@@ -146,22 +142,24 @@ namespace undead_universal_patch_il2cpp.Galvanic
public static void GetToken()
{
string url = NameserverConfig.NewUrl.Value;
UriBuilder uri = new(url);
uri.Path = "/user/auth";
uri.Query = "";
UriBuilder uri = new(url)
{
Path = "/user/auth",
Query = ""
};
var info = GalvanicAuth.GetServerInfo();
Plugin.Log.LogInfo($"Sending authentication request to server ID '{info.id}'");
if (uri.Scheme == "http") Plugin.Log.LogWarning("The server is not secure! Please use HTTPS.");
UserAuthPayload payload = new UserAuthPayload
UserAuthPayload payload = new()
{
timestamp = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds(),
nonce = Guid.NewGuid().ToString(),
server_id = info.id,
};
UserAuthRequest request = new UserAuthRequest
UserAuthRequest request = new()
{
message = payload,
signature = GalvanicAuth.SignPayload($"{JsonSerializer.Serialize(payload)}"),

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace undead_universal_patch_il2cpp.Core.UndeadGameManager;
public static class GameFreeSpawns
{
static readonly string name = "GameFreeSpawns";
public static DediConfig<List<string>> config = new(name, "[]", null);
}

View File

@@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace undead_universal_patch_il2cpp.Core.UndeadGameManager;
public class TeamConfiguration
{
public int Size { get; set; }
}
public class GameConfiguration
{
public string GameName { get; set; }
public List<TeamConfiguration> TeamConfigurations { get; set; }
}
public static class TeamConfigurator
{
static readonly string name = "TeamConfigurator";
public static DediConfig<List<GameConfiguration>> config = new(name, "[]", null);
}

View File

@@ -2,7 +2,7 @@
using System.Reflection;
using HarmonyLib;
namespace undead_universal_patch_il2cpp
namespace undead_universal_patch_il2cpp.Core
{
public class Util
{

View File

@@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using HarmonyLib;
using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.AssetChanges;
namespace undead_universal_patch_il2cpp.Patches.AssetManager
{
public static class AGConfigEntryPatch_Common
{
public static Dictionary<string, List<RecChange>> Config = AGRoomChanges.config.Get();
}
[HarmonyPatch]
public class AGConfigEntryPatch_RoomScene
{
static readonly string TargetTypeName = "AGRoomSettings";
static readonly string TargetMethodName = "TryGetRoomSceneConfigById";
static readonly string Description = "AGRoomSettings data patch";
static readonly Type targetType = AccessTools.TypeByName(TargetTypeName);
public static bool Prepare()
{
if (!AssetChangesConfig.AGRoomChanges.Value) return false;
if (targetType == null)
{
Plugin.Log.LogWarning($"'{Description}' disabled. The type for this patch was not found.");
return false;
}
if (AccessTools.Method(targetType, TargetMethodName) == null)
{
Plugin.Log.LogWarning($"'{Description}' disabled. The method for this patch was not found.");
return false;
}
Plugin.Log.LogInfo($"'{Description}' succeeded validation.");
return true;
}
static MethodBase TargetMethod() => AccessTools.Method(targetType, TargetMethodName);
static void Postfix(ref string replicationId, ref AGRoomRuntimeConfig.Room roomConfig, ref AGRoomRuntimeConfig.RoomScene roomSceneConfig)
{
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"RoomScene patch for repid '{replicationId}'");
if (!AGConfigEntryPatch_Common.Config.ContainsKey(replicationId)) return;
var changes = AGConfigEntryPatch_Common.Config[replicationId];
foreach (var change in changes)
{
PropertyInfo prop = AccessTools.Property(roomSceneConfig.GetType(), change.RecKey);
if (prop == null)
{
Plugin.Log.LogWarning($"Key '{change.RecKey}' does not exist on the room scene for replication ID '{replicationId}'!");
continue;
}
if (change.RecValue is JsonElement element)
{
object value;
if (prop.PropertyType.IsEnum) value = Enum.Parse(prop.PropertyType, element.GetRawText());
else value = JsonSerializer.Deserialize(element.GetRawText(), prop.PropertyType);
prop.SetValue(roomSceneConfig, value);
}
else prop.SetValue(roomSceneConfig, change.RecValue);
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"'{change.RecKey}' on repId '{replicationId}' is now '{change.RecValue}'");
}
}
}
[HarmonyPatch]
public class AGConfigEntryPatch_Location
{
static readonly string TargetTypeName = "AGRoomSettings";
static readonly string TargetMethodName = "TryGetLocationConfigById";
static readonly MethodInfo connectMethod = AccessTools.Method(AccessTools.TypeByName(TargetTypeName), TargetMethodName);
static bool Prepare() => AGConfigEntryPatch_RoomScene.Prepare();
static MethodBase TargetMethod() => connectMethod;
static void Postfix(ref string replicationId, ref AGRoomRuntimeConfig.Location locationConfig)
{
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"Location patch for repid '{replicationId}'");
if (!AGConfigEntryPatch_Common.Config.ContainsKey(replicationId)) return;
var changes = AGConfigEntryPatch_Common.Config[replicationId];
foreach (var change in changes)
{
PropertyInfo prop = AccessTools.Property(locationConfig.GetType(), change.RecKey);
if (prop == null)
{
Plugin.Log.LogWarning($"Key '{change.RecKey}' does not exist on the room scene for replication ID '{replicationId}'!");
continue;
}
if (change.RecValue is JsonElement element)
{
object value;
if (prop.PropertyType.IsEnum) value = Enum.Parse(prop.PropertyType, element.GetRawText());
else value = JsonSerializer.Deserialize(element.GetRawText(), prop.PropertyType);
prop.SetValue(locationConfig, value);
}
else prop.SetValue(locationConfig, change.RecValue);
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"'{change.RecKey}' on repId '{replicationId}' is now '{change.RecValue}'");
}
}
}
[HarmonyPatch]
public class AGConfigEntryPatch_Room
{
static readonly string TargetTypeName = "AGRoomSettings";
static readonly string TargetMethodName = "TryGetRoomConfigById";
static readonly MethodInfo connectMethod = AccessTools.Method(AccessTools.TypeByName(TargetTypeName), TargetMethodName);
static bool Prepare() => AGConfigEntryPatch_RoomScene.Prepare();
static MethodBase TargetMethod() => connectMethod;
static void Postfix(ref string replicationId, ref AGRoomRuntimeConfig.Room roomConfig)
{
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"Roomconfig patch for repid '{replicationId}'");
if (!AGConfigEntryPatch_Common.Config.ContainsKey(replicationId)) return;
var changes = AGConfigEntryPatch_Common.Config[replicationId];
foreach (var change in changes)
{
PropertyInfo prop = AccessTools.Property(roomConfig.GetType(), change.RecKey);
if (prop == null)
{
Plugin.Log.LogWarning($"Key '{change.RecKey}' does not exist on the room scene for replication ID '{replicationId}'!");
continue;
}
if (change.RecValue is JsonElement element)
{
object value;
if (prop.PropertyType.IsEnum) value = Enum.Parse(prop.PropertyType, element.GetRawText());
else value = JsonSerializer.Deserialize(element.GetRawText(), prop.PropertyType);
prop.SetValue(roomConfig, value);
}
else prop.SetValue(roomConfig, change.RecValue);
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"'{change.RecKey}' on repId '{replicationId}' is now '{change.RecValue}'");
}
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Reflection;
using HarmonyLib;
using undead_universal_patch_il2cpp.Core;
namespace undead_universal_patch_il2cpp.Patches
{
@@ -12,7 +13,6 @@ namespace undead_universal_patch_il2cpp.Patches
static string Description = "Unobfuscated BestHTTP request URL rewrite patch";
static readonly Type targetType = AccessTools.TypeByName(TargetTypeName);
static readonly Type requestType = AccessTools.TypeByName("BestHTTP.HTTPRequest");
static readonly MethodInfo targetMethod = AccessTools.Method(targetType, TargetMethodName, [requestType]);
static bool Prepare()
{
@@ -21,7 +21,12 @@ namespace undead_universal_patch_il2cpp.Patches
Plugin.Log.LogWarning($"'{Description}' disabled. The type for this patch was not found.");
return false;
}
if (targetMethod == null)
if (requestType == null)
{
Plugin.Log.LogWarning($"'{Description}' disabled. The request type for this patch was not found.");
return false;
}
if (AccessTools.Method(targetType, TargetMethodName, [requestType]) == null)
{
Plugin.Log.LogWarning($"'{Description}' disabled. The method for this patch was not found.");
return false;
@@ -31,7 +36,7 @@ namespace undead_universal_patch_il2cpp.Patches
return true;
}
static MethodBase TargetMethod() => targetMethod;
static MethodBase TargetMethod() => AccessTools.Method(targetType, TargetMethodName, [requestType]);
[HarmonyPrefix]
static void Prefix(ref object request)
@@ -72,11 +77,11 @@ namespace undead_universal_patch_il2cpp.Patches
{
// refresh the token if it expired
// this is somewhat inefficient, but we don't hook into many requests (see above) so it should be fine
Galvanic.GalvanicWebAuth.TokenExpiry();
GalvanicWebAuth.TokenExpiry();
Type httpRequestType = request.GetType();
MethodInfo addHeaderMethod = httpRequestType.GetMethod("AddHeader");
addHeaderMethod.Invoke(request, ["GalvanicAuth", Galvanic.GalvanicWebAuth.Token]);
addHeaderMethod.Invoke(request, ["GalvanicAuth", GalvanicWebAuth.Token]);
break;
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.UndeadGameManager;
namespace undead_universal_patch_il2cpp.Patches.UndeadGameManager;
[HarmonyPatch]
public class FreeSpawnsPatch_Array
{
public static List<string> config = GameFreeSpawns.config.Get();
public static string TargetTypeName = "GamePlayerSpawnPoint";
public static string TargetMethodName = "Awake";
public static string Description = "FreeSpawns event patch";
public static Type targetType = AccessTools.TypeByName(TargetTypeName);
public static bool Prepare()
{
if (!GameManagerConfig.AnyGameFreeSpawn.Value) return false;
if (AccessTools.Method(targetType, TargetMethodName) == null)
{
Plugin.Log.LogWarning($"'{Description}' disabled. The method for this patch was not found.");
return false;
}
Plugin.Log.LogInfo($"'{Description}' succeeded validation.");
return true;
}
public static MethodBase TargetMethod() => AccessTools.Method(targetType, TargetMethodName);
public static void Postfix(ref GamePlayerSpawnPoint __instance)
{
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("Attempting FreeSpawns patch");
GameManager man = NetworkedSingletonMonoBehaviour<GameManager>.instance;
if (man.CurrentGameConfiguration == null)
{
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("CurrentGameConfiguration was null");
return;
}
if (man.CurrentGameConfiguration.configurationData == null)
{
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("CurrentGameConfiguration.configurationData was null");
return;
}
if (!config.Contains(man.CurrentGameConfiguration.configurationData.Name))
{
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"Game '{man.CurrentGameConfiguration.configurationData.Name}' is not specified by GameFreeSpawns");
return;
}
__instance.GameTeamPlayerIndex = GameTeamPlayerIndex.ANY_INDEX;
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using RecRoom.Core.GameManagement;
using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.UndeadGameManager;
namespace undead_universal_patch_il2cpp.Patches.UndeadGameManager;
[HarmonyPatch]
public class TeamConfiguratorPatch
{
static List<GameConfiguration> gameConfig = TeamConfigurator.config.Get();
static string TargetTypeName = "GameConfigurationTool";
static string TargetMethodName = "SetGameConfiguration";
static string Description = "TeamConfigurator event patch";
static Type targetType = AccessTools.TypeByName(TargetTypeName);
static MethodInfo targetMethod = AccessTools.Method(targetType, TargetMethodName);
public static bool Prepare()
{
if (!GameManagerConfig.StaticGameTeamConfig.Value) return false;
if (targetMethod == null)
{
Plugin.Log.LogWarning($"'{Description}' disabled. The method for this patch was not found.");
return false;
}
Plugin.Log.LogInfo($"'{Description}' succeeded validation.");
return true;
}
public static MethodBase TargetMethod() => targetMethod;
public static void Prefix(ref GameConfigurationAsset config, ref bool showNotification)
{
var conf = config;
showNotification = GenericConfig.PatchDebug.Value;
var teamConf = gameConfig.Find(match => match.GameName == conf.Name);
if (teamConf == null)
{
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"'{config.Name}' does not have explicit team configurations");
return;
}
var arr = new Il2CppStructArray<TeamConfiguration>(teamConf.TeamConfigurations.Count);
for (int i = 0; i < arr.Length; i++) arr[i] = new TeamConfiguration { MaxTeamSize = teamConf.TeamConfigurations[i].Size };
config.TeamConfigurations = arr;
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"New team configuration length for '{config.Name}': {arr.Length}");
}
}

View File

@@ -1,5 +1,6 @@
using System.Reflection;
using HarmonyLib;
using undead_universal_patch_il2cpp.Core;
using UnityEngine;
namespace undead_universal_patch_il2cpp.Patches
@@ -10,11 +11,10 @@ namespace undead_universal_patch_il2cpp.Patches
static readonly string TargetTypeName = "RecNet.Core";
static readonly string TargetMethodName = "ConnectToRecNet";
static readonly string Description = "Hile Patch event method"; // It's convenient. Could patch at a different time. But this part was easy.
static readonly MethodInfo connectMethod = AccessTools.Method(AccessTools.TypeByName(TargetTypeName), TargetMethodName);
static bool Prepare()
{
if (connectMethod == null)
if (AccessTools.Method(AccessTools.TypeByName(TargetTypeName), TargetMethodName) == null)
{
Plugin.Log.LogWarning($"'{Description}' disabled. The method for this patch was not found.");
return false;
@@ -23,7 +23,7 @@ namespace undead_universal_patch_il2cpp.Patches
Plugin.Log.LogInfo($"'{Description}' succeeded validation.");
return true;
}
static MethodBase TargetMethod() => connectMethod;
static MethodBase TargetMethod() => AccessTools.Method(AccessTools.TypeByName(TargetTypeName), TargetMethodName);
static void Prefix()
{
if (GenericConfig.HilePatch.Value) HilePatch.Patch();

View File

@@ -1,6 +1,7 @@
using System;
using HarmonyLib;
using System.Reflection;
using undead_universal_patch_il2cpp.Core;
namespace undead_universal_patch_il2cpp.Patches
{
@@ -11,7 +12,6 @@ namespace undead_universal_patch_il2cpp.Patches
public static string TargetMethodName = "VerifySignature";
public static string Description = "Image signature patch";
public static Type targetType = AccessTools.TypeByName(TargetTypeName);
public static MethodBase targetMethod = targetType.GetMethod(TargetMethodName);
public static bool Prepare()
{
@@ -21,7 +21,7 @@ namespace undead_universal_patch_il2cpp.Patches
Plugin.Log.LogWarning($"'{Description}' disabled. The type for this patch was not found.");
return false;
}
if (targetMethod == null)
if (targetType.GetMethod(TargetMethodName) == null)
{
Plugin.Log.LogWarning($"'{Description}' disabled. The method for this patch was not found.");
return false;
@@ -31,11 +31,11 @@ namespace undead_universal_patch_il2cpp.Patches
return true;
}
public static MethodBase TargetMethod() => targetMethod;
public static MethodBase TargetMethod() => targetType.GetMethod(TargetMethodName);
public static bool Prefix(ref bool __result)
{
Plugin.Log.LogDebug("Verified image signature");
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("Verified image signature");
__result = true;
return false;
}

View File

@@ -1,10 +1,7 @@
using System;
using System.Reflection;
using BepInEx.Configuration;
using HarmonyLib;
using Il2CppSystem.Security.Cryptography;
using RecNet;
using UnityEngine;
using undead_universal_patch_il2cpp.Core;
namespace undead_universal_patch_il2cpp.Patches
{
@@ -15,7 +12,6 @@ namespace undead_universal_patch_il2cpp.Patches
static readonly string TargetMethodName = "ConnectUsingSettings";
static readonly string Description = "Photon ConnectUsingSettings patch event";
static readonly Type targetType = AccessTools.TypeByName(TargetTypeName);
static readonly MethodInfo targetMethod = AccessTools.Method(targetType, TargetMethodName);
static bool Prepare()
{
@@ -25,7 +21,7 @@ namespace undead_universal_patch_il2cpp.Patches
Plugin.Log.LogWarning($"'{Description}' disabled. The type for this patch was not found.");
return false;
}
if (targetMethod == null)
if (AccessTools.Method(targetType, TargetMethodName) == null)
{
Plugin.Log.LogWarning($"'{Description}' disabled. The method for this patch was not found.");
return false;
@@ -35,10 +31,11 @@ namespace undead_universal_patch_il2cpp.Patches
return true;
}
static MethodInfo TargetMethod() => targetMethod;
static MethodInfo TargetMethod() => AccessTools.Method(targetType, TargetMethodName);
static void Prefix()
{
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("Attempting Photon patch");
PhotonPatch.Patch();
}
}
@@ -101,7 +98,7 @@ namespace undead_universal_patch_il2cpp.Patches
__state.code = photonRegionIdProperty.GetValue(targetGameSession);
photonRegionIdProperty.SetValue(targetGameSession, 4);
Plugin.Log.LogDebug("Forcing masterserver");
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("Forcing masterserver");
}
static void Postfix(ref PUNNetworkManager __instance, ref HarmonyState __state)
@@ -126,7 +123,7 @@ namespace undead_universal_patch_il2cpp.Patches
}
photonRegionIdProperty.SetValue(targetGameSession, __state.code);
Plugin.Log.LogDebug("Masterserver regionId roundtrip");
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("Masterserver regionId roundtrip");
}
}
@@ -144,7 +141,6 @@ namespace undead_universal_patch_il2cpp.Patches
{
Plugin.Log.LogError("Cannot patch Photon: PhotonNetwork or ServerSettings types were not found. Is this build supported?");
return;
// safe to reference enums proceeding this
}
PropertyInfo photonServerSettingsProperty = photonNetworkType.GetRuntimeProperty("PhotonServerSettings");
@@ -161,6 +157,8 @@ namespace undead_universal_patch_il2cpp.Patches
PropertyInfo appIdProperty = serverSettingsType.GetRuntimeProperty("AppID");
PropertyInfo voiceAppIdProperty = serverSettingsType.GetRuntimeProperty("VoiceAppID");
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"New Photon AppID: '{PhotonConfig.AppID.Value}'");
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"New Photon AppID: '{PhotonConfig.VoiceAppID.Value}'");
appIdProperty.SetValue(serverSettings, PhotonConfig.AppID.Value);
voiceAppIdProperty.SetValue(serverSettings, PhotonConfig.VoiceAppID.Value);
@@ -177,8 +175,6 @@ namespace undead_universal_patch_il2cpp.Patches
Type authValuesType = AccessTools.TypeByName("AuthenticationValues");
if (authValuesType != null)
{
PropertyInfo userIdProperty = authValuesType.GetRuntimeProperty("UserId");
PropertyInfo networkingPeerProperty = photonNetworkType.GetRuntimeProperty("networkingPeer");
if (networkingPeerProperty == null)
{
@@ -193,12 +189,14 @@ namespace undead_universal_patch_il2cpp.Patches
}
string id = Util.LocalInstanceGuid;
Plugin.Log.LogDebug($"Instance GUID is {id}");
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"Instance GUID is {id}");
PhotonNetwork.AuthValues = new AuthenticationValues();
PhotonNetwork.AuthValues.UserId = id;
PhotonNetwork.AuthValues = new AuthenticationValues
{
UserId = id
};
Plugin.Log.LogDebug($"Set the authValues userId to {id}");
if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"Set the authValues userId to {id}");
}
Plugin.Log.LogInfo($"Photon patch ({(PhotonConfig.SelfHosted.Value ? "self-hosted" : "cloud")}) succeeded.");

36
Patches/Registration.cs Normal file
View File

@@ -0,0 +1,36 @@
using System;
using System.Reflection;
using HarmonyLib;
using undead_universal_patch_il2cpp.Core;
namespace undead_universal_patch_il2cpp.Patches
{
[HarmonyPatch]
public class RegistrationPatch
{
static bool Prepare()
{
if (!GenericConfig.RegistrationPatch.Value) return false;
Type unityEngineType = AccessTools.TypeByName("UnityEngine.PlayerPrefs");
MethodInfo getMethod = unityEngineType.GetMethod("GetInt", [typeof(string), typeof(int)]);
return unityEngineType != null && getMethod != null;
}
static MethodInfo TargetMethod()
{
Type tutorialManagerType = AccessTools.TypeByName("UnityEngine.PlayerPrefs");
return tutorialManagerType.GetMethod("GetInt", [ typeof(string), typeof(int) ]);
}
static void Postfix(ref string key, ref int defaultValue, ref int __result)
{
if (key.StartsWith("IncompleteRegistration-"))
{
__result = 0;
Plugin.Log.LogInfo("Detour'd IncompleteRegistration pref key");
}
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Reflection;
using HarmonyLib;
using undead_universal_patch_il2cpp.Core;
namespace undead_universal_patch_il2cpp.Patches
{
@@ -11,7 +12,6 @@ namespace undead_universal_patch_il2cpp.Patches
public static string TargetMethodName = "WithSeparator";
public static string Description = "SignalR Handshake Fix (quotes vs apostrophes)";
public static Type targetType = AccessTools.TypeByName(TargetTypeName);
public static MethodBase targetMethod = AccessTools.Method(targetType, TargetMethodName);
public static bool Prepare()
{
@@ -21,7 +21,7 @@ namespace undead_universal_patch_il2cpp.Patches
Plugin.Log.LogWarning($"'{Description}' disabled. The type for this patch was not found.");
return false;
}
if (targetMethod == null)
if (AccessTools.Method(targetType, TargetMethodName) == null)
{
Plugin.Log.LogWarning($"'{Description}' disabled. The method for this patch was not found.");
return false;
@@ -30,7 +30,7 @@ namespace undead_universal_patch_il2cpp.Patches
Plugin.Log.LogInfo($"'{Description}' succeeded validation.");
return true;
}
public static MethodBase TargetMethod() => targetMethod;
public static MethodBase TargetMethod() => AccessTools.Method(targetType, TargetMethodName);
public static void Prefix(ref string str)
{
if (str.Contains("protocol':"))

View File

@@ -1,6 +1,7 @@
using System;
using System.Reflection;
using HarmonyLib;
using undead_universal_patch_il2cpp.Core;
namespace undead_universal_patch_il2cpp.Patches
{
@@ -11,17 +12,16 @@ namespace undead_universal_patch_il2cpp.Patches
static string TargetMethodName = "NotifyServerCertificate";
static string Description = "Certificate patch";
static Type targetType = AccessTools.TypeByName(TargetTypeName);
static MethodInfo targetMethod = AccessTools.Method(targetType, TargetMethodName);
static bool Prepare()
{
if (!GenericConfig.CertificatePatch.Value) return false;
if (targetMethod == null)
if (targetType == null)
{
Plugin.Log.LogWarning($"'{Description}' disabled. The type for this patch was not found.");
return false;
}
if (targetMethod == null)
if (AccessTools.Method(targetType, TargetMethodName) == null)
{
Plugin.Log.LogWarning($"'{Description}' disabled. The method for this patch was not found.");
return false;
@@ -31,7 +31,7 @@ namespace undead_universal_patch_il2cpp.Patches
return true;
}
static MethodBase TargetMethod() => targetMethod;
static MethodBase TargetMethod() => AccessTools.Method(targetType, TargetMethodName);
static bool Prefix()
{

View File

@@ -1,12 +1,16 @@
using System;
using System.Text.Json;
using BepInEx;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.AssetChanges;
using undead_universal_patch_il2cpp.Core.UndeadGameManager;
namespace undead_universal_patch_il2cpp;
[BepInPlugin("dev.proxnet.recroom.universalpatch.noneac.il2cpp", "Undead Universal Patch", "1.3.0")]
[BepInPlugin("dev.proxnet.recroom.universalpatch.noneac.il2cpp", "Undead Universal Patch", "1.4.0")]
public class Plugin : BasePlugin
{
public static new readonly ManualLogSource Log = Logger.CreateLogSource("UUPatch");
@@ -24,20 +28,32 @@ public class Plugin : BasePlugin
{
Util.LocalInstanceGuid = Guid.NewGuid().ToString();
Log.LogInfo("It's time to Room some Rec and chew bubble gum, and I'm all out of gum");
Log.LogInfo("It's time to Room some Rec and chew bubble gum..... and I'm all out of gum");
GenericConfig.PatchDebug = Config.Bind("Generic", "PatchDebug", GenericConfigDefaults.PatchDebug,
"Enable logging messages sent by patches for debugging. Usually not needed. Enable when submitting issues or bug reports.");
GenericConfig.LogAllRequests = Config.Bind("Generic", "LogAllRequests", GenericConfigDefaults.LogAllRequests,
"Log all HTTP requests sent by the game.");
"Log all BestHTTP requests sent by the game.");
GenericConfig.CertificatePatch = Config.Bind("Generic", "CertificatePatch", GenericConfigDefaults.CertificatePatch,
"The game expects a certain certificate from rec.net when making HTTPS requests. Enable this to allow any valid certificate.");
GenericConfig.HilePatch = Config.Bind("Generic", "HilePatch", GenericConfigDefaults.HilePatch,
"The game will close after a short period of time if BepInEx is found. Enable to stop this from happening.");
"The game will close after a short period of time if BepInEx is found. Enable to stop this from happening." +
"This will also enable the AGRoomRuntimeConfig patch. See the README for more info.");
GenericConfig.SignalRHandshakeFix = Config.Bind("Generic", "SignalRHandshakeFix", GenericConfigDefaults.SignalRHandshakeFix,
"Replace apostrophes with quotes in the initial SignalR handshake.");
GenericConfig.ImageSignaturePatch = Config.Bind("Generic", "ImageSignaturePatch", GenericConfigDefaults.ImageSignaturePatch,
"When enabled, all image signatures will be valid." +
"\nWorks only if the server appends a properly formatted signature header (signature does not need to be valid)");
GenericConfig
GenericConfig.RegistrationPatch = Config.Bind("Generic", "RegistrationPatch", GenericConfigDefaults.RegistrationPatch,
"Always disable the registration prompt.");
AssetChangesConfig.AGRoomChanges = Config.Bind("AssetChangesConfig", "AGRoomChanges", AssetChangesConfigDefaults.AGRoomChanges,
$"Use patches from '{AGRoomChanges.config.path}' to change the properties of internal AGRooms. See README.md");
GameManagerConfig.AnyGameFreeSpawn = Config.Bind("GameManagerConfig", "AnyGameFreeSpawn", GameManagerConfigDefaults.AnyGameFreeSpawn,
$"Use patches from '{GameFreeSpawns.config.path}' to spawn in any valid player spawnpoint in specified games. See README.md");
GameManagerConfig.StaticGameTeamConfig = Config.Bind("GameManagerConfig", "StaticGameTeamConfig", GameManagerConfigDefaults.StaticGameTeamConfig,
$"Use patches from '{TeamConfigurator.config.path}' to set the static configuration of teams in specified games. See README.md");
PhotonConfig.PatchPhotonIds = Config.Bind("Photon", "PatchPhotonIds", PhotonConfigDefaults.PatchPhotonIds,
"Patch Photon configuration.");
PhotonConfig.PunLogging = Config.Bind("Photon", "PunLogging", PhotonConfigDefaults.PunLogging,
@@ -55,10 +71,12 @@ public class Plugin : BasePlugin
"Address of the Photon master server (ignored if not using self-hosted)");
PhotonConfig.ServerPort = Config.Bind("Photon", "ServerPort", PhotonConfigDefaults.ServerPort,
"Photon master server UDP port (ignored if not using self-hosted)");
NameserverConfig.Rewrite = Config.Bind("Nameserver", "Rewrite", NameserverConfigDefaults.Rewrite,
"Enable/disable rewriting the URL for nameserver requests.");
NameserverConfig.NewUrl = Config.Bind("Nameserver", "NewUrl", NameserverConfigDefaults.NewUrl,
"The new full URL to use when sending a nameserver request.");
GalvanicConfig.Enabled = Config.Bind("GalvanicCorrosion", "Enabled", GalvanicConfigDefaults.Enabled,
"Use Galvanic Corrosion features. Authenticates with a GC server." +
"\nThe host and protocol in Nameserver.NewUrl will be used for the server's base URL." +
@@ -76,15 +94,35 @@ public class Plugin : BasePlugin
"\nbe unnecessarily written and read to and from the disk.");
_hi.PatchAll();
if (GalvanicConfig.RegenerateKeypair.Value)
if (GalvanicConfig.Enabled.Value && GalvanicConfig.RegenerateKeypair.Value)
{
Log.LogInfo("Regenerating keypair");
Galvanic.GalvanicAuth.PrepareKeys();
GalvanicAuth.PrepareKeys();
}
if (GalvanicConfig.Export.Value == "IWantToExportMyKeys") Galvanic.GalvanicAuth.Export();
if (GalvanicConfig.Import.Value) Galvanic.GalvanicAuth.Import();
if (GalvanicConfig.Enabled.Value) Galvanic.GalvanicWebAuth.GetToken();
if (GalvanicConfig.Enabled.Value && GalvanicConfig.Export.Value == "IWantToExportMyKeys") GalvanicAuth.Export();
if (GalvanicConfig.Enabled.Value && GalvanicConfig.Import.Value) GalvanicAuth.Import();
if (GalvanicConfig.Enabled.Value) GalvanicWebAuth.GetToken();
Log.LogInfo("PATCH LIST START =========================");
foreach (var method in _hi.GetPatchedMethods()) Log.LogInfo($"- {method.ToString()}");
Log.LogInfo("PATCH LIST END =========================");
Log.LogInfo("Undead Universal IL2CPP Patch for Rec Room by @zombieb; loaded.");
try
{
CacheChangePatchConfigs();
}
catch (Exception ex)
{
Log.LogError($"Could not load change patches: {ex}");
}
}
private static void CacheChangePatchConfigs()
{
if (AssetChangesConfig.AGRoomChanges.Value) AGRoomChanges.config.Get();
if (GameManagerConfig.AnyGameFreeSpawn.Value) GameFreeSpawns.config.Get();
if (GameManagerConfig.StaticGameTeamConfig.Value) TeamConfigurator.config.Get();
}
}

View File

@@ -23,6 +23,7 @@ Run BepInEx interop on your build and add the following assemblies to new folder
* `BepInEx/interop/Photon3Unity3D.dll`
* `BepInEx/interop/RecRoom.Datastructures.Runtime`
* `BepInEx/interop/UnityEngine.CoreModule.dll`
* `UniverseLib.BIE.IL2CPP.Interop.dll` [UniverseLib](https://github.com/yukieiji/UniverseLib); (can be obtained from UnityExplorer)
### Linux users
Wine currently has problems generating the keypair used in Galvanic authentication.
@@ -34,4 +35,76 @@ If you're unsure how to start your build on linux:
* Add Rec Room as a non-steam game
* Set the compatibility mode to the latest stable version of proton
* Use protontricks to add the doorstop DLL
* Start Rec Room through Steam
* Start Rec Room through Steam
### AGRoomRuntimeConfig Patches
Properties of the object in the patch file will be used to set the properties of the respective object type in the game's assembly.
Each object type can be configured as follows:
* `AGRoomRuntimeConfig.Room` or `AGRoomRuntimeConfig.RoomScene` or `AGRoomRuntimeConfig.Location`
- Set the key to the replicationId of the object you want to change
* Any object with a replicationId can be changed
- Objects in the list have properties "Key" and "Value"
* The key represents the name of the property to modify
* The value will replace the current value in the assembly
- Example:
```json
{"76d98498-60a1-430c-ab76-b54a29b7a163": [
{
"Key": "ReleaseStatus",
"Value": 0
}
]}
```
will prevent you from loading into the dorm, because you can't go to editor-only rooms.
Although you can set rooms with ReleaseStatus:0 to 2 if you'd like to go to those rooms.
* `GameConfigurationAsset`
- Set the key to the name of the object you want to change
* Ex. "Crescendo Of The Blood Moon" (CrescendoOfTheBloodMoon_Config, 20200306)
- Value must match a certain schema (example):
```json
{
"Name": "Crescendo Of The Blood Moon",
"TeamConfigurations": [
{
"MaxTeamSize": 1
},
{
"MaxTeamSize": 2
},
{
"MaxTeamSize": 3
}
],
"TeamSelectionMethod": 0,
"ManualGameStartRequirement": {
"MinPlayerCount": 1,
"MinTeamCount": 1,
"MinTeamSize": 1
},
"AutomaticGameStartSupported": false,
"AutomaticGameStartRequirement": {
"MinPlayerCount": -1,
"MinTeamCount": -1,
"MinTeamSize": -1
},
"GameStartDelay": 10.0,
"EndGameResultsDuration": 15.0
}
```
* You must set every property.
* Only `MaxTeamSize` is supported for every team in `TeamConfigurations`.
* See `TeamSelectionMethod` enum members for more information.
* Changes to arrays (lists) are made in order. To change the value of a team size in the middle of an array, you must duplicate the previous data.
- Example:
```json
{ "Crescendo Of The Blood Moon": {
"AutomaticGameStartSupported": true,
"TeamConfigurations": [
{
"MaxTeamSize": 18
}
]
} }
```

View File

@@ -4,7 +4,7 @@
<TargetFramework>net6.0</TargetFramework>
<AssemblyName>undead_universal_patch_il2cpp</AssemblyName>
<Description>Non-EAC, IL2CPP build patcher for Rec Room (Late 2018*-*April-2020) </Description>
<Version>1.3.0</Version>
<Version>1.4.0</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
<RestoreAdditionalProjectSources>
@@ -38,9 +38,8 @@
<Reference Include="UnityEngine.CoreModule">
<HintPath>AssemblyReferences\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="BasePatches\Configurables\" />
<Reference Include="Google.Protobuf">
<HintPath>AssemblyReferences\Google.Protobuf.dll</HintPath>
</Reference>
</ItemGroup>
</Project>