mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 15:08:47 -07:00
Merge pull request #64 from DorkSquadRR/fix/login-sign-in-instead-of-auto-create
fix: open the sign-in page instead of creating a new account on a fresh client
This commit is contained in:
@@ -156,6 +156,14 @@ public class Mod : MelonMod
|
|||||||
// "N"). Empty = no hotkey. A hotkey also arms the feature when
|
// "N"). Empty = no hotkey. A hotkey also arms the feature when
|
||||||
// RecRoyaleNightMode is false, so it can be switched on in-game.
|
// RecRoyaleNightMode is false, so it can be switched on in-game.
|
||||||
public static string RecRoyaleNightToggleKey = "";
|
public static string RecRoyaleNightToggleKey = "";
|
||||||
|
|
||||||
|
// A client with no remembered account for this machine. false: the
|
||||||
|
// title screen opens the sign-in page so the player logs into their
|
||||||
|
// existing account. true: Rec Room's own behaviour, which creates a
|
||||||
|
// new account automatically -- only useful when the server allows
|
||||||
|
// sign-ups (otherwise the title screen keeps retrying and failing).
|
||||||
|
// See TitleScreenLoginPatches.
|
||||||
|
public static bool AutoCreateAccount = false;
|
||||||
}
|
}
|
||||||
public override void OnInitializeMelon()
|
public override void OnInitializeMelon()
|
||||||
{
|
{
|
||||||
@@ -225,6 +233,14 @@ public class Mod : MelonMod
|
|||||||
postfix: nameof(QuestTeamSize.RelaxEmptySpawnFilter_Postfix));
|
postfix: nameof(QuestTeamSize.RelaxEmptySpawnFilter_Postfix));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Title-screen auto-login: with no remembered account, open the
|
||||||
|
// sign-in page instead of creating a new account.
|
||||||
|
if (!Cfg.AutoCreateAccount)
|
||||||
|
{
|
||||||
|
TryPatchByName("RRUI.Data.TitleScreenFlowModel", "IAKHIJIHAPJ",
|
||||||
|
prefix: nameof(TitleScreenLoginPatches.AutoLogin_Prefix));
|
||||||
|
}
|
||||||
|
|
||||||
// Rec Royale night mode: armed when enabled OR when a hotkey is set
|
// Rec Royale night mode: armed when enabled OR when a hotkey is set
|
||||||
// (so it can be switched on in-game). Scene detection + mood apply
|
// (so it can be switched on in-game). Scene detection + mood apply
|
||||||
// run from OnSceneWasLoaded / OnUpdate below.
|
// run from OnSceneWasLoaded / OnUpdate below.
|
||||||
@@ -457,6 +473,7 @@ public class Mod : MelonMod
|
|||||||
if (TryGetConfigValue(r, "DesktopScreenShareRaisePhotonRate", out v)) Cfg.DesktopScreenShareRaisePhotonRate = v.GetBoolean();
|
if (TryGetConfigValue(r, "DesktopScreenShareRaisePhotonRate", out v)) Cfg.DesktopScreenShareRaisePhotonRate = v.GetBoolean();
|
||||||
if (TryGetConfigValue(r, "DesktopScreenShareResolution", out v)) Cfg.DesktopScreenShareResolution = v.GetInt32();
|
if (TryGetConfigValue(r, "DesktopScreenShareResolution", out v)) Cfg.DesktopScreenShareResolution = v.GetInt32();
|
||||||
if (TryGetConfigValue(r, "DesktopScreenShareQuality", out v)) Cfg.DesktopScreenShareQuality = v.GetInt32();
|
if (TryGetConfigValue(r, "DesktopScreenShareQuality", out v)) Cfg.DesktopScreenShareQuality = v.GetInt32();
|
||||||
|
if (TryGetConfigValue(r, "AutoCreateAccount", out v)) Cfg.AutoCreateAccount = v.GetBoolean();
|
||||||
if (TryGetConfigValue(r, "QuestMaxTeamSize", out v)) Cfg.QuestMaxTeamSize = v.GetInt32();
|
if (TryGetConfigValue(r, "QuestMaxTeamSize", out v)) Cfg.QuestMaxTeamSize = v.GetInt32();
|
||||||
if (TryGetConfigValue(r, "QuestMaxTeamSizeRooms", out v) && v.ValueKind == JsonValueKind.Array)
|
if (TryGetConfigValue(r, "QuestMaxTeamSizeRooms", out v) && v.ValueKind == JsonValueKind.Array)
|
||||||
{
|
{
|
||||||
@@ -662,7 +679,7 @@ public class Mod : MelonMod
|
|||||||
private MethodInfo GetPatchMethod(string name)
|
private MethodInfo GetPatchMethod(string name)
|
||||||
{
|
{
|
||||||
// Look in the core + feature patch holder classes.
|
// Look in the core + feature patch holder classes.
|
||||||
foreach (var holder in new[] { typeof(UriPatches), typeof(AntiCheatPatches), typeof(AntiTamperPatches), typeof(FileHashCheckerPatches), typeof(ToxModPatches), typeof(TlsPatches), typeof(ScreenSharePatches), typeof(QuestTeamSize), typeof(RecRoyaleNight) })
|
foreach (var holder in new[] { typeof(UriPatches), typeof(AntiCheatPatches), typeof(AntiTamperPatches), typeof(FileHashCheckerPatches), typeof(ToxModPatches), typeof(TlsPatches), typeof(ScreenSharePatches), typeof(QuestTeamSize), typeof(RecRoyaleNight), typeof(TitleScreenLoginPatches) })
|
||||||
{
|
{
|
||||||
var m = holder.GetMethod(name, BindingFlags.Public | BindingFlags.Static);
|
var m = holder.GetMethod(name, BindingFlags.Public | BindingFlags.Static);
|
||||||
if (m is not null) return m;
|
if (m is not null) return m;
|
||||||
@@ -903,6 +920,52 @@ internal static class FileHashCheckerPatches
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
internal static class TitleScreenLoginPatches
|
||||||
|
{
|
||||||
|
// TitleScreenFlowModel.IAKHIJIHAPJ(cachedLogin) is the boot-time
|
||||||
|
// auto-login: the title screen downloads this machine's remembered
|
||||||
|
// accounts (CFILOMKCAPL), picks one (BFHOLLPGBPB) and logs into it here.
|
||||||
|
// With none (null) it creates a brand-new account instead (PJMGFADOEEM ->
|
||||||
|
// grant_type create_account). A server with sign-ups off refuses that, the
|
||||||
|
// error handler (IBDFENJNMAA) shows the connection-error page and the flow
|
||||||
|
// tries again, forever. Open the sign-in page instead (GoToLogin, what the
|
||||||
|
// "Log in" button does). ManuallyLogin carries the player into the game
|
||||||
|
// from there on its own, so the boot step gets a promise that never
|
||||||
|
// settles (MAMJDBJDFAF, the game's non-generic promise).
|
||||||
|
private static Type? _promiseType;
|
||||||
|
|
||||||
|
public static bool AutoLogin_Prefix(object __instance, object? __0, MethodBase __originalMethod, ref object? __result)
|
||||||
|
{
|
||||||
|
if (__0 is not null || Mod.Cfg.AutoCreateAccount) return true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_promiseType ??= Mod.ResolveType("MAMJDBJDFAF");
|
||||||
|
var goToLogin = __instance.GetType().GetMethod("GoToLogin", BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null);
|
||||||
|
if (_promiseType is null || goToLogin is null)
|
||||||
|
{
|
||||||
|
Mod.Log.Warning("[title] no remembered account, but the sign-in page could not be opened; creating one as Rec Room does");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Il2CppInterop wraps the interface return type (LDGADANDBIO) as
|
||||||
|
// its own class, so hand back the new promise's native object
|
||||||
|
// wrapped in that type.
|
||||||
|
var pending = (Il2CppObjectBase)Activator.CreateInstance(_promiseType)!;
|
||||||
|
var returnType = ((MethodInfo)__originalMethod).ReturnType;
|
||||||
|
var result = Activator.CreateInstance(returnType, pending.Pointer);
|
||||||
|
goToLogin.Invoke(__instance, null);
|
||||||
|
__result = result;
|
||||||
|
Mod.Log.Msg("[title] no remembered account; opened the sign-in page instead of creating a new account");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// goToLogin.Invoke wraps the real failure in TargetInvocationException.
|
||||||
|
var cause = ex is TargetInvocationException { InnerException: not null } tie ? tie.InnerException! : ex;
|
||||||
|
Mod.Log.Warning($"[title] sign-in redirect failed, creating an account as Rec Room does: {cause.GetType().Name}: {cause.Message}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
internal static class ToxModPatches
|
internal static class ToxModPatches
|
||||||
{
|
{
|
||||||
public static bool CanInitializeToxMod_Prefix(ref bool __result)
|
public static bool CanInitializeToxMod_Prefix(ref bool __result)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"ServerHost": "{HOST}",
|
"ServerHost": "{HOST}",
|
||||||
"EnableTlsTrustBypass": {ENABLE_TLS_TRUST_BYPASS},
|
"EnableTlsTrustBypass": {ENABLE_TLS_TRUST_BYPASS},
|
||||||
|
"AutoCreateAccount": false,
|
||||||
"EnableDebugConsole": false,
|
"EnableDebugConsole": false,
|
||||||
"DebugConsoleToggleKey": "BackQuote",
|
"DebugConsoleToggleKey": "BackQuote",
|
||||||
"DevCommands": [],
|
"DevCommands": [],
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ Photon shards with PC DorkNet players with no server-side version change.
|
|||||||
| image RSA/SHA1 verify | accept Content-Signatures the server signs with its own key | PC ImageSigning |
|
| image RSA/SHA1 verify | accept Content-Signatures the server signs with its own key | PC ImageSigning |
|
||||||
| hile-warning / unknown-DLL / file-hash / ToxMod callbacks | neutralised so a re-signed, hook-loaded client is not warned/quit | `ClientMod` AntiTamper/ToxMod/FileHash |
|
| hile-warning / unknown-DLL / file-hash / ToxMod callbacks | neutralised so a re-signed, hook-loaded client is not warned/quit | `ClientMod` AntiTamper/ToxMod/FileHash |
|
||||||
| integrity report + quit funnels | **log only** — names any new rejection in logcat | — |
|
| integrity report + quit funnels | **log only** — names any new rejection in logcat | — |
|
||||||
| Oculus `ovr_*` platform calls | (no-Meta build only) steer the entitlement/login path to the client's own credential screen | — |
|
| Oculus `ovr_*` platform calls | (no-Meta build only) pass the entitlement / user-proof / logged-in-user requests Meta refuses for the renamed app (including the SDK's second check on the inner native message), answering the logged-in user with the placeholder user id `1` | — |
|
||||||
|
| title-screen auto-login (`TitleScreenFlowModel`) | a headset with no remembered account opens the **sign-in page** instead of auto-creating an account (`AutoCreateAccount = true` restores Rec Room's behaviour) | — |
|
||||||
|
|
||||||
### Two builds, selected by `OculusAppId`
|
### Two builds, selected by `OculusAppId`
|
||||||
|
|
||||||
@@ -204,6 +205,8 @@ every `armed <target>`, then `intercepted https://ns.rec.net/ -> https://ns.<hos
|
|||||||
| `[http<-]` shows a Cloudflare challenge page | edge WAF blocked the request before your origin |
|
| `[http<-]` shows a Cloudflare challenge page | edge WAF blocked the request before your origin |
|
||||||
| multiplayer never connects | Photon app ids not set in the cfg |
|
| multiplayer never connects | Photon app ids not set in the cfg |
|
||||||
| Quest and PC players never see each other's rooms | `PhotonRegion` differs from the PC ClientMod `PhotonCloudRegion` |
|
| Quest and PC players never see each other's rooms | `PhotonRegion` differs from the PC ClientMod `PhotonCloudRegion` |
|
||||||
|
| "Unable to load Oculus user account" | old build (before the GetLoggedInUser bypass), or a Meta build whose app id the account isn't entitled to |
|
||||||
|
| game restarts while "creating a new account" | `AutoCreateAccount = true` on a server with sign-ups off — set it to `false` so the sign-in page opens |
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ EnableTlsTrustBypass = true
|
|||||||
# Accept image Content-Signatures the server signs with its own key.
|
# Accept image Content-Signatures the server signs with its own key.
|
||||||
AcceptUnsignedImages = true
|
AcceptUnsignedImages = true
|
||||||
|
|
||||||
|
# A headset with no remembered account. false: open the sign-in page so the
|
||||||
|
# player logs into their existing DorkNet account. true: Rec Room's own
|
||||||
|
# behaviour, which creates a new account automatically -- only useful when the
|
||||||
|
# server allows sign-ups (otherwise the game keeps retrying and failing).
|
||||||
|
AutoCreateAccount = false
|
||||||
|
|
||||||
# ---- diagnostics (adb logcat -s DORKNET) ----
|
# ---- diagnostics (adb logcat -s DORKNET) ----
|
||||||
|
|
||||||
LogHttp = true
|
LogHttp = true
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ KEYS = {
|
|||||||
"PhotonRegion": ("DN_PHOTON_REGION", "str"),
|
"PhotonRegion": ("DN_PHOTON_REGION", "str"),
|
||||||
"EnableTlsTrustBypass": ("DN_TLS_TRUST_BYPASS", "bool"),
|
"EnableTlsTrustBypass": ("DN_TLS_TRUST_BYPASS", "bool"),
|
||||||
"AcceptUnsignedImages": ("DN_ACCEPT_UNSIGNED_IMAGES", "bool"),
|
"AcceptUnsignedImages": ("DN_ACCEPT_UNSIGNED_IMAGES", "bool"),
|
||||||
|
"AutoCreateAccount": ("DN_AUTO_CREATE_ACCOUNT", "bool"),
|
||||||
"LogHttp": ("DN_LOG_HTTP", "bool"),
|
"LogHttp": ("DN_LOG_HTTP", "bool"),
|
||||||
"Debug": ("DN_DEBUG_HTTP", "bool"),
|
"Debug": ("DN_DEBUG_HTTP", "bool"),
|
||||||
"LogHttpBodyMax": ("DN_LOG_BODY_MAX", "int"),
|
"LogHttpBodyMax": ("DN_LOG_BODY_MAX", "int"),
|
||||||
|
|||||||
Binary file not shown.
@@ -57,6 +57,11 @@
|
|||||||
#ifndef DN_ACCEPT_UNSIGNED_IMAGES
|
#ifndef DN_ACCEPT_UNSIGNED_IMAGES
|
||||||
# define DN_ACCEPT_UNSIGNED_IMAGES true
|
# define DN_ACCEPT_UNSIGNED_IMAGES true
|
||||||
#endif
|
#endif
|
||||||
|
// A headset with no remembered account: false opens the sign-in page, true
|
||||||
|
// keeps Rec Room's behaviour of creating a new account automatically.
|
||||||
|
#ifndef DN_AUTO_CREATE_ACCOUNT
|
||||||
|
# define DN_AUTO_CREATE_ACCOUNT false
|
||||||
|
#endif
|
||||||
#ifndef DN_LOG_HTTP
|
#ifndef DN_LOG_HTTP
|
||||||
# define DN_LOG_HTTP true
|
# define DN_LOG_HTTP true
|
||||||
#endif
|
#endif
|
||||||
@@ -82,7 +87,7 @@ void dn_load_runtime_config(char *server_host, size_t server_host_cap,
|
|||||||
char *photon_realtime, char *photon_voice, char *photon_chat,
|
char *photon_realtime, char *photon_voice, char *photon_chat,
|
||||||
char *photon_server, size_t photon_server_cap, int *photon_port,
|
char *photon_server, size_t photon_server_cap, int *photon_port,
|
||||||
char *photon_region, size_t photon_region_cap,
|
char *photon_region, size_t photon_region_cap,
|
||||||
bool *tls_bypass, bool *accept_images,
|
bool *tls_bypass, bool *accept_images, bool *auto_create_account,
|
||||||
bool *log_http, bool *debug_http, int *body_max);
|
bool *log_http, bool *debug_http, int *body_max);
|
||||||
|
|
||||||
#endif // DORKNET_QUEST_CONFIG_H
|
#endif // DORKNET_QUEST_CONFIG_H
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ static int g_photon_port = DN_PHOTON_PORT;
|
|||||||
static char g_photon_region[16] = DN_PHOTON_REGION;
|
static char g_photon_region[16] = DN_PHOTON_REGION;
|
||||||
static bool g_tls_bypass = DN_TLS_TRUST_BYPASS;
|
static bool g_tls_bypass = DN_TLS_TRUST_BYPASS;
|
||||||
static bool g_accept_images = DN_ACCEPT_UNSIGNED_IMAGES;
|
static bool g_accept_images = DN_ACCEPT_UNSIGNED_IMAGES;
|
||||||
|
static bool g_auto_create_account = DN_AUTO_CREATE_ACCOUNT;
|
||||||
static bool g_log_http = DN_LOG_HTTP;
|
static bool g_log_http = DN_LOG_HTTP;
|
||||||
static bool g_debug_http = DN_DEBUG_HTTP;
|
static bool g_debug_http = DN_DEBUG_HTTP;
|
||||||
static int g_body_max = DN_LOG_BODY_MAX;
|
static int g_body_max = DN_LOG_BODY_MAX;
|
||||||
@@ -89,8 +90,12 @@ static const size_t kArrayDataOff = 0x20;
|
|||||||
// Oculus platform message types (dump.cs ONGCNIAMKCG.DJCEHCFKHME).
|
// Oculus platform message types (dump.cs ONGCNIAMKCG.DJCEHCFKHME).
|
||||||
static const unsigned kMsgEntitlementGetIsViewerEntitled = 409688241u;
|
static const unsigned kMsgEntitlementGetIsViewerEntitled = 409688241u;
|
||||||
static const unsigned kMsgUserGetUserProof = 578880643u;
|
static const unsigned kMsgUserGetUserProof = 578880643u;
|
||||||
|
static const unsigned kMsgUserGetLoggedInUser = 1131361373u;
|
||||||
static const char *kPlaceholderNonce = "dorknet-no-platform-account";
|
static const char *kPlaceholderNonce = "dorknet-no-platform-account";
|
||||||
static const unsigned long long kNoPlatformUserId = 1ULL;
|
static const unsigned long long kNoPlatformUserId = 1ULL;
|
||||||
|
// Stand-in ovrUserHandle for a GetLoggedInUser that Meta refused. Only our
|
||||||
|
// hooked ovr_User_* accessors ever see it; they never pass it to the SDK.
|
||||||
|
static void *const kPlaceholderUser = (void *)0x2;
|
||||||
|
|
||||||
static const size_t kLogChunk = 1500;
|
static const size_t kLogChunk = 1500;
|
||||||
static const uint32_t kUnityTlsSuccess = 0; // UNITYTLS_X509VERIFY_SUCCESS / UNITYTLS_SUCCESS
|
static const uint32_t kUnityTlsSuccess = 0; // UNITYTLS_X509VERIFY_SUCCESS / UNITYTLS_SUCCESS
|
||||||
@@ -103,12 +108,22 @@ typedef void *(*il2cpp_class_get_method_from_name_t)(void *klass, const char *na
|
|||||||
typedef void *(*il2cpp_object_new_t)(void *klass);
|
typedef void *(*il2cpp_object_new_t)(void *klass);
|
||||||
typedef void *(*il2cpp_string_new_t)(const char *str);
|
typedef void *(*il2cpp_string_new_t)(const char *str);
|
||||||
typedef void *(*il2cpp_runtime_invoke_t)(void *method, void *obj, void **params, void **exc);
|
typedef void *(*il2cpp_runtime_invoke_t)(void *method, void *obj, void **params, void **exc);
|
||||||
|
typedef void *(*il2cpp_domain_get_t)();
|
||||||
|
typedef void **(*il2cpp_domain_get_assemblies_t)(void *domain, size_t *size);
|
||||||
|
typedef void *(*il2cpp_assembly_get_image_t)(void *assembly);
|
||||||
|
typedef const char *(*il2cpp_image_get_name_t)(void *image);
|
||||||
|
typedef void *(*il2cpp_class_from_name_t)(void *image, const char *ns, const char *name);
|
||||||
|
|
||||||
static il2cpp_object_get_class_t il2cpp_object_get_class;
|
static il2cpp_object_get_class_t il2cpp_object_get_class;
|
||||||
static il2cpp_class_get_method_from_name_t il2cpp_class_get_method_from_name;
|
static il2cpp_class_get_method_from_name_t il2cpp_class_get_method_from_name;
|
||||||
static il2cpp_object_new_t il2cpp_object_new;
|
static il2cpp_object_new_t il2cpp_object_new;
|
||||||
static il2cpp_string_new_t il2cpp_string_new;
|
static il2cpp_string_new_t il2cpp_string_new;
|
||||||
static il2cpp_runtime_invoke_t il2cpp_runtime_invoke;
|
static il2cpp_runtime_invoke_t il2cpp_runtime_invoke;
|
||||||
|
static il2cpp_domain_get_t il2cpp_domain_get;
|
||||||
|
static il2cpp_domain_get_assemblies_t il2cpp_domain_get_assemblies;
|
||||||
|
static il2cpp_assembly_get_image_t il2cpp_assembly_get_image;
|
||||||
|
static il2cpp_image_get_name_t il2cpp_image_get_name;
|
||||||
|
static il2cpp_class_from_name_t il2cpp_class_from_name;
|
||||||
|
|
||||||
static uintptr_t g_il2cpp_base;
|
static uintptr_t g_il2cpp_base;
|
||||||
|
|
||||||
@@ -139,6 +154,30 @@ static const unsigned char *invoke_bytes(void *obj, const char *method, size_t *
|
|||||||
return (const unsigned char *)((char *)arr + kArrayDataOff);
|
return (const unsigned char *)((char *)arr + kArrayDataOff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool invoke_void(void *obj, const char *method) {
|
||||||
|
if (!obj) return false;
|
||||||
|
void *cls = il2cpp_object_get_class(obj);
|
||||||
|
if (!cls) return false;
|
||||||
|
void *m = il2cpp_class_get_method_from_name(cls, method, 0);
|
||||||
|
if (!m) return false;
|
||||||
|
void *exc = nullptr;
|
||||||
|
il2cpp_runtime_invoke(m, obj, nullptr, &exc);
|
||||||
|
return !exc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *find_class(const char *image_name, const char *ns, const char *name) {
|
||||||
|
if (!il2cpp_domain_get || !il2cpp_domain_get_assemblies || !il2cpp_assembly_get_image ||
|
||||||
|
!il2cpp_image_get_name || !il2cpp_class_from_name) return nullptr;
|
||||||
|
size_t n = 0;
|
||||||
|
void **assemblies = il2cpp_domain_get_assemblies(il2cpp_domain_get(), &n);
|
||||||
|
for (size_t i = 0; assemblies && i < n; i++) {
|
||||||
|
void *image = il2cpp_assembly_get_image(assemblies[i]);
|
||||||
|
const char *iname = image ? il2cpp_image_get_name(image) : nullptr;
|
||||||
|
if (iname && !strcmp(iname, image_name)) return il2cpp_class_from_name(image, ns, name);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
static void invoke_str(void *obj, const char *method, char *out, size_t cap) {
|
static void invoke_str(void *obj, const char *method, char *out, size_t cap) {
|
||||||
out[0] = '\0';
|
out[0] = '\0';
|
||||||
if (!obj) return;
|
if (!obj) return;
|
||||||
@@ -438,14 +477,73 @@ typedef unsigned (*ovr_GetType_t)(void *m);
|
|||||||
static ovr_IsError_t orig_ovr_IsError;
|
static ovr_IsError_t orig_ovr_IsError;
|
||||||
static ovr_GetType_t ovr_GetType;
|
static ovr_GetType_t ovr_GetType;
|
||||||
|
|
||||||
|
static bool is_bypassed_type(unsigned t) {
|
||||||
|
return t == kMsgEntitlementGetIsViewerEntitled || t == kMsgUserGetUserProof ||
|
||||||
|
t == kMsgUserGetLoggedInUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The SDK's Message constructor checks IsError twice: on the message, and --
|
||||||
|
// when that passes -- on ovr_Message_GetNativeMessage(message). The inner
|
||||||
|
// message carries the platform's refusal too but doesn't report the outer
|
||||||
|
// type, so remember the inner handle of a bypassed message (same thread, the
|
||||||
|
// constructor asks about it immediately) and pass it as well.
|
||||||
|
static __thread void *t_bypassed_native;
|
||||||
|
static __thread unsigned t_bypassed_type;
|
||||||
|
|
||||||
static bool hk_ovr_IsError(void *m) {
|
static bool hk_ovr_IsError(void *m) {
|
||||||
bool real = orig_ovr_IsError(m);
|
bool real = orig_ovr_IsError(m);
|
||||||
if (!real || !ovr_GetType) return real;
|
if (!real) return real;
|
||||||
unsigned t = ovr_GetType(m);
|
if (m && m == t_bypassed_native) return false;
|
||||||
if (t == kMsgEntitlementGetIsViewerEntitled || t == kMsgUserGetUserProof) return false;
|
if (ovr_GetType && is_bypassed_type(ovr_GetType(m))) return false;
|
||||||
return real;
|
return real;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef void *(*ovr_GetNativeMessage_t)(void *m);
|
||||||
|
static ovr_GetNativeMessage_t orig_ovr_GetNativeMessage;
|
||||||
|
static void *hk_ovr_GetNativeMessage(void *m) {
|
||||||
|
void *native = orig_ovr_GetNativeMessage(m);
|
||||||
|
unsigned t = ovr_GetType ? ovr_GetType(m) : 0;
|
||||||
|
t_bypassed_native = is_bypassed_type(t) ? native : nullptr;
|
||||||
|
t_bypassed_type = t;
|
||||||
|
return native;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OculusPlatformManager.LoadUser asks for the logged-in user; on a headset
|
||||||
|
// that reaches Meta, Meta refuses it for this re-packaged app, LoadUser
|
||||||
|
// rejects ("GetLoggedInUser failed") and Initialize shows "Unable to load
|
||||||
|
// Oculus user account". A headset that can't reach Meta gets a cached user
|
||||||
|
// and never hits this. Answer a refused request with a placeholder user whose
|
||||||
|
// ID is kNoPlatformUserId -- the same placeholder the server already treats
|
||||||
|
// as "no platform account" (PlayerService.IsPlaceholderPlatformId).
|
||||||
|
static void *(*orig_ovr_Message_GetUser)(void *m);
|
||||||
|
static void *hk_ovr_Message_GetUser(void *m) {
|
||||||
|
void *u = orig_ovr_Message_GetUser ? orig_ovr_Message_GetUser(m) : nullptr;
|
||||||
|
if (u) return u;
|
||||||
|
// The SDK reads the user from the inner native message
|
||||||
|
// (GetUser(GetNativeMessage(msg))), so match that handle as well.
|
||||||
|
bool logged_in_user = (m && m == t_bypassed_native && t_bypassed_type == kMsgUserGetLoggedInUser) ||
|
||||||
|
(ovr_GetType && ovr_GetType(m) == kMsgUserGetLoggedInUser);
|
||||||
|
if (!logged_in_user) return u;
|
||||||
|
LOGI("GetLoggedInUser refused by the platform; using the no-platform placeholder user");
|
||||||
|
return kPlaceholderUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accessors the SDK's User model reads. Real users pass straight through; the
|
||||||
|
// placeholder gets empty strings and zero values.
|
||||||
|
#define DN_USER_STR_ACCESSOR(name) static const char *(*orig_##name)(void *u); static const char *hk_##name(void *u) { if (u == kPlaceholderUser) return ""; return orig_##name ? orig_##name(u) : ""; }
|
||||||
|
#define DN_USER_VAL_ACCESSOR(name) static uintptr_t (*orig_##name)(void *u); static uintptr_t hk_##name(void *u) { if (u == kPlaceholderUser) return 0; return orig_##name ? orig_##name(u) : 0; }
|
||||||
|
DN_USER_STR_ACCESSOR(ovr_User_GetDisplayName)
|
||||||
|
DN_USER_STR_ACCESSOR(ovr_User_GetOculusID)
|
||||||
|
DN_USER_STR_ACCESSOR(ovr_User_GetImageUrl)
|
||||||
|
DN_USER_STR_ACCESSOR(ovr_User_GetSmallImageUrl)
|
||||||
|
DN_USER_STR_ACCESSOR(ovr_User_GetInviteToken)
|
||||||
|
DN_USER_STR_ACCESSOR(ovr_User_GetPresence)
|
||||||
|
DN_USER_STR_ACCESSOR(ovr_User_GetPresenceDeeplinkMessage)
|
||||||
|
DN_USER_STR_ACCESSOR(ovr_User_GetPresenceDestinationApiName)
|
||||||
|
DN_USER_STR_ACCESSOR(ovr_User_GetPresenceLobbySessionId)
|
||||||
|
DN_USER_STR_ACCESSOR(ovr_User_GetPresenceMatchSessionId)
|
||||||
|
DN_USER_VAL_ACCESSOR(ovr_User_GetPresenceStatus) // ovrUserPresenceStatus: 0 = Unknown
|
||||||
|
|
||||||
static void *(*orig_ovr_GetUserProof)(void *m);
|
static void *(*orig_ovr_GetUserProof)(void *m);
|
||||||
static void *hk_ovr_GetUserProof(void *m) {
|
static void *hk_ovr_GetUserProof(void *m) {
|
||||||
void *p = orig_ovr_GetUserProof ? orig_ovr_GetUserProof(m) : nullptr;
|
void *p = orig_ovr_GetUserProof ? orig_ovr_GetUserProof(m) : nullptr;
|
||||||
@@ -469,10 +567,46 @@ static unsigned long long hk_ovr_GetLoggedInUserID() {
|
|||||||
|
|
||||||
static unsigned long long (*orig_ovr_User_GetID)(void *u);
|
static unsigned long long (*orig_ovr_User_GetID)(void *u);
|
||||||
static unsigned long long hk_ovr_User_GetID(void *u) {
|
static unsigned long long hk_ovr_User_GetID(void *u) {
|
||||||
|
if (u == kPlaceholderUser) return kNoPlatformUserId;
|
||||||
unsigned long long real = orig_ovr_User_GetID ? orig_ovr_User_GetID(u) : 0;
|
unsigned long long real = orig_ovr_User_GetID ? orig_ovr_User_GetID(u) : 0;
|
||||||
return real ? real : kNoPlatformUserId;
|
return real ? real : kNoPlatformUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------ title screen --
|
||||||
|
|
||||||
|
// TitleScreenFlowModel.IAKHIJIHAPJ(cachedLogin) is the boot-time auto-login:
|
||||||
|
// the title screen downloads the headset's remembered accounts and logs into
|
||||||
|
// one; with none (null) it creates a brand-new account instead
|
||||||
|
// (PJMGFADOEEM -> grant_type create_account). A server with sign-ups off
|
||||||
|
// refuses that, the error handler shows the connection-error page and the
|
||||||
|
// flow tries again, forever. Unless AutoCreateAccount is on, open the sign-in
|
||||||
|
// page instead (TitleScreenFlowModel.GoToLogin, what the "Log in" button
|
||||||
|
// does). ManuallyLogin carries the player into the game from there on its own,
|
||||||
|
// so the boot step gets a promise that never settles.
|
||||||
|
static void *(*orig_TitleAutoLogin)(void *thiz, void *cached, void *method);
|
||||||
|
static void *new_pending_promise() {
|
||||||
|
// MAMJDBJDFAF: the game's non-generic promise (RecRoom.Promises.Runtime);
|
||||||
|
// a fresh one stays pending.
|
||||||
|
static void *cls = find_class("RecRoom.Promises.Runtime.dll", "", "MAMJDBJDFAF");
|
||||||
|
if (!cls) return nullptr;
|
||||||
|
void *ctor = il2cpp_class_get_method_from_name(cls, ".ctor", 0);
|
||||||
|
if (!ctor) return nullptr;
|
||||||
|
void *p = il2cpp_object_new(cls);
|
||||||
|
void *exc = nullptr;
|
||||||
|
if (p) il2cpp_runtime_invoke(ctor, p, nullptr, &exc);
|
||||||
|
return exc ? nullptr : p;
|
||||||
|
}
|
||||||
|
static void *hk_TitleAutoLogin(void *thiz, void *cached, void *method) {
|
||||||
|
if (cached || g_auto_create_account) return orig_TitleAutoLogin(thiz, cached, method);
|
||||||
|
void *pending = new_pending_promise();
|
||||||
|
if (!pending || !invoke_void(thiz, "GoToLogin")) {
|
||||||
|
LOGE("no remembered account, but the sign-in page could not be opened; creating one as Rec Room does");
|
||||||
|
return orig_TitleAutoLogin(thiz, cached, method);
|
||||||
|
}
|
||||||
|
LOGI("no remembered account on this headset; opened the sign-in page instead of creating a new account");
|
||||||
|
return pending;
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------- init ------
|
// ----------------------------------------------------------------- init ------
|
||||||
|
|
||||||
static uintptr_t module_base(const char *name) {
|
static uintptr_t module_base(const char *name) {
|
||||||
@@ -516,6 +650,12 @@ static bool resolve_il2cpp(void *h) {
|
|||||||
SYM(il2cpp_string_new)
|
SYM(il2cpp_string_new)
|
||||||
SYM(il2cpp_runtime_invoke)
|
SYM(il2cpp_runtime_invoke)
|
||||||
#undef SYM
|
#undef SYM
|
||||||
|
// Optional: only the title-screen sign-in redirect needs these.
|
||||||
|
il2cpp_domain_get = (il2cpp_domain_get_t)dlsym(h, "il2cpp_domain_get");
|
||||||
|
il2cpp_domain_get_assemblies = (il2cpp_domain_get_assemblies_t)dlsym(h, "il2cpp_domain_get_assemblies");
|
||||||
|
il2cpp_assembly_get_image = (il2cpp_assembly_get_image_t)dlsym(h, "il2cpp_assembly_get_image");
|
||||||
|
il2cpp_image_get_name = (il2cpp_image_get_name_t)dlsym(h, "il2cpp_image_get_name");
|
||||||
|
il2cpp_class_from_name = (il2cpp_class_from_name_t)dlsym(h, "il2cpp_class_from_name");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,6 +677,8 @@ static void arm_oculus_platform_bypass() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void *a;
|
void *a;
|
||||||
|
if ((a = dlsym(ovr, "ovr_Message_GetNativeMessage")))
|
||||||
|
A64HookFunction(a, (void *)hk_ovr_GetNativeMessage, (void **)&orig_ovr_GetNativeMessage);
|
||||||
if ((a = dlsym(ovr, "ovr_GetLoggedInUserID")))
|
if ((a = dlsym(ovr, "ovr_GetLoggedInUserID")))
|
||||||
A64HookFunction(a, (void *)hk_ovr_GetLoggedInUserID, (void **)&orig_ovr_GetLoggedInUserID);
|
A64HookFunction(a, (void *)hk_ovr_GetLoggedInUserID, (void **)&orig_ovr_GetLoggedInUserID);
|
||||||
if ((a = dlsym(ovr, "ovr_User_GetID")))
|
if ((a = dlsym(ovr, "ovr_User_GetID")))
|
||||||
@@ -545,6 +687,21 @@ static void arm_oculus_platform_bypass() {
|
|||||||
A64HookFunction(a, (void *)hk_ovr_GetUserProof, (void **)&orig_ovr_GetUserProof);
|
A64HookFunction(a, (void *)hk_ovr_GetUserProof, (void **)&orig_ovr_GetUserProof);
|
||||||
if ((a = dlsym(ovr, "ovr_UserProof_GetNonce")))
|
if ((a = dlsym(ovr, "ovr_UserProof_GetNonce")))
|
||||||
A64HookFunction(a, (void *)hk_ovr_GetNonce, (void **)&orig_ovr_GetNonce);
|
A64HookFunction(a, (void *)hk_ovr_GetNonce, (void **)&orig_ovr_GetNonce);
|
||||||
|
if ((a = dlsym(ovr, "ovr_Message_GetUser")))
|
||||||
|
A64HookFunction(a, (void *)hk_ovr_Message_GetUser, (void **)&orig_ovr_Message_GetUser);
|
||||||
|
#define DN_HOOK_USER(name) if ((a = dlsym(ovr, #name))) A64HookFunction(a, (void *)hk_##name, (void **)&orig_##name);
|
||||||
|
DN_HOOK_USER(ovr_User_GetDisplayName)
|
||||||
|
DN_HOOK_USER(ovr_User_GetOculusID)
|
||||||
|
DN_HOOK_USER(ovr_User_GetImageUrl)
|
||||||
|
DN_HOOK_USER(ovr_User_GetSmallImageUrl)
|
||||||
|
DN_HOOK_USER(ovr_User_GetInviteToken)
|
||||||
|
DN_HOOK_USER(ovr_User_GetPresence)
|
||||||
|
DN_HOOK_USER(ovr_User_GetPresenceDeeplinkMessage)
|
||||||
|
DN_HOOK_USER(ovr_User_GetPresenceDestinationApiName)
|
||||||
|
DN_HOOK_USER(ovr_User_GetPresenceLobbySessionId)
|
||||||
|
DN_HOOK_USER(ovr_User_GetPresenceMatchSessionId)
|
||||||
|
DN_HOOK_USER(ovr_User_GetPresenceStatus)
|
||||||
|
#undef DN_HOOK_USER
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *init_thread(void *) {
|
static void *init_thread(void *) {
|
||||||
@@ -562,7 +719,7 @@ static void *init_thread(void *) {
|
|||||||
g_photon_realtime, g_photon_voice, g_photon_chat,
|
g_photon_realtime, g_photon_voice, g_photon_chat,
|
||||||
g_photon_server, sizeof(g_photon_server), &g_photon_port,
|
g_photon_server, sizeof(g_photon_server), &g_photon_port,
|
||||||
g_photon_region, sizeof(g_photon_region),
|
g_photon_region, sizeof(g_photon_region),
|
||||||
&g_tls_bypass, &g_accept_images, &g_log_http, &g_debug_http, &g_body_max);
|
&g_tls_bypass, &g_accept_images, &g_auto_create_account, &g_log_http, &g_debug_http, &g_body_max);
|
||||||
|
|
||||||
LOGI("DorkNet.Quest init: build %s/%s il2cpp @ 0x%lx; host-rewrite -> *.%s",
|
LOGI("DorkNet.Quest init: build %s/%s il2cpp @ 0x%lx; host-rewrite -> *.%s",
|
||||||
DN_TARGET_VERSION_NAME, DN_TARGET_VERSION_CODE, (unsigned long)g_il2cpp_base, g_server_host);
|
DN_TARGET_VERSION_NAME, DN_TARGET_VERSION_CODE, (unsigned long)g_il2cpp_base, g_server_host);
|
||||||
@@ -586,6 +743,8 @@ static void *init_thread(void *) {
|
|||||||
arm(&dn_targets.pin_bc_verifier, (void *)hk_pin_true4, nullptr);
|
arm(&dn_targets.pin_bc_verifier, (void *)hk_pin_true4, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arm(&dn_targets.title_auto_login, (void *)hk_TitleAutoLogin, (void **)&orig_TitleAutoLogin);
|
||||||
|
|
||||||
if (g_accept_images)
|
if (g_accept_images)
|
||||||
arm(&dn_targets.image_signature_verify, (void *)hk_image_true, nullptr);
|
arm(&dn_targets.image_signature_verify, (void *)hk_image_true, nullptr);
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,12 @@
|
|||||||
"length": 412,
|
"length": 412,
|
||||||
"fingerprint": "0xb12b0044ac4f844f",
|
"fingerprint": "0xb12b0044ac4f844f",
|
||||||
"why": "Log-only: prints the quit code and caller."
|
"why": "Log-only: prints the quit code and caller."
|
||||||
|
},
|
||||||
|
"title_auto_login": {
|
||||||
|
"rva": 58892048,
|
||||||
|
"length": 668,
|
||||||
|
"fingerprint": "0x83bebb9a30983b33",
|
||||||
|
"why": "Title-screen auto-login. With no remembered account (null) it creates a new account; unless AutoCreateAccount is on, open the sign-in page instead."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,12 @@
|
|||||||
"class": "public class SessionManager ",
|
"class": "public class SessionManager ",
|
||||||
"method": "public static void TryApplicationQuit(int DKOGJICFAEP)",
|
"method": "public static void TryApplicationQuit(int DKOGJICFAEP)",
|
||||||
"why": "Log-only: prints the quit code and caller."
|
"why": "Log-only: prints the quit code and caller."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "title_auto_login",
|
||||||
|
"class": "public class TitleScreenFlowModel ",
|
||||||
|
"method": "private LDGADANDBIO IAKHIJIHAPJ(LBNJFPOLCDL.MDGJJCFGJNF AJCKAHHJDEN)",
|
||||||
|
"why": "Title-screen auto-login. With no remembered account (null) it creates a new account; unless AutoCreateAccount is on, open the sign-in page instead."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,6 +216,16 @@ keep the prefab value) trade per-frame size for bandwidth if a high FPS
|
|||||||
saturates the link. Only the player *sharing* needs the override. Default
|
saturates the link. Only the player *sharing* needs the override. Default
|
||||||
`0` (off).
|
`0` (off).
|
||||||
|
|
||||||
|
### Sign-in page instead of a new account
|
||||||
|
|
||||||
|
When the title screen finds no remembered account for the machine, Rec Room
|
||||||
|
creates a brand-new account automatically. On a server with sign-ups off that
|
||||||
|
is refused and the title screen keeps retrying. By default the mod opens the
|
||||||
|
**sign-in page** instead, so players log into their existing DorkNet account.
|
||||||
|
Set `"AutoCreateAccount": true` in `dorknet-clientmod.json` to keep Rec Room's
|
||||||
|
behaviour on a server that allows sign-ups. The Quest build has the same
|
||||||
|
option (`AutoCreateAccount` in `dorknet-quest.cfg`).
|
||||||
|
|
||||||
### RRO quest team size (opt-in)
|
### RRO quest team size (opt-in)
|
||||||
|
|
||||||
RRO quests ship with a baked 4-player `GameConfigurationAsset` team cap.
|
RRO quests ship with a baked 4-player `GameConfigurationAsset` team cap.
|
||||||
|
|||||||
@@ -55,6 +55,17 @@ an account, never matched by the cached-login lookup
|
|||||||
players are keyed by `deviceId` alone, exactly like the PC Steam-emulator path.
|
players are keyed by `deviceId` alone, exactly like the PC Steam-emulator path.
|
||||||
A real Meta platform id (the Meta build) still tags and lists normally.
|
A real Meta platform id (the Meta build) still tags and lists normally.
|
||||||
|
|
||||||
|
Because the placeholder never lists a remembered account, the title screen
|
||||||
|
would normally create a new account on every fresh headset. With
|
||||||
|
`AutoCreateAccount = false` (the default) the hook library opens the sign-in
|
||||||
|
page instead, so players log into their existing DorkNet account; set it to
|
||||||
|
`true` only on a server that allows sign-ups.
|
||||||
|
|
||||||
|
On a headset that reaches Meta, the Oculus platform also refuses the
|
||||||
|
"logged-in user" request for the renamed app ("Unable to load Oculus user
|
||||||
|
account"). The no-Meta build passes that refusal and hands the game the same
|
||||||
|
placeholder user id `1`.
|
||||||
|
|
||||||
Thanks to [RecFlare](https://github.com/recflare) for documenting this
|
Thanks to [RecFlare](https://github.com/recflare) for documenting this
|
||||||
pitfall in their Quest client notes.
|
pitfall in their Quest client notes.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user