Compare commits

..

31 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
0f0dcf6a70 Update SERVERS.md 2025-08-20 00:32:43 +00:00
cef766f23d Update SERVERS.md
add auth mentions
2025-08-20 00:31:56 +00:00
6ab33a36ae Update LINUX.md 2025-08-20 00:26:00 +00:00
1a92e2dea7 Update LINUX.md
add back link
2025-08-20 00:25:45 +00:00
8fd4e6f92d Update README.md 2025-08-20 00:25:09 +00:00
526f5264f8 Update LINUX.md 2025-08-20 00:07:41 +00:00
5d54c59817 Add Linux docs 2025-08-20 00:06:54 +00:00
95cfd7ebb0 fix that again 2025-08-18 22:40:58 -04:00
2c647e7103 fix links 2025-08-18 22:40:18 -04:00
b80d92eef8 and this 2025-08-18 22:34:15 -04:00
aeb6a8f6b2 Merge branch 'master' of https://gitea.proxnet.dev/zombieb/undead-universal-patch-il2cpp 2025-08-18 22:33:53 -04:00
eeae720d56 UpdatePhotonThrottling only runs when using Photon over UDP, is disabled for TCP.
Added Photon server config documentation
2025-08-18 22:33:48 -04:00
37 changed files with 715 additions and 145 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> ImageSignaturePatch;
public static ConfigEntry<bool> RegistrationPatch; public static ConfigEntry<bool> RegistrationPatch;
public static ConfigEntry<bool> AFKPatch; public static ConfigEntry<bool> AFKPatch;
public static ConfigEntry<bool> RefreshTokenFix;
public static ConfigEntry<bool> ProtonDeviceIdFix;
} }
public static class PatchConfigDefaults public static class PatchConfigDefaults
{ {
@@ -31,16 +33,24 @@ namespace undead_universal_patch_il2cpp.Core.Config
public static bool ImageSignaturePatch = false; public static bool ImageSignaturePatch = false;
public static bool RegistrationPatch = false; public static bool RegistrationPatch = false;
public static bool AFKPatch = false; public static bool AFKPatch = false;
public static bool RefreshTokenFix = false;
public static bool ProtonDeviceIdFix = false;
} }
public static class ServerPatchesConfig public static class ServerPatchesConfig
{ {
public static ConfigEntry<bool> CustomEmotes; public static ConfigEntry<bool> CustomEmotes;
public static ConfigEntry<bool> CustomPhoton; 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 class ServerPatchesConfigDefaults
{ {
public static bool CustomEmotes = false; public static bool CustomEmotes = false;
public static bool CustomPhoton = 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 public static class GameManagerConfig
{ {

View File

@@ -2,7 +2,7 @@ using System;
using System.IO; using System.IO;
using System.Text.Json; using System.Text.Json;
namespace undead_universal_patch_il2cpp.Core; namespace undead_universal_patch_il2cpp.Core.Config;
public class DediConfig<T> public class DediConfig<T>
{ {
@@ -23,7 +23,8 @@ public class DediConfig<T>
_options = options; _options = options;
} }
private string GetAlways() { private string GetAlways()
{
if (!File.Exists(path)) if (!File.Exists(path))
{ {
File.WriteAllText(path, _default); File.WriteAllText(path, _default);
@@ -36,11 +37,11 @@ public class DediConfig<T>
if (loaded) return cached; if (loaded) return cached;
string data = GetAlways(); string data = GetAlways();
cached = JsonSerializer.Deserialize<T>(data, _options == null ? new JsonSerializerOptions() cached = JsonSerializer.Deserialize<T>(data, _options ?? new JsonSerializerOptions()
{ {
PropertyNameCaseInsensitive = true, PropertyNameCaseInsensitive = true,
AllowTrailingCommas = true AllowTrailingCommas = true
} : _options); });
loaded = true; loaded = true;
return cached; 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 System;
using BestHTTP; using BestHTTP;
using Il2CppInterop.Runtime;
using undead_universal_patch_il2cpp.Core.Config; using undead_universal_patch_il2cpp.Core.Config;
using undead_universal_patch_il2cpp.Patches.Photon; using undead_universal_patch_il2cpp.Patches.Photon;
using UnityEngine; using UnityEngine;
@@ -24,13 +23,11 @@ public class CustomPhoton : MonoBehaviour
public void Start() public void Start()
{ {
RecNetInteractions.postLocalAccountActions.Add(DownloadServerConfig); if (ServerPatchesConfig.CustomPhoton.Value) RecNetInteractions.postLocalAccountActions.Add(DownloadServerConfig);
else if (PhotonConfig.PatchPhotonIds.Value) LocalPatch();
} }
void OnServerConfigFailed(HTTPResponse res) void LocalPatch()
{
ServerConfigFailed = true;
if (PhotonConfig.PatchPhotonIds.Value)
{ {
UniversalPatchPlugin.Log.LogInfo("Attempting Photon patch from local configuration"); UniversalPatchPlugin.Log.LogInfo("Attempting Photon patch from local configuration");
PhotonPatch.Patch(new PhotonConfigDTO PhotonPatch.Patch(new PhotonConfigDTO
@@ -44,11 +41,14 @@ public class CustomPhoton : MonoBehaviour
ConnectionProtocol = PhotonConfig.ConnectionProtocol.Value ConnectionProtocol = PhotonConfig.ConnectionProtocol.Value
}); });
} }
void OnServerConfigFailed(HTTPResponse res)
{
ServerConfigFailed = true;
if (PhotonConfig.PatchPhotonIds.Value) LocalPatch();
} }
void ApplyPhotonConfig(HTTPResponse res, PhotonConfigDTO photonConfig) void ApplyPhotonConfig(HTTPResponse res, PhotonConfigDTO photonConfig)
{ {
if (!ServerPatchesConfig.CustomPhoton.Value) return;
try try
{ {
UniversalPatchPlugin.Log.LogInfo("Attempting Photon patch from server configuration"); 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> postNameServerActions = [];
public static List<Action> postAuthenticationActions = []; public static List<Action> postAuthenticationActions = [];
public static List<Action> postLocalAccountActions = []; 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) 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Mapster; using Mapster;
using undead_universal_patch_il2cpp.Core.Config; 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.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.CustomRecNet.CustomPhoton;
using undead_universal_patch_il2cpp.Core.Content.UndeadGameManager; using undead_universal_patch_il2cpp.Core.Content.UndeadGameManager;
using undead_universal_patch_il2cpp.Patches.Video;
using UnityEngine; using UnityEngine;
namespace undead_universal_patch_il2cpp.Core; namespace undead_universal_patch_il2cpp.Core;
@@ -32,10 +36,14 @@ public class Initialization
{ {
UniversalPatchPlugin.Log.LogInfo("PATCH LIST START ========================="); UniversalPatchPlugin.Log.LogInfo("PATCH LIST START =========================");
foreach (var method in UniversalPatchPlugin.Instance.HarmonyInstance.GetPatchedMethods()) 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 ========================="); UniversalPatchPlugin.Log.LogInfo("PATCH LIST END =========================");
} }
// to be removed in a future update
try try
{ {
CacheChangePatchConfigs(); CacheChangePatchConfigs();
@@ -48,16 +56,18 @@ public class Initialization
private static void CacheChangePatchConfigs() private static void CacheChangePatchConfigs()
{ {
if (GameManagerConfig.AnyGameFreeSpawn.Value) GameFreeSpawns.config.Get(); VideoTamperPatch.config.Get();
if (GameManagerConfig.StaticGameConfig.Value) GameConfigurator.config.Get();
} }
private static void AttachGameObjects() private static void AttachGameObjects()
{ {
UniversalPatchPlugin.Log.LogInfo("Attaching game objects"); UniversalPatchPlugin.Log.LogInfo("Attaching game objects");
if (ServerPatchesConfig.CustomEmotes.Value) UniversalPatchPlugin.Instance.AddComponent<CustomEmotes>(); UniversalPatchPlugin.Instance.AddComponent<SteamPlatform>();
UniversalPatchPlugin.Instance.AddComponent<CustomPhoton>(); 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() private static void FetchConfigurations()
@@ -68,55 +78,66 @@ public class Initialization
"Log all BestHTTP requests sent by the game."); "Log all BestHTTP requests sent by the game.");
GenericConfig.VerboseRequestLogs = UniversalPatchPlugin.Instance.Config.Bind("Generic", "VerboseRequestLogs", GenericConfigDefaults.VerboseRequestLogs, GenericConfig.VerboseRequestLogs = UniversalPatchPlugin.Instance.Config.Bind("Generic", "VerboseRequestLogs", GenericConfigDefaults.VerboseRequestLogs,
"Add additional request information to BestHTTPProxy logs. Requires LogAllRequest to be enabled."); "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."); "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." + "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."); "\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."); "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." + "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)"); "\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."); "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."); "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, 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."); "Modify the game's emote text with a configuration from the server.");
ServerPatchesConfig.CustomPhoton = UniversalPatchPlugin.Instance.Config.Bind("ServerPatches", "CustomPhoton", ServerPatchesConfigDefaults.CustomPhoton, ServerPatchesConfig.CustomPhoton = UniversalPatchPlugin.Instance.Config.Bind("ServerPatches", "CustomPhoton", ServerPatchesConfigDefaults.CustomPhoton,
"Patch ServerSettings values with a configuration from the server." + "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." + "\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."); "\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, 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, 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, PhotonConfig.PatchPhotonIds = UniversalPatchPlugin.Instance.Config.Bind("Photon", "PatchPhotonIds", PhotonConfigDefaults.PatchPhotonIds,
"Patch Photon configuration."); "Patch Photon configuration.");
PhotonConfig.PunLogging = UniversalPatchPlugin.Instance.Config.Bind("Photon", "PunLogging", PhotonConfigDefaults.PunLogging, PhotonConfig.PunLogging = UniversalPatchPlugin.Instance.Config.Bind("Photon", "PunLogging", PhotonConfigDefaults.PunLogging,
"Enable all logging sent by Photon/PUN/Voice (useful for server debugging)"); "Enable all PUN/Voice logging (useful for server debugging)");
PhotonConfig.SelfHosted = UniversalPatchPlugin.Instance.Config.Bind("Photon", "IsSelfHosted", PhotonConfigDefaults.SelfHosted, PhotonConfig.SelfHosted = UniversalPatchPlugin.Instance.Config.Bind("Photon", "IsSelfHosted", PhotonConfigDefaults.SelfHosted,
"When enabled, use a self-hosted 'OnPremises' PhotonSocketServer. (EXPERIMENTAL)" + "When enabled, use a self-hosted ('OnPremises' or 'PhotonSocketServer') Photon server." +
"\nWhen disabled, AppID and VoiceAppID are sent to Photon Cloud and a cloud masterserver is used."); "\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, PhotonConfig.AppID = UniversalPatchPlugin.Instance.Config.Bind("Photon", "AppID", PhotonConfigDefaults.AppID,
"The new target (PUN) App ID from the Photon dashboard." + "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')"); "\nWhen self-hosting, this should be the name of your Photon application (usually 'Master' without quotes)");
PhotonConfig.VoiceAppID = UniversalPatchPlugin.Instance.Config.Bind("Photon", "VoiceAppID", PhotonConfigDefaults.VoiceAppID, PhotonConfig.VoiceAppID = UniversalPatchPlugin.Instance.Config.Bind("Photon", "VoiceAppID", PhotonConfigDefaults.VoiceAppID,
"The new target Voice App ID from the Photon dashboard." + "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."); "\nWhen self-hosting, this should be the same as AppID.");
PhotonConfig.ServerAddress = UniversalPatchPlugin.Instance.Config.Bind("Photon", "ServerAddress", PhotonConfigDefaults.ServerAddress, PhotonConfig.ServerAddress = UniversalPatchPlugin.Instance.Config.Bind("Photon", "ServerAddress", PhotonConfigDefaults.ServerAddress,
"Address of the Photon target server (ignored if not using self-hosted)"); "Address of the Photon Master server (ignored if using cloud)");
PhotonConfig.ServerPort = UniversalPatchPlugin.Instance.Config.Bind("Photon", "ServerPort", PhotonConfigDefaults.ServerPort, PhotonConfig.ServerPort = UniversalPatchPlugin.Instance.Config.Bind("Photon", "ServerPort", PhotonConfigDefaults.ServerPort,
"Photon target server port (ignored if not using self-hosted)." + "Photon Master server port (ignored if using cloud)." +
"\nYou can set this port to the matching protocol port from the server, e.g. 5055 for UDP, 9091 for WebSockets"); "\nYou can set this port to the matching protocol port from the server, e.g. 5055 for UDP, 9091 for WebSockets");
PhotonConfig.ConnectionProtocol = UniversalPatchPlugin.Instance.Config.Bind("Photon", "ConnectionProtocol", PhotonConfigDefaults.ConnectionProtocol, PhotonConfig.ConnectionProtocol = UniversalPatchPlugin.Instance.Config.Bind("Photon", "ConnectionProtocol", PhotonConfigDefaults.ConnectionProtocol,
"Connection protocol to use when connecting to the target port (ignored if not using self-hosted)." + "Connection protocol to use when connecting to the Photon servers (ignored if using cloud)." +
"\n0: UDP, 1: TCP;" + "\n0: UDP, 1: TCP;" +
"\nWebSockets are not supported by Photon in this build."); "\nWebSockets are not supported by Photon in this build. TCP is experimental and may cause instability/crashes.");
NameserverConfig.Rewrite = UniversalPatchPlugin.Instance.Config.Bind("Nameserver", "Rewrite", NameserverConfigDefaults.Rewrite, NameserverConfig.Rewrite = UniversalPatchPlugin.Instance.Config.Bind("Nameserver", "Rewrite", NameserverConfigDefaults.Rewrite,
"Enable/disable rewriting the URL for nameserver requests."); "Enable/disable rewriting the URL for nameserver requests.");

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 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

29
LINUX.md Normal file
View File

@@ -0,0 +1,29 @@
# Linux Users
[<-- 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.
### General process
* Install Steam
* Add Recroom_Release.exe as a non-steam game
* Install `protontricks`; depends on your distribution (see sections)
* Add the doorstop DLL using `protontricks --gui` [(more info)](https://linux-gaming.kwindu.eu/index.php?title=Installing_dlls)
- Select default wineprefix
- Install DLL
- Select the doorstop DLL `winhttp.dll`
* Run the game through Steam
### Arch Linux
`protontricks` can be installed from the [AUR](https://aur.archlinux.org/packages/protontricks)<br>
You should use the built-in Steam package [(instructions here)](https://wiki.archlinux.org/title/Steam) if you don't have Steam installed already.
### Other distributions
If you have any success doing this on Debian-based systems (Ubuntu 2x.x, elementary OS, etc)<br>
or any other kind of system, add an issue and explain the process - I'll add it to this document.<br>
If you feel like making a pull request, that's okay as well.

View File

@@ -1,9 +1,12 @@
using System; using System;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using BestHTTP; using BestHTTP;
using BestHTTP.Forms;
using HarmonyLib; using HarmonyLib;
using undead_universal_patch_il2cpp.Core; using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.Config; using undead_universal_patch_il2cpp.Core.Config;
using UnityEngine;
namespace undead_universal_patch_il2cpp.Patches 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 getHeaderMethod = requestType?.GetMethod("GetFirstHeaderValue");
static readonly MethodInfo addHeaderMethod = requestType?.GetMethod("AddHeader"); 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 methodTypeProp = requestType?.GetProperty("MethodType");
static readonly PropertyInfo uriProp = requestType?.GetProperty("Uri"); static readonly PropertyInfo uriProp = requestType?.GetProperty("Uri");
static readonly PropertyInfo customCertProp = requestType?.GetProperty("CustomCertificateVerifyer"); static readonly PropertyInfo customCertProp = requestType?.GetProperty("CustomCertificateVerifyer");
@@ -30,13 +35,13 @@ namespace undead_universal_patch_il2cpp.Patches
getHeaderMethod, getHeaderMethod,
addHeaderMethod, addHeaderMethod,
methodTypeProp, methodTypeProp,
getFormFields,
formImplProp,
uriProp, uriProp,
customCertProp customCertProp
]).Success; ]).Success;
static MethodBase TargetMethod() => patchResult.Method; static MethodBase TargetMethod() => patchResult.Method;
[HarmonyPrefix]
static void Prefix(ref object request) static void Prefix(ref object request)
{ {
if (PatchConfig.CertificatePatch.Value) customCertProp.GetSetMethod().Invoke(request, [null]); 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); 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 // Finish request changes
string afterUrl = newUri.ToString(); string afterUrl = newUri.ToString();
@@ -66,14 +81,13 @@ namespace undead_universal_patch_il2cpp.Patches
if (GenericConfig.LogAllRequests.Value) 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 Before : {beforeUrl}\n" +
$" URL After : {(beforeUrl == afterUrl ? "(unmodified)" : afterUrl)}\n" + $" URL After : {(beforeUrl == afterUrl ? "(unmodified)" : afterUrl)}\n" +
$" Method : {method}\n" + $" Method : {method}\n" +
$" Content-Type : {contentType ?? "(not set)"}"); $" Content-Type : {contentType ?? "(not set)"}");
else UniversalPatchPlugin.Log.LogInfo("BestHTTPProxy Request Log\n" + else UniversalPatchPlugin.Log.LogInfo("BestHTTPProxy Request\n" +
$" Before : {beforeUrl}\n" + $" {method} {afterUrl}");
$" After : {afterUrl}");
} }
} }
} }

View File

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

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using HarmonyLib; using HarmonyLib;
@@ -13,7 +12,7 @@ namespace undead_universal_patch_il2cpp.Patches.UndeadGameManager;
[HarmonyPatch] [HarmonyPatch]
public class GameConfiguratorPatch public class GameConfiguratorPatch
{ {
static Dictionary<string, GameConfigurationAssetDTO> gameConfig = GameConfigurator.config.Get(); public static Dictionary<string, GameConfigurationAssetDTO> gameConfig = null;
static PatchTypesResult patchResult = Util.ConfigPreparePatchTypes( static PatchTypesResult patchResult = Util.ConfigPreparePatchTypes(
GameManagerConfig.StaticGameConfig, GameManagerConfig.StaticGameConfig,
@@ -28,6 +27,12 @@ public class GameConfiguratorPatch
public static void Prefix(ref GameConfigurationAsset config, ref bool showNotification) public static void Prefix(ref GameConfigurationAsset config, ref bool showNotification)
{ {
if (gameConfig == null)
{
Util.ConditionalDebug("gameconfigassets was not yet fetched!");
return;
}
var conf = config; var conf = config;
showNotification = GenericConfig.PatchDebug.Value; 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 HarmonyLib;
using undead_universal_patch_il2cpp.Core; using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.Config; using undead_universal_patch_il2cpp.Core.Config;
@@ -22,19 +24,16 @@ namespace undead_universal_patch_il2cpp.Patches
static void Prefix() static void Prefix()
{ {
if (PatchConfig.HilePatch.Value) HilePatch.Patch(); if (!ServerPatchesConfig.CustomKnownDlls.Value && PatchConfig.HilePatch.Value) HilePatch.Patch();
} }
} }
public static class HilePatch public static class HilePatch
{ {
public static void Patch() public static void Patch([Optional] string[] dlls)
{ {
GameObject cheatManagerObject = GameObject.Find("[CheatManager]"); Util.ConditionalDebug($"Patching KnownDlls with {(dlls != null ? dlls.Length : 0)} new filenames");
HileManager hileManager = cheatManagerObject.GetComponent<HileManager>(); string[] baseDlls = [
PropertyInfo knownDllsProperty = AccessTools.Property(typeof(HileManager), "KnownDlls");
knownDllsProperty.SetValue(hileManager, new Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStringArray([
"GameAssembly.dll", "GameAssembly.dll",
"UnityPlayer.dll", "UnityPlayer.dll",
"WinPixEventRuntime.dll", "WinPixEventRuntime.dll",
@@ -46,9 +45,16 @@ namespace undead_universal_patch_il2cpp.Patches
"ddraw.dll", "ddraw.dll",
"dxgi.dll", "dxgi.dll",
"winhttp.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; static MethodBase TargetMethod() => patchResult.Method;
private static bool RanPostActions { get; set; } = false;
static void Postfix(ref string accessToken) static void Postfix(ref string accessToken)
{ {
UniversalPatchPlugin.Log.LogInfo("Intercepted AccessToken"); UniversalPatchPlugin.Log.LogInfo("Intercepted AccessToken");
RecNetInteractions.AccessToken = accessToken; RecNetInteractions.AccessToken = accessToken;
if (!RanPostActions) UniversalPatchPlugin.Log.LogInfo("Running post-authentication actions");
{
bool value = (bool)hasAccessTokenProperty.GetValue(patchResult.Type);
if (value) UniversalPatchPlugin.Log.LogInfo("Running post-authentication actions");
foreach (var action in RecNetInteractions.postAuthenticationActions) foreach (var action in RecNetInteractions.postAuthenticationActions)
action(); action();
} }
else RanPostActions = true;
}
} }

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

@@ -1,5 +1,7 @@
using System.Linq;
using System.Reflection; using System.Reflection;
using HarmonyLib; using HarmonyLib;
using Mapster;
using undead_universal_patch_il2cpp.Core; using undead_universal_patch_il2cpp.Core;
using undead_universal_patch_il2cpp.Core.Config; using undead_universal_patch_il2cpp.Core.Config;
using undead_universal_patch_il2cpp.Core.Content.CustomRecNet.CustomPhoton; using undead_universal_patch_il2cpp.Core.Content.CustomRecNet.CustomPhoton;
@@ -101,7 +103,12 @@ public class PhotonThrottlingPatch
"UpdatePhotonThrottling" "UpdatePhotonThrottling"
); );
static bool Prepare() => patchTypesResult.Success; static bool Prepare()
{
if (PhotonConfig.ConnectionProtocol.Value == 1) return patchTypesResult.Success;
else return false;
}
static MethodBase TargetMethod() => patchTypesResult.Method; static MethodBase TargetMethod() => patchTypesResult.Method;
static bool Prefix() => PhotonConfig.ConnectionProtocol.Value != 1; static bool Prefix() => PhotonConfig.ConnectionProtocol.Value != 1;

View File

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

View File

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

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; namespace undead_universal_patch_il2cpp;
[BepInPlugin("dev.proxnet.recroom.universalpatch.noneac.il2cpp", "Undead Universal Patch", "2.1.0")] [BepInPlugin("dev.proxnet.recroom.universalpatch.noneac.il2cpp", "Undead Universal Patch", "3.1.0")]
public class UniversalPatchPlugin : BasePlugin public class UniversalPatchPlugin : BasePlugin
{ {
public static new readonly ManualLogSource Log = Logger.CreateLogSource("UUPatch"); 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) 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/Il2CppSystem.dll`
- `BepInEx/interop/Photon3Unity3D.dll` - `BepInEx/interop/Photon3Unity3D.dll`
- `BepInEx/interop/RecRoom.Datastructures.Runtime` - `BepInEx/interop/RecRoom.Datastructures.Runtime`
- `BepInEx/interop/RecRoom.Promises.Runtime`
- `BepInEx/interop/UnityEngine.CoreModule.dll` - `BepInEx/interop/UnityEngine.CoreModule.dll`
- `BepInEx/interop/UnityEngine.TextRenderingModule.dll`
## Dependencies ## Dependencies
@@ -40,14 +47,7 @@ Run BepInEx interop on your build and add the following assemblies to new folder
- [Mapster on GitHub](https://github.com/MapsterMapper/Mapster) - [Mapster on GitHub](https://github.com/MapsterMapper/Mapster)
## Linux users ## Linux users
See [the Linux installation instructions](./LINUX.md). You may need to work around your distribution's package manager to install the necessary packages.
If you're unsure how to start your build on linux:
- Install Steam
- Add Rec Room as a non-steam game
- Set the compatibility mode to the latest stable version of proton
- Use protontricks to add the doorstop DLL
- Start Rec Room through Steam
## GameManager Patches ## GameManager Patches

View File

@@ -5,18 +5,43 @@
Requests are sent after any of these events: Requests are sent after any of these events:
* Nameserver response * Nameserver response
* Authentication response * Authentication response
* Local account fetch response * Local account fetch response (has auth)
## Endpoints ## Endpoints
### `API: GET "/api/undead/v1/emotes"`
Expects: `List<EmoteConfigDTO>`
[EmoteConfigDTO](#emoteconfigdto) ### `API: GET "/api/undead/v1/emotes"` (after local account response)
Expects: `List<EmoteConfigDTO>` [(to DTO)](#emoteconfigdto)
Replace existing emotes in the game with these, keyed by `UniqueName`. Replace existing emotes in the game with these, keyed by `UniqueName`.<br>
See [the source](./Core/CustomRecNet/CustomEmotes/RecNetEmotes.cs) for more information. This is mostly a change to VR emotes. See "ContextualEmotesConfig" in the game's assets.
See [the source](./Core/Content/CustomRecNet/CustomEmotes/RecNetEmotes.cs) for more information.
### `API: GET "/api/undead/v1/photon"` (after local account response)
Expects: `PhotonConfigDTO` [(to DTO)](#photonconfigdto)
The patch uses a Photon configuration given by the server.<br>
When this fails, the patch will fall back to the local config, if enabled.
`ConnectionProtocol` is 0 for UDP, or 1 for TCP. Use UDP if you don't know what to use.<br>
`ServerAddress`, `ServerPort`, and `ConnectionProtocol` are ignored when `SelfHosted` is enabled.
When using a self-hosted server, ideally, you should set the `AppID` *and* `VoiceAppID` to the name<br>
of your application, e.g. Master when using the port for a default Masterserver.<br>
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 ## DTOs
### `EmoteConfigDTO` ### `EmoteConfigDTO`
```c# ```c#
public class EmoteConfigDTO public class EmoteConfigDTO
@@ -29,3 +54,17 @@ public class EmoteConfigDTO
public bool OnlyBroadcastToTeam { get; set; } public bool OnlyBroadcastToTeam { get; set; }
} }
``` ```
### `PhotonConfigDTO`
```c#
public class PhotonConfigDTO
{
public bool Logging { get; set; }
public string AppID { get; set; }
public string VoiceAppID { get; set; }
public bool SelfHosted { get; set; }
public string ServerAddress { get; set; }
public int ServerPort { get; set; }
public byte ConnectionProtocol { get; set; }
}
```

View File

@@ -4,7 +4,7 @@
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<AssemblyName>undead_universal_patch_il2cpp</AssemblyName> <AssemblyName>undead_universal_patch_il2cpp</AssemblyName>
<Description>Non-EAC, IL2CPP build patcher for Rec Room (Late 2018*-*April 2020) </Description> <Description>Non-EAC, IL2CPP build patcher for Rec Room (Late 2018*-*April 2020) </Description>
<Version>2.1.0</Version> <Version>3.1.0</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<RestoreAdditionalProjectSources> <RestoreAdditionalProjectSources>
@@ -15,6 +15,10 @@
<RootNamespace>undead_universal_patch_il2cpp</RootNamespace> <RootNamespace>undead_universal_patch_il2cpp</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Include="Core\Content\CustomRecNet\CheatManager\CustomCheatManager.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.*" IncludeAssets="compile" /> <PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.*" IncludeAssets="compile" />
<PackageReference Include="Mapster" Version="7.4.0" /> <PackageReference Include="Mapster" Version="7.4.0" />
@@ -42,5 +46,8 @@
<Reference Include="UnityEngine.CoreModule"> <Reference Include="UnityEngine.CoreModule">
<HintPath>AssemblyReferences\UnityEngine.CoreModule.dll</HintPath> <HintPath>AssemblyReferences\UnityEngine.CoreModule.dll</HintPath>
</Reference> </Reference>
<Reference Include="UnityEngine.TextRenderingModule">
<HintPath>AssemblyReferences\UnityEngine.TextRenderingModule.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>