Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ArabicChatFix v1.0.7
plugins/ArabicRepo.dll
Decompiled 14 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ArabicRepo")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.5.0")] [assembly: AssemblyInformationalVersion("1.0.5")] [assembly: AssemblyProduct("Arabic Chat Fix")] [assembly: AssemblyTitle("ArabicRepo")] [assembly: AssemblyVersion("1.0.5.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ArabicRepo { public static class ArabicFixer { private struct Glyph { public char Join; public int Iso; public int Ini; public int Med; public int Fin; public Glyph(char j, int iso, int ini, int med, int fin) { Join = j; Iso = iso; Ini = ini; Med = med; Fin = fin; } } [CompilerGenerated] private sealed class <WrapLogical>d__22 : IEnumerable<string>, IEnumerable, IEnumerator<string>, IEnumerator, IDisposable { private int <>1__state; private string <>2__current; private int <>l__initialThreadId; private string line; public string <>3__line; private StringBuilder <sb>5__2; private string[] <>7__wrap2; private int <>7__wrap3; private string <w>5__5; string IEnumerator<string>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WrapLogical>d__22(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <sb>5__2 = null; <>7__wrap2 = null; <w>5__5 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; if (MaxLineChars <= 0 || line.Length <= MaxLineChars) { <>2__current = line; <>1__state = 1; return true; } string[] array = line.Split(' '); <sb>5__2 = new StringBuilder(); <>7__wrap2 = array; <>7__wrap3 = 0; goto IL_015e; } case 1: <>1__state = -1; return false; case 2: <>1__state = -1; <sb>5__2.Length = 0; <sb>5__2.Append(<w>5__5); goto IL_0149; case 3: { <>1__state = -1; break; } IL_0150: <>7__wrap3++; goto IL_015e; IL_0149: <w>5__5 = null; goto IL_0150; IL_015e: if (<>7__wrap3 < <>7__wrap2.Length) { <w>5__5 = <>7__wrap2[<>7__wrap3]; if (<w>5__5.Length != 0) { if (<sb>5__2.Length == 0) { <sb>5__2.Append(<w>5__5); } else { if (<sb>5__2.Length + 1 + <w>5__5.Length > MaxLineChars) { <>2__current = <sb>5__2.ToString(); <>1__state = 2; return true; } <sb>5__2.Append(' ').Append(<w>5__5); } goto IL_0149; } goto IL_0150; } <>7__wrap2 = null; if (<sb>5__2.Length > 0) { <>2__current = <sb>5__2.ToString(); <>1__state = 3; return true; } break; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<string> IEnumerable<string>.GetEnumerator() { <WrapLogical>d__22 <WrapLogical>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; <WrapLogical>d__ = this; } else { <WrapLogical>d__ = new <WrapLogical>d__22(0); } <WrapLogical>d__.line = <>3__line; return <WrapLogical>d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<string>)this).GetEnumerator(); } } public static bool CombineAllah = true; public static bool KeepLtrRuns = true; public static bool ReverseWordOrder = true; public static string WordJoiner = "\u00a0"; public static bool PreventWordSplit = true; public static int MaxLineChars = 0; private static readonly Dictionary<int, Glyph> Forms = new Dictionary<int, Glyph> { { 1569, new Glyph('U', 65152, 0, 0, 0) }, { 1570, new Glyph('R', 65153, 0, 0, 65154) }, { 1571, new Glyph('R', 65155, 0, 0, 65156) }, { 1572, new Glyph('R', 65157, 0, 0, 65158) }, { 1573, new Glyph('R', 65159, 0, 0, 65160) }, { 1574, new Glyph('D', 65161, 65163, 65164, 65162) }, { 1575, new Glyph('R', 65165, 0, 0, 65166) }, { 1576, new Glyph('D', 65167, 65169, 65170, 65168) }, { 1577, new Glyph('R', 65171, 0, 0, 65172) }, { 1578, new Glyph('D', 65173, 65175, 65176, 65174) }, { 1579, new Glyph('D', 65177, 65179, 65180, 65178) }, { 1580, new Glyph('D', 65181, 65183, 65184, 65182) }, { 1581, new Glyph('D', 65185, 65187, 65188, 65186) }, { 1582, new Glyph('D', 65189, 65191, 65192, 65190) }, { 1583, new Glyph('R', 65193, 0, 0, 65194) }, { 1584, new Glyph('R', 65195, 0, 0, 65196) }, { 1585, new Glyph('R', 65197, 0, 0, 65198) }, { 1586, new Glyph('R', 65199, 0, 0, 65200) }, { 1587, new Glyph('D', 65201, 65203, 65204, 65202) }, { 1588, new Glyph('D', 65205, 65207, 65208, 65206) }, { 1589, new Glyph('D', 65209, 65211, 65212, 65210) }, { 1590, new Glyph('D', 65213, 65215, 65216, 65214) }, { 1591, new Glyph('D', 65217, 65219, 65220, 65218) }, { 1592, new Glyph('D', 65221, 65223, 65224, 65222) }, { 1593, new Glyph('D', 65225, 65227, 65228, 65226) }, { 1594, new Glyph('D', 65229, 65231, 65232, 65230) }, { 1600, new Glyph('C', 1600, 1600, 1600, 1600) }, { 1601, new Glyph('D', 65233, 65235, 65236, 65234) }, { 1602, new Glyph('D', 65237, 65239, 65240, 65238) }, { 1603, new Glyph('D', 65241, 65243, 65244, 65242) }, { 1604, new Glyph('D', 65245, 65247, 65248, 65246) }, { 1605, new Glyph('D', 65249, 65251, 65252, 65250) }, { 1606, new Glyph('D', 65253, 65255, 65256, 65254) }, { 1607, new Glyph('D', 65257, 65259, 65260, 65258) }, { 1608, new Glyph('R', 65261, 0, 0, 65262) }, { 1609, new Glyph('R', 65263, 0, 0, 65264) }, { 1610, new Glyph('D', 65265, 65267, 65268, 65266) } }; private static readonly Dictionary<int, int[]> LamAlef = new Dictionary<int, int[]> { { 1570, new int[2] { 65269, 65270 } }, { 1571, new int[2] { 65271, 65272 } }, { 1573, new int[2] { 65273, 65274 } }, { 1575, new int[2] { 65275, 65276 } } }; private static char JType(int cp) { if (Forms.TryGetValue(cp, out var value)) { return value.Join; } if (cp < 1611 || cp > 1631) { switch (cp) { default: if ((cp < 1759 || cp > 1764) && (cp < 1767 || cp > 1768) && (cp < 1770 || cp > 1773)) { return 'U'; } break; case 1648: case 1750: case 1751: case 1752: case 1753: case 1754: case 1755: case 1756: break; } } return 'T'; } private static bool IsArabicLetter(int cp) { if (cp < 1536 || cp > 1791) { if (cp >= 1872) { return cp <= 1919; } return false; } return true; } public static bool ContainsArabic(string s) { if (string.IsNullOrEmpty(s)) { return false; } for (int i = 0; i < s.Length; i++) { if (IsArabicLetter(s[i])) { return true; } } return false; } public static bool IsAlreadyShaped(string s) { if (string.IsNullOrEmpty(s)) { return false; } foreach (char c in s) { if ((c >= 'ﭐ' && c <= '\ufdff') || (c >= 'ﹰ' && c <= '\ufeff')) { return true; } } return false; } private static string Reshape(string text) { if (CombineAllah) { text = CollapseAllah(text); } int length = text.Length; int[] array = new int[length]; for (int i = 0; i < length; i++) { array[i] = text[i]; } StringBuilder stringBuilder = new StringBuilder(length); int num = 0; while (num < length) { int num2 = array[num]; char c = JType(num2); if (num2 == 1604) { int num3 = NextNonTransparent(array, length, num); if (num3 < length && LamAlef.ContainsKey(array[num3])) { int num4 = PrevNonTransparent(array, num); char c2 = ((num4 >= 0) ? JType(array[num4]) : 'U'); bool flag = c2 == 'D' || c2 == 'C'; int[] array2 = LamAlef[array[num3]]; stringBuilder.Append((char)(flag ? array2[1] : array2[0])); for (int j = num + 1; j < num3; j++) { stringBuilder.Append((char)array[j]); } num = num3 + 1; continue; } } if (!Forms.TryGetValue(num2, out var value)) { stringBuilder.Append((char)num2); num++; continue; } int num5 = PrevNonTransparent(array, num); int num6 = NextNonTransparent(array, length, num); char c3 = ((num5 >= 0) ? JType(array[num5]) : 'U'); char c4 = ((num6 < length) ? JType(array[num6]) : 'U'); bool flag2 = (c == 'D' || c == 'R') && (c3 == 'D' || c3 == 'C'); bool flag3 = (c == 'D' || c == 'C') && (c4 == 'D' || c4 == 'R' || c4 == 'C'); int num7 = ((flag2 && flag3) ? ((value.Med != 0) ? value.Med : ((value.Fin != 0) ? value.Fin : value.Iso)) : (flag2 ? ((value.Fin != 0) ? value.Fin : value.Iso) : ((!flag3) ? value.Iso : ((value.Ini != 0) ? value.Ini : value.Iso)))); stringBuilder.Append((char)num7); num++; } return stringBuilder.ToString(); } private static int PrevNonTransparent(int[] cps, int idx) { int num = idx - 1; while (num >= 0 && JType(cps[num]) == 'T') { num--; } return num; } private static int NextNonTransparent(int[] cps, int n, int idx) { int i; for (i = idx + 1; i < n && JType(cps[i]) == 'T'; i++) { } return i; } private static string CollapseAllah(string text) { int num = text.IndexOf("الله", StringComparison.Ordinal); if (num < 0) { return text; } StringBuilder stringBuilder = new StringBuilder(text.Length); int num2 = 0; while (num >= 0) { bool num3 = num == 0 || !IsArabicLetter(text[num - 1]); int num4 = num + "الله".Length; bool flag = num4 >= text.Length || !IsArabicLetter(text[num4]); stringBuilder.Append(text, num2, num - num2); if (num3 && flag) { stringBuilder.Append('ﷲ'); } else { stringBuilder.Append("الله"); } num2 = num4; num = text.IndexOf("الله", num2, StringComparison.Ordinal); } stringBuilder.Append(text, num2, text.Length - num2); return stringBuilder.ToString(); } private static bool IsLtr(int cp) { if ((cp < 65 || cp > 90) && (cp < 97 || cp > 122) && (cp < 192 || cp > 591)) { return cp == 8206; } return true; } private static bool IsDigit(int cp) { if ((cp < 48 || cp > 57) && (cp < 1632 || cp > 1641)) { if (cp >= 1776) { return cp <= 1785; } return false; } return true; } private static bool IsLtrGlue(int cp) { switch ((char)(ushort)cp) { case '#': case '%': case '&': case '+': case ',': case '-': case '.': case '/': case ':': case '=': case '?': case '@': case '_': return true; default: return false; } } private static string BidiLine(string s) { int length = s.Length; int[] array = new int[length]; for (int i = 0; i < length; i++) { array[i] = s[length - 1 - i]; } StringBuilder stringBuilder = new StringBuilder(length); int num = 0; while (num < length) { int num2 = array[num]; bool flag = IsLtr(num2) || IsDigit(num2); if (KeepLtrRuns && flag) { int num3 = num; while (num3 < length) { int num4 = array[num3]; if (IsLtr(num4) || IsDigit(num4)) { num3++; continue; } if ((num4 != 32 && !IsLtrGlue(num4)) || num3 + 1 >= length || (!IsLtr(array[num3 + 1]) && !IsDigit(array[num3 + 1]))) { break; } num3++; } for (int num5 = num3 - 1; num5 >= num; num5--) { stringBuilder.Append((char)array[num5]); } num = num3; } else { stringBuilder.Append((char)num2); num++; } } return stringBuilder.ToString(); } public static string Fix(string text) { if (string.IsNullOrEmpty(text)) { return text; } if (!ContainsArabic(text)) { return text; } if (IsAlreadyShaped(text)) { return text; } string[] array = text.Replace("\r", "").Split('\n'); List<string> list = new List<string>(); string[] array2 = array; for (int i = 0; i < array2.Length; i++) { foreach (string item in WrapLogical(array2[i])) { list.Add(ProcessLine(item)); } } return string.Join("\n", list); } [IteratorStateMachine(typeof(<WrapLogical>d__22))] private static IEnumerable<string> WrapLogical(string line) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WrapLogical>d__22(-2) { <>3__line = line }; } private static string ProcessLine(string line) { line = line.Trim(' ', '\t'); if (line.Length == 0) { return line; } string text; if (ReverseWordOrder) { text = BidiLine(Reshape(line)).Trim(' ', '\t'); } else { string[] array = line.Split(' '); for (int i = 0; i < array.Length; i++) { if (ContainsArabic(array[i])) { array[i] = BidiLine(Reshape(array[i])); } } text = string.Join(" ", array); } if (PreventWordSplit && WordJoiner != " ") { text = text.Replace(" ", WordJoiner); } return text; } } [BepInPlugin("bandar.arabicrepo", "Arabic Chat Fix", "1.0.5")] public class Plugin : BaseUnityPlugin { [CompilerGenerated] private sealed class <SmoothFontRoutine>d__23 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Plugin <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <SmoothFontRoutine>d__23(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown int num = <>1__state; Plugin plugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(4f); <>1__state = 1; return true; case 1: <>1__state = -1; plugin.TrySmoothFont(); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <WaitForChatThenHook>d__27 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Plugin <>4__this; private Type <t>5__2; private float <deadline>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WaitForChatThenHook>d__27(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <t>5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown int num = <>1__state; Plugin plugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <t>5__2 = null; <deadline>5__3 = Time.realtimeSinceStartup + 120f; break; case 1: <>1__state = -1; break; } if (Time.realtimeSinceStartup < <deadline>5__3) { <t>5__2 = AccessTools.TypeByName(plugin._cfgChatType.Value); if (!(<t>5__2 != null)) { <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 1; return true; } } if (<t>5__2 == null) { Log.LogWarning((object)("Chat class '" + plugin._cfgChatType.Value + "' not found after 120s. If your chat comes from another mod, set ChatTypeName in the config.")); return false; } if (plugin._cfgDump.Value) { plugin.DumpType(<t>5__2); } plugin.ResolveMessageField(<t>5__2); plugin.HookSendMethods(<t>5__2); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string Guid = "bandar.arabicrepo"; public const string Name = "Arabic Chat Fix"; public const string Version = "1.0.5"; internal static ManualLogSource Log; private Harmony _harmony; private ConfigEntry<bool> _cfgFixOutgoing; private ConfigEntry<bool> _cfgFixDisplay; private ConfigEntry<bool> _cfgCombineAllah; private ConfigEntry<bool> _cfgKeepLtrRuns; private ConfigEntry<bool> _cfgReverseWordOrder; private ConfigEntry<string> _cfgWordSeparator; private ConfigEntry<int> _cfgMaxLineChars; private ConfigEntry<bool> _cfgSmoothFont; private ConfigEntry<string> _cfgSmoothFontName; private ConfigEntry<string> _cfgChatType; private ConfigEntry<string> _cfgSendMethods; private ConfigEntry<string> _cfgMessageField; private ConfigEntry<bool> _cfgAggressive; private ConfigEntry<bool> _cfgDump; private ConfigEntry<bool> _cfgDebug; internal static FieldInfo MessageField; internal static bool DebugLog; private void Awake() { //IL_0312: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; _cfgFixOutgoing = ((BaseUnityPlugin)this).Config.Bind<bool>("1. Behaviour", "FixOutgoing", true, "Convert your typed Arabic to its baked form on send, so EVERYONE (even players without the mod) sees it correctly."); _cfgFixDisplay = ((BaseUnityPlugin)this).Config.Bind<bool>("1. Behaviour", "FixIncomingDisplay", true, "Also reshape raw Arabic in text the game displays locally, so you can read Arabic typed by players who DON'T have the mod."); _cfgCombineAllah = ((BaseUnityPlugin)this).Config.Bind<bool>("1. Behaviour", "CombineAllahLigature", true, "Collapse the standalone word الله into the single ﷲ ligature glyph."); _cfgKeepLtrRuns = ((BaseUnityPlugin)this).Config.Bind<bool>("1. Behaviour", "KeepLatinAndNumbersInOrder", true, "Keep Latin words, numbers, emails and URLs in normal reading order inside Arabic text."); _cfgReverseWordOrder = ((BaseUnityPlugin)this).Config.Bind<bool>("1. Behaviour", "ReverseWordOrder", true, "true = full right-to-left word order. false = shape each word but keep words in the order you typed them (use this if multi-word messages stall on the first word in-game)."); _cfgWordSeparator = ((BaseUnityPlugin)this).Config.Bind<string>("1. Behaviour", "WordSeparator", "nbsp", "How to separate words so R.E.P.O.'s word-by-word chat reader doesn't stop at the first space. Options: nbsp (non-breaking space, default), narrow (narrower no-break space), thin, none (words touch), space (normal space - only if the game handles it)."); _cfgMaxLineChars = ((BaseUnityPlugin)this).Config.Bind<int>("1. Behaviour", "MaxLineChars", 18, "0 = let the game wrap long messages (the start can end up on the bottom line). Set to a number (try ~22) to wrap lines yourself: the first words stay on the TOP line and each line reads right-to-left. Lower it if lines still wrap oddly, raise it for fewer line breaks."); _cfgSmoothFont = ((BaseUnityPlugin)this).Config.Bind<bool>("3. Appearance", "SmoothFont", true, "Render Arabic from a clean system font so the letters look genuine. Affects only screens that have this mod (you and modded friends); players without the mod still use the game's font."); _cfgSmoothFontName = ((BaseUnityPlugin)this).Config.Bind<string>("3. Appearance", "SmoothFontName", "Tahoma", "Name of an installed system font with good Arabic. Try: Tahoma, Segoe UI, Arial, Sakkal Majalla, Traditional Arabic, Dubai. Use the exact font name as installed on your PC."); _cfgChatType = ((BaseUnityPlugin)this).Config.Bind<string>("2. Hook (advanced)", "ChatTypeName", "ChatManager", "The game class that handles chat. Change only if a game update renames it (see the member dump in the log)."); _cfgSendMethods = ((BaseUnityPlugin)this).Config.Bind<string>("2. Hook (advanced)", "SendMethodOverride", "", "Comma-separated method name(s) on the chat class to treat as the 'send' point. Leave empty to auto-detect."); _cfgMessageField = ((BaseUnityPlugin)this).Config.Bind<string>("2. Hook (advanced)", "MessageFieldOverride", "", "Name of the string field that holds the text you typed. Leave empty to auto-detect."); _cfgAggressive = ((BaseUnityPlugin)this).Config.Bind<bool>("2. Hook (advanced)", "AggressiveAutoHook", true, "If no curated send method is found, hook every plausible send-like method. Safe because the conversion is a no-op on non-Arabic / already-converted text."); _cfgDump = ((BaseUnityPlugin)this).Config.Bind<bool>("2. Hook (advanced)", "DumpChatClassToLog", true, "Write all fields/methods of the chat class to the BepInEx log once, to help diagnose hooks after a game update."); _cfgDebug = ((BaseUnityPlugin)this).Config.Bind<bool>("2. Hook (advanced)", "DebugLogging", true, "Log the raw and converted text on every send (char codes), for troubleshooting."); DebugLog = _cfgDebug.Value; ArabicFixer.CombineAllah = _cfgCombineAllah.Value; ArabicFixer.KeepLtrRuns = _cfgKeepLtrRuns.Value; ArabicFixer.ReverseWordOrder = _cfgReverseWordOrder.Value; ArabicFixer.MaxLineChars = _cfgMaxLineChars.Value; switch ((_cfgWordSeparator.Value ?? "nbsp").Trim().ToLowerInvariant()) { case "space": ArabicFixer.WordJoiner = " "; ArabicFixer.PreventWordSplit = false; break; case "none": ArabicFixer.WordJoiner = ""; ArabicFixer.PreventWordSplit = true; break; case "narrow": ArabicFixer.WordJoiner = "\u202f"; ArabicFixer.PreventWordSplit = true; break; case "thin": ArabicFixer.WordJoiner = "\u2009"; ArabicFixer.PreventWordSplit = true; break; default: ArabicFixer.WordJoiner = "\u00a0"; ArabicFixer.PreventWordSplit = true; break; } _harmony = new Harmony("bandar.arabicrepo"); if (_cfgFixDisplay.Value) { TryHookTmpDisplay(); } if (_cfgFixOutgoing.Value) { ((MonoBehaviour)this).StartCoroutine(WaitForChatThenHook()); } else { Log.LogInfo((object)"FixOutgoing disabled; only local display reshaping is active."); } if (_cfgSmoothFont.Value) { ((MonoBehaviour)this).StartCoroutine(SmoothFontRoutine()); } Log.LogInfo((object)"Arabic Chat Fix v1.0.5 loaded."); } [IteratorStateMachine(typeof(<SmoothFontRoutine>d__23))] private IEnumerator SmoothFontRoutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <SmoothFontRoutine>d__23(0) { <>4__this = this }; } private void TrySmoothFont() { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("TMPro.TMP_FontAsset"); Type type2 = AccessTools.TypeByName("TMPro.TMP_Settings"); if (type == null || type2 == null) { Log.LogWarning((object)"TMP not found; font smoothing skipped."); return; } string text = (string.IsNullOrEmpty(_cfgSmoothFontName.Value) ? "Tahoma" : _cfgSmoothFontName.Value); Font val = null; try { val = Font.CreateDynamicFontFromOSFont(text, 90); } catch { } if ((Object)(object)val == (Object)null) { try { val = new Font(text); } catch { } } if ((Object)(object)val == (Object)null) { Log.LogWarning((object)("System font '" + text + "' not found; font smoothing skipped.")); return; } MethodInfo methodInfo = null; MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2.Name != "CreateFontAsset")) { ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length >= 1 && parameters[0].ParameterType == typeof(Font)) { methodInfo = methodInfo2; break; } } } if (methodInfo == null) { Log.LogWarning((object)"TMP_FontAsset.CreateFontAsset(Font) not available."); return; } ParameterInfo[] parameters2 = methodInfo.GetParameters(); object[] array = new object[parameters2.Length]; array[0] = val; for (int j = 1; j < parameters2.Length; j++) { array[j] = (parameters2[j].HasDefaultValue ? parameters2[j].DefaultValue : (parameters2[j].ParameterType.IsValueType ? Activator.CreateInstance(parameters2[j].ParameterType) : null)); } object obj3 = methodInfo.Invoke(null, array); if (obj3 == null) { Log.LogWarning((object)"Could not create TMP font asset."); return; } PropertyInfo property = type2.GetProperty("fallbackFontAssets", BindingFlags.Static | BindingFlags.Public); IList list = ((property != null) ? (property.GetValue(null) as IList) : null); if (list == null) { object obj4 = type2.GetProperty("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); FieldInfo field = type2.GetField("m_fallbackFontAssets", BindingFlags.Instance | BindingFlags.NonPublic); if (obj4 != null && field != null) { list = field.GetValue(obj4) as IList; } } if (list == null) { Log.LogWarning((object)"Could not access TMP fallback list; font smoothing skipped."); return; } list.Add(obj3); Log.LogInfo((object)("Font smoothing on: Arabic will render from '" + text + "' (local view only).")); } catch (Exception ex) { Log.LogError((object)("Font smoothing failed: " + ex.Message)); } } private void TryHookTmpDisplay() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("TMPro.TMP_Text"); if (type == null) { Log.LogWarning((object)"TMPro.TMP_Text not found; display reshaping off."); return; } MethodInfo methodInfo = AccessTools.PropertySetter(type, "text"); if (methodInfo == null) { Log.LogWarning((object)"TMP_Text.text setter not found; display reshaping off."); return; } _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Plugin).GetMethod("TmpTextSetterPrefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Display reshaping hooked onto TMP_Text.text."); } catch (Exception ex) { Log.LogError((object)("Display hook failed: " + ex)); } } private static void TmpTextSetterPrefix(ref string value) { try { if (!string.IsNullOrEmpty(value) && ArabicFixer.ContainsArabic(value) && !ArabicFixer.IsAlreadyShaped(value)) { value = ArabicFixer.Fix(value); } } catch { } } [IteratorStateMachine(typeof(<WaitForChatThenHook>d__27))] private IEnumerator WaitForChatThenHook() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WaitForChatThenHook>d__27(0) { <>4__this = this }; } private void ResolveMessageField(Type t) { try { if (!string.IsNullOrEmpty(_cfgMessageField.Value)) { MessageField = AccessTools.Field(t, _cfgMessageField.Value); if (MessageField != null) { Log.LogInfo((object)("Message field (override): " + MessageField.Name)); return; } Log.LogWarning((object)("MessageFieldOverride '" + _cfgMessageField.Value + "' not found; auto-detecting.")); } string[] obj = new string[7] { "chatMessage", "chatText", "message", "currentText", "typedText", "input", "text" }; FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); string[] array = obj; foreach (string b in array) { FieldInfo[] array2 = fields; foreach (FieldInfo fieldInfo in array2) { if (fieldInfo.FieldType == typeof(string) && string.Equals(fieldInfo.Name, b, StringComparison.OrdinalIgnoreCase)) { MessageField = fieldInfo; Log.LogInfo((object)("Message field (auto): " + fieldInfo.Name)); return; } } } Log.LogInfo((object)"No obvious message string field; relying on string-argument hooks."); } catch (Exception ex) { Log.LogError((object)("ResolveMessageField failed: " + ex)); } } private void HookSendMethods(Type t) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown List<MethodInfo> list = new List<MethodInfo>(); HarmonyMethod val = new HarmonyMethod(typeof(Plugin).GetMethod("SendArgPrefix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val2 = new HarmonyMethod(typeof(Plugin).GetMethod("SendFieldPrefix", BindingFlags.Static | BindingFlags.NonPublic)); if (!string.IsNullOrEmpty(_cfgSendMethods.Value)) { string[] array = _cfgSendMethods.Value.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } try { MethodInfo methodInfo = AccessTools.DeclaredMethod(t, text, (Type[])null, (Type[])null); if (methodInfo != null) { list.Add(methodInfo); } else { Log.LogWarning((object)("SendMethodOverride '" + text + "' not found on " + t.Name)); } } catch (Exception ex) { Log.LogWarning((object)("override '" + text + "' skipped: " + ex.Message)); } } } if (list.Count == 0) { string[] array = new string[9] { "MessageSend", "ForceSendMessage", "ForceConfirmChat", "SendChatMessage", "ChatSend", "SendChat", "SubmitMessage", "ConfirmMessage", "PostMessage" }; foreach (string text2 in array) { try { MethodInfo methodInfo2 = AccessTools.DeclaredMethod(t, text2, (Type[])null, (Type[])null); if (methodInfo2 != null && methodInfo2.ReturnType == typeof(void)) { list.Add(methodInfo2); } } catch (Exception ex2) { Log.LogWarning((object)("curated '" + text2 + "' skipped: " + ex2.Message)); } } } if (list.Count == 0 && _cfgAggressive.Value) { MethodInfo[] methods = t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo3 in methods) { if (!(methodInfo3.ReturnType != typeof(void)) && !methodInfo3.IsAbstract && !(methodInfo3.GetBaseDefinition().DeclaringType != t)) { string text3 = methodInfo3.Name.ToLowerInvariant(); bool num = text3.Contains("send") || text3.Contains("submit") || text3.Contains("confirm") || text3.Contains("post") || text3 == "say"; ParameterInfo[] parameters = methodInfo3.GetParameters(); bool flag = parameters.Length >= 1 && parameters[0].ParameterType == typeof(string); if (num || (flag && text3.Contains("chat"))) { list.Add(methodInfo3); } } } } if (list.Count == 0) { Log.LogWarning((object)"Could not locate a chat send method automatically. Open the member dump in the log, find the method called when you press Enter, and set 'SendMethodOverride' in the config."); return; } foreach (MethodInfo item in list) { try { bool flag2 = false; ParameterInfo[] parameters2 = item.GetParameters(); for (int i = 0; i < parameters2.Length; i++) { if (parameters2[i].ParameterType == typeof(string)) { flag2 = true; break; } } _harmony.Patch((MethodBase)item, flag2 ? val : ((MessageField != null) ? val2 : val), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (!flag2 && MessageField != null) { Log.LogInfo((object)("Hooked send (field-mode): " + item.Name)); continue; } Log.LogInfo((object)("Hooked send (arg-mode): " + item.Name + "(" + Sig(item) + ")")); } catch (Exception ex3) { Log.LogError((object)("Failed to hook " + item.Name + ": " + ex3)); } } } private static string Dump(string s) { StringBuilder stringBuilder = new StringBuilder(); foreach (char c in s) { if (stringBuilder.Length > 0) { stringBuilder.Append(' '); } int num = c; stringBuilder.Append(num.ToString("X4")); } return stringBuilder.ToString(); } private static void SendArgPrefix(object[] __args) { try { if (__args == null) { return; } for (int i = 0; i < __args.Length; i++) { if (!(__args[i] is string text) || string.IsNullOrEmpty(text)) { continue; } if (DebugLog) { Log.LogInfo((object)("[send-arg] IN len=" + text.Length + " : " + Dump(text))); } if (ArabicFixer.ContainsArabic(text) && !ArabicFixer.IsAlreadyShaped(text)) { string text2 = (string)(__args[i] = ArabicFixer.Fix(text)); if (DebugLog) { Log.LogInfo((object)("[send-arg] OUT len=" + text2.Length + " : " + Dump(text2))); } } break; } } catch (Exception ex) { Log.LogError((object)("SendArgPrefix: " + ex)); } } private static void SendFieldPrefix(object __instance) { try { if (MessageField == null || __instance == null) { return; } string text = MessageField.GetValue(__instance) as string; if (string.IsNullOrEmpty(text)) { return; } if (DebugLog) { Log.LogInfo((object)("[send-field] IN len=" + text.Length + " : " + Dump(text))); } if (ArabicFixer.ContainsArabic(text) && !ArabicFixer.IsAlreadyShaped(text)) { string text2 = ArabicFixer.Fix(text); MessageField.SetValue(__instance, text2); if (DebugLog) { Log.LogInfo((object)("[send-field] OUT len=" + text2.Length + " : " + Dump(text2))); } } } catch (Exception ex) { Log.LogError((object)("SendFieldPrefix: " + ex)); } } private static string Sig(MethodInfo m) { StringBuilder stringBuilder = new StringBuilder(); ParameterInfo[] parameters = m.GetParameters(); foreach (ParameterInfo parameterInfo in parameters) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(parameterInfo.ParameterType.Name).Append(' ').Append(parameterInfo.Name); } return stringBuilder.ToString(); } private void DumpType(Type t) { try { Log.LogInfo((object)("===== members of " + t.FullName + " (use for SendMethodOverride / MessageFieldOverride) =====")); Log.LogInfo((object)"-- string fields --"); FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(string)) { Log.LogInfo((object)(" " + fieldInfo.Name)); } } Log.LogInfo((object)"-- void instance methods --"); MethodInfo[] methods = t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.ReturnType == typeof(void) && methodInfo.GetBaseDefinition().DeclaringType == t) { Log.LogInfo((object)(" " + methodInfo.Name + "(" + Sig(methodInfo) + ")")); } } Log.LogInfo((object)"===== end member dump ====="); } catch (Exception ex) { Log.LogError((object)("DumpType failed: " + ex)); } } private void OnDestroy() { try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch { } } } }