mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 15:09:26 -07:00
201 lines
7.3 KiB
C#
201 lines
7.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using HarmonyLib;
|
|
using Il2CppInterop.Runtime;
|
|
using Il2CppInterop.Runtime.InteropTypes;
|
|
using TMPro;
|
|
using UnityEngine.UI;
|
|
|
|
internal static class ClientTextRewrites
|
|
{
|
|
private static readonly (string From, string To)[] Rules =
|
|
{
|
|
("https://recroom.zendesk.com/home", "https://support.radicade.app/"),
|
|
("recroom.zendesk.com", "support.radicade.app"),
|
|
("https://www.recroom.com/privacy-policy/", "https://radicade.app/privacy-policy/"),
|
|
("https://recroom.com/terms-of-service", "https://radicade.app/terms-of-service/"),
|
|
("https://www.recroom.com/community/", "https://radicade.app/community/"),
|
|
};
|
|
|
|
private static readonly (string From, string To)[] ExactRules =
|
|
{
|
|
("This account has been locked for security reasons. Contact us at recroom.zendesk.com.",
|
|
LockedAccountSentence),
|
|
("This account has been locked for security reasons. Contact us at support.radicade.app.",
|
|
LockedAccountSentence),
|
|
};
|
|
|
|
private const string GenericLoginFailure = "Login failed. Please try again.";
|
|
private const string LockedAccountErrorCode = "ACCOUNT_PERMALOCKED";
|
|
|
|
internal const string LockedAccountSentence =
|
|
"This account has been locked for security reasons. Contact us at https://support.radicade.app.";
|
|
|
|
private static bool _rewriting;
|
|
|
|
internal static void Apply(Harmony harmony)
|
|
{
|
|
var mode = AsciiLower((Environment.GetEnvironmentVariable("RADICADE_CLIENT_TEXT") ?? string.Empty).Trim());
|
|
if (mode == "0" || mode == "off")
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool onEnable = mode.Length == 0 || mode == "1" || mode == "all" || mode == "onenable";
|
|
bool onSet = mode.Length == 0 || mode == "1" || mode == "all" || mode == "settext";
|
|
|
|
var seen = new HashSet<MethodBase>();
|
|
var names = new List<string>();
|
|
|
|
if (onEnable)
|
|
{
|
|
Hook(harmony, seen, names, AccessTools.Method(typeof(TextMeshProUGUI), "OnEnable"), nameof(GraphicEvent), "TextMeshProUGUI.OnEnable");
|
|
Hook(harmony, seen, names, AccessTools.Method(typeof(Text), "OnEnable"), nameof(GraphicEvent), "UI.Text.OnEnable");
|
|
}
|
|
if (onSet)
|
|
{
|
|
Hook(harmony, seen, names, AccessTools.Method(typeof(TMP_Text), "set_text"), nameof(GraphicEvent), "TMP_Text.set_text");
|
|
Hook(harmony, seen, names, AccessTools.Method(typeof(TMP_Text), "SetTextInternal"), nameof(GraphicEvent), "TMP_Text.SetTextInternal");
|
|
Hook(harmony, seen, names, AccessTools.Method(typeof(Text), "set_text"), nameof(GraphicEvent), "UI.Text.set_text");
|
|
}
|
|
|
|
if (names.Count == 0)
|
|
RadicadeEacBypassPlugin.LogSource.LogWarning("[ClientText] no text hooks could be installed (mode=" + (mode.Length == 0 ? "all" : mode) + "); the watch UI keeps its original text.");
|
|
}
|
|
|
|
private static void Hook(Harmony harmony, HashSet<MethodBase> seen, List<string> names,
|
|
MethodBase target, string handler, string label)
|
|
{
|
|
if (target == null)
|
|
{
|
|
RadicadeEacBypassPlugin.LogSource.LogWarning("[ClientText] hook target missing: " + label);
|
|
return;
|
|
}
|
|
if (!seen.Add(target)) return;
|
|
|
|
try
|
|
{
|
|
var method = new HarmonyMethod(typeof(ClientTextRewrites)
|
|
.GetMethod(handler, BindingFlags.Static | BindingFlags.NonPublic));
|
|
harmony.Patch(target, postfix: method);
|
|
names.Add(label);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RadicadeEacBypassPlugin.LogSource.LogWarning("[ClientText] could not hook " + label + ": "
|
|
+ ex.GetType().Name + ": " + ex.Message);
|
|
}
|
|
}
|
|
|
|
private static void GraphicEvent(object __instance)
|
|
{
|
|
try
|
|
{
|
|
if (__instance is not Il2CppObjectBase obj) return;
|
|
if (obj.TryCast<TMP_Text>() is { } tmp) { Fix(tmp); return; }
|
|
if (obj.TryCast<Text>() is { } txt) { Fix(txt); }
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private static void Fix(TMP_Text instance)
|
|
{
|
|
try
|
|
{
|
|
if (instance == null || _rewriting) return;
|
|
var current = instance.text;
|
|
var rewritten = Rewrite(current);
|
|
if (ReferenceEquals(current, rewritten) || string.Equals(rewritten, current, StringComparison.Ordinal)) return;
|
|
_rewriting = true;
|
|
try { instance.text = rewritten; } finally { _rewriting = false; }
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private static void Fix(Text instance)
|
|
{
|
|
try
|
|
{
|
|
if (instance == null || _rewriting) return;
|
|
var current = instance.text;
|
|
var rewritten = Rewrite(current);
|
|
if (ReferenceEquals(current, rewritten) || string.Equals(rewritten, current, StringComparison.Ordinal)) return;
|
|
_rewriting = true;
|
|
try { instance.text = rewritten; } finally { _rewriting = false; }
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private static string Rewrite(string value)
|
|
{
|
|
if (string.IsNullOrEmpty(value)) return value;
|
|
|
|
if (HubSocketAuth.LastLoginRefusalCode == LockedAccountErrorCode &&
|
|
IndexOfAsciiIgnoreCase(value, GenericLoginFailure, 0) >= 0)
|
|
{
|
|
var locked = Replace(value, GenericLoginFailure, LockedAccountSentence);
|
|
return locked;
|
|
}
|
|
|
|
if (IndexOfAsciiIgnoreCase(value, "recroom", 0) < 0 &&
|
|
IndexOfAsciiIgnoreCase(value, "radicade", 0) < 0) return value;
|
|
|
|
var result = value;
|
|
foreach (var (from, to) in ExactRules)
|
|
if (IndexOfAsciiIgnoreCase(result, from, 0) >= 0)
|
|
result = Replace(result, from, to);
|
|
|
|
foreach (var (from, to) in Rules)
|
|
if (IndexOfAsciiIgnoreCase(result, from, 0) >= 0)
|
|
result = Replace(result, from, to);
|
|
|
|
return result;
|
|
}
|
|
|
|
private static string Replace(string source, string from, string to)
|
|
{
|
|
var sb = new System.Text.StringBuilder(source.Length);
|
|
var i = 0;
|
|
while (i < source.Length)
|
|
{
|
|
var hit = IndexOfAsciiIgnoreCase(source, from, i);
|
|
if (hit < 0) { sb.Append(source, i, source.Length - i); break; }
|
|
sb.Append(source, i, hit - i).Append(to);
|
|
i = hit + from.Length;
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static int IndexOfAsciiIgnoreCase(string haystack, string needle, int start)
|
|
{
|
|
if (haystack == null || needle == null) return -1;
|
|
var n = needle.Length;
|
|
var h = haystack.Length;
|
|
if (n == 0) return start <= h ? start : -1;
|
|
if (n > h) return -1;
|
|
var last = h - n;
|
|
for (var i = (start < 0 ? 0 : start); i <= last; i++)
|
|
{
|
|
var j = 0;
|
|
while (j < n && LowerAscii(haystack[i + j]) == LowerAscii(needle[j])) j++;
|
|
if (j == n) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private static char LowerAscii(char c) => c >= 'A' && c <= 'Z' ? (char)(c + 32) : c;
|
|
|
|
private static string AsciiLower(string s)
|
|
{
|
|
for (var i = 0; i < s.Length; i++)
|
|
if (s[i] >= 'A' && s[i] <= 'Z')
|
|
return string.Create(s.Length, s, (span, state) =>
|
|
{
|
|
for (var k = 0; k < state.Length; k++) span[k] = LowerAscii(state[k]);
|
|
});
|
|
return s;
|
|
}
|
|
|
|
}
|