Compare commits

...

19 Commits

Author SHA1 Message Date
ae54aab490 Update README.md 2026-05-30 22:44:36 +00:00
4800fe995a Update README.md 2026-05-21 20:03:09 +00:00
9f5eb531b6 Linux fix w/ config. Might implement properly later. 2025-09-23 23:10:24 -04:00
b861c0236a linux is broken atm 2025-09-22 19:16:35 +00:00
18eab45ec3 do not the static 2025-09-22 00:26:30 -04:00
2ff60f8399 OnLogout, Steam platform auth in account creation. Not configurable to ensure server uses ticket to validate. 2025-09-22 00:24:04 -04:00
4649042ac1 move to internals 2025-09-21 17:46:15 -04:00
672b34b556 bump ver 2025-09-21 17:43:14 -04:00
10e8797ba2 Internal add hash to login form for all logins, fix registration patch 2025-09-21 17:42:06 -04:00
584f19b016 Merge branch 'master' of https://gitea.proxnet.dev/zombieb/undead-universal-patch-il2cpp 2025-09-19 22:37:25 -04:00
a924465919 refresh token fix 2025-09-19 22:37:22 -04:00
8ac42d3fc5 asdf 2025-09-19 22:35:28 -04:00
628977263e fix this as well 2025-09-11 19:57:49 -04:00
8b3665e873 fix this 2025-09-11 19:57:05 -04:00
9318ac75d6 haha forgot to push,,, 2025-09-11 19:54:25 -04:00
d45fc2c953 thanks sporkybean 2025-08-20 15:48:11 -04:00
909a433979 why did it copy that 2025-08-20 19:41:08 +00:00
a05d6aac7c Merge branch 'master' of https://gitea.proxnet.dev/zombieb/undead-universal-patch-il2cpp 2025-08-20 15:40:50 -04:00
b796a6b075 VideoTamperPatch, configurable outside of the BepInEx config 2025-08-20 15:40:39 -04:00
36 changed files with 631 additions and 119 deletions

View File

@@ -0,0 +1,6 @@
namespace undead_universal_patch_il2cpp.Core.Config;
public class BaseOptionConfig
{
public bool Enabled { get; set; } = false;
}

View File

@@ -22,6 +22,8 @@ namespace undead_universal_patch_il2cpp.Core.Config
public static ConfigEntry<bool> ImageSignaturePatch;
public static ConfigEntry<bool> RegistrationPatch;
public static ConfigEntry<bool> AFKPatch;
public static ConfigEntry<bool> RefreshTokenFix;
public static ConfigEntry<bool> ProtonDeviceIdFix;
}
public static class PatchConfigDefaults
{
@@ -31,16 +33,24 @@ namespace undead_universal_patch_il2cpp.Core.Config
public static bool ImageSignaturePatch = false;
public static bool RegistrationPatch = false;
public static bool AFKPatch = false;
public static bool RefreshTokenFix = false;
public static bool ProtonDeviceIdFix = false;
}
public static class ServerPatchesConfig
{
public static ConfigEntry<bool> CustomEmotes;
public static ConfigEntry<bool> CustomPhoton;
public static ConfigEntry<bool> CustomMarquee;
public static ConfigEntry<bool> CustomKnownDlls;
public static ConfigEntry<bool> CustomGameConfigurations;
}
public static class ServerPatchesConfigDefaults
{
public static bool CustomEmotes = false;
public static bool CustomPhoton = false;
public static bool CustomMarquee = false;
public static bool CustomKnownDlls = false;
public static bool CustomGameConfigurations = false;
}
public static class GameManagerConfig
{

View File

@@ -2,7 +2,7 @@ using System;
using System.IO;
using System.Text.Json;
namespace undead_universal_patch_il2cpp.Core;
namespace undead_universal_patch_il2cpp.Core.Config;
public class DediConfig<T>
{
@@ -23,7 +23,8 @@ public class DediConfig<T>
_options = options;
}
private string GetAlways() {
private string GetAlways()
{
if (!File.Exists(path))
{
File.WriteAllText(path, _default);
@@ -36,11 +37,11 @@ public class DediConfig<T>
if (loaded) return cached;
string data = GetAlways();
cached = JsonSerializer.Deserialize<T>(data, _options == null ? new JsonSerializerOptions()
cached = JsonSerializer.Deserialize<T>(data, _options ?? new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
AllowTrailingCommas = true
} : _options);
});
loaded = true;
return cached;

View File

@@ -0,0 +1,38 @@
using BestHTTP;
using System.Collections.Generic;
using undead_universal_patch_il2cpp.Core.Config;
using undead_universal_patch_il2cpp.Patches;
using UnityEngine;
namespace undead_universal_patch_il2cpp.Core.Content.CustomRecNet.CheatManager;
public class CustomCheatManager : MonoBehaviour
{
public void Start()
{
if (ServerPatchesConfig.CustomKnownDlls.Value) RecNetInteractions.postNameServerActions.Add(DownloadKnownDlls);
}
void Patch(HTTPResponse _res, List<string> dlls)
{
UniversalPatchPlugin.Log.LogInfo("Setting new KnownDlls (server)");
HilePatch.Patch([.. dlls]);
}
void OnFailed(HTTPResponse _res)
{
if (PatchConfig.HilePatch.Value)
{
UniversalPatchPlugin.Log.LogInfo("Setting new KnownDlls (local fallback)");
HilePatch.Patch();
}
}
void DownloadKnownDlls()
{
RecNetInteractions.SendRequest<List<string>>(HTTPMethods.Get, RecNet.Service.API, "/api/undead/v1/knowndlls", Patch, OnFailed);
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using BestHTTP;
using undead_universal_patch_il2cpp.Patches.UndeadGameManager;
using UnityEngine;
namespace undead_universal_patch_il2cpp.Core.Content.CustomRecNet.CustomGameManager;
public class CustomFreeSpawns : MonoBehaviour
{
public void Start()
{
RecNetInteractions.postAuthenticationActions.Add(DownloadFreeSpawns);
}
public void OnFailed([Optional] HTTPResponse res)
{
Util.ConditionalDebug($"CustomFreeSpawns failed: HTTP Error {res.StatusCode}");
}
public void Finished(HTTPResponse res, List<string> spawns)
{
FreeSpawnsPatch_Array.spawns = spawns;
}
public void DownloadFreeSpawns()
{
RecNetInteractions.SendRequest<List<string>>(HTTPMethods.Get, RecNet.Service.API, "/api/undead/v1/freespawns", Finished, OnFailed);
}
}

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using BestHTTP;
using undead_universal_patch_il2cpp.Core.Content.UndeadGameManager;
using undead_universal_patch_il2cpp.Patches.UndeadGameManager;
using UnityEngine;
namespace undead_universal_patch_il2cpp.Core.Content.CustomRecNet.CustomGameManager;
public class CustomGameManager : MonoBehaviour
{
public void Start()
{
RecNetInteractions.postAuthenticationActions.Add(DownloadGameConfigAssets);
}
public void OnFailed([Optional] HTTPResponse res)
{
Util.ConditionalDebug($"CustomGameManager failed: HTTP Error {res.StatusCode}");
}
public void Finished(HTTPResponse res, Dictionary<string, GameConfigurationAssetDTO> configs)
{
GameConfiguratorPatch.gameConfig = configs;
}
public void DownloadGameConfigAssets()
{
RecNetInteractions.SendRequest<Dictionary<string, GameConfigurationAssetDTO>>(HTTPMethods.Get, RecNet.Service.API, "/api/undead/v1/gameconfigassets", Finished, OnFailed);
}
}

View File

@@ -1,6 +1,5 @@
using System;
using BestHTTP;
using Il2CppInterop.Runtime;
using undead_universal_patch_il2cpp.Core.Config;
using undead_universal_patch_il2cpp.Patches.Photon;
using UnityEngine;
@@ -24,31 +23,32 @@ public class CustomPhoton : MonoBehaviour
public void Start()
{
RecNetInteractions.postLocalAccountActions.Add(DownloadServerConfig);
if (ServerPatchesConfig.CustomPhoton.Value) RecNetInteractions.postLocalAccountActions.Add(DownloadServerConfig);
else if (PhotonConfig.PatchPhotonIds.Value) LocalPatch();
}
void LocalPatch()
{
UniversalPatchPlugin.Log.LogInfo("Attempting Photon patch from local configuration");
PhotonPatch.Patch(new PhotonConfigDTO
{
Logging = PhotonConfig.PunLogging.Value,
AppID = PhotonConfig.AppID.Value,
VoiceAppID = PhotonConfig.VoiceAppID.Value,
SelfHosted = PhotonConfig.SelfHosted.Value,
ServerAddress = PhotonConfig.ServerAddress.Value,
ServerPort = PhotonConfig.ServerPort.Value,
ConnectionProtocol = PhotonConfig.ConnectionProtocol.Value
});
}
void OnServerConfigFailed(HTTPResponse res)
{
ServerConfigFailed = true;
if (PhotonConfig.PatchPhotonIds.Value)
{
UniversalPatchPlugin.Log.LogInfo("Attempting Photon patch from local configuration");
PhotonPatch.Patch(new PhotonConfigDTO
{
Logging = PhotonConfig.PunLogging.Value,
AppID = PhotonConfig.AppID.Value,
VoiceAppID = PhotonConfig.VoiceAppID.Value,
SelfHosted = PhotonConfig.SelfHosted.Value,
ServerAddress = PhotonConfig.ServerAddress.Value,
ServerPort = PhotonConfig.ServerPort.Value,
ConnectionProtocol = PhotonConfig.ConnectionProtocol.Value
});
}
if (PhotonConfig.PatchPhotonIds.Value) LocalPatch();
}
void ApplyPhotonConfig(HTTPResponse res, PhotonConfigDTO photonConfig)
{
if (!ServerPatchesConfig.CustomPhoton.Value) return;
try
{
UniversalPatchPlugin.Log.LogInfo("Attempting Photon patch from server configuration");

View File

@@ -15,6 +15,9 @@ public class RecNetInteractions
public static List<Action> postNameServerActions = [];
public static List<Action> postAuthenticationActions = [];
public static List<Action> postLocalAccountActions = [];
public static List<Action> onNotificationsOpen = [];
public static List<Action> onLogout = [];
public static List<Action> onPlatformInitialize = [];
public static Il2CppSystem.Uri CreateServiceUri(Service service, string pathAndQuery)
{

View File

@@ -1,9 +0,0 @@
using System.Collections.Generic;
namespace undead_universal_patch_il2cpp.Core.Content.UndeadGameManager;
public static class GameConfigurator
{
static readonly string name = "GameConfigurations";
public static DediConfig<Dictionary<string, GameConfigurationAssetDTO>> config = new(name, "{}", null);
}

View File

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

View File

@@ -1,12 +1,16 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Mapster;
using undead_universal_patch_il2cpp.Core.Config;
using undead_universal_patch_il2cpp.Core.Content.CustomRecNet.CheatManager;
using undead_universal_patch_il2cpp.Core.Content.CustomRecNet.CustomEmotes;
using undead_universal_patch_il2cpp.Core.Content.CustomRecNet.CustomGameManager;
using undead_universal_patch_il2cpp.Core.Content.CustomRecNet.CustomPhoton;
using undead_universal_patch_il2cpp.Core.Content.UndeadGameManager;
using undead_universal_patch_il2cpp.Patches.Video;
using UnityEngine;
namespace undead_universal_patch_il2cpp.Core;
@@ -32,10 +36,14 @@ public class Initialization
{
UniversalPatchPlugin.Log.LogInfo("PATCH LIST START =========================");
foreach (var method in UniversalPatchPlugin.Instance.HarmonyInstance.GetPatchedMethods())
UniversalPatchPlugin.Log.LogInfo($"- {method.ToString()}");
{
var paramStr = string.Join(" ", [.. method.GetParameters().Select(param => param.ParameterType.FullName)]);
UniversalPatchPlugin.Log.LogInfo($"- {method.DeclaringType.FullName} {method.Name}({paramStr})");
}
UniversalPatchPlugin.Log.LogInfo("PATCH LIST END =========================");
}
// to be removed in a future update
try
{
CacheChangePatchConfigs();
@@ -48,16 +56,18 @@ public class Initialization
private static void CacheChangePatchConfigs()
{
if (GameManagerConfig.AnyGameFreeSpawn.Value) GameFreeSpawns.config.Get();
if (GameManagerConfig.StaticGameConfig.Value) GameConfigurator.config.Get();
VideoTamperPatch.config.Get();
}
private static void AttachGameObjects()
{
UniversalPatchPlugin.Log.LogInfo("Attaching game objects");
if (ServerPatchesConfig.CustomEmotes.Value) UniversalPatchPlugin.Instance.AddComponent<CustomEmotes>();
UniversalPatchPlugin.Instance.AddComponent<SteamPlatform>();
UniversalPatchPlugin.Instance.AddComponent<CustomPhoton>();
if (ServerPatchesConfig.CustomEmotes.Value) UniversalPatchPlugin.Instance.AddComponent<CustomEmotes>();
if (ServerPatchesConfig.CustomKnownDlls.Value) UniversalPatchPlugin.Instance.AddComponent<CustomCheatManager>();
if (ServerPatchesConfig.CustomGameConfigurations.Value) UniversalPatchPlugin.Instance.AddComponent<CustomGameManager>();
}
private static void FetchConfigurations()
@@ -68,20 +78,25 @@ public class Initialization
"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,
PatchConfig.CertificatePatch = UniversalPatchPlugin.Instance.Config.Bind("Patches", "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,
PatchConfig.HilePatch = UniversalPatchPlugin.Instance.Config.Bind("Patches", "HilePatch", PatchConfigDefaults.HilePatch,
"The game will close after a short period of time if BepInEx is found. Enable to stop this from happening." +
"\nThis will also enable the AGRoomRuntimeConfig patch. See the README for more info.");
PatchConfig.SignalRHandshakeFix = UniversalPatchPlugin.Instance.Config.Bind("Generic", "SignalRHandshakeFix", PatchConfigDefaults.SignalRHandshakeFix,
PatchConfig.SignalRHandshakeFix = UniversalPatchPlugin.Instance.Config.Bind("Patches", "SignalRHandshakeFix", PatchConfigDefaults.SignalRHandshakeFix,
"Replace apostrophes with quotes in the initial SignalR handshake.");
PatchConfig.ImageSignaturePatch = UniversalPatchPlugin.Instance.Config.Bind("Generic", "ImageSignaturePatch", PatchConfigDefaults.ImageSignaturePatch,
PatchConfig.ImageSignaturePatch = UniversalPatchPlugin.Instance.Config.Bind("Patches", "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,
PatchConfig.RegistrationPatch = UniversalPatchPlugin.Instance.Config.Bind("Patches", "RegistrationPatch", PatchConfigDefaults.RegistrationPatch,
"Always disable the registration prompt.");
PatchConfig.AFKPatch = UniversalPatchPlugin.Instance.Config.Bind("Generic", "AFKPatch", PatchConfigDefaults.AFKPatch,
PatchConfig.AFKPatch = UniversalPatchPlugin.Instance.Config.Bind("Patches", "AFKPatch", PatchConfigDefaults.AFKPatch,
"Always present patch. Never get kicked to dorm for being AFK.");
PatchConfig.RefreshTokenFix = UniversalPatchPlugin.Instance.Config.Bind("Patches", "RefreshTokenFix", PatchConfigDefaults.RefreshTokenFix,
"Fix for the game needlessly requesting a refresh token in a loop. Cause for this issue is unknown." +
"\nDon't enable unless you know what this does.");
PatchConfig.ProtonDeviceIdFix = UniversalPatchPlugin.Instance.Config.Bind("Patches", "ProtonDeviceIdFix", PatchConfigDefaults.ProtonDeviceIdFix,
"Fix for device IDs on Wine/Proton. Enable if you get a null reference exception related to cryptography APIs during connect/token.");
ServerPatchesConfig.CustomEmotes = UniversalPatchPlugin.Instance.Config.Bind("ServerPatches", "CustomEmotes", ServerPatchesConfigDefaults.CustomEmotes,
"Modify the game's emote text with a configuration from the server.");
@@ -89,11 +104,17 @@ public class Initialization
"Patch Photon ServerSettings values with a configuration from the server." +
"\nWhen the server fails to provide a valid configuration, values from the local config will be used." +
"\nPhoton.PatchPhotonIds must be enabled for this fallback to work.");
ServerPatchesConfig.CustomMarquee = UniversalPatchPlugin.Instance.Config.Bind("ServerPatches", "CustomMarquee", ServerPatchesConfigDefaults.CustomMarquee,
"Set custom text on the ^reccenter theater marquee.");
ServerPatchesConfig.CustomKnownDlls = UniversalPatchPlugin.Instance.Config.Bind("ServerPatches", "CustomKnownDlls", ServerPatchesConfigDefaults.CustomKnownDlls,
"Add items to the list of known DLLs.");
ServerPatchesConfig.CustomGameConfigurations = UniversalPatchPlugin.Instance.Config.Bind("ServerPatches", "CustomGameConfigurations", ServerPatchesConfigDefaults.CustomGameConfigurations,
"Custom GameAssetConfigurations.");
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");
$"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");
$"Set new configurations for built-in games. See README.md");
PhotonConfig.PatchPhotonIds = UniversalPatchPlugin.Instance.Config.Bind("Photon", "PatchPhotonIds", PhotonConfigDefaults.PatchPhotonIds,
"Patch Photon configuration.");

14
Core/PluginHash.cs Normal file
View File

@@ -0,0 +1,14 @@
using System;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
namespace undead_universal_patch_il2cpp.Core;
public class PluginHash
{
public static string GetHash()
{
return BitConverter.ToString(MD5.Create().ComputeHash(File.OpenRead(Assembly.GetExecutingAssembly().Location))).Replace("-", "").ToLowerInvariant();
}
}

46
Core/SteamPlatform.cs Normal file
View File

@@ -0,0 +1,46 @@
using Il2CppInterop.Runtime;
using Steamworks;
using System;
using undead_universal_patch_il2cpp.Core.Content.CustomRecNet;
using UnityEngine;
namespace undead_universal_patch_il2cpp.Core;
public class SteamPlatform : MonoBehaviour
{
public static string AuthTicket { get; set; } = null;
void GetAuthTicket() {
SteamPlatformManager manager = PlatformManager.Instance.GetComponentInChildren<SteamPlatformManager>();
manager.GetAuthSessionTicket().Then(
DelegateSupport.ConvertDelegate<Il2CppSystem.Action<SteamPlatformManager.AuthSessionTicket>>((SteamPlatformManager.AuthSessionTicket ticket) =>
{
if (!string.IsNullOrEmpty(ticket.Error)) UniversalPatchPlugin.Log.LogError($"Could not get Steam auth ticket!: {ticket.Error}");
else
{
AuthTicket = BitConverter.ToString(ticket.Ticket).Replace("-", "").ToUpperInvariant().TrimEnd('0');
Util.ConditionalDebug($"Got new Steam auth ticket");
}
}));
}
void Start()
{
RecNetInteractions.onLogout.Add(GetAuthTicket);
RecNetInteractions.onPlatformInitialize.Add(GetAuthTicket);
/*
Every time the user logs out of matchmaking
or when PlatformManager initializes, fetch a new ticket.
The user might be logging out to the account selection screen
where they might create a new one; a Steam auth ticket is added to
the auth params in the create request and it must be valid.
It *is* possible (though very unlikely) that the user creates a new account
before the first ticket is fetched, since the method that gets a ticket is
an IPromise.
If this way of doing things isn't the best, fix it and I'll merge
*/
}
}

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 proxnet.dev
Copyright (c) 2025 proxnet.dev by @zombieb
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -2,6 +2,8 @@
[<-- Back to README.md](./README.md)
**Linux support is currently broken due to Proton bugs with .NET cryptography APIs. This may be resolved in a future update.**
No native Linux build exists for the game. Proton, however, works just fine. You can install it using the instructions below.
Reminder that EAC or Referee builds will never be supported by Undead Universal Patch.

View File

@@ -1,9 +1,12 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using BestHTTP;
using BestHTTP.Forms;
using HarmonyLib;
using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.Config;
using UnityEngine;
namespace undead_universal_patch_il2cpp.Patches
{
@@ -22,6 +25,8 @@ namespace undead_universal_patch_il2cpp.Patches
static readonly MethodInfo getHeaderMethod = requestType?.GetMethod("GetFirstHeaderValue");
static readonly MethodInfo addHeaderMethod = requestType?.GetMethod("AddHeader");
static readonly MethodInfo getFormFields = requestType?.GetMethod("GetFormFields");
static readonly PropertyInfo formImplProp = requestType?.GetProperty("FormImpl");
static readonly PropertyInfo methodTypeProp = requestType?.GetProperty("MethodType");
static readonly PropertyInfo uriProp = requestType?.GetProperty("Uri");
static readonly PropertyInfo customCertProp = requestType?.GetProperty("CustomCertificateVerifyer");
@@ -30,13 +35,13 @@ namespace undead_universal_patch_il2cpp.Patches
getHeaderMethod,
addHeaderMethod,
methodTypeProp,
getFormFields,
formImplProp,
uriProp,
customCertProp
]).Success;
static MethodBase TargetMethod() => patchResult.Method;
[HarmonyPrefix]
static void Prefix(ref object request)
{
if (PatchConfig.CertificatePatch.Value) customCertProp.GetSetMethod().Invoke(request, [null]);
@@ -59,6 +64,16 @@ namespace undead_universal_patch_il2cpp.Patches
if (newUri.Host.Contains("ns.rec.net")) newUri = new Il2CppSystem.Uri(NameserverConfig.NewUrl.Value);
bool isAccCreate = newUri.PathAndQuery.Contains("account/create");
if (isAccCreate && SteamPlatform.AuthTicket != null)
{
HTTPFormBase form = (HTTPFormBase)formImplProp.GetValue(request, null);
form.AddField("x-steam-ticket", SteamPlatform.AuthTicket);
Util.ConditionalDebug("Added Steam ticket to create request");
}
else if (isAccCreate) UniversalPatchPlugin.Log.LogError("The Steam auth ticket has not yet been fetched, account creation might fail!");
// Finish request changes
string afterUrl = newUri.ToString();
@@ -66,14 +81,13 @@ namespace undead_universal_patch_il2cpp.Patches
if (GenericConfig.LogAllRequests.Value)
{
if (GenericConfig.VerboseRequestLogs.Value) UniversalPatchPlugin.Log.LogInfo("BestHTTP Request Log\n" +
if (GenericConfig.VerboseRequestLogs.Value) UniversalPatchPlugin.Log.LogInfo($"BestHTTP Request Log{(customCertProp.GetGetMethod().Invoke(request, []) == null ? "" : " (verify)")}\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}");
else UniversalPatchPlugin.Log.LogInfo("BestHTTPProxy Request\n" +
$" {method} {afterUrl}");
}
}
}

View File

@@ -1,17 +1,15 @@
using System;
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.Content.UndeadGameManager;
namespace undead_universal_patch_il2cpp.Patches.UndeadGameManager;
[HarmonyPatch]
public class FreeSpawnsPatch_Array
{
public static List<string> config = GameFreeSpawns.config.Get();
public static List<string> spawns = null;
static PatchTypesResult patchResult = Util.ConfigPreparePatchTypes(
GameManagerConfig.AnyGameFreeSpawn,
@@ -26,6 +24,12 @@ public class FreeSpawnsPatch_Array
public static void Postfix(ref GamePlayerSpawnPoint __instance)
{
if (spawns == null)
{
Util.ConditionalDebug("FreeSpawns was not yet fetched!");
return;
}
Util.ConditionalDebug("Attempting FreeSpawns patch");
GameManager man = NetworkedSingletonMonoBehaviour<GameManager>.instance;
@@ -39,7 +43,7 @@ public class FreeSpawnsPatch_Array
Util.ConditionalDebug("CurrentGameConfiguration.configurationData was null");
return;
}
if (!config.Contains(man.CurrentGameConfiguration.configurationData.Name))
if (!spawns.Contains(man.CurrentGameConfiguration.configurationData.Name))
{
Util.ConditionalDebug($"Game '{man.CurrentGameConfiguration.configurationData.Name}' is not specified by GameFreeSpawns");
return;

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
@@ -13,7 +12,7 @@ namespace undead_universal_patch_il2cpp.Patches.UndeadGameManager;
[HarmonyPatch]
public class GameConfiguratorPatch
{
static Dictionary<string, GameConfigurationAssetDTO> gameConfig = GameConfigurator.config.Get();
public static Dictionary<string, GameConfigurationAssetDTO> gameConfig = null;
static PatchTypesResult patchResult = Util.ConfigPreparePatchTypes(
GameManagerConfig.StaticGameConfig,
@@ -28,6 +27,12 @@ public class GameConfiguratorPatch
public static void Prefix(ref GameConfigurationAsset config, ref bool showNotification)
{
if (gameConfig == null)
{
Util.ConditionalDebug("gameconfigassets was not yet fetched!");
return;
}
var conf = config;
showNotification = GenericConfig.PatchDebug.Value;

View File

@@ -1,4 +1,6 @@
using System.Reflection;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using HarmonyLib;
using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.Config;
@@ -22,19 +24,16 @@ namespace undead_universal_patch_il2cpp.Patches
static void Prefix()
{
if (PatchConfig.HilePatch.Value) HilePatch.Patch();
if (!ServerPatchesConfig.CustomKnownDlls.Value && PatchConfig.HilePatch.Value) HilePatch.Patch();
}
}
public static class HilePatch
{
public static void Patch()
public static void Patch([Optional] string[] dlls)
{
GameObject cheatManagerObject = GameObject.Find("[CheatManager]");
HileManager hileManager = cheatManagerObject.GetComponent<HileManager>();
PropertyInfo knownDllsProperty = AccessTools.Property(typeof(HileManager), "KnownDlls");
knownDllsProperty.SetValue(hileManager, new Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStringArray([
Util.ConditionalDebug($"Patching KnownDlls with {(dlls != null ? dlls.Length : 0)} new filenames");
string[] baseDlls = [
"GameAssembly.dll",
"UnityPlayer.dll",
"WinPixEventRuntime.dll",
@@ -46,9 +45,16 @@ namespace undead_universal_patch_il2cpp.Patches
"ddraw.dll",
"dxgi.dll",
"winhttp.dll"
]));
];
if (dlls != null) baseDlls = baseDlls.Concat(dlls).ToArray();
UniversalPatchPlugin.Log.LogInfo("Hile patch succeeded.");
GameObject cheatManagerObject = GameObject.Find("[CheatManager]");
HileManager hileManager = cheatManagerObject.GetComponent<HileManager>();
PropertyInfo knownDllsProperty = AccessTools.Property(typeof(HileManager), "KnownDlls");
knownDllsProperty.SetValue(hileManager, new Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStringArray(baseDlls));
UniversalPatchPlugin.Log.LogInfo($"Hile patch succeeded: {baseDlls.Length} new KnownDlls.");
}
}
}

View File

@@ -0,0 +1,29 @@
using System.Reflection;
using System.Text;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.Config;
namespace undead_universal_patch_il2cpp.Patches.Internals;
[HarmonyPatch]
public class DeviceIdBuilder
{
static PatchTypesResult typesResult = Util.ConfigPreparePatchTypes(
PatchConfig.ProtonDeviceIdFix,
"Proton quickfix for device ID errors",
"RecRoom.Utils.DeviceIdBuilder",
"CalculateOtherDeviceId"
);
static MethodBase TargetMethod() => typesResult.Method;
static bool Prepare() => typesResult.Success;
static bool Prefix(ref Il2CppStructArray<byte> __result)
{
Util.ConditionalDebug("Device ID patched");
__result = new Il2CppStructArray<byte>(Encoding.UTF8.GetBytes("Wine/Proton"));
return false;
}
}

View File

@@ -0,0 +1,30 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using undead_universal_patch_il2cpp.Core;
using UnityEngine;
namespace undead_universal_patch_il2cpp.Patches.Internals
{
/*[HarmonyPatch]
public class LockerroomOOBEFlow
{
static PatchTypesResult patchResult = Util.PreparePatchTypes(
"Event patch for changing marquee text in the reccenter",
"LockerroomOOBEFlow",
"Start"
);
static bool Prepare() => patchResult.Success;
static MethodBase TargetMethod() => patchResult.Method;
static void Postfix()
{
}
}*/
}

View File

@@ -0,0 +1,30 @@
using BestHTTP.Forms;
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using undead_universal_patch_il2cpp.Core;
namespace undead_universal_patch_il2cpp.Patches.Internals;
[HarmonyPatch]
public class LoginHelperFieldsPatch
{
static PatchTypesResult typesResult = Util.PreparePatchTypes(
"Add patch hash to the login form",
"RecNet.Login",
"LoginHelper"
);
static MethodBase TargetMethod() => typesResult.Method;
static bool Prepare() => typesResult.Success;
static void Prefix(ref HTTPUrlEncodedForm loginParams)
{
loginParams.AddField("x-patch-plugin-hash", PluginHash.GetHash());
Util.ConditionalDebug("Added hash to login form");
}
}

View File

@@ -0,0 +1,43 @@
using Il2CppInterop.Runtime;
using System.Collections.Generic;
using System.Linq;
using undead_universal_patch_il2cpp.Core.Config;
using UnityEngine;
namespace undead_universal_patch_il2cpp.Patches.Internals.NotificationTargets
{
public class MarqueeTexts : MonoBehaviour
{
void Start()
{
if (ServerPatchesConfig.CustomMarquee.Value)
{
UniversalPatchPlugin.Log.LogWarning("CustomMarquee patch is unavailable at this time. A future update may resolve this.");
//RecNetInteractions.onNotificationsOpen.Add(OnSocketOpen);
}
}
/*void OnSocketOpen()
{
var d = DelegateSupport.ConvertDelegate<RecNet.Notifications.NotificationHandler>(OnTextChange);
RecNet.Notifications.RegisterHandler("MarqueeTexts", d);
}
void OnTextChange(Dictionary<string, string> args)
{
GameObject go = GameObject.Find("DynamicObjects/[RecCenter_Marquee]");
string[] transforms = ["Text", "Now Playing", "SubText"];
var texts = transforms.Select(str => go.transform.Find(str).GetComponent<TextMesh>()).ToArray();
foreach (var t in transforms)
{
TextMesh tm = go.transform.Find(t).GetComponent<TextMesh>();
args.TryGetValue(t, out string res);
tm.text = res;
}
Core.Util.ConditionalDebug("Replaced marquee texts");
}*/
}
}

View File

@@ -0,0 +1,28 @@
using HarmonyLib;
using Il2CppInterop.Runtime;
using RecNet;
using System.Reflection;
using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.Content.CustomRecNet;
namespace undead_universal_patch_il2cpp.Patches.Internals
{
[HarmonyPatch]
public class Notifications
{
static PatchTypesResult patchResult = Core.Util.PreparePatchTypes(
"Event patch for notification availability",
"RecNet.Notifications",
"OnOpenInternal"
);
static bool Prepare() => patchResult.Success;
static MethodBase TargetMethod() => patchResult.Method;
static void Postfix(ref SignalRHubConnection hub)
{
UniversalPatchPlugin.Log.LogInfo("Running onNotificationsOpen actions");
foreach (var action in RecNetInteractions.onNotificationsOpen) action();
}
}
}

View File

@@ -0,0 +1,30 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.Content.CustomRecNet;
namespace undead_universal_patch_il2cpp.Patches.Internals;
[HarmonyPatch]
public class LogoutEvent
{
static PatchTypesResult typesResult = Util.PreparePatchTypes(
"RecNet logout event patch",
"RecNet.Login",
"OnLogout"
);
static MethodBase TargetMethod() => typesResult.Method;
static bool Prepare() => typesResult.Success;
static void Postfix()
{
Util.ConditionalDebug("Running onLogout actions");
foreach (var action in RecNetInteractions.onLogout) action();
}
}

View File

@@ -0,0 +1,29 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.Content.CustomRecNet;
namespace undead_universal_patch_il2cpp.Patches.Internals;
[HarmonyPatch]
public class SteamPlatformManagerEvent
{
static PatchTypesResult typesResult = Util.PreparePatchTypes(
"On SteamPlatformManager.Initialize",
"SteamPlatformManager",
"Initialize"
);
static MethodBase TargetMethod() => typesResult.Method;
static bool Prepare() => typesResult.Success;
static void Postfix()
{
UniversalPatchPlugin.Log.LogInfo("Running post-steam platform initialize actions");
foreach (var action in RecNetInteractions.onPlatformInitialize) action();
}
}

View File

@@ -22,20 +22,13 @@ public class AuthenticationEventPatch
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;
UniversalPatchPlugin.Log.LogInfo("Running post-authentication actions");
foreach (var action in RecNetInteractions.postAuthenticationActions)
action();
}
}

View File

@@ -0,0 +1,27 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.Config;
namespace undead_universal_patch_il2cpp.Patches.Internals;
[HarmonyPatch]
public class RefreshTokenFix
{
static PatchTypesResult patchResult = Util.ConfigPreparePatchTypes(
PatchConfig.RefreshTokenFix,
"Refresh token implementation fix for some servers",
"RecNet.Login",
"OnLocalAccountUpdated"
);
static MethodBase TargetMethod() => patchResult.Method;
static bool Prepare() => patchResult.Success;
static bool Prefix() => false;
}

View File

@@ -1,18 +0,0 @@
using System.Reflection;
using HarmonyLib;
using undead_universal_patch_il2cpp.Core;
namespace undead_universal_patch_il2cpp.Patches.Photon;
[HarmonyPatch]
public class NameserverTest
{
static PatchTypesResult patchTypesResult = Util.PreparePatchTypes(
"Photon nameserver test",
"NetworkingPeer",
"GetNameServerAddress"
);
static bool Prepare() => patchTypesResult.Success;
static MethodBase TargetMethod() => patchTypesResult.Method;
}

View File

@@ -37,7 +37,7 @@ public class PhotonPatch
PropertyInfo voiceAppIdProperty = serverSettingsType.GetRuntimeProperty("VoiceAppID");
Util.ConditionalDebug($"New Photon AppID: '{photonConfig.AppID}'");
Util.ConditionalDebug($"New Photon AppID: '{photonConfig.VoiceAppID}'");
Util.ConditionalDebug($"New Photon VoiceAppID: '{photonConfig.VoiceAppID}'");
appIdProperty.SetValue(serverSettings, photonConfig.AppID);
voiceAppIdProperty.SetValue(serverSettings, photonConfig.VoiceAppID);

View File

@@ -10,22 +10,15 @@ namespace undead_universal_patch_il2cpp.Patches
{
static PatchTypesResult patchResult = Util.ConfigPreparePatchTypes(
PatchConfig.RegistrationPatch,
"Registration patch (to be vetted, might not work)",
"UnityEngine.PlayerPrefs",
"GetInt"
"Registration patch",
"ProfileWatchUIFlow",
"Button_Email"
);
static bool Prepare() => patchResult.Success;
static MethodInfo TargetMethod() => patchResult.Method;
static void Postfix(ref string key, ref int defaultValue, ref int __result)
{
if (key.StartsWith("IncompleteRegistration-"))
{
__result = 0;
UniversalPatchPlugin.Log.LogInfo("Detour'd IncompleteRegistration pref key");
}
}
static bool Prefix() => false;
}
}

View File

@@ -0,0 +1,63 @@
using System.Reflection;
using System.Text.Json;
using HarmonyLib;
using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.Config;
namespace undead_universal_patch_il2cpp.Patches.Video;
[HarmonyPatch]
public class VideoTamperPatch
{
public static DediConfig<BaseOptionConfig> config = new("VideoTamperPatchConfig", JsonSerializer.Serialize(
new BaseOptionConfig
{
Enabled = false
}
), null);
static PatchTypesResult patchTypesResult = Util.PreparePatchTypes(
"Video anti-tamper hash disabler patch",
"RecRoom.Core.Cinematics.CinematicVideoPlayer",
"Start"
);
static bool Prepare()
{
if (config.Get().Enabled) return patchTypesResult.Success;
else return false;
}
static MethodBase TargetMethod() => patchTypesResult.Method;
static void Prefix(ref object __instance)
{
PropertyInfo useProp = __instance.GetType().GetRuntimeProperty("useAntiTamperHash");
if (useProp == null)
{
UniversalPatchPlugin.Log.LogError("VideoTamperPatch prefix: useAntiTamperHash was not found!");
return;
}
useProp.SetValue(__instance, false);
}
static void Postfix(ref object __instance)
{
PropertyInfo autoProp = __instance.GetType().GetRuntimeProperty("autoPlay");
if (autoProp == null)
{
UniversalPatchPlugin.Log.LogError("VideoTamperPatch postfix: autoPlay was not found!");
return;
}
MethodInfo playMethod = __instance.GetType().GetMethod("Play");
if (playMethod == null)
{
UniversalPatchPlugin.Log.LogError("VideoTamperPatch postfix: playMethod was not found!");
return;
}
if ((bool)autoProp.GetValue(__instance)) playMethod.Invoke(__instance, []);
}
}

View File

@@ -6,7 +6,7 @@ using undead_universal_patch_il2cpp.Core;
namespace undead_universal_patch_il2cpp;
[BepInPlugin("dev.proxnet.recroom.universalpatch.noneac.il2cpp", "Undead Universal Patch", "2.1.1")]
[BepInPlugin("dev.proxnet.recroom.universalpatch.noneac.il2cpp", "Undead Universal Patch", "3.1.0")]
public class UniversalPatchPlugin : BasePlugin
{
public static new readonly ManualLogSource Log = Logger.CreateLogSource("UUPatch");

View File

@@ -1,4 +1,9 @@
# Undead Universal Patch
## Undead Universal Patch
# Moved
Moved to https://gitea.proxnet.dev/GalvanicCorrosion/UndeadUniversalPatch
## Legacy Patch Plugin
Non-EAC, IL2CPP build patcher for Rec Room (Dec 2018\*-*Apr 3 2020)
@@ -31,7 +36,9 @@ Run BepInEx interop on your build and add the following assemblies to new folder
- `BepInEx/interop/Il2CppSystem.dll`
- `BepInEx/interop/Photon3Unity3D.dll`
- `BepInEx/interop/RecRoom.Datastructures.Runtime`
- `BepInEx/interop/RecRoom.Promises.Runtime`
- `BepInEx/interop/UnityEngine.CoreModule.dll`
- `BepInEx/interop/UnityEngine.TextRenderingModule.dll`
## Dependencies

View File

@@ -32,6 +32,14 @@ Voice will go over the same gameserver port as PUN does when selfhosting.
See [the source](./Core/Content/CustomRecNet/CustomPhoton/CustomPhoton.cs) for more information.
### `API: GET "/api/undead/v1/knowndlls"` (post-nameserver)
Expects: `List<string>`
Add custom DLL filenames to `CheatManager.KnownDlls`. `winhttp.dll` is added by default and does not need to be specified by the server.
When the server patch is enabled at the same time as the local patch (HilePatch), the local patch will be used as a fallback and will add only `winhttp.dll`.
See [the source](./Core/Content/CustomRecNet/CheatManager/CustomCheatManager.cs) for more information.
## DTOs
### `EmoteConfigDTO`

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>2.1.1</Version>
<Version>3.1.0</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
<RestoreAdditionalProjectSources>
@@ -15,6 +15,10 @@
<RootNamespace>undead_universal_patch_il2cpp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Include="Core\Content\CustomRecNet\CheatManager\CustomCheatManager.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.*" IncludeAssets="compile" />
<PackageReference Include="Mapster" Version="7.4.0" />
@@ -42,5 +46,8 @@
<Reference Include="UnityEngine.CoreModule">
<HintPath>AssemblyReferences\UnityEngine.CoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule">
<HintPath>AssemblyReferences\UnityEngine.TextRenderingModule.dll</HintPath>
</Reference>
</ItemGroup>
</Project>