diff --git a/.editorconfig b/.editorconfig index df5ff76..c50a9bb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,3 +2,6 @@ # CS8632: The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. dotnet_diagnostic.CS8632.severity = silent + +# CA1050: Declare types in namespaces +dotnet_diagnostic.CA1050.severity = silent \ No newline at end of file diff --git a/Core/AssetChanges/AGRoomChanges.cs b/Core/AssetChanges/AGRoomChanges.cs deleted file mode 100644 index 8d37156..0000000 --- a/Core/AssetChanges/AGRoomChanges.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -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>> config = new(name, "[]", null); -} \ No newline at end of file diff --git a/Core/Config.cs b/Core/Config/Config.cs similarity index 84% rename from Core/Config.cs rename to Core/Config/Config.cs index 0552ec7..f22aefe 100644 --- a/Core/Config.cs +++ b/Core/Config/Config.cs @@ -1,11 +1,21 @@ using BepInEx.Configuration; -namespace undead_universal_patch_il2cpp.Core +namespace undead_universal_patch_il2cpp.Core.Config { public static class GenericConfig { public static ConfigEntry PatchDebug; public static ConfigEntry LogAllRequests; + public static ConfigEntry VerboseRequestLogs; + } + public static class GenericConfigDefaults + { + public static bool PatchDebug = false; + public static bool LogAllRequests = false; + public static bool VerboseRequestLogs = false; + } + public static class PatchConfig + { public static ConfigEntry CertificatePatch; public static ConfigEntry HilePatch; public static ConfigEntry SignalRHandshakeFix; @@ -13,10 +23,8 @@ namespace undead_universal_patch_il2cpp.Core public static ConfigEntry RegistrationPatch; public static ConfigEntry AFKPatch; } - public static class GenericConfigDefaults + public static class PatchConfigDefaults { - 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; @@ -24,13 +32,13 @@ namespace undead_universal_patch_il2cpp.Core public static bool RegistrationPatch = false; public static bool AFKPatch = false; } - public static class AssetChangesConfig + public static class ServerPatchesConfig { - public static ConfigEntry AGRoomChanges; + public static ConfigEntry CustomEmotes; } - public static class AssetChangesConfigDefaults + public static class ServerPatchesConfigDefaults { - public static bool AGRoomChanges = false; + public static bool CustomEmotes = false; } public static class GameManagerConfig { @@ -56,9 +64,9 @@ namespace undead_universal_patch_il2cpp.Core { public static ConfigEntry PatchPhotonIds; public static ConfigEntry PunLogging; - public static ConfigEntry SelfHosted; public static ConfigEntry AppID; public static ConfigEntry VoiceAppID; + public static ConfigEntry SelfHosted; public static ConfigEntry ServerAddress; public static ConfigEntry ServerPort; } @@ -66,9 +74,9 @@ namespace undead_universal_patch_il2cpp.Core { public static bool PatchPhotonIds = false; public static bool PunLogging = false; - public static bool SelfHosted = false; public static string AppID = "replace-me-please"; public static string VoiceAppID = "replace-me-please"; + public static bool SelfHosted = false; public static string ServerAddress = "127.0.0.1"; public static int ServerPort = 5055; } diff --git a/Core/CustomRecNet/CustomEmotes/RecNetEmotes.cs b/Core/CustomRecNet/CustomEmotes/RecNetEmotes.cs new file mode 100644 index 0000000..1420434 --- /dev/null +++ b/Core/CustomRecNet/CustomEmotes/RecNetEmotes.cs @@ -0,0 +1,64 @@ +using UnityEngine; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Mapster; +using System; +using System.Linq; +using undead_universal_patch_il2cpp.Core.Config; + +namespace undead_universal_patch_il2cpp.Core.CustomRecNet.CustomEmotes; + +public class EmoteConfigDTO +{ + public string UniqueName { get; set; } + public string NewText { get; set; } + public string RoomChatText { get; set; } + public string FacialExpression { get; set; } + public bool ForceEmoteBubble { get; set; } + public bool OnlyBroadcastToTeam { get; set; } +} + +public class EmoteConfig +{ + public static void Patch(List emotes) + { + UniversalPatchPlugin.Log.LogInfo("Setting new emotes"); + + var internalEmotes = RecRoom.AGUI.Expresso.ContextualEmotesSettings.ConfigAsset.Emotes.ToList(); + foreach (var emote in emotes) + { + var internalEmote = internalEmotes.Find(match => match.emoteUniqueName == emote.UniqueName); + if (internalEmote != null) + { + emote.Adapt(internalEmote); + } + } + + Util.ConditionalDebug($"{emotes.Count} new emote configurations"); + } +} + +public class CustomEmotes : MonoBehaviour +{ + private static readonly ConcurrentQueue mainThreadQueue = new ConcurrentQueue(); + + public void Start() + { + if (ServerPatchesConfig.CustomEmotes.Value) + RecNetInteractions.postAuthenticationActions.Add(DownloadCustomEmotes); + } + + void Update() + { + while (mainThreadQueue.TryDequeue(out var action)) + { + try { action(); } + catch (Exception ex) { UniversalPatchPlugin.Log.LogError(ex); } + } + } + + void DownloadCustomEmotes() + { + RecNetInteractions.SendRequest>(BestHTTP.HTTPMethods.Get, RecNet.Service.API, "/api/undead/v1/emotes", EmoteConfig.Patch); + } +} \ No newline at end of file diff --git a/Core/CustomRecNet/CustomRecNet.cs b/Core/CustomRecNet/CustomRecNet.cs new file mode 100644 index 0000000..96714e7 --- /dev/null +++ b/Core/CustomRecNet/CustomRecNet.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using BestHTTP; +using Il2CppInterop.Runtime; +using RecNet; +using RecRoom.Async; + +namespace undead_universal_patch_il2cpp.Core.CustomRecNet; + +public class RecNetInteractions +{ + public static string AccessToken { get; set; } + + public static List postNameServerActions = []; + public static List postAuthenticationActions = []; + + public static Il2CppSystem.Uri CreateServiceUri(Service service, string pathAndQuery) + { + Il2CppSystem.Uri uri = RecNet.Core.GetServiceUri(service) ?? throw new Exception("Service is not available"); + var newUri = new Il2CppSystem.Uri(uri, pathAndQuery); + + return newUri; + } + + public static bool HasNameserverConnected() + { + return RecNet.Core.ServiceUris.Count > 1; + } + + public static void SendRequest(HTTPMethods method, Service service, string requestUri, Action reqFinished) + { + var res = RecNet.Core.SendRequest(method, service, requestUri); + + res.Then(DelegateSupport.ConvertDelegate>((HTTPResponse res) => + { + try + { + var data = JsonSerializer.Deserialize(res.DataAsText); + reqFinished(data); + } + catch (Exception ex) + { + UniversalPatchPlugin.Log.LogError($"'{requestUri}' failed\n{ex}"); + } + })); + } +} \ No newline at end of file diff --git a/Core/Init.cs b/Core/Init.cs new file mode 100644 index 0000000..850a139 --- /dev/null +++ b/Core/Init.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using Mapster; +using undead_universal_patch_il2cpp.Core.Config; +using undead_universal_patch_il2cpp.Core.CustomRecNet.CustomEmotes; +using undead_universal_patch_il2cpp.Core.UndeadGameManager; + +namespace undead_universal_patch_il2cpp.Core; + +public class Initialization +{ + public static void Initialize() + { + Util.LocalInstanceGuid = Guid.NewGuid().ToString(); + + AddMapsterGlobalTypeConfigs(); + FetchConfigurations(); + AttachGameObjects(); + + try + { + UniversalPatchPlugin.Instance.HarmonyInstance.PatchAll(); + } + catch (Exception ex) + { + UniversalPatchPlugin.Log.LogError($"--------> PATCH ERROR! Please report this in the gitea repo!\n{ex}"); + } + + if (GenericConfig.PatchDebug.Value) + { + UniversalPatchPlugin.Log.LogInfo("PATCH LIST START ========================="); + foreach (var method in UniversalPatchPlugin.Instance.HarmonyInstance.GetPatchedMethods()) + UniversalPatchPlugin.Log.LogInfo($"- {method.ToString()}"); + UniversalPatchPlugin.Log.LogInfo("PATCH LIST END ========================="); + } + + try + { + CacheChangePatchConfigs(); + } + catch (Exception ex) + { + UniversalPatchPlugin.Log.LogError($"Could not load change patches: {ex}"); + } + } + + private static void CacheChangePatchConfigs() + { + if (GameManagerConfig.AnyGameFreeSpawn.Value) GameFreeSpawns.config.Get(); + if (GameManagerConfig.StaticGameConfig.Value) GameConfigurator.config.Get(); + } + + private static void AttachGameObjects() + { + UniversalPatchPlugin.Log.LogInfo("Attaching game objects"); + UniversalPatchPlugin.Instance.AddComponent(); + } + + private static void FetchConfigurations() + { + GenericConfig.PatchDebug = UniversalPatchPlugin.Instance.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 = UniversalPatchPlugin.Instance.Config.Bind("Generic", "LogAllRequests", GenericConfigDefaults.LogAllRequests, + "Log all BestHTTP requests sent by the game."); + GenericConfig.VerboseRequestLogs = UniversalPatchPlugin.Instance.Config.Bind("Generic", "VerboseRequestLogs", GenericConfigDefaults.VerboseRequestLogs, + "Add additional request information to BestHTTPProxy logs. Requires LogAllRequest to be enabled."); + PatchConfig.CertificatePatch = UniversalPatchPlugin.Instance.Config.Bind("Generic", "CertificatePatch", PatchConfigDefaults.CertificatePatch, + "The game expects a certain certificate from rec.net when making HTTPS requests. Enable this to allow any valid certificate."); + PatchConfig.HilePatch = UniversalPatchPlugin.Instance.Config.Bind("Generic", "HilePatch", PatchConfigDefaults.HilePatch, + "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."); + PatchConfig.SignalRHandshakeFix = UniversalPatchPlugin.Instance.Config.Bind("Generic", "SignalRHandshakeFix", PatchConfigDefaults.SignalRHandshakeFix, + "Replace apostrophes with quotes in the initial SignalR handshake."); + PatchConfig.ImageSignaturePatch = UniversalPatchPlugin.Instance.Config.Bind("Generic", "ImageSignaturePatch", PatchConfigDefaults.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)"); + PatchConfig.RegistrationPatch = UniversalPatchPlugin.Instance.Config.Bind("Generic", "RegistrationPatch", PatchConfigDefaults.RegistrationPatch, + "Always disable the registration prompt."); + PatchConfig.AFKPatch = UniversalPatchPlugin.Instance.Config.Bind("Generic", "AFKPatch", PatchConfigDefaults.AFKPatch, + "Always present patch. Never get kicked to dorm."); + + ServerPatchesConfig.CustomEmotes = UniversalPatchPlugin.Instance.Config.Bind("ServerPatches", "CustomEmotes", ServerPatchesConfigDefaults.CustomEmotes, + "Modify the game's emote text with a configuration from the server. Requires a custom server."); + + GameManagerConfig.AnyGameFreeSpawn = UniversalPatchPlugin.Instance.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.StaticGameConfig = UniversalPatchPlugin.Instance.Config.Bind("GameManagerConfig", "StaticGameConfig", GameManagerConfigDefaults.StaticGameConfig, + $"Use patches from '{GameConfigurator.config.path}' to set new configurations for built-in games. See README.md"); + + PhotonConfig.PatchPhotonIds = UniversalPatchPlugin.Instance.Config.Bind("Photon", "PatchPhotonIds", PhotonConfigDefaults.PatchPhotonIds, + "Patch Photon configuration."); + PhotonConfig.PunLogging = UniversalPatchPlugin.Instance.Config.Bind("Photon", "PunLogging", PhotonConfigDefaults.PunLogging, + "Enable all logging sent by Photon/PUN/Voice (useful for server debugging)"); + PhotonConfig.SelfHosted = UniversalPatchPlugin.Instance.Config.Bind("Photon", "IsSelfHosted", PhotonConfigDefaults.SelfHosted, + "When enabled, use a self-hosted 'OnPremises' PhotonSocketServer. (EXPERIMENTAL)" + + "\nWhen disabled, AppID and VoiceAppID are sent to Photon Cloud and a cloud masterserver is used."); + PhotonConfig.AppID = UniversalPatchPlugin.Instance.Config.Bind("Photon", "AppID", PhotonConfigDefaults.AppID, + "The new target (PUN) App ID from the Photon dashboard." + + "\nWhen self-hosting, this should be the name of your application (either 'Master' or 'Game')"); + PhotonConfig.VoiceAppID = UniversalPatchPlugin.Instance.Config.Bind("Photon", "VoiceAppID", PhotonConfigDefaults.VoiceAppID, + "The new target Voice App ID from the Photon dashboard." + + "\nWhen self-hosting, this value is ignored, since Photon voice in Rec Room is based on PUN."); + PhotonConfig.ServerAddress = UniversalPatchPlugin.Instance.Config.Bind("Photon", "ServerAddress", PhotonConfigDefaults.ServerAddress, + "Address of the Photon master server (ignored if not using self-hosted)"); + PhotonConfig.ServerPort = UniversalPatchPlugin.Instance.Config.Bind("Photon", "ServerPort", PhotonConfigDefaults.ServerPort, + "Photon master server UDP port (ignored if not using self-hosted)"); + + NameserverConfig.Rewrite = UniversalPatchPlugin.Instance.Config.Bind("Nameserver", "Rewrite", NameserverConfigDefaults.Rewrite, + "Enable/disable rewriting the URL for nameserver requests."); + NameserverConfig.NewUrl = UniversalPatchPlugin.Instance.Config.Bind("Nameserver", "NewUrl", NameserverConfigDefaults.NewUrl, + "The new full URL to use when sending a nameserver request."); + } + + private static void AddMapsterGlobalTypeConfigs() + { + TypeAdapterConfig.GlobalSettings + .ForType, Il2CppStructArray>() + .MapWith(src => new Il2CppStructArray(src.Select(x => x.Adapt()).ToArray())); + TypeAdapterConfig.GlobalSettings + .ForType, Il2CppReferenceArray>() + .MapWith(src => new Il2CppReferenceArray(src.Select(x => x.Adapt()).ToArray())); + TypeAdapterConfig.GlobalSettings + .ForType() + .Map(dest => dest.emoteUniqueName, source => source.UniqueName) + .Map(dest => dest.emoteText, source => source.NewText) + .Map(dest => dest.emoteRoomChatText, source => source.RoomChatText) + .Map(dest => dest.facialExpression, source => source.FacialExpression) + .Map(dest => dest.forceEmoteBubble, source => source.ForceEmoteBubble) + .Map(dest => dest.onlyBroadcastToTeam, source => source.OnlyBroadcastToTeam); + } +} \ No newline at end of file diff --git a/Core/Util.cs b/Core/Util.cs index ebebb43..7f6b8cf 100644 --- a/Core/Util.cs +++ b/Core/Util.cs @@ -1,7 +1,123 @@ -namespace undead_universal_patch_il2cpp.Core +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using BepInEx.Configuration; +using HarmonyLib; +using undead_universal_patch_il2cpp.Core.Config; + +namespace undead_universal_patch_il2cpp.Core { + public class PatchTypesResult + { + public string Description; + public Type Type; + public MethodInfo Method; + public bool Success; + } + public class Util { + private static readonly PatchTypesResult UnsuccessfulPatchResult = new() + { + Type = null, + Method = null, + Success = false + }; + public static string LocalInstanceGuid { set; get; } + + public static void ConditionalDebug(string msg) + { + if (GenericConfig.PatchDebug.Value) UniversalPatchPlugin.Log.LogDebug(msg); + } + + public static PatchTypesResult ConfigsPreparePatchTypes(ConfigEntry[] config, string description, string targetTypeName, string methodName) + { + if (config.All(conf => conf.Value)) return VerifyPatchTypes(description, targetTypeName, methodName); + else return UnsuccessfulPatchResult; + } + + public static PatchTypesResult ConfigPreparePatchTypes(ConfigEntry config, string description, string targetTypeName, string methodName) + { + if (config.Value) return VerifyPatchTypes(description, targetTypeName, methodName); + else return UnsuccessfulPatchResult; + } + + public static PatchTypesResult PreparePatchTypes(string description, string targetTypeName, string methodName) + { + return VerifyPatchTypes(description, targetTypeName, methodName); + } + + public static PatchTypesResult PreparePatchSpecificTypes(string description, string targetTypeName, string methodName, string[] generics) + { + return VerifyPatchTypes(description, targetTypeName, methodName, [.. generics]); + } + + private static PatchTypesResult VerifyPatchTypes(string description, string targetTypeName, string methodName, List generics = null) + { + ConditionalDebug($"Loading patch '{description}'"); + + Type targetType = AccessTools.TypeByName(targetTypeName); + if (targetType == null) + { + UniversalPatchPlugin.Log.LogWarning($"Patch '{description}' disabled. The type for this patch was not found."); + return UnsuccessfulPatchResult; + } + + List types = null; + if (generics != null) + { + types = generics.Select(AccessTools.TypeByName).ToList(); + if (!types.All(type => type != null)) + { + UniversalPatchPlugin.Log.LogWarning($"Patch '{description}' disabled. One or more generics for this patch were not found."); + return UnsuccessfulPatchResult; + } + } + + MethodInfo method; + if (generics != null && types != null) + { + ConditionalDebug($"Fetching method type: Type '{targetType.Name}', Method '{methodName}', Generics: {types.Count}"); + method = AccessTools.Method(targetType, methodName, [.. types]); + } + else + { + ConditionalDebug($"Fetching method type: Type '{targetType.Name}', Method '{methodName}'"); + method = AccessTools.Method(targetType, methodName); + } + if (method == null) + { + UniversalPatchPlugin.Log.LogWarning($"Patch '{description}' disabled. The method for this patch was not found."); + return UnsuccessfulPatchResult; + } + + UniversalPatchPlugin.Log.LogInfo($"Patch '{description}' succeeded validation."); + return new PatchTypesResult + { + Description = description, + Type = targetType, + Method = method, + Success = true + }; + } + + public static PatchTypesResult PostRequireTypes(PatchTypesResult result, object[] objects) + { + if (result.Success) + { + if (!objects.All(obj => obj != null)) + { + UniversalPatchPlugin.Log.LogError($"'{result.Description}' failed: A postrequire type was not found."); + return UnsuccessfulPatchResult; + } + else return result; + } + else return UnsuccessfulPatchResult; + } + } } diff --git a/Patches/AFKPatch.cs b/Patches/AFKPatch.cs index 1a7feab..19c873a 100644 --- a/Patches/AFKPatch.cs +++ b/Patches/AFKPatch.cs @@ -1,30 +1,26 @@ using System.Reflection; using HarmonyLib; using undead_universal_patch_il2cpp.Core; +using undead_universal_patch_il2cpp.Core.Config; namespace undead_universal_patch_il2cpp.Patches; [HarmonyPatch] public class AFKPatch { - static readonly string TargetTypeName = "Player"; - static readonly string TargetMethodName = "UpdateAFKStatus"; - static readonly string Description = "Always present, never AFK"; + static PatchTypesResult patchResult = Util.ConfigPreparePatchTypes( + PatchConfig.AFKPatch, + "Always present, never AFK", + "Player", + "UpdateAFKStatus" + ); - static bool Prepare() - { - if (AccessTools.Method(AccessTools.TypeByName(TargetTypeName), TargetMethodName) == null) - { - Plugin.Log.LogWarning($"'{Description}' disabled. The method for this patch was not found."); - return false; - } + static bool Prepare() => patchResult.Success; + + static MethodBase TargetMethod() => patchResult.Method; - Plugin.Log.LogInfo($"'{Description}' succeeded validation."); - return true; - } - static MethodBase TargetMethod() => AccessTools.Method(AccessTools.TypeByName(TargetTypeName), TargetMethodName); static void Prefix(ref bool userIsPresent) { - if (GenericConfig.AFKPatch.Value) userIsPresent = true; + if (PatchConfig.AFKPatch.Value) userIsPresent = true; } } diff --git a/Patches/AssetManager/AGConfigEntryPatches.cs b/Patches/AssetManager/AGConfigEntryPatches.cs deleted file mode 100644 index 3ca7450..0000000 --- a/Patches/AssetManager/AGConfigEntryPatches.cs +++ /dev/null @@ -1,152 +0,0 @@ -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> 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}'"); - } - } - } - -} \ No newline at end of file diff --git a/Patches/BestHTTP.cs b/Patches/BestHTTP.cs index 8e179d7..39b7fd8 100644 --- a/Patches/BestHTTP.cs +++ b/Patches/BestHTTP.cs @@ -1,62 +1,55 @@ using System; using System.Reflection; +using BestHTTP; using HarmonyLib; using undead_universal_patch_il2cpp.Core; +using undead_universal_patch_il2cpp.Core.Config; namespace undead_universal_patch_il2cpp.Patches { [HarmonyPatch] - public class BestHTTP_Unob + public class BestHTTPProxy { - static string TargetTypeName = "BestHTTP.HTTPManager"; - static string TargetMethodName = "SendRequest"; - static string Description = "Unobfuscated BestHTTP request URL rewrite patch"; - static readonly Type targetType = AccessTools.TypeByName(TargetTypeName); + static PatchTypesResult patchResult = Util.PreparePatchSpecificTypes( + "BestHTTP request URL rewrite patch", + "BestHTTP.HTTPManager", + "SendRequest", + [ + "BestHTTP.HTTPRequest", + ] + ); static readonly Type requestType = AccessTools.TypeByName("BestHTTP.HTTPRequest"); - static bool Prepare() - { - if (targetType == null) - { - Plugin.Log.LogWarning($"'{Description}' disabled. The type for this patch was not found."); - return false; - } - 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; - } + static readonly MethodInfo getHeaderMethod = requestType?.GetMethod("GetFirstHeaderValue"); + static readonly PropertyInfo methodTypeProp = requestType?.GetProperty("MethodType"); + static readonly PropertyInfo uriProp = requestType?.GetProperty("Uri"); + static readonly PropertyInfo customCertProp = requestType?.GetProperty("CustomCertificateVerifyer"); - Plugin.Log.LogInfo($"'{Description}' succeeded validation."); - return true; - } - - static MethodBase TargetMethod() => AccessTools.Method(targetType, TargetMethodName, [requestType]); + static bool Prepare() => Util.PostRequireTypes(patchResult, [ + getHeaderMethod, + methodTypeProp, + uriProp, + customCertProp + ]).Success; + static MethodBase TargetMethod() => patchResult.Method; + [HarmonyPrefix] static void Prefix(ref object request) { - PropertyInfo uriProperty = AccessTools.Property(requestType, "Uri"); - if (uriProperty == null) - { - Plugin.Log.LogFatal("BestHTTP_Unob failed: uriProperty was null."); - return; - } + if (PatchConfig.CertificatePatch.Value) customCertProp.GetSetMethod().Invoke(request, [null]); - var uriInstance = (Il2CppSystem.Uri)uriProperty.GetValue(request, null); + string contentType = (string)getHeaderMethod.Invoke(request, ["Content-Type"]); + HTTPMethods method = (HTTPMethods)methodTypeProp.GetGetMethod().Invoke(request, []); + + var uriInstance = (Il2CppSystem.Uri)uriProp.GetValue(request, null); if (uriInstance == null) { - Plugin.Log.LogFatal("BestHTTP_Unob failed: uriInstance was null."); + UniversalPatchPlugin.Log.LogFatal("BestHTTPProxy failed: uriInstance was null."); return; } - if (GenericConfig.LogAllRequests.Value) Plugin.Log.LogInfo($"BestHTTP_Unob request b-URL: {uriInstance.ToString()}"); - if (!NameserverConfig.Rewrite.Value) return; + string beforeUrl = uriInstance.ToString(); Il2CppSystem.Uri newUri = new(uriInstance.ToString()); @@ -64,8 +57,22 @@ namespace undead_universal_patch_il2cpp.Patches if (newUri.ToString().Contains("ns.rec.net")) newUri = new Il2CppSystem.Uri(NameserverConfig.NewUrl.Value); - if (GenericConfig.LogAllRequests.Value) Plugin.Log.LogInfo($"BestHTTP_Unob request a-URL: {newUri.ToString()}"); - uriProperty.SetValue(request, NameserverConfig.Rewrite.Value ? newUri : uriInstance, null); + // Finish request changes + + string afterUrl = newUri.ToString(); + uriProp.SetValue(request, NameserverConfig.Rewrite.Value ? newUri : uriInstance, null); + + if (GenericConfig.LogAllRequests.Value) + { + if (GenericConfig.VerboseRequestLogs.Value) UniversalPatchPlugin.Log.LogInfo("BestHTTP Request Log\n" + + $" URL Before : {beforeUrl}\n" + + $" URL After : {(beforeUrl == afterUrl ? "(unmodified)" : afterUrl)}\n" + + $" Method : {method}\n" + + $" Content-Type : {contentType ?? "(not set)"}"); + else UniversalPatchPlugin.Log.LogInfo("BestHTTPProxy Request Log\n" + + $" Before : {beforeUrl}\n" + + $" After : {afterUrl}"); + } } } } diff --git a/Patches/GameManager/FreeSpawns.cs b/Patches/GameManager/FreeSpawns.cs index 841054c..96743e1 100644 --- a/Patches/GameManager/FreeSpawns.cs +++ b/Patches/GameManager/FreeSpawns.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Reflection; using HarmonyLib; using undead_universal_patch_il2cpp.Core; +using undead_universal_patch_il2cpp.Core.Config; using undead_universal_patch_il2cpp.Core.UndeadGameManager; namespace undead_universal_patch_il2cpp.Patches.UndeadGameManager; @@ -12,44 +13,35 @@ public class FreeSpawnsPatch_Array { public static List 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); + static PatchTypesResult patchResult = Util.ConfigPreparePatchTypes( + GameManagerConfig.AnyGameFreeSpawn, + "FreeSpawns event patch", + "GamePlayerSpawnPoint", + "Awake" + ); - 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; - } + public static bool Prepare() => patchResult.Success; - Plugin.Log.LogInfo($"'{Description}' succeeded validation."); - return true; - } - - public static MethodBase TargetMethod() => AccessTools.Method(targetType, TargetMethodName); + public static MethodBase TargetMethod() => patchResult.Method; public static void Postfix(ref GamePlayerSpawnPoint __instance) { - if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("Attempting FreeSpawns patch"); + Util.ConditionalDebug("Attempting FreeSpawns patch"); GameManager man = NetworkedSingletonMonoBehaviour.instance; if (man.CurrentGameConfiguration == null) { - if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("CurrentGameConfiguration was null"); + Util.ConditionalDebug("CurrentGameConfiguration was null"); return; } if (man.CurrentGameConfiguration.configurationData == null) { - if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("CurrentGameConfiguration.configurationData was null"); + Util.ConditionalDebug("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"); + Util.ConditionalDebug($"Game '{man.CurrentGameConfiguration.configurationData.Name}' is not specified by GameFreeSpawns"); return; } diff --git a/Patches/GameManager/GameConfigurator.cs b/Patches/GameManager/GameConfigurator.cs index a47cb3f..93be8ea 100644 --- a/Patches/GameManager/GameConfigurator.cs +++ b/Patches/GameManager/GameConfigurator.cs @@ -4,7 +4,7 @@ using System.Reflection; using HarmonyLib; using Mapster; using RecRoom.Core.GameManagement; -using RecRoom.Protobuf; +using undead_universal_patch_il2cpp.Core.Config; using undead_universal_patch_il2cpp.Core; using undead_universal_patch_il2cpp.Core.UndeadGameManager; @@ -15,29 +15,16 @@ public class GameConfiguratorPatch { static Dictionary gameConfig = GameConfigurator.config.Get(); - static string TargetTypeName = "GameConfigurationTool"; - static string TargetMethodName = "SetGameConfiguration"; - static string Description = "GameConfigurator event patch"; + static PatchTypesResult patchResult = Util.ConfigPreparePatchTypes( + GameManagerConfig.StaticGameConfig, + "GameConfigurator event patch", + "GameConfigurationTool", + "SetGameConfiguration" + ); - static Type targetType = AccessTools.TypeByName(TargetTypeName); - static MethodInfo targetMethod = AccessTools.Method(targetType, TargetMethodName); + static bool Prepare() => patchResult.Success; - public static bool Prepare() - { - if (!GameManagerConfig.StaticGameConfig.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 GameConfigurationData res; + static MethodBase TargetMethod() => patchResult.Method; public static void Prefix(ref GameConfigurationAsset config, ref bool showNotification) { @@ -47,14 +34,14 @@ public class GameConfiguratorPatch var gameConf = gameConfig.GetValueOrDefault(conf.Name, null); if (gameConf == null) { - if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"Game '{conf.Name}' does not have external configuration"); + if (GenericConfig.PatchDebug.Value) UniversalPatchPlugin.Log.LogDebug($"Game '{conf.Name}' does not have external configuration"); return; } gameConf.Adapt(conf); config = conf; - if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"Applied external game configuration for '{config.Name}'"); + if (GenericConfig.PatchDebug.Value) UniversalPatchPlugin.Log.LogDebug($"Applied external game configuration for '{config.Name}'"); } } \ No newline at end of file diff --git a/Patches/HilePatch.cs b/Patches/HilePatch.cs index d1695ae..d3ca85a 100644 --- a/Patches/HilePatch.cs +++ b/Patches/HilePatch.cs @@ -1,6 +1,7 @@ using System.Reflection; using HarmonyLib; using undead_universal_patch_il2cpp.Core; +using undead_universal_patch_il2cpp.Core.Config; using UnityEngine; namespace undead_universal_patch_il2cpp.Patches @@ -8,27 +9,23 @@ namespace undead_universal_patch_il2cpp.Patches [HarmonyPatch] public class ConnectToRecNetPatchEvent { - 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 PatchTypesResult patchResult = Util.ConfigPreparePatchTypes( + PatchConfig.HilePatch, + "Hile Patch event method", + "RecNet.Core", + "ConnectToRecNet" + ); - static bool Prepare() - { - if (AccessTools.Method(AccessTools.TypeByName(TargetTypeName), TargetMethodName) == null) - { - Plugin.Log.LogWarning($"'{Description}' disabled. The method for this patch was not found."); - return false; - } + static bool Prepare() => patchResult.Success; + + static MethodBase TargetMethod() => patchResult.Method; - Plugin.Log.LogInfo($"'{Description}' succeeded validation."); - return true; - } - static MethodBase TargetMethod() => AccessTools.Method(AccessTools.TypeByName(TargetTypeName), TargetMethodName); static void Prefix() { - if (GenericConfig.HilePatch.Value) HilePatch.Patch(); + if (PatchConfig.HilePatch.Value) HilePatch.Patch(); } } + public static class HilePatch { public static void Patch() @@ -51,7 +48,7 @@ namespace undead_universal_patch_il2cpp.Patches "winhttp.dll" ])); - Plugin.Log.LogInfo("Hile patch succeeded."); + UniversalPatchPlugin.Log.LogInfo("Hile patch succeeded."); } } } diff --git a/Patches/ImageSignature.cs b/Patches/ImageSignature.cs index 9c4ff90..8b76fcf 100644 --- a/Patches/ImageSignature.cs +++ b/Patches/ImageSignature.cs @@ -2,40 +2,27 @@ using HarmonyLib; using System.Reflection; using undead_universal_patch_il2cpp.Core; +using undead_universal_patch_il2cpp.Core.Config; namespace undead_universal_patch_il2cpp.Patches { [HarmonyPatch] public class ImageSignaturePatch { - public static string TargetTypeName = "Images"; - public static string TargetMethodName = "VerifySignature"; - public static string Description = "Image signature patch"; - public static Type targetType = AccessTools.TypeByName(TargetTypeName); + static PatchTypesResult patchResult = Util.ConfigPreparePatchTypes( + PatchConfig.ImageSignaturePatch, + "Image signature patch", + "RecNet.Images", + "VerifySignature" + ); - public static bool Prepare() + static bool Prepare() => patchResult.Success; + + static MethodBase TargetMethod() => patchResult.Method; + + static bool Prefix(ref bool __result) { - if (!GenericConfig.ImageSignaturePatch.Value) return false; - if (targetType == null) - { - Plugin.Log.LogWarning($"'{Description}' disabled. The type for this patch was not found."); - return false; - } - if (targetType.GetMethod(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() => targetType.GetMethod(TargetMethodName); - - public static bool Prefix(ref bool __result) - { - if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("Verified image signature"); + Util.ConditionalDebug("Verified image signature"); __result = true; return false; } diff --git a/Patches/PhotonPatch.cs b/Patches/PhotonPatch.cs index 78dd9fd..5e9cb86 100644 --- a/Patches/PhotonPatch.cs +++ b/Patches/PhotonPatch.cs @@ -2,40 +2,27 @@ using System.Reflection; using HarmonyLib; using undead_universal_patch_il2cpp.Core; +using undead_universal_patch_il2cpp.Core.Config; namespace undead_universal_patch_il2cpp.Patches { [HarmonyPatch] public class PhotonPatchEvent { - static readonly string TargetTypeName = "PhotonNetwork"; - static readonly string TargetMethodName = "ConnectUsingSettings"; - static readonly string Description = "Photon ConnectUsingSettings patch event"; - static readonly Type targetType = AccessTools.TypeByName(TargetTypeName); + static PatchTypesResult patchResult = Util.ConfigPreparePatchTypes( + PhotonConfig.PatchPhotonIds, + "Photon ConnectUsingSettings event path", + "PhotonNetwork", + "ConnectUsingSettings" + ); - static bool Prepare() - { - if (!PhotonConfig.PatchPhotonIds.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; - } + static bool Prepare() => patchResult.Success; - Plugin.Log.LogInfo($"'{Description}' succeeded validation."); - return true; - } - - static MethodInfo TargetMethod() => AccessTools.Method(targetType, TargetMethodName); + static MethodInfo TargetMethod() => patchResult.Method; static void Prefix() { - if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("Attempting Photon patch"); + Util.ConditionalDebug("Attempting Photon patch"); PhotonPatch.Patch(); } } @@ -43,36 +30,24 @@ namespace undead_universal_patch_il2cpp.Patches [HarmonyPatch] public class ForceSelfHostedPhoton { - class HarmonyState + class HarmonyState { public object code; } - static readonly string TargetTypeName = "PUNNetworkManager"; - static readonly string TargetMethodName = "OnConnectedToMaster"; - static readonly string Description = "Force JoinOrCreateRoom when connected to master"; - static readonly Type targetType = AccessTools.TypeByName(TargetTypeName); - static readonly MethodInfo targetMethod = AccessTools.Method(targetType, TargetMethodName); + static PatchTypesResult patchResult = Util.ConfigsPreparePatchTypes( + [ + PhotonConfig.PatchPhotonIds, + PhotonConfig.SelfHosted + ], + "Force JoinOrCreateRoom when connected to master", + "PUNNetworkManager", + "OnConnectedToMaster" + ); - static bool Prepare() - { - if (!(PhotonConfig.PatchPhotonIds.Value && PhotonConfig.SelfHosted.Value)) return false; - if (targetType == null) - { - Plugin.Log.LogWarning($"'{Description}' disabled. The type for this patch was not found."); - return false; - } - if (targetMethod == null) - { - Plugin.Log.LogWarning($"'{Description}' disabled. The method for this patch was not found."); - return false; - } + static bool Prepare() => patchResult.Success; - Plugin.Log.LogInfo($"'{Description}' succeeded validation."); - return true; - } - - static MethodBase TargetMethod() => targetMethod; + static MethodBase TargetMethod() => patchResult.Method; static void Prefix(ref PUNNetworkManager __instance, ref HarmonyState __state) { @@ -80,25 +55,25 @@ namespace undead_universal_patch_il2cpp.Patches PropertyInfo targetGameSessionProperty = __instance.GetType().GetRuntimeProperty("targetGameSession"); if (targetGameSessionProperty == null) { - Plugin.Log.LogFatal("Cannot force masterserver: targetGameSessionProperty was null."); + UniversalPatchPlugin.Log.LogFatal("Cannot force masterserver: targetGameSessionProperty was null."); return; } var targetGameSession = targetGameSessionProperty.GetValue(__instance); if (targetGameSession == null) { - Plugin.Log.LogFatal("Cannot force masterserver: targetGameSession was null."); + UniversalPatchPlugin.Log.LogFatal("Cannot force masterserver: targetGameSession was null."); return; } PropertyInfo photonRegionIdProperty = targetGameSession.GetType().GetRuntimeProperty("PhotonRegionId"); if (photonRegionIdProperty == null) { - Plugin.Log.LogFatal("Cannot force masterserver: photonRegionIdProperty was null."); + UniversalPatchPlugin.Log.LogFatal("Cannot force masterserver: photonRegionIdProperty was null."); return; } __state.code = photonRegionIdProperty.GetValue(targetGameSession); photonRegionIdProperty.SetValue(targetGameSession, 4); - if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("Forcing masterserver"); + Util.ConditionalDebug("Forcing masterserver"); } static void Postfix(ref PUNNetworkManager __instance, ref HarmonyState __state) @@ -106,24 +81,24 @@ namespace undead_universal_patch_il2cpp.Patches PropertyInfo targetGameSessionProperty = __instance.GetType().GetRuntimeProperty("targetGameSession"); if (targetGameSessionProperty == null) { - Plugin.Log.LogFatal("Cannot force masterserver postfix: targetGameSessionProperty was null."); + UniversalPatchPlugin.Log.LogFatal("Cannot force masterserver postfix: targetGameSessionProperty was null."); return; } var targetGameSession = targetGameSessionProperty.GetValue(__instance); if (targetGameSession == null) { - Plugin.Log.LogFatal("Cannot force masterserver postfix: targetGameSession was null."); + UniversalPatchPlugin.Log.LogFatal("Cannot force masterserver postfix: targetGameSession was null."); return; } PropertyInfo photonRegionIdProperty = targetGameSession.GetType().GetRuntimeProperty("PhotonRegionId"); if (photonRegionIdProperty == null) { - Plugin.Log.LogFatal("Cannot force masterserver: photonRegionIdProperty was null."); + UniversalPatchPlugin.Log.LogFatal("Cannot force masterserver: photonRegionIdProperty was null."); return; } photonRegionIdProperty.SetValue(targetGameSession, __state.code); - if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug("Masterserver regionId roundtrip"); + Util.ConditionalDebug("Masterserver regionId roundtrip"); } } @@ -139,7 +114,7 @@ namespace undead_universal_patch_il2cpp.Patches if (photonNetworkType == null || serverSettingsType == null) { - Plugin.Log.LogError("Cannot patch Photon: PhotonNetwork or ServerSettings types were not found. Is this build supported?"); + UniversalPatchPlugin.Log.LogError("Cannot patch Photon: PhotonNetwork or ServerSettings types were not found. Is this build supported?"); return; } @@ -157,8 +132,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}'"); + Util.ConditionalDebug($"New Photon AppID: '{PhotonConfig.AppID.Value}'"); + Util.ConditionalDebug($"New Photon AppID: '{PhotonConfig.VoiceAppID.Value}'"); appIdProperty.SetValue(serverSettings, PhotonConfig.AppID.Value); voiceAppIdProperty.SetValue(serverSettings, PhotonConfig.VoiceAppID.Value); @@ -178,28 +153,28 @@ namespace undead_universal_patch_il2cpp.Patches PropertyInfo networkingPeerProperty = photonNetworkType.GetRuntimeProperty("networkingPeer"); if (networkingPeerProperty == null) { - Plugin.Log.LogError("Cannot patch Photon: networkingPeerProperty was null. Is this build supported?"); + UniversalPatchPlugin.Log.LogError("Cannot patch Photon: networkingPeerProperty was null. Is this build supported?"); return; } if (networkingPeerProperty.GetValue(null) == null) { - Plugin.Log.LogError("Cannot patch Photon: networkingPeerInstance was null. Is this build supported?"); + UniversalPatchPlugin.Log.LogError("Cannot patch Photon: networkingPeerInstance was null. Is this build supported?"); return; } string id = Core.Util.LocalInstanceGuid; - if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"Instance GUID is {id}"); + Util.ConditionalDebug($"Instance GUID is {id}"); PhotonNetwork.AuthValues = new AuthenticationValues { UserId = id }; - if (GenericConfig.PatchDebug.Value) Plugin.Log.LogDebug($"Set the authValues userId to {id}"); + Util.ConditionalDebug($"Set the authValues userId to {id}"); } - Plugin.Log.LogInfo($"Photon patch ({(PhotonConfig.SelfHosted.Value ? "self-hosted" : "cloud")}) succeeded."); + UniversalPatchPlugin.Log.LogInfo($"Photon patch ({(PhotonConfig.SelfHosted.Value ? "self-hosted" : "cloud")}) succeeded."); } } } diff --git a/Patches/PostAuthentication.cs b/Patches/PostAuthentication.cs new file mode 100644 index 0000000..0b96e87 --- /dev/null +++ b/Patches/PostAuthentication.cs @@ -0,0 +1,44 @@ +using System.Reflection; +using HarmonyLib; +using Il2CppInterop.Runtime; +using RecRoom.Async; +using undead_universal_patch_il2cpp.Core; +using undead_universal_patch_il2cpp.Core.CustomRecNet; + +namespace undead_universal_patch_il2cpp.Patches; + +[HarmonyPatch] +public class AuthenticationEventPatch +{ + static PatchTypesResult patchResult = Util.PreparePatchTypes( + "RecNet AccessToken event patch", + "RecNet.Login", + "SetAccessToken" + ); + + static readonly PropertyInfo hasAccessTokenProperty = patchResult.Type?.GetProperty("HasAccessToken"); + + static bool Prepare() => Util.PostRequireTypes(patchResult, [ + hasAccessTokenProperty + ]).Success; + + static MethodBase TargetMethod() => patchResult.Method; + + + private static bool RanPostActions { get; set; } = false; + static void Postfix(ref string accessToken) + { + UniversalPatchPlugin.Log.LogInfo("Intercepted AccessToken"); + RecNetInteractions.AccessToken = accessToken; + + if (!RanPostActions) + { + bool value = (bool)hasAccessTokenProperty.GetValue(patchResult.Type); + if (value) UniversalPatchPlugin.Log.LogInfo("Running post-authentication actions"); + foreach (var action in RecNetInteractions.postAuthenticationActions) + action(); + } + else RanPostActions = true; + + } +} \ No newline at end of file diff --git a/Patches/PostNameserver.cs b/Patches/PostNameserver.cs new file mode 100644 index 0000000..afd2974 --- /dev/null +++ b/Patches/PostNameserver.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using Il2CppInterop.Runtime; +using RecRoom.Async; +using undead_universal_patch_il2cpp.Core; +using undead_universal_patch_il2cpp.Core.CustomRecNet; + +namespace undead_universal_patch_il2cpp.Patches; + +public class NameserverConnectEventPatch +{ + static PatchTypesResult patchResult = Util.PreparePatchTypes( + "Nameserver connect event patch", + "RecNet.Core", + "ConnectToRecNet" + ); + + static bool Prepare() => patchResult.Success; + + static MethodBase TargetMethod() => patchResult.Method; + + static void Postfix(ref IPromise __result) + { + __result.Finally(DelegateSupport.ConvertDelegate(() => + { + if (RecNetInteractions.HasNameserverConnected()) + { + UniversalPatchPlugin.Log.LogInfo("Running post-nameserver actions"); + foreach (var action in RecNetInteractions.postAuthenticationActions) action(); + } + else Util.ConditionalDebug("The nameserver request did not resolve successfully, skipping post-nameserver actions."); + })); + } +} \ No newline at end of file diff --git a/Patches/Registration.cs b/Patches/Registration.cs index a1edc78..c6cbc9e 100644 --- a/Patches/Registration.cs +++ b/Patches/Registration.cs @@ -1,36 +1,31 @@ -using System; -using System.Reflection; +using System.Reflection; using HarmonyLib; using undead_universal_patch_il2cpp.Core; +using undead_universal_patch_il2cpp.Core.Config; namespace undead_universal_patch_il2cpp.Patches { [HarmonyPatch] public class RegistrationPatch { - static bool Prepare() - { - if (!GenericConfig.RegistrationPatch.Value) return false; + static PatchTypesResult patchResult = Util.ConfigPreparePatchTypes( + PatchConfig.RegistrationPatch, + "Registration patch (to be vetted, might not work)", + "UnityEngine.PlayerPrefs", + "GetInt" + ); - Type unityEngineType = AccessTools.TypeByName("UnityEngine.PlayerPrefs"); - MethodInfo getMethod = unityEngineType.GetMethod("GetInt", [typeof(string), typeof(int)]); + static bool Prepare() => patchResult.Success; - return unityEngineType != null && getMethod != null; - } - - static MethodInfo TargetMethod() - { - Type tutorialManagerType = AccessTools.TypeByName("UnityEngine.PlayerPrefs"); - return tutorialManagerType.GetMethod("GetInt", [ typeof(string), typeof(int) ]); - } + static MethodInfo TargetMethod() => patchResult.Method; 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"); + UniversalPatchPlugin.Log.LogInfo("Detour'd IncompleteRegistration pref key"); } } } -} +} \ No newline at end of file diff --git a/Patches/SignalRHandshakeFix.cs b/Patches/SignalRHandshakeFix.cs index 0d5916f..283f8a0 100644 --- a/Patches/SignalRHandshakeFix.cs +++ b/Patches/SignalRHandshakeFix.cs @@ -1,37 +1,25 @@ -using System; -using System.Reflection; +using System.Reflection; using HarmonyLib; using undead_universal_patch_il2cpp.Core; +using undead_universal_patch_il2cpp.Core.Config; namespace undead_universal_patch_il2cpp.Patches { [HarmonyPatch] public class SignalRHandshakeFix { - public static string TargetTypeName = "JsonProtocol"; - public static string TargetMethodName = "WithSeparator"; - public static string Description = "SignalR Handshake Fix (quotes vs apostrophes)"; - public static Type targetType = AccessTools.TypeByName(TargetTypeName); + static PatchTypesResult patchResult = Util.ConfigPreparePatchTypes( + PatchConfig.SignalRHandshakeFix, + "SignalR Handshake Fix (quotes vs apostrophes)", + "JsonProtocol", + "WithSeparator" + ); - public static bool Prepare() - { - if (!GenericConfig.SignalRHandshakeFix.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; - } + static bool Prepare() => patchResult.Success; - Plugin.Log.LogInfo($"'{Description}' succeeded validation."); - return true; - } - public static MethodBase TargetMethod() => AccessTools.Method(targetType, TargetMethodName); - public static void Prefix(ref string str) + static MethodBase TargetMethod() => patchResult.Method; + + static void Prefix(ref string str) { if (str.Contains("protocol':")) { diff --git a/Patches/TLS.cs b/Patches/TLS.cs deleted file mode 100644 index 258fb95..0000000 --- a/Patches/TLS.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Reflection; -using HarmonyLib; -using undead_universal_patch_il2cpp.Core; - -namespace undead_universal_patch_il2cpp.Patches -{ - [HarmonyPatch] - public class TLSPatch - { - static string TargetTypeName = "Org.BouncyCastle.Crypto.Tls.LegacyTlsAuthentication"; - static string TargetMethodName = "NotifyServerCertificate"; - static string Description = "Certificate patch"; - static Type targetType = AccessTools.TypeByName(TargetTypeName); - - static bool Prepare() - { - if (!GenericConfig.CertificatePatch.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 bool Prefix() - { - return false; - } - - } -} diff --git a/Plugin.cs b/Plugin.cs index 89f1ccf..625c198 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -1,116 +1,34 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using BepInEx; +using BepInEx; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using HarmonyLib; -using Il2CppInterop.Runtime.InteropTypes.Arrays; -using Mapster; 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.4.1")] -public class Plugin : BasePlugin +[BepInPlugin("dev.proxnet.recroom.universalpatch.noneac.il2cpp", "Undead Universal Patch", "2.0.0")] +public class UniversalPatchPlugin : BasePlugin { public static new readonly ManualLogSource Log = Logger.CreateLogSource("UUPatch"); - static Harmony _hi = new("dev.proxnet.recroom.universalpatch.noneac.il2cpp"); + public Harmony HarmonyInstance = new("dev.proxnet.recroom.universalpatch.noneac.il2cpp"); public override bool Unload() { Log.LogInfo("Destroying."); - _hi.UnpatchSelf(); + HarmonyInstance.UnpatchSelf(); return true; } + public static UniversalPatchPlugin Instance; + public override void Load() { - Util.LocalInstanceGuid = Guid.NewGuid().ToString(); - + Instance = this; 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 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." + - "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.RegistrationPatch = Config.Bind("Generic", "RegistrationPatch", GenericConfigDefaults.RegistrationPatch, - "Always disable the registration prompt."); - GenericConfig.AFKPatch = Config.Bind("Generic", "AFKPatch", GenericConfigDefaults.AFKPatch, - "Always present patch. Never get kicked to dorm."); + Initialization.Initialize(); - 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.StaticGameConfig = Config.Bind("GameManagerConfig", "StaticGameConfig", GameManagerConfigDefaults.StaticGameConfig, - $"Use patches from '{GameConfigurator.config.path}' to set new configurations for built-in games. See README.md"); - - PhotonConfig.PatchPhotonIds = Config.Bind("Photon", "PatchPhotonIds", PhotonConfigDefaults.PatchPhotonIds, - "Patch Photon configuration."); - PhotonConfig.PunLogging = Config.Bind("Photon", "PunLogging", PhotonConfigDefaults.PunLogging, - "Enable all logging sent by Photon/PUN/Voice (useful for server debugging)"); - PhotonConfig.SelfHosted = Config.Bind("Photon", "IsSelfHosted", PhotonConfigDefaults.SelfHosted, - "When enabled, use a self-hosted 'OnPremises' PhotonSocketServer. (EXPERIMENTAL)" + - "\nWhen disabled, AppID and VoiceAppID are sent to Photon Cloud and a cloud masterserver is used."); - PhotonConfig.AppID = Config.Bind("Photon", "AppID", PhotonConfigDefaults.AppID, - "The new target (PUN) App ID from the Photon dashboard." + - "\nWhen self-hosting, this should be the name of your application (either 'Master' or 'Game')"); - PhotonConfig.VoiceAppID = Config.Bind("Photon", "VoiceAppID", PhotonConfigDefaults.VoiceAppID, - "The new target Voice App ID from the Photon dashboard." + - "\nWhen self-hosting, this value is ignored, since Photon voice in Rec Room is based on PUN."); - PhotonConfig.ServerAddress = Config.Bind("Photon", "ServerAddress", PhotonConfigDefaults.ServerAddress, - "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."); - - _hi.PatchAll(); - - 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}"); - } - - TypeAdapterConfig.GlobalSettings - .ForType, Il2CppStructArray>() - .MapWith(src => new Il2CppStructArray(src.Select(x => x.Adapt()).ToArray())); - TypeAdapterConfig.GlobalSettings - .ForType, Il2CppReferenceArray>() - .MapWith(src => new Il2CppReferenceArray(src.Select(x => x.Adapt()).ToArray())); - } - - private static void CacheChangePatchConfigs() - { - if (AssetChangesConfig.AGRoomChanges.Value) AGRoomChanges.config.Get(); - if (GameManagerConfig.AnyGameFreeSpawn.Value) GameFreeSpawns.config.Get(); - if (GameManagerConfig.StaticGameConfig.Value) GameConfigurator.config.Get(); + Log.LogInfo("Undead Universal Patch (IL2CPP) for Rec Room by @zombieb; loaded."); } } \ No newline at end of file diff --git a/README.md b/README.md index 8661e79..8f06e11 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Non-EAC, IL2CPP build patcher for Rec Room (Dec 2018\*-*Apr 3 2020) -**Currently only supports unobfuscated builds** +**Only supports unobfuscated builds. EAC build support will never be provided.** Part two of two universal patches. The Mono patch is available at https://git.proxnet.dev/zombieb/undead-universal-patch-mono. diff --git a/undead-universal-patch-il2cpp.csproj b/undead-universal-patch-il2cpp.csproj index 3d9c729..d396b41 100644 --- a/undead-universal-patch-il2cpp.csproj +++ b/undead-universal-patch-il2cpp.csproj @@ -3,8 +3,8 @@ net6.0 undead_universal_patch_il2cpp - Non-EAC, IL2CPP build patcher for Rec Room (Late 2018*-*April-2020) - 1.4.1 + Non-EAC, IL2CPP build patcher for Rec Room (Late 2018*-*April 2020) + 2.0.0 true latest @@ -36,6 +36,9 @@ AssemblyReferences\RecRoom.Datastructures.Runtime.dll + + AssemblyReferences\RecRoom.Promises.Runtime.dll + AssemblyReferences\UnityEngine.CoreModule.dll