using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
using UnityEngine.SceneManagement;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Crawl Localization")]
[assembly: AssemblyDescription("Menu localization and per-language font replacement for Crawl")]
[assembly: AssemblyFileVersion("0.13.0.0")]
[assembly: AssemblyVersion("0.13.0.0")]
namespace CrawlLocalization;
[HarmonyPatch(typeof(LevelTitle), "Start")]
internal static class ChapterTitlePatch
{
private static void Postfix(TextMesh ___m_textLevel, TextMesh ___m_textFloor)
{
if (!((Object)(object)Plugin.Instance == (Object)null))
{
Bind(___m_textLevel);
Bind(___m_textFloor);
}
}
private static void Bind(TextMesh mesh)
{
if (!((Object)(object)mesh == (Object)null))
{
LocalizedMenuLabel orCreate = LocalizedMenuLabel.GetOrCreate(((Component)mesh).gameObject);
orCreate.Standalone = true;
orCreate.ResetSource(mesh.text);
orCreate.Refresh();
}
}
}
internal static class ChineseTextWrapper
{
private static readonly Regex Units = new Regex("</?(?:b|i|size|color|material)(?:=[^>]*)?>|<quad\\s[^>]*>|\\r\\n|[\\r\\n]|[A-Za-z0-9]+(?:[-'][A-Za-z0-9]+)*|.", RegexOptions.IgnoreCase);
private const string Closing = ",。!?;:、)】》」』”’%%.,!?;:)";
private const string Opening = "(【《「『“‘(";
private static bool IsTag(string unit)
{
if (unit.StartsWith("<", StringComparison.Ordinal))
{
return unit.EndsWith(">", StringComparison.Ordinal);
}
return false;
}
internal static bool ContainsChinese(string text)
{
if (text == null)
{
return false;
}
foreach (char c in text)
{
if (c >= '㐀' && c <= '鿿')
{
return true;
}
}
return false;
}
internal static string Wrap(string text, float width, Func<char, float> advance, Func<string, float> tagWidth = null)
{
if (string.IsNullOrEmpty(text) || width <= 0f)
{
return text;
}
List<string> list = new List<string>();
foreach (Match item in Units.Matches(text))
{
string value = item.Value;
if (list.Count > 0 && !IsTag(list[list.Count - 1]) && value.Length == 1 && ",。!?;:、)】》」』”’%%.,!?;:)".IndexOf(value[0]) >= 0 && list[list.Count - 1] != "\n" && list[list.Count - 1] != "\r" && list[list.Count - 1] != "\r\n")
{
list[list.Count - 1] += value;
}
else if (list.Count > 0 && list[list.Count - 1].Length == 1 && "(【《「『“‘(".IndexOf(list[list.Count - 1][0]) >= 0 && !IsTag(value) && value != "\n" && value != "\r" && value != "\r\n")
{
list[list.Count - 1] += value;
}
else
{
list.Add(value);
}
}
StringBuilder stringBuilder = new StringBuilder();
float num = 0f;
foreach (string item2 in list)
{
switch (item2)
{
case "\r\n":
case "\r":
case "\n":
stringBuilder.Append('\n');
num = 0f;
continue;
}
if (IsTag(item2))
{
float num2 = tagWidth?.Invoke(item2) ?? 0f;
if (num2 > 0f && num > 0f && num + num2 > width)
{
stringBuilder.Append('\n');
num = 0f;
}
stringBuilder.Append(item2);
num += num2;
continue;
}
float num3 = 0f;
string text2 = item2;
foreach (char arg in text2)
{
num3 += advance(arg);
}
if (num > 0f && num + num3 > width)
{
stringBuilder.Append('\n');
num = 0f;
}
if (num == 0f && item2 == " ")
{
continue;
}
text2 = item2;
foreach (char c in text2)
{
float num4 = advance(c);
if (num > 0f && num + num4 > width)
{
stringBuilder.Append('\n');
num = 0f;
}
stringBuilder.Append(c);
num += num4;
}
}
return stringBuilder.ToString();
}
}
internal sealed class FontChoice
{
internal Font Font;
internal int Size;
internal FontStyle Style;
internal float Scale = 1f;
}
internal sealed class FontService : IDisposable
{
private readonly Action<string> warn;
private readonly Action<string> info;
private readonly Dictionary<string, Font> fonts = new Dictionary<string, Font>(StringComparer.Ordinal);
private readonly Dictionary<string, AssetBundle> bundles = new Dictionary<string, AssetBundle>(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<string> privateFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly List<Font> ownedFonts = new List<Font>();
private static readonly Regex RichTags = new Regex("</?(?:b|i|size|color|material)(?:=[^>]*)?>|<quad\\s[^>]*>", RegexOptions.IgnoreCase);
private const uint PrivateFont = 16u;
[DllImport("gdi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int AddFontResourceEx(string file, uint flags, IntPtr reserved);
[DllImport("gdi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool RemoveFontResourceEx(string file, uint flags, IntPtr reserved);
internal FontService(Action<string> warn, Action<string> info)
{
this.warn = warn;
this.info = info;
}
internal bool TryChoose(LanguagePack language, Font original, int size, FontStyle style, string text, out FontChoice choice)
{
//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
//IL_006d: Unknown result type (might be due to invalid IL or missing references)
//IL_0117: Unknown result type (might be due to invalid IL or missing references)
//IL_0119: Unknown result type (might be due to invalid IL or missing references)
//IL_008c: Unknown result type (might be due to invalid IL or missing references)
//IL_008e: Unknown result type (might be due to invalid IL or missing references)
//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
//IL_0185: Unknown result type (might be due to invalid IL or missing references)
//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
choice = null;
if (language.Fonts.TryGetValue(((Object)(object)original == (Object)null) ? "" : ((Object)original).name, out var value) || language.Fonts.TryGetValue("*", out value))
{
Font val = Load(value);
int num = ((value.Kind == "system") ? 32 : SamplingSize(language, size, text));
if (Covers(val, text, num, style))
{
choice = new FontChoice
{
Font = val,
Size = num,
Style = style,
Scale = value.Scale * ((value.Kind == "system") ? HeightRatio(original, size, style, val, num, text) : ((float)size / (float)num))
};
return true;
}
warn("Mapped font unavailable or missing glyphs: " + value.Name + "; trying original/system fonts.");
}
int num2 = SamplingSize(language, size, text);
if (Covers(original, text, num2, style))
{
choice = new FontChoice
{
Font = original,
Size = num2,
Style = style,
Scale = (float)size / (float)num2
};
return true;
}
string[] fallbackFonts = language.FallbackFonts;
foreach (string text2 in fallbackFonts)
{
Font val2 = Load(new FontRule
{
Kind = "system",
File = "",
Name = text2.Trim(),
Scale = 1f
});
if (Covers(val2, text, 32, style))
{
choice = new FontChoice
{
Font = val2,
Size = 32,
Style = style,
Scale = HeightRatio(original, size, style, val2, 32, text)
};
return true;
}
}
warn("No font covers translation: " + text + "; retaining original menu text.");
return false;
}
internal bool TryChooseGui(LanguagePack language, Font original, int size, FontStyle style, string text, out Font font)
{
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
font = original;
if (language.Fonts.TryGetValue("CrawlOnlineOverlay", out var value))
{
Font val = Load(value);
if (Covers(val, text, size, style))
{
font = val;
return true;
}
}
if (Covers(original, text, size, style))
{
return true;
}
string[] fallbackFonts = language.FallbackFonts;
foreach (string text2 in fallbackFonts)
{
Font val2 = Load(new FontRule
{
Kind = "system",
File = "",
Name = text2.Trim(),
Scale = 1f
});
if (Covers(val2, text, size, style))
{
font = val2;
return true;
}
}
return false;
}
internal static int SamplingSize(LanguagePack language, int size, string text)
{
if (!language.UsesChineseTextRules || size <= 0 || size >= 64 || text == null)
{
return size;
}
foreach (char c in text)
{
if (c >= '㐀' && c <= '鿿')
{
return size * ((160 + size - 1) / size);
}
}
return size;
}
private Font Load(FontRule rule)
{
string text = rule.Kind + "|" + rule.File + "|" + rule.Name;
if (fonts.TryGetValue(text, out var value))
{
return value;
}
value = null;
try
{
if (rule.Kind == "bundle")
{
if (!bundles.TryGetValue(rule.File, out var value2))
{
if (!File.Exists(rule.File))
{
throw new FileNotFoundException("Font bundle not found.", rule.File);
}
value2 = AssetBundle.LoadFromFile(rule.File);
bundles.Add(rule.File, value2);
}
if ((Object)(object)value2 == (Object)null)
{
throw new InvalidOperationException("Cannot load bundle; use a Windows bundle compatible with Unity 5.4.2.");
}
value = value2.LoadAsset<Font>(rule.Name);
if ((Object)(object)value == (Object)null)
{
throw new InvalidOperationException("Font asset not found: " + rule.Name);
}
}
else
{
if (rule.Kind == "ttf" && !privateFiles.Contains(rule.File))
{
if (Environment.OSVersion.Platform != PlatformID.Win32NT)
{
throw new PlatformNotSupportedException("Private TTF loading requires Windows. Use a compatible bundle on other platforms.");
}
if (!File.Exists(rule.File))
{
throw new FileNotFoundException("Font file not found.", rule.File);
}
if (AddFontResourceEx(rule.File, 16u, IntPtr.Zero) == 0)
{
throw new InvalidOperationException("Windows rejected font file (error " + Marshal.GetLastWin32Error() + ").");
}
privateFiles.Add(rule.File);
}
if (rule.Kind == "system" && !Array.Exists(Font.GetOSInstalledFontNames(), (string name) => string.Equals(name, rule.Name, StringComparison.OrdinalIgnoreCase)))
{
throw new InvalidOperationException("System font is not installed: " + rule.Name);
}
value = Font.CreateDynamicFontFromOSFont(rule.Name, 32);
if ((Object)(object)value == (Object)null)
{
throw new InvalidOperationException("Unity could not create font: " + rule.Name);
}
Object.DontDestroyOnLoad((Object)(object)value);
ownedFonts.Add(value);
}
}
catch (Exception ex)
{
warn("Font load failed [" + text + "]: " + ex.Message);
}
fonts[text] = value;
if ((Object)(object)value != (Object)null)
{
info(((rule.Kind == "bundle") ? "Loaded embedded font: " : "Created font by family (file identity unverified): ") + text);
}
return value;
}
private bool Covers(Font font, string text, int size, FontStyle style)
{
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_0065: Unknown result type (might be due to invalid IL or missing references)
try
{
if ((Object)(object)font == (Object)null || (Object)(object)font.material == (Object)null)
{
return false;
}
text = RichTags.Replace(text, "");
if (font.dynamic)
{
font.RequestCharactersInTexture(text, size, style);
}
string text2 = text;
CharacterInfo val = default(CharacterInfo);
foreach (char c in text2)
{
if (!char.IsWhiteSpace(c) && !char.IsControl(c) && (!font.GetCharacterInfo(c, ref val, size, style) || ((CharacterInfo)(ref val)).glyphWidth <= 0 || ((CharacterInfo)(ref val)).glyphHeight <= 0))
{
return false;
}
}
return true;
}
catch (Exception ex)
{
warn("Font glyph check failed; trying next font: " + ex.Message);
return false;
}
}
private static float HeightRatio(Font original, int originalSize, FontStyle style, Font replacement, int size, string text)
{
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_0081: Unknown result type (might be due to invalid IL or missing references)
float num = (float)originalSize * 0.7f;
CharacterInfo val = default(CharacterInfo);
if ((Object)(object)original != (Object)null)
{
if (original.dynamic)
{
original.RequestCharactersInTexture("A", originalSize, style);
}
if (original.GetCharacterInfo('A', ref val, originalSize, style))
{
num = Mathf.Max(1, ((CharacterInfo)(ref val)).maxY - ((CharacterInfo)(ref val)).minY);
}
}
string text2 = RichTags.Replace(text, "");
foreach (char c in text2)
{
if (c > '\u007f' && !char.IsWhiteSpace(c) && replacement.GetCharacterInfo(c, ref val, size, style))
{
return num / (float)Mathf.Max(1, ((CharacterInfo)(ref val)).maxY - ((CharacterInfo)(ref val)).minY);
}
}
return num / Mathf.Max(1f, (float)size * 0.7f);
}
public void Dispose()
{
foreach (Font ownedFont in ownedFonts)
{
if ((Object)(object)ownedFont != (Object)null)
{
Object.Destroy((Object)(object)ownedFont);
}
}
foreach (AssetBundle value in bundles.Values)
{
if ((Object)(object)value != (Object)null)
{
value.Unload(true);
}
}
foreach (string privateFile in privateFiles)
{
RemoveFontResourceEx(privateFile, 16u, IntPtr.Zero);
}
fonts.Clear();
bundles.Clear();
ownedFonts.Clear();
privateFiles.Clear();
}
}
[HarmonyPatch(typeof(GUIActionIndicator), "SetText")]
internal static class GameplayActionPatch
{
private static void Postfix(string text, TextMesh ___m_textMesh)
{
if ((Object)(object)___m_textMesh == (Object)null || (Object)(object)Plugin.Instance == (Object)null)
{
return;
}
LocalizedMenuLabel localizedMenuLabel = ((Component)___m_textMesh).GetComponent<LocalizedMenuLabel>();
if ((Object)(object)localizedMenuLabel == (Object)null)
{
if (!Plugin.Instance.Language.TryTranslate(text, out var _))
{
return;
}
localizedMenuLabel = LocalizedMenuLabel.GetOrCreate(((Component)___m_textMesh).gameObject);
}
localizedMenuLabel.Skip = false;
localizedMenuLabel.RefreshDirect(text, null);
}
}
[HarmonyPatch]
internal static class GameplayStaticTextPatch
{
private static IEnumerable<MethodBase> TargetMethods()
{
Type[] array = new Type[5]
{
typeof(PropPortal),
typeof(PropSign),
typeof(PropStoreTeleporter),
typeof(PropActionable),
typeof(GUIGame)
};
foreach (Type type in array)
{
yield return AccessTools.Method(type, "Start", (Type[])null, (Type[])null);
}
}
private static void Postfix(MonoBehaviour __instance)
{
if ((Object)(object)Plugin.Instance != (Object)null)
{
MenuTextMeshes.Scan(((Component)__instance).gameObject);
}
}
}
[HarmonyPatch(typeof(GUIPlayer), "Start")]
internal static class GameplayRewardPatch
{
private static void Postfix(GUIPlayer __instance, MenuFlashEffect ___m_favourTallyContainer)
{
if ((Object)(object)Plugin.Instance == (Object)null)
{
return;
}
if ((Object)(object)___m_favourTallyContainer != (Object)null)
{
TextMesh[] componentsInChildren = ((Component)___m_favourTallyContainer).GetComponentsInChildren<TextMesh>(true);
foreach (TextMesh val in componentsInChildren)
{
string text = null;
if (string.Equals(val.text, "gold", StringComparison.OrdinalIgnoreCase))
{
text = "@gameplay.reward-unit:gold";
}
else if (string.Equals(val.text, "REWARD", StringComparison.OrdinalIgnoreCase))
{
text = "REWARD";
}
if (text != null)
{
LocalizedMenuLabel.GetOrCreate(((Component)val).gameObject).RefreshDirect(text, null);
}
}
}
MenuTextMeshes.Scan(((Component)__instance).gameObject);
}
}
[HarmonyPatch(typeof(GUIGame), "ShowKillStreak")]
internal static class GameplayStreakPatch
{
private static void Postfix(GameObject ___m_killStreak)
{
if (!((Object)(object)Plugin.Instance == (Object)null) && !((Object)(object)___m_killStreak == (Object)null))
{
TextMesh component = ___m_killStreak.GetComponent<TextMesh>();
if ((Object)(object)component != (Object)null)
{
LocalizedMenuLabel.GetOrCreate(((Component)component).gameObject).RefreshDirect(component.text, null);
}
}
}
}
internal static class HeroStatsDisplay
{
internal static void Refresh(TextMesh mesh, string lookup, string fallback)
{
if (!((Object)(object)mesh == (Object)null) && !((Object)(object)Plugin.Instance == (Object)null))
{
LocalizedMenuLabel.GetOrCreate(((Component)mesh).gameObject).RefreshDirect(lookup, fallback);
}
}
}
[HarmonyPatch(typeof(GUIHeroStats), "UpdateText")]
internal static class HeroStatsNamesPatch
{
private static void Postfix(PlayerGameData ___m_player, TextMesh ___m_weaponNoSplit, TextMesh ___m_weaponSplit1, TextMesh ___m_weaponSplit2, TextMesh ___m_spell)
{
if ((Object)(object)Plugin.Instance == (Object)null || ___m_player == null || ___m_player.GetWeaponData() == null)
{
return;
}
if ((Object)(object)___m_weaponNoSplit != (Object)null && ((Component)___m_weaponNoSplit).gameObject.activeSelf)
{
HeroStatsDisplay.Refresh(___m_weaponNoSplit, ___m_weaponNoSplit.text, null);
}
else
{
if ((Object)(object)___m_weaponSplit1 != (Object)null)
{
HeroStatsDisplay.Refresh(___m_weaponSplit1, "@shop.prefix:" + ___m_weaponSplit1.text, null);
}
if ((Object)(object)___m_weaponSplit2 != (Object)null)
{
HeroStatsDisplay.Refresh(___m_weaponSplit2, ___m_weaponSplit2.text, null);
}
}
if ((Object)(object)___m_spell != (Object)null)
{
HeroStatsDisplay.Refresh(___m_spell, ___m_spell.text, null);
}
}
}
[HarmonyPatch(typeof(GUIHeroStats), "UpdateArtifactIcons")]
internal static class HeroStatsArtifactsPatch
{
private static void Prefix(int ___m_numArtifacts, out int __state)
{
__state = ___m_numArtifacts;
}
private static void Postfix(bool onlyIfCountChanged, int __state, int ___m_numArtifacts, PlayerGameData ___m_player, List<GUIArtifactIcon> ___m_artifactIcons)
{
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Invalid comparison between Unknown and I4
if ((Object)(object)Plugin.Instance == (Object)null || ___m_player == null || ___m_artifactIcons == null || (onlyIfCountChanged && __state == ___m_numArtifacts))
{
return;
}
int num = 0;
foreach (Powerup powerup in ___m_player.GetPowerups())
{
if ((int)powerup.GetPurchasableType() == 2)
{
if (num >= ___m_numArtifacts || num >= ___m_artifactIcons.Count)
{
break;
}
GUIArtifactIcon val = ___m_artifactIcons[num++];
if (!((Object)(object)val == (Object)null))
{
string lookup = (string.IsNullOrEmpty(powerup.m_displayNameHud) ? powerup.m_displayName : powerup.m_displayNameHud);
HeroStatsDisplay.Refresh(val.m_text, lookup, powerup.m_displayName);
}
}
}
}
}
internal sealed class FontRule
{
internal string Kind;
internal string File;
internal string Name;
internal float Scale;
}
internal sealed class LanguagePack
{
internal string Id;
internal string[] FallbackFonts = new string[0];
internal readonly Dictionary<string, FontRule> Fonts = new Dictionary<string, FontRule>(StringComparer.Ordinal);
private readonly Dictionary<string, string> exact = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private readonly List<NumericTranslation> numeric = new List<NumericTranslation>();
internal readonly HashSet<string> TextMeshPaths = new HashSet<string>(StringComparer.Ordinal);
internal bool UsesChineseTextRules
{
get
{
if (Id != null)
{
if (!string.Equals(Id, "zh", StringComparison.OrdinalIgnoreCase) && !Id.StartsWith("zh-", StringComparison.OrdinalIgnoreCase))
{
return Id.StartsWith("zh_", StringComparison.OrdinalIgnoreCase);
}
return true;
}
return false;
}
}
internal int Count => exact.Count + numeric.Count;
internal static string NormalizeId(string id)
{
id = (id ?? "").Trim();
if (string.Equals(id, "zh-CN", StringComparison.OrdinalIgnoreCase) || string.Equals(id, "zh-Hans", StringComparison.OrdinalIgnoreCase))
{
return "zh-Hans";
}
if (string.Equals(id, "zh-TW", StringComparison.OrdinalIgnoreCase) || string.Equals(id, "zh-Hant", StringComparison.OrdinalIgnoreCase))
{
return "zh-Hant";
}
if (string.Equals(id, "en", StringComparison.OrdinalIgnoreCase))
{
return "en";
}
if (!Regex.IsMatch(id, "\\A[A-Za-z0-9][A-Za-z0-9_-]*\\z"))
{
return "en";
}
return id;
}
internal static string ResolveFile(string root, string relative)
{
if (string.IsNullOrEmpty(relative) || Path.IsPathRooted(relative))
{
throw new FormatException("Expected a relative file path.");
}
string text = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
char directorySeparatorChar = Path.DirectorySeparatorChar;
string text2 = text + directorySeparatorChar;
string fullPath = Path.GetFullPath(Path.Combine(text2, relative));
if (!fullPath.StartsWith(text2, StringComparison.OrdinalIgnoreCase))
{
throw new FormatException("File must stay inside the plugin folder.");
}
return fullPath;
}
internal static LanguagePack Load(string root, string id, Action<string> warn)
{
LanguagePack pack = new LanguagePack
{
Id = NormalizeId(id)
};
if (pack.Id == "en")
{
return pack;
}
string path = Path.Combine(Path.Combine(root, "Languages"), pack.Id);
string[] array = new string[10] { "translations.tsv", "vault-translations.tsv", "deity-translations.tsv", "chapter-translations.tsv", "shop-translations.tsv", "gameplay-translations.tsv", "progression-translations.tsv", "shop-il-translations.tsv", "crawlonline-translations.tsv", "tutorial-translations.tsv" };
foreach (string text in array)
{
string path2 = Path.Combine(path, text);
if (text != "translations.tsv" && !File.Exists(path2))
{
continue;
}
ReadLines(path2, warn, delegate(string line)
{
string[] array2 = line.Split(new char[1] { '\t' });
if ((array2.Length != 2 && array2.Length != 3) || array2[0].Length == 0 || (array2[1].Length == 0 && !array2[0].StartsWith("@shop.il:", StringComparison.Ordinal)))
{
throw new FormatException("Expected source<TAB>translation[<TAB>comment].");
}
if (!(array2[1] == "@TODO"))
{
string text2 = NormalizeText(Unescape(array2[0]));
string text3 = Unescape(array2[1]);
NumericTranslation numericTranslation = NumericTranslation.Create(text2, text3);
if (text2.StartsWith("@shop.", StringComparison.Ordinal) || text2.StartsWith("@progression.", StringComparison.Ordinal) || text2.StartsWith("@online:", StringComparison.Ordinal))
{
numericTranslation = null;
}
if (numericTranslation != null)
{
foreach (NumericTranslation item in pack.numeric)
{
if (string.Equals(item.Source, text2, StringComparison.OrdinalIgnoreCase))
{
throw new FormatException("Duplicate source: " + text2);
}
}
pack.numeric.Add(numericTranslation);
}
else
{
if (pack.exact.ContainsKey(text2))
{
throw new FormatException("Duplicate source: " + text2);
}
pack.exact.Add(text2, text3);
}
}
});
}
ReadLines(Path.Combine(path, "fonts.tsv"), warn, delegate(string line)
{
string[] array2 = line.Split(new char[1] { '\t' });
if (array2.Length != 5 || array2[0].Length == 0 || array2[3].Length == 0 || !float.TryParse(array2[4], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || float.IsNaN(result) || float.IsInfinity(result) || result <= 0f || result > 10f)
{
throw new FormatException("Expected original<TAB>kind<TAB>file<TAB>family/asset<TAB>scale (0..10).");
}
if (array2[1] != "ttf" && array2[1] != "bundle" && array2[1] != "system")
{
throw new FormatException("Font kind must be ttf, bundle or system.");
}
string file = ((array2[1] == "system") ? "" : ResolveFile(root, array2[2]));
if (pack.Fonts.ContainsKey(array2[0]))
{
throw new FormatException("Duplicate font mapping: " + array2[0]);
}
pack.Fonts.Add(array2[0], new FontRule
{
Kind = array2[1],
File = file,
Name = array2[3],
Scale = result
});
});
ReadLines(Path.Combine(path, "language.ini"), warn, delegate(string line)
{
int num = line.IndexOf('=');
if (num <= 0)
{
throw new FormatException("Expected key=value.");
}
string text2 = line.Substring(0, num).Trim();
if (text2 == "FallbackFonts")
{
pack.FallbackFonts = line.Substring(num + 1).Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
}
else if (text2 != "Name")
{
throw new FormatException("Unknown setting: " + text2);
}
});
string path3 = Path.Combine(path, "textmesh-paths.txt");
if (File.Exists(path3))
{
ReadLines(path3, warn, delegate(string line)
{
pack.TextMeshPaths.Add(line.Trim());
});
}
return pack;
}
internal static string[] ReadTextLines(string path)
{
using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
using StreamReader streamReader = new StreamReader(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true));
List<string> list = new List<string>();
string item;
while ((item = streamReader.ReadLine()) != null)
{
list.Add(item);
}
return list.ToArray();
}
private static void ReadLines(string path, Action<string> warn, Action<string> read)
{
try
{
string[] array = ReadTextLines(path);
for (int i = 0; i < array.Length; i++)
{
if (array[i].Trim().Length != 0 && !array[i].StartsWith("#", StringComparison.Ordinal))
{
try
{
read(array[i]);
}
catch (Exception ex)
{
warn(path + ":" + (i + 1) + ": " + ex.Message);
}
}
}
}
catch (Exception ex2)
{
warn("Cannot read " + path + ": " + ex2.Message + "; keeping original text/font where needed.");
}
}
internal bool TryTranslate(string source, out string translated)
{
translated = null;
if (source == null)
{
return false;
}
source = NormalizeText(source);
if (exact.TryGetValue(source, out translated))
{
return true;
}
foreach (NumericTranslation item in numeric)
{
if (item.TryTranslate(source, out translated))
{
return true;
}
}
return false;
}
internal static string NormalizeText(string text)
{
return text.Replace("\r\n", "\n").Replace('\r', '\n');
}
private static string Unescape(string value)
{
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < value.Length; i++)
{
if (value[i] != '\\')
{
stringBuilder.Append(value[i]);
continue;
}
if (++i == value.Length)
{
throw new FormatException("Trailing backslash; escape literal backslashes.");
}
switch (value[i])
{
case 'n':
stringBuilder.Append('\n');
break;
case 'r':
stringBuilder.Append('\r');
break;
case 't':
stringBuilder.Append('\t');
break;
case '\\':
stringBuilder.Append('\\');
break;
default:
throw new FormatException("Unsupported escape sequence.");
}
}
return stringBuilder.ToString();
}
}
[HarmonyPatch(typeof(MenuTextMenuItem), "Initialise")]
internal static class MenuInitialisePatch
{
private static void Prefix(MenuTextMenuItem __instance, MenuTextMenuItemData data)
{
if (!((Object)(object)Plugin.Instance == (Object)null) && data != null)
{
LocalizedMenuLabel orCreate = LocalizedMenuLabel.GetOrCreate(((Component)__instance).gameObject);
if (!((Object)(object)orCreate == (Object)null))
{
orCreate.Skip = data.m_message == "MsgCharSelect" || data.m_message == "MsgNameSelect";
orCreate.ResetSource(data.m_text);
}
}
}
private static void Postfix(MenuTextMenuItem __instance)
{
MenuTextMeshes.Scan(((Component)__instance).gameObject);
}
}
[HarmonyPatch(typeof(MenuItemTextButton), "Initialise")]
internal static class TextButtonInitialisePatch
{
private static void Postfix(MenuItemTextButton __instance)
{
LocalizedMenuLabel component = ((Component)__instance).GetComponent<LocalizedMenuLabel>();
if ((Object)(object)component != (Object)null)
{
component.Refresh();
}
}
}
[HarmonyPatch(typeof(GUIText), "SetText")]
internal static class MenuDisplayPatch
{
private static void Prefix(GUIText __instance, ref string text)
{
if (!((Object)(object)Plugin.Instance == (Object)null))
{
LocalizedMenuLabel localizedMenuLabel = ((Component)__instance).GetComponent<LocalizedMenuLabel>();
if ((Object)(object)localizedMenuLabel == (Object)null && (Object)(object)((Component)__instance).GetComponent<MenuTextMenuItem>() != (Object)null)
{
localizedMenuLabel = LocalizedMenuLabel.GetOrCreate(((Component)__instance).gameObject);
}
if ((Object)(object)localizedMenuLabel == (Object)null)
{
localizedMenuLabel = MenuTextMeshes.Bind(((Component)__instance).gameObject, refresh: false);
}
if ((Object)(object)localizedMenuLabel != (Object)null)
{
text = localizedMenuLabel.Translate(text);
}
}
}
private static void Postfix(GUIText __instance)
{
LocalizedMenuLabel component = ((Component)__instance).GetComponent<LocalizedMenuLabel>();
if ((Object)(object)component != (Object)null)
{
component.CaptureRendered();
}
}
}
[HarmonyPatch(typeof(MenuFlashEffect), "StartFlash")]
internal static class MenuFlashPatch
{
private static void Prefix(MenuFlashEffect __instance)
{
LocalizedMenuLabel component = ((Component)__instance).GetComponent<LocalizedMenuLabel>();
if ((Object)(object)component != (Object)null)
{
component.Refresh();
}
}
}
[HarmonyPatch(typeof(GUIText), "SetFontScale")]
internal static class MenuFontScalePatch
{
private static void Prefix(GUIText __instance, ref float scale)
{
LocalizedMenuLabel component = ((Component)__instance).GetComponent<LocalizedMenuLabel>();
if (!((Object)(object)component == (Object)null))
{
component.BaseScale = scale;
scale *= component.ScaleFactor;
}
}
}
public sealed class LocalizedMenuLabel : MonoBehaviour
{
internal static Func<string, Func<LanguagePack, string>> ExternalProvider;
private Func<LanguagePack, string> boundExternalProvider;
internal bool Skip;
internal bool Standalone;
internal Func<LanguagePack, string> TextProvider;
internal float BaseScale;
internal float ScaleFactor = 1f;
private TextMesh mesh;
private Renderer textRenderer;
private Font originalFont;
private Material originalMaterial;
private Material localMaterial;
private int originalSize;
private FontStyle originalStyle;
private string source;
private string display;
private string rendered;
private string directLookup;
private string directFallback;
private bool usesDirectLookup;
internal static LocalizedMenuLabel GetOrCreate(GameObject obj)
{
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Unknown result type (might be due to invalid IL or missing references)
LocalizedMenuLabel component = obj.GetComponent<LocalizedMenuLabel>();
if ((Object)(object)component != (Object)null)
{
return component;
}
TextMesh component2 = obj.GetComponent<TextMesh>();
if ((Object)(object)component2 == (Object)null)
{
return null;
}
component = obj.AddComponent<LocalizedMenuLabel>();
component.mesh = component2;
component.textRenderer = ((Component)component2).GetComponent<Renderer>();
component.originalFont = component2.font;
component.originalMaterial = (((Object)(object)component.textRenderer == (Object)null) ? null : component.textRenderer.sharedMaterial);
component.originalSize = component2.fontSize;
component.originalStyle = component2.fontStyle;
component.BaseScale = component2.characterSize;
Font.textureRebuilt += component.OnTextureRebuilt;
Plugin.Instance.Labels.Add(component);
return component;
}
private void BindExternal(string text)
{
if (ExternalProvider == null)
{
return;
}
Func<LanguagePack, string> func = ExternalProvider(text);
if (func != null)
{
TextProvider = func;
boundExternalProvider = func;
}
else if (boundExternalProvider != null && text != source && text != display)
{
if ((object)TextProvider == boundExternalProvider)
{
TextProvider = null;
}
boundExternalProvider = null;
}
}
internal void ResetSource(string text)
{
BindExternal(text);
source = text;
display = null;
}
internal void RefreshDirect(string lookup, string fallback)
{
string text = mesh.text;
if (usesDirectLookup && source == text && directLookup == lookup && directFallback == fallback && rendered != null)
{
mesh.text = rendered;
return;
}
directLookup = lookup;
directFallback = fallback;
if (!usesDirectLookup)
{
TextProvider = TranslateDirect;
usesDirectLookup = true;
}
ResetSource(text);
Refresh();
}
private string TranslateDirect(LanguagePack pack)
{
if (pack.TryTranslate(directLookup, out var translated))
{
return translated;
}
if (directFallback == null || !pack.TryTranslate(directFallback, out translated))
{
return null;
}
return translated;
}
internal string Translate(string text)
{
//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
BindExternal(text);
if (display == null || text != display)
{
source = text;
}
try
{
string translated = null;
Plugin instance = Plugin.Instance;
int size = ((originalSize > 0) ? originalSize : (((Object)(object)originalFont != (Object)null && originalFont.fontSize > 0) ? originalFont.fontSize : 16));
bool flag = false;
if (!Skip)
{
translated = ((TextProvider == null) ? null : TextProvider(instance.Language));
flag = translated != null || instance.Language.TryTranslate(source, out translated);
}
if (flag && instance.Fonts.TryChoose(instance.Language, originalFont, size, originalStyle, translated, out var choice))
{
Apply(choice);
display = translated;
return display;
}
}
catch (Exception ex)
{
Plugin.Instance.Warn("Menu localization failed for " + source + ": " + ex.Message);
}
Restore(restoreText: false);
display = source;
return source;
}
private void Apply(FontChoice choice)
{
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
//IL_00c5: Expected O, but got Unknown
ScaleFactor = choice.Scale;
mesh.font = choice.Font;
mesh.fontSize = choice.Size;
mesh.fontStyle = choice.Style;
mesh.characterSize = BaseScale * ScaleFactor;
if ((Object)(object)textRenderer == (Object)null)
{
return;
}
if ((Object)(object)choice.Font == (Object)(object)originalFont)
{
textRenderer.sharedMaterial = originalMaterial;
return;
}
if ((Object)(object)localMaterial == (Object)null)
{
localMaterial = new Material(((Object)(object)originalMaterial != (Object)null) ? originalMaterial : choice.Font.material);
}
textRenderer.sharedMaterial = localMaterial;
UpdateAtlas();
}
private void OnTextureRebuilt(Font font)
{
if ((Object)(object)mesh != (Object)null && (Object)(object)font == (Object)(object)mesh.font && (Object)(object)font != (Object)(object)originalFont)
{
UpdateAtlas();
}
}
private void UpdateAtlas()
{
if (!((Object)(object)mesh == (Object)null) && !((Object)(object)mesh.font == (Object)null) && !((Object)(object)mesh.font.material == (Object)null) && !((Object)(object)localMaterial == (Object)null))
{
Texture mainTexture = mesh.font.material.mainTexture;
if ((Object)(object)mainTexture != (Object)null)
{
mainTexture.filterMode = (FilterMode)0;
}
localMaterial.mainTexture = mainTexture;
if ((Object)(object)textRenderer != (Object)null && (Object)(object)textRenderer.sharedMaterial != (Object)null && (Object)(object)textRenderer.sharedMaterial != (Object)(object)originalMaterial)
{
textRenderer.sharedMaterial.mainTexture = mainTexture;
}
}
}
internal void Refresh()
{
if (source != null && !((Object)(object)mesh == (Object)null))
{
GUIText component = ((Component)this).GetComponent<GUIText>();
if ((Object)(object)component != (Object)null)
{
component.SetText(source);
}
else
{
mesh.text = Translate(source);
}
if (!((Component)this).gameObject.activeInHierarchy)
{
mesh.text = display ?? source;
}
CaptureRendered();
}
}
internal void CaptureRendered()
{
if ((Object)(object)mesh != (Object)null)
{
rendered = mesh.text;
}
}
private void LateUpdate()
{
RefreshChangedText();
}
internal void RefreshChangedText()
{
if (Standalone && !((Object)(object)mesh == (Object)null) && !(mesh.text == rendered))
{
mesh.text = Translate(mesh.text);
CaptureRendered();
}
}
internal void Restore(bool restoreText)
{
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)mesh == (Object)null)
{
return;
}
mesh.font = originalFont;
mesh.fontSize = originalSize;
mesh.fontStyle = originalStyle;
mesh.characterSize = BaseScale;
ScaleFactor = 1f;
if ((Object)(object)textRenderer != (Object)null)
{
textRenderer.sharedMaterial = originalMaterial;
}
if (restoreText && source != null)
{
GUIText component = ((Component)this).GetComponent<GUIText>();
if ((Object)(object)component != (Object)null)
{
component.SetText(source);
}
mesh.text = source;
}
}
private void OnDestroy()
{
Font.textureRebuilt -= OnTextureRebuilt;
if ((Object)(object)Plugin.Instance != (Object)null)
{
Plugin.Instance.Labels.Remove(this);
}
if ((Object)(object)localMaterial != (Object)null)
{
Object.Destroy((Object)(object)localMaterial);
}
}
}
internal static class MenuTextMeshes
{
internal static LocalizedMenuLabel Bind(GameObject obj, bool refresh)
{
Plugin instance = Plugin.Instance;
if ((Object)(object)instance == (Object)null || (Object)(object)obj.GetComponent<MenuTextMenuItem>() != (Object)null)
{
return null;
}
LocalizedMenuLabel component = obj.GetComponent<LocalizedMenuLabel>();
if ((Object)(object)component != (Object)null)
{
return component;
}
List<string> list = new List<string>();
Transform val = obj.transform;
while ((Object)(object)val != (Object)null)
{
list.Insert(0, ((Object)val).name);
val = val.parent;
}
if (!MenuTextPolicy.MatchesPath(instance.Language.TextMeshPaths, string.Join("/", list.ToArray())))
{
return null;
}
TextMesh component2 = obj.GetComponent<TextMesh>();
if ((Object)(object)component2 == (Object)null)
{
return null;
}
LocalizedMenuLabel orCreate = LocalizedMenuLabel.GetOrCreate(obj);
orCreate.Standalone = true;
orCreate.ResetSource(component2.text);
if (refresh)
{
orCreate.Refresh();
}
return orCreate;
}
internal static void Scan(GameObject root)
{
TextMesh[] componentsInChildren = root.GetComponentsInChildren<TextMesh>(true);
for (int i = 0; i < componentsInChildren.Length; i++)
{
Bind(((Component)componentsInChildren[i]).gameObject, refresh: true);
}
}
internal static void ScanLoadedScenes()
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene sceneAt = SceneManager.GetSceneAt(i);
if (((Scene)(ref sceneAt)).isLoaded)
{
GameObject[] rootGameObjects = ((Scene)(ref sceneAt)).GetRootGameObjects();
for (int j = 0; j < rootGameObjects.Length; j++)
{
Scan(rootGameObjects[j]);
}
}
}
}
}
[HarmonyPatch(typeof(MenuStateMachine), "SetState", new Type[] { typeof(int) })]
internal static class MenuStateTextPatch
{
private static void Postfix(MenuStateMachine __instance)
{
MenuTextMeshes.Scan(((Component)__instance).gameObject);
}
}
[HarmonyPatch(typeof(GUIText), "OnEnable")]
internal static class MenuStaticTextEnablePatch
{
private static void Postfix(GUIText __instance)
{
MenuTextMeshes.Bind(((Component)__instance).gameObject, refresh: true);
}
}
[HarmonyPatch(typeof(MenuOptions), "SetItemTextReplayTutorial")]
internal static class ReplayTutorialDisplayPatch
{
private static bool Prefix(MenuTextMenu ___m_menuOptions)
{
if ((Object)(object)___m_menuOptions == (Object)null)
{
return true;
}
bool startTutorialHACK = Singleton<SystemUnlocks>.Get.GetStartTutorialHACK();
___m_menuOptions.SetItemString(4, MenuTextPolicy.ReplayTutorialText(startTutorialHACK, SystemTutorial.GetShouldStartTutorial()));
return false;
}
}
internal static class MenuTextPolicy
{
internal static string ReplayTutorialText(bool requested, bool allowed)
{
if (requested)
{
if (!allowed)
{
return "Replay Tutorial: ON (unavailable)";
}
return "Replay Tutorial: ON";
}
return "Replay Tutorial: OFF";
}
internal static bool MatchesPath(IEnumerable<string> paths, string hierarchy)
{
hierarchy = hierarchy.Replace("(Clone)", "");
hierarchy = Regex.Replace(hierarchy, "(?<=MenuJoinPlayer)[1-4](?=/)", "*");
foreach (string path in paths)
{
if (hierarchy == path || hierarchy.EndsWith("/" + path, StringComparison.Ordinal))
{
return true;
}
}
return false;
}
}
[HarmonyPatch(typeof(TextWrapper), "WrapText")]
internal static class MenuChineseWrapPatch
{
private static bool Prefix(string input, float width, TextMesh ___textMesh, ref string __result)
{
//IL_0118: Unknown result type (might be due to invalid IL or missing references)
//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)Plugin.Instance == (Object)null || !Plugin.Instance.Language.UsesChineseTextRules || (Object)(object)___textMesh == (Object)null || !ChineseTextWrapper.ContainsChinese(input) || (Object)(object)((Component)___textMesh).GetComponent<LocalizedMenuLabel>() == (Object)null || ((Object)(object)((Component)___textMesh).GetComponentInParent<MenuLibrary>() == (Object)null && (Object)(object)((Component)___textMesh).GetComponentInParent<MenuDeitySelectPlayer>() == (Object)null && (Object)(object)((Component)___textMesh).GetComponentInParent<GUIStore>() == (Object)null && (Object)(object)((Component)___textMesh).GetComponentInParent<MenuPrompt>() == (Object)null))
{
return true;
}
TextMesh mesh = ___textMesh;
Font font = mesh.font;
if ((Object)(object)font == (Object)null)
{
return true;
}
int size = ((mesh.fontSize > 0) ? mesh.fontSize : font.fontSize);
if (font.dynamic)
{
font.RequestCharactersInTexture(input, size, mesh.fontStyle);
}
float scale = mesh.characterSize * 0.1f * Mathf.Abs(((Component)mesh).transform.lossyScale.x);
CharacterInfo val = default(CharacterInfo);
__result = ChineseTextWrapper.Wrap(input, width, (char ch) => (!font.GetCharacterInfo(ch, ref val, size, mesh.fontStyle)) ? ((float)size * scale) : ((float)((CharacterInfo)(ref val)).advance * scale));
return false;
}
}
internal sealed class NumericTranslation
{
private static readonly Regex Tokens = new Regex("\\{([0-9])\\}");
internal readonly string Source;
private readonly string target;
private readonly Regex pattern;
private NumericTranslation(string source, string target, string pattern)
{
Source = source;
this.target = target;
this.pattern = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
internal static NumericTranslation Create(string source, string target)
{
MatchCollection matchCollection = Tokens.Matches(source);
if (matchCollection.Count == 0)
{
return null;
}
HashSet<string> hashSet = new HashSet<string>();
StringBuilder stringBuilder = new StringBuilder("\\A");
int num = 0;
foreach (Match item in matchCollection)
{
stringBuilder.Append(Regex.Escape(source.Substring(num, item.Index - num)));
string value = item.Groups[1].Value;
stringBuilder.Append(hashSet.Add(value) ? ("(?<n" + value + ">[0-9]+)") : ("\\k<n" + value + ">"));
num = item.Index + item.Length;
}
stringBuilder.Append(Regex.Escape(source.Substring(num))).Append("\\z");
HashSet<string> hashSet2 = new HashSet<string>();
foreach (Match item2 in Tokens.Matches(target))
{
hashSet2.Add(item2.Groups[1].Value);
}
if (!hashSet.SetEquals(hashSet2))
{
throw new FormatException("Numeric translation must preserve all source placeholders without adding new ones.");
}
return new NumericTranslation(source, target, stringBuilder.ToString());
}
internal bool TryTranslate(string source, out string translated)
{
Match match = pattern.Match(source);
translated = null;
if (match.Success)
{
translated = Tokens.Replace(target, (Match token) => match.Groups["n" + token.Groups[1].Value].Value);
}
return match.Success;
}
}
internal static class OnlineIntegration
{
internal static readonly string[] MenuMethods = new string[11]
{
"ItemText", "ShowPlayOnline", "ShowHostType", "ShowServerList", "ShowServerListResults", "JoinEntry", "ShowLobby", "ConfirmCloseLobby", "ShowError", "LobbyText",
"UpdatePanel"
};
internal static readonly string[] OverlayMethods = new string[6] { "Draw", "StatusLine", "DrawWindow", "DrawIdle", "DrawHost", "DrawClient" };
internal static void Install(Harmony harmony, Action<string> warn)
{
//IL_012d: Unknown result type (might be due to invalid IL or missing references)
//IL_013b: Expected O, but got Unknown
//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
//IL_00c4: Expected O, but got Unknown
Type type = AccessTools.TypeByName("CrawlOnline.UI.OnlineMenu");
if ((object)type == null)
{
return;
}
LocalizedMenuLabel.ExternalProvider = Provider;
Type type2 = AccessTools.TypeByName("CrawlOnline.UI.Overlay");
Type[] array = new Type[2] { type, type2 };
foreach (Type type3 in array)
{
if ((object)type3 == null)
{
continue;
}
string[] array2 = (((object)type3 == type) ? MenuMethods : OverlayMethods);
foreach (string text in array2)
{
MethodInfo methodInfo = AccessTools.Method(type3, text, (Type[])null, (Type[])null);
if ((object)methodInfo == null)
{
warn("CrawlOnline UI method unavailable: " + type3.Name + "." + text);
continue;
}
try
{
harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(OnlineIntegration), "Transpiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
}
catch (Exception ex)
{
warn("CrawlOnline localization unavailable for " + text + ": " + ex.Message);
}
}
}
MethodInfo methodInfo2 = AccessTools.Method(type, "ShowError", (Type[])null, (Type[])null);
if ((object)methodInfo2 == null)
{
return;
}
try
{
harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(OnlineIntegration), "ErrorPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
catch (Exception ex2)
{
warn("CrawlOnline error-message localization unavailable: " + ex2.Message);
}
}
internal static Func<LanguagePack, string> Provider(string source)
{
OnlineRecipe recipe = OnlineText.Find(source);
if (recipe != null)
{
return (LanguagePack p) => (!(p.Id == "en")) ? recipe.Render(p) : null;
}
return null;
}
private static void ErrorPrefix(ref string message)
{
message = OnlineText.Message(message);
}
private static MethodInfo Wrapper(MethodBase method)
{
Type declaringType = method.DeclaringType;
string text = null;
ParameterInfo[] parameters = method.GetParameters();
if ((object)declaringType == typeof(StringBuilder))
{
if (method is ConstructorInfo && parameters.Length == 0)
{
text = "Create";
}
if (method.Name == "Append" && parameters.Length == 1)
{
text = (((object)parameters[0].ParameterType == typeof(string)) ? "Append" : (((object)parameters[0].ParameterType == typeof(char)) ? "AppendChar" : null));
}
if (method.Name == "ToString" && parameters.Length == 0)
{
text = "Finish";
}
}
if ((object)declaringType == typeof(object) && method.Name == "ToString" && parameters.Length == 0)
{
text = "ObjectString";
}
if ((object)declaringType == typeof(string) && method.Name == "Concat")
{
if (parameters.Length == 1)
{
text = (((object)parameters[0].ParameterType == typeof(string[])) ? "ConcatStrings" : (((object)parameters[0].ParameterType == typeof(object[])) ? "ConcatArray" : null));
}
else if ((object)parameters[0].ParameterType == typeof(string))
{
text = ((parameters.Length == 2) ? "ConcatTwo" : ((parameters.Length == 3) ? "ConcatThree" : ((parameters.Length == 4) ? "ConcatFour" : null)));
}
else if (parameters.Length == 3)
{
text = "ConcatObjects";
}
}
if (text != null)
{
return AccessTools.Method(typeof(OnlineText), text, (Type[])null, (Type[])null);
}
if ((object)declaringType == typeof(TextMesh) && method.Name == "set_text")
{
return AccessTools.Method(typeof(OnlineOverlay), "SetText", (Type[])null, (Type[])null);
}
if ((object)declaringType == typeof(GUILayout) || (object)declaringType == typeof(GUI))
{
Type[] array = new Type[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
array[i] = parameters[i].ParameterType;
}
return typeof(OnlineOverlay).GetMethod((((object)declaringType == typeof(GUI)) ? "Gui" : "") + method.Name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, array, null);
}
return null;
}
internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
foreach (CodeInstruction original in instructions)
{
CodeInstruction val = new CodeInstruction(original);
MethodBase method = val.operand as MethodBase;
if ((object)method != null && (val.opcode == OpCodes.Call || val.opcode == OpCodes.Callvirt || val.opcode == OpCodes.Newobj))
{
MethodInfo methodInfo = Wrapper(method);
if ((object)methodInfo != null)
{
val.opcode = OpCodes.Call;
val.operand = methodInfo;
}
}
yield return val;
string text = null;
if (original.opcode == OpCodes.Ldstr)
{
text = "Literal";
}
if (original.operand is FieldInfo fieldInfo && (object)fieldInfo.FieldType == typeof(string) && (original.opcode == OpCodes.Ldfld || original.opcode == OpCodes.Ldsfld))
{
string fullName = fieldInfo.DeclaringType.FullName;
if ((fullName == "CrawlOnline.Online" && (fieldInfo.Name == "LastError" || fieldInfo.Name == "Status")) || (fullName == "CrawlOnline.Net.LobbyBrowser" && fieldInfo.Name == "Error"))
{
text = "Message";
}
if (fullName == "CrawlOnline.Net.LobbyEntry" && fieldInfo.Name == "State")
{
text = "Literal";
}
}
if ((object)method != null && method.DeclaringType.FullName == "CrawlOnline.Net.SteamLobby" && method.Name == "TypeName")
{
text = "Literal";
}
if ((object)method != null && method.DeclaringType.FullName == "CrawlOnline.Net.LobbyBrowser" && method.Name == "get_Error")
{
text = "Message";
}
if ((object)method != null && (object)method.DeclaringType == typeof(List<string>.Enumerator) && method.Name == "get_Current")
{
text = "Roster";
}
if (text != null)
{
yield return new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(OnlineText), text, (Type[])null, (Type[])null));
}
}
}
}
internal static class OnlineOverlay
{
private sealed class StyleEntry
{
internal GUIStyle Original;
internal GUIStyle Display;
internal Font Font;
}
private static readonly List<StyleEntry> styles = new List<StyleEntry>();
internal static void Clear()
{
styles.Clear();
}
private static string Prepare(string source, ref GUIStyle style)
{
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
//IL_00f8: Expected O, but got Unknown
Plugin instance = Plugin.Instance;
OnlineRecipe onlineRecipe = OnlineText.Find(source);
if ((Object)(object)instance == (Object)null || onlineRecipe == null || instance.Language.Id == "en")
{
return source;
}
string text = onlineRecipe.Render(instance.Language);
if (text == source)
{
return source;
}
int num = ((style.fontSize > 0) ? style.fontSize : 12);
if (!instance.Fonts.TryChooseGui(instance.Language, style.font ?? GUI.skin.font, num, style.fontStyle, text, out var font))
{
return source;
}
foreach (StyleEntry style2 in styles)
{
if (style2.Original == style && (Object)(object)style2.Font == (Object)(object)font)
{
style = style2.Display;
return text;
}
}
GUIStyle val = new GUIStyle(style);
val.font = font;
val.fontSize = num;
styles.Add(new StyleEntry
{
Original = style,
Display = val,
Font = font
});
style = val;
return text;
}
internal static void Label(string text, params GUILayoutOption[] options)
{
GUIStyle style = GUI.skin.label;
text = Prepare(text, ref style);
GUILayout.Label(text, style, options);
}
internal static void Label(string text, GUIStyle style, params GUILayoutOption[] options)
{
text = Prepare(text, ref style);
GUILayout.Label(text, style, options);
}
internal static bool Button(string text, params GUILayoutOption[] options)
{
GUIStyle style = GUI.skin.button;
text = Prepare(text, ref style);
return GUILayout.Button(text, style, options);
}
internal static bool Toggle(bool value, string text, params GUILayoutOption[] options)
{
GUIStyle style = GUI.skin.toggle;
text = Prepare(text, ref style);
return GUILayout.Toggle(value, text, style, options);
}
internal static bool Toggle(bool value, string text, GUIStyle style, params GUILayoutOption[] options)
{
text = Prepare(text, ref style);
return GUILayout.Toggle(value, text, style, options);
}
internal static void GuiLabel(Rect position, string text, GUIStyle style)
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
text = Prepare(text, ref style);
GUI.Label(position, text, style);
}
internal static Rect Window(int id, Rect rect, WindowFunction function, string text, params GUILayoutOption[] options)
{
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
GUIStyle style = GUI.skin.window;
text = Prepare(text, ref style);
return GUILayout.Window(id, rect, function, text, style, options);
}
internal static void SetText(TextMesh mesh, string source)
{
mesh.text = source;
if (!((Object)(object)mesh == (Object)null) && !((Object)(object)Plugin.Instance == (Object)null))
{
LocalizedMenuLabel orCreate = LocalizedMenuLabel.GetOrCreate(((Component)mesh).gameObject);
orCreate.ResetSource(source);
orCreate.Refresh();
}
}
}
internal sealed class OnlineRecipe
{
internal readonly string Source;
private readonly bool literal;
private readonly OnlineRecipe[] parts;
internal OnlineRecipe(string source, bool literal, OnlineRecipe[] parts)
{
Source = new string((source ?? "").ToCharArray());
this.literal = literal;
this.parts = parts;
}
internal string Render(LanguagePack pack)
{
if (pack.Id == "en")
{
return Source;
}
if (parts != null)
{
StringBuilder stringBuilder = new StringBuilder();
OnlineRecipe[] array = parts;
foreach (OnlineRecipe onlineRecipe in array)
{
stringBuilder.Append(onlineRecipe.Render(pack));
}
return stringBuilder.ToString();
}
if (!literal || !pack.TryTranslate("@online:" + Source, out var translated))
{
return Source;
}
return translated;
}
}
internal static class OnlineText
{
private sealed class Trace
{
internal readonly List<OnlineRecipe> Parts = new List<OnlineRecipe>();
}
private static readonly WeakIdentityMap<OnlineRecipe> strings = new WeakIdentityMap<OnlineRecipe>();
private static readonly WeakIdentityMap<Trace> builders = new WeakIdentityMap<Trace>();
internal static OnlineRecipe Find(string source)
{
return strings.Get(source);
}
private static OnlineRecipe Capture(string source)
{
return Find(source) ?? new OnlineRecipe(source, literal: false, null);
}
internal static string Literal(string source)
{
if (string.IsNullOrEmpty(source))
{
return source;
}
string text = new string(source.ToCharArray());
strings.Set(text, new OnlineRecipe(source, literal: true, null));
return text;
}
private static void Remember(string source, OnlineRecipe[] parts)
{
if (!string.IsNullOrEmpty(source))
{
StringBuilder stringBuilder = new StringBuilder();
foreach (OnlineRecipe onlineRecipe in parts)
{
stringBuilder.Append(onlineRecipe.Source);
}
if (stringBuilder.ToString() == source)
{
strings.Set(source, new OnlineRecipe(source, literal: false, parts));
}
}
}
internal static StringBuilder Create()
{
StringBuilder stringBuilder = new StringBuilder();
builders.Set(stringBuilder, new Trace());
return stringBuilder;
}
internal static StringBuilder Append(StringBuilder b, string s)
{
b.Append(s);
builders.Get(b)?.Parts.Add(Capture(s));
return b;
}
internal static StringBuilder AppendChar(StringBuilder b, char c)
{
b.Append(c);
builders.Get(b)?.Parts.Add(Capture(c.ToString()));
return b;
}
internal static string Finish(StringBuilder b)
{
string text = b.ToString();
Trace trace = builders.Get(b);
if (trace != null)
{
Remember(text, trace.Parts.ToArray());
}
return text;
}
internal static string ObjectString(object o)
{
if (o is StringBuilder b)
{
return Finish(b);
}
return o.ToString();
}
private static string Join(string source, object[] args)
{
OnlineRecipe[] array = new OnlineRecipe[args.Length];
for (int i = 0; i < args.Length; i++)
{
object obj = args[i];
if (obj != null && !(obj is string) && !obj.GetType().IsPrimitive && !(obj is decimal))
{
return source;
}
array[i] = Capture((obj == null) ? "" : Convert.ToString(obj, CultureInfo.CurrentCulture));
}
Remember(source, array);
return source;
}
internal static string ConcatTwo(string a, string b)
{
return Join(a + b, new object[2] { a, b });
}
internal static string ConcatThree(string a, string b, string c)
{
return Join(a + b + c, new object[3] { a, b, c });
}
internal static string ConcatFour(string a, string b, string c, string d)
{
return Join(a + b + c + d, new object[4] { a, b, c, d });
}
internal static string ConcatStrings(string[] a)
{
return Join(string.Concat(a), a);
}
internal static string ConcatObjects(object a, object b, object c)
{
return Join(string.Concat(a, b, c), new object[3] { a, b, c });
}
internal static string ConcatArray(object[] a)
{
return Join(string.Concat(a), a);
}
internal static string Roster(string source)
{
if (source == null)
{
return null;
}
Match match = Regex.Match(source, "\\A(.*)( [0-9]+ ms \\()([0-9]+)( controllers?)(\\))\\z", RegexOptions.Singleline);
if (!match.Success)
{
return source;
}
return ConcatStrings(new string[5]
{
match.Groups[1].Value,
match.Groups[2].Value,
match.Groups[3].Value,
Literal(match.Groups[4].Value),
match.Groups[5].Value
});
}
internal static string Message(string source)
{
if (source == null || Find(source) != null)
{
return source;
}
string[] array = new string[7] { "Host failed: ", "UDP host failed: ", "Connect failed: ", "lobby creation failed: ", "could not enter lobby (", "Rejected: ", "Host closed the session: " };
foreach (string text in array)
{
if (source.StartsWith(text, StringComparison.Ordinal))
{
string text2 = source.Substring(text.Length);
if (text == "Rejected: " || text == "Host closed the session: ")
{
text2 = Message(text2);
}
return ConcatTwo(Literal(text), text2);
}
}
if (source.StartsWith("Hosting (Steam, ", StringComparison.Ordinal) && source.EndsWith(" lobby)", StringComparison.Ordinal))
{
return ConcatThree(Literal("Hosting (Steam, "), Literal(source.Substring(16, source.Length - 23)), Literal(" lobby)"));
}
if (source.StartsWith("Hosting (UDP port ", StringComparison.Ordinal) && source.EndsWith(")", StringComparison.Ordinal))
{
return ConcatThree(Literal("Hosting (UDP port "), source.Substring(18, source.Length - 19), ")");
}
if (source.StartsWith("Joining ", StringComparison.Ordinal) && source.EndsWith("...", StringComparison.Ordinal))
{
return ConcatThree(Literal("Joining "), source.Substring(8, source.Length - 11), "...");
}
if (source.StartsWith("Crawl Online version mismatch: host has ", StringComparison.Ordinal))
{
int num = source.IndexOf(", you have ", "Crawl Online version mismatch: host has ".Length, StringComparison.Ordinal);
if (num >= 0)
{
string text3 = source.Substring(num + 11);
string text4 = ". Use the same version.";
if (text3.EndsWith(text4, StringComparison.Ordinal))
{
text3 = ConcatTwo(text3.Substring(0, text3.Length - text4.Length), Literal(text4));
}
return ConcatFour(Literal("Crawl Online version mismatch: host has "), source.Substring("Crawl Online version mismatch: host has ".Length, num - "Crawl Online version mismatch: host has ".Length), Literal(", you have "), text3);
}
}
return Literal(source);
}
}
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInPlugin("crawl.localization", "Crawl Localization", "0.13.0")]
public sealed class Plugin : BaseUnityPlugin
{
public const string Id = "crawl.localization";
internal static Plugin Instance;
internal LanguagePack Language;
internal FontService Fonts;
internal readonly HashSet<LocalizedMenuLabel> Labels = new HashSet<LocalizedMenuLabel>();
private readonly HashSet<string> warnings = new HashSet<string>();
private ConfigEntry<string> language;
private ConfigEntry<KeyCode> reloadKey;
private Harmony harmony;
private string folder;
private void Awake()
{
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
//IL_009f: Expected O, but got Unknown
Instance = this;
folder = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
language = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Language", "zh-Hans", "Language folder under Languages, e.g. zh-Hans, es or ru. Use en for original text. zh-TW selects zh-Hant. Missing entries or packs keep original game text.");
reloadKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "ReloadKey", (KeyCode)289, "Reload configuration and language tables. Restart to replace font files.");
Fonts = new FontService(Warn, delegate(string message)
{
((BaseUnityPlugin)this).Logger.LogInfo((object)message);
});
LoadLanguage();
harmony = new Harmony("crawl.localization");
harmony.PatchAll(typeof(Plugin).Assembly);
OnlineIntegration.Install(harmony, Warn);
SceneManager.sceneLoaded += OnSceneLoaded;
}
private void Start()
{
MenuTextMeshes.ScanLoadedScenes();
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
MenuTextMeshes.ScanLoadedScenes();
}
private void Update()
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
if ((int)reloadKey.Value == 0 || !Input.GetKeyDown(reloadKey.Value))
{
return;
}
try
{
((BaseUnityPlugin)this).Config.Reload();
LoadLanguage();
MenuTextMeshes.ScanLoadedScenes();
foreach (LocalizedMenuLabel item in new List<LocalizedMenuLabel>(Labels))
{
if ((Object)(object)item != (Object)null)
{
item.Refresh();
}
}
}
catch (Exception ex)
{
Warn("Reload failed: " + ex.Message);
}
}
private void LoadLanguage()
{
LanguagePack languagePack = (Language = LanguagePack.Load(folder, language.Value, Warn));
((BaseUnityPlugin)this).Logger.LogInfo((object)("Language=" + languagePack.Id + "; menu translations=" + languagePack.Count + "; font mappings=" + languagePack.Fonts.Count));
}
internal void Warn(string message)
{
if (warnings.Add(message))
{
((BaseUnityPlugin)this).Logger.LogWarning((object)message);
}
}
private void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
if (harmony != null)
{
harmony.UnpatchSelf();
}
foreach (LocalizedMenuLabel item in new List<LocalizedMenuLabel>(Labels))
{
if ((Object)(object)item != (Object)null)
{
item.Restore(restoreText: true);
Object.Destroy((Object)(object)item);
}
}
LocalizedMenuLabel.ExternalProvider = null;
OnlineOverlay.Clear();
if (Fonts != null)
{
Fonts.Dispose();
}
Instance = null;
}
}
[HarmonyPatch(typeof(MenuPrompt), "Initialise")]
internal static class PausePromptPatch
{
private static void Prefix(string title, string text, GUIText ___m_textTitle, GUIText ___m_textText)
{
if ((Object)(object)Plugin.Instance == (Object)null)
{
return;
}
PausePlayerText snapshot = null;
bool joined = false;
if (title != null && title.StartsWith("_P", StringComparison.Ordinal) && (title.EndsWith("_DROPPED_IN_", StringComparison.Ordinal) || title.EndsWith("_DROPPED_OUT_", StringComparison.Ordinal)))
{
joined = title.EndsWith("_DROPPED_IN_", StringComparison.Ordinal);
for (int i = 0; i < SystemPlayers.GetMaxPlayers(); i++)
{
PlayerData player = SystemPlayers.GetPlayer(i);
if (player == null)
{
continue;
}
Deity val = ((joined && player.GameData != null) ? player.GameData.GetDeity() : null);
if (!joined || val != null)
{
PausePlayerText pausePlayerText = new PausePlayerText(i + 1, player.Name, player.Level.ToString(), (val == null) ? "" : val.GetName(), (val == null) ? "" : val.GetText());
if (pausePlayerText.Matches(title, text, joined))
{
snapshot = pausePlayerText;
break;
}
}
}
}
Bind(___m_textTitle, title, (snapshot == null) ? null : ((Func<LanguagePack, string>)((LanguagePack pack) => snapshot.Title(pack, joined))));
Bind(___m_textText, text, (snapshot == null) ? null : ((Func<LanguagePack, string>)((LanguagePack pack) => snapshot.Body(pack, joined))));
}
private static void Bind(GUIText gui, string source, Func<LanguagePack, string> provider)
{
if (!((Object)(object)gui == (Object)null))
{
LocalizedMenuLabel orCreate = LocalizedMenuLabel.GetOrCreate(((Component)gui).gameObject);
if (!((Object)(object)orCreate == (Object)null))
{
orCreate.TextProvider = provider;
orCreate.ResetSource(source);
}
}
}
}
[HarmonyPatch(typeof(MenuEvolvePlayer), "Initialise")]
internal static class EvolutionBindPatch
{
private static void Postfix(MenuEvolvePlayer __instance)
{
if ((Object)(object)Plugin.Instance != (Object)null)
{
MenuTextMeshes.Scan(((Component)__instance).gameObject);
}
}
}
[HarmonyPatch(typeof(MenuEvolvePlayer), "UpdateMenuDescriptionText")]
internal static class EvolutionDescriptionPatch
{
private static void Postfix(TextMesh ___m_textMenuDescription)
{
if (!((Object)(object)Plugin.Instance == (Object)null) && !((Object)(object)___m_textMenuDescription == (Object)null))
{
LocalizedMenuLabel localizedMenuLabel = MenuTextMeshes.Bind(((Component)___m_textMenuDescription).gameObject, refresh: true);
if ((Object)(object)localizedMenuLabel != (Object)null)
{
localizedMenuLabel.RefreshChangedText();
}
}
}
}
[HarmonyPatch(typeof(MenuWin), "Start")]
internal static class ResultsTextPatch
{
private static void Postfix(MenuWin __instance, bool ___m_heroWon, TextMesh ___m_winText, TextMesh ___m_bossKillerText)
{
if ((Object)(object)Plugin.Instance == (Object)null)
{
return;
}
if (___m_heroWon && (Object)(object)___m_bossKillerText != (Object)null)
{
LocalizedMenuLabel.GetOrCreate(((Component)___m_bossKillerText).gameObject).RefreshDirect(___m_bossKillerText.text.Trim(), null);
}
MenuTextMeshes.Scan(((Component)__instance).gameObject);
if ((Object)(object)___m_winText != (Object)null)
{
LocalizedMenuLabel localizedMenuLabel = MenuTextMeshes.Bind(((Component)___m_winText).gameObject, refresh: true);
if ((Object)(object)localizedMenuLabel != (Object)null)
{
localizedMenuLabel.RefreshChangedText();
}
}
}
}
[HarmonyPatch(typeof(MenuTextMenuItem), "Start")]
internal static class BossEntranceTextPatch
{
private static void Postfix(MenuTextMenuItem __instance)
{
if (!((Object)(object)Plugin.Instance == (Object)null))
{
string text = ((Object)((Component)__instance).gameObject).name.Replace("(Clone)", "");
if ((!(text != "EffectBossName") || !(text != "EffectBossAttemptText")) && !((Object)(object)((Component)__instance).GetComponent<TextMesh>() == (Object)null))
{
LocalizedMenuLabel orCreate = LocalizedMenuLabel.GetOrCreate(((Component)__instance).gameObject);
orCreate.Standalone = true;
orCreate.RefreshChangedText();
}
}
}
}
internal sealed class PausePlayerText
{
internal readonly string Number;
internal readonly string Name;
internal readonly string Level;
internal readonly string DeityName;
internal readonly string DeityText;
internal PausePlayerText(int number, string name, string level, string deityName, string deityText)
{
Number = number.ToString(CultureInfo.InvariantCulture);
Name = name;
Level = level;
DeityName = deityName;
DeityText = deityText;
}
internal string OriginalTitle(bool joined)
{
return "_P" + Number + "_'" + Name.ToUpper() + "'_DROPPED_" + (joined ? "IN_" : "OUT_");
}
internal string OriginalBody(bool joined)
{
if (!joined)
{
return "Player " + Number + " (" + Name + ") will be replaced with a bot.";
}
return "Joined as Player " + Number + " with alias: '" + Name + "'\n\nHero level: " + Level + "\nWorships: " + DeityName + "\n(" + DeityText + ")";
}
internal bool Matches(string title, string body, bool joined)
{
if (title == OriginalTitle(joined))
{
return body == OriginalBody(joined);
}
return false;
}
internal string Title(LanguagePack pack, bool joined)
{
return Format(pack, joined ? "@progression.join-title:{0}:{1}" : "@progression.leave-title:{0}:{1}", Number, Name.ToUpper());
}
internal string Body(LanguagePack pack, bool joined)
{
if (!joined)
{
return Format(pack, "@progression.leave-body:{0}:{1}", Number, Name);
}
return Format(pack, "@progression.join-body:{0}:{1}:{2}:{3}:{4}", Number, Name, Level, Term(pack, DeityName), Term(pack, DeityText));
}
private static string Term(LanguagePack pack, string source)
{
if (!pack.TryTranslate(source, out var translated))
{
return source;
}
return translated;
}
private static string Format(LanguagePack pack, string key, params object[] args)
{
if (!pack.TryTranslate(key, out var translated))
{
return null;
}
try
{
return string.Format(CultureInfo.InvariantCulture, translated, args);
}
catch (FormatException)
{
return null;
}
}
}
internal static class ShopDisplay
{
private static LocalizedMenuLabel Bind(TextMesh mesh, string source)
{
if ((Object)(object)mesh == (Object)null || (Object)(object)Plugin.Instance == (Object)null)
{
return null;
}
ShopTextRecipe recipe = ShopText.Capture(source);
LocalizedMenuLabel localizedMenuLabel = ((Component)mesh).GetComponent<LocalizedMenuLabel>();
bool flag = false;
if (source != null)
{
for (int i = 0; i < source.Length; i++)
{
if (char.IsLetter(source[i]))
{
flag = true;
break;
}
}
}
if ((Object)(object)localizedMenuLabel == (Object)null && !flag)
{
return null;
}
if ((Object)(object)localizedMenuLabel == (Object)null)
{
localizedMenuLabel = LocalizedMenuLabel.GetOrCreate(((Component)mesh).gameObject);
}
localizedMenuLabel.TextProvider = (LanguagePack pack) => recipe.Translate(pack);
localizedMenuLabel.ResetSource(source);
return localizedMenuLabel;
}
internal static void SetText(TextMesh mesh, string source)
{
mesh.text = source;
LocalizedMenuLabel localizedMenuLabel = Bind(mesh, source);
if ((Object)(object)localizedMenuLabel != (Object)null)
{
localizedMenuLabel.Refresh();
}
}
internal static void SetGuiText(GUIText gui, string source)
{
Bind(((Component)gui).GetComponent<TextMesh>(), source);
gui.SetText(source);
}
}
internal sealed class WeakIdentityMap<T> where T : class
{
private sealed class Entry
{
internal WeakReference Key;
internal T Value;
}
private readonly Dictionary<int, List<Entry>> buckets = new Dictionary<int, List<Entry>>();
private int writes;
internal T Get(object key)
{
if (key == null)
{
return null;
}
lock (buckets)
{
if (!buckets.TryGetValue(RuntimeHelpers.GetHashCode(key), out var value))
{
return null;
}
foreach (Entry item in value)
{
if (item.Key.Target == key)
{
return item.Value;
}
}
return null;
}
}
internal void Set(object key, T value)
{
if (key == null)
{
return;
}
lock (buckets)
{
if (++writes % 128 == 0)
{
foreach (int item in new List<int>(buckets.Keys))
{
buckets[item].RemoveAll((Entry e) => !e.Key.IsAlive);
if (buckets[item].Count == 0)
{
buckets.Remove(item);
}
}
}
int hashCode = RuntimeHelpers.GetHashCode(key);
if (!buckets.TryGetValue(hashCode, out var value2))
{
value2 = new List<Entry>();
buckets.Add(hashCode, value2);
}
foreach (Entry item2 in value2)
{
if (item2.Key.Target == key)
{
item2.Value = value;
return;
}
}
value2.Add(new Entry
{
Key = new WeakReference(key),
Value = value
});
}
}
}
internal sealed class ShopTextRecipe
{
internal readonly string Source;
private readonly ShopTextRecipe[] parts;
private readonly string format;
private readonly object[] arguments;
private readonly CultureInfo culture;
private static readonly Regex Structural = new Regex("</?(?:b|i|size|color|material)(?:=[^>]*)?>|\\{[0-9]+(?:[^}]*)?\\}");
internal ShopTextRecipe(string source, ShopTextRecipe[] parts, string format, object[] arguments)
{
Source = new string((source ?? "").ToCharArray());
this.parts = parts;
this.format = ((format == null) ? null : new string(format.ToCharArray()));
this.arguments = arguments;
culture = CultureInfo.CurrentCulture;
}
private static string Term(LanguagePack pack, string source, bool format)
{
if (pack.TryTranslate("@shop.il:" + source, out var translated) || pack.TryTranslate(source, out translated))
{
return translated;
}
string[] array = new string[5] { "@shop.prefix:", "@shop.magic-name:", "@shop.status:", "@shop.magic:", "@shop.stat:" };
foreach (string text in array)
{
if (pack.TryTranslate(text + source, out translated))
{
return translated;
}
}
if (!format && !source.EndsWith(".", StringComparison.Ordinal) && pack.TryTranslate(source + ".", out translated))
{
return translated;
}
string text2 = Structural.Replace(source, "");
for (int i = 0; i < text2.Length; i++)
{
if (char.IsLetter(text2[i]))
{
return null;
}
}
return source;
}
internal string Translate(LanguagePack pack)
{
if (pack.Id == "en")
{
return null;
}
try
{
return Render(pack);
}
catch (FormatException)
{
return null;
}
}
private string Render(LanguagePack pack)
{
if (pack.TryTranslate(Source, out var translated))
{
return translated;
}
if (parts != null)
{
StringBuilder stringBuilder = new StringBuilder();
ShopTextRecipe[] array = parts;
for (int i = 0; i < array.Length; i++)
{
string text = array[i].Render(pack);
if (text == null)
{
return null;
}
stringBuilder.Append(text);
}
return stringBuilder.ToString();
}
if (format != null)
{
string text2 = Term(pack, format, format: true);
if (text2 == null)
{
return null;
}
object[] array2 = new object[arguments.Length];
for (int j = 0; j < array2.Length; j++)
{
ShopTextRecipe shopTextRecipe = arguments[j] as ShopTextRecipe;
array2[j] = ((shopTextRecipe == null) ? arguments[j] : shopTextRecipe.Render(pack));
if (shopTextRecipe != null && array2[j] == null)
{
return null;
}
}
return string.Format(culture, text2, array2);
}
return Term(pack, Source, format: false);
}
}
internal static class ShopText
{
private sealed class BuilderTrace
{
internal readonly List<ShopTextRecipe> Parts = new List<ShopTextRecipe>();
internal bool Invalid;
}
private static readonly WeakIdentityMap<BuilderTrace> builders = new WeakIdentityMap<BuilderTrace>();
private static readonly WeakIdentityMap<ShopTextRecipe> strings = new WeakIdentityMap<ShopTextRecipe>();
internal static ShopTextRecipe Capture(string source)
{
return strings.Get(source) ?? new ShopTextRecipe(source, null, null, null);
}
private static ShopTextRecipe Sequence(string source, ShopTextRecipe[] parts)
{
StringBuilder stringBuilder = new StringBuilder();
foreach (ShopTextRecipe shopTextRecipe in parts)
{
stringBuilder.Append(shopTextRecipe.Source);
}
if (!(stringBuilder.ToString() == source))
{
return null;
}
return new ShopTextRecipe(source, parts, null, null);
}
private static void Remember(string source, ShopTextRecipe recipe)
{
if (!string.IsNullOrEmpty(source) && recipe != null)
{
strings.Set(source, recipe);
}
}
private static ShopTextRecipe FormatRecipe(string source, string format, object[] args)
{
object[] array = new object[args.Length];
for (int i = 0; i < args.Length; i++)
{
object obj = args[i];
if (obj is string)
{
array[i] = Capture((string)obj);
continue;
}
if (obj == null || obj.GetType().IsPrimitive || obj is decimal)
{
array[i] = obj;
continue;
}
return null;
}
return new ShopTextRecipe(source, null, format, array);
}
private static void Add(StringBuilder builder, ShopTextRecipe node)
{
BuilderTrace builderTrace = builders.Get(builder);
if (builderTrace != null)
{
if (node == null)
{
builderTrace.Invalid = true;
}
else
{
builderTrace.Parts.Add(node);
}
}
}
internal static StringBuilder Create()
{
StringBuilder stringBuilder = new StringBuilder();
builders.Set(stringBuilder, new BuilderTrace());
return stringBuilder;
}
internal static StringBuilder Append(StringBuilder builder, string value)
{
builder.Append(value);
try
{
Add(builder, Capture(value));
}
catch (Exception)
{
Invalidate(builder);
}
return builder;
}
internal static StringBuilder AppendChar(StringBuilder builder, char value)
{
builder.Append(value);
try
{
Add(builder, Capture(value.ToString()));
}
catch (Exception)
{
Invalidate(builder);
}
return builder;
}
private static void Invalidate(StringBuilder builder)
{
BuilderTrace builderTrace = builders.Get(builder);
if (builderTrace != null)
{
builderTrace.Invalid = true;
}
}
internal static StringBuilder AppendFormatOne(StringBuilder builder, string format, object arg)
{
return AppendFormatMany(builder, format, new object[1] { arg });
}
internal static StringBuilder AppendFormatTwo(StringBuilder builder, string format, object a, object b)
{
return AppendFormatMany(builder, format, new object[2] { a, b });
}
internal static StringBuilder AppendFormatMany(StringBuilder builder, string format, object[] args)
{
int length = builder.Length;
builder.AppendFormat(format, args);
try
{
Add(builder, FormatRecipe(builder.ToString(length, builder.Length - length), format, args));
}
catch (Exception)
{
Invalidate(builder);
}
return builder;
}
internal static string Finish(StringBuilder builder)
{
string text = builder.ToString();
RecordFinished(builder, text);
return text;
}
internal static string ObjectString(object value)
{
string text = value.ToString();
if (value is StringBuilder builder)
{
RecordFinished(builder, text);
}
return text;
}
private static void RecordFinished(StringBuilder builder, string source)
{
try
{
BuilderTrace builderTrace = builders.Get(builder);
if (builderTrace != null && !builderTrace.Invalid)
{
Remember(source, Sequence(source, builderTrace.Parts.ToArray()));
}
}
catch (Exception)
{
}
}
internal static string FormatOne(string format, object arg)
{
string text = string.Format(format, arg);
try
{
Remember(text, FormatRecipe(text, format, new object[1] { arg }));
}
catch (Exception)
{
}
return text;
}
internal static string ConcatTwo(string a, string b)
{
return Join(a + b, new object[2] { a, b });
}
internal static string ConcatThree(string a, string b, string c)
{
return Join(a + b + c, new object[3] { a, b, c });
}
internal static string ConcatObjects(object a, object b, object c)
{
return Join(string.Concat(a, b, c), new object[3] { a, b, c });
}
internal static string ConcatArray(object[] args)
{
return Join(string.Concat(args), args);
}
private static string Join(string source, object[] args)
{
try
{
ShopTextRecipe[] array = new ShopTextRecipe[args.Length];
for (int i = 0; i < args.Length; i++)
{
object obj = args[i];
if (obj != null && !(obj is string) && !obj.GetType().IsPrimitive && !(obj is decimal))
{
return source;
}
array[i] = Capture((obj == null) ? "" : Convert.ToString(obj, CultureInfo.CurrentCulture));
}
Remember(source, Sequence(source, array));
}
catch (Exception)
{
}
return source;
}
}
[HarmonyPatch]
internal static class ShopTextIlPatch
{
private static readonly Dictionary<MethodBase, MethodInfo> replacements = CreateMap();
private static void Map(Dictionary<MethodBase, MethodInfo> map, Type type, string name, Type[] args, string replacement)
{
MethodBase key = ((name == ".ctor") ? ((MethodBase)type.GetConstructor(args)) : ((MethodBase)type.GetMethod(name, args)));
map.Add(key, AccessTools.Method(typeof(ShopText), replacement, (Type[])null, (Type[])null));
}
private static Dictionary<MethodBase, MethodInfo> CreateMap()
{
Dictionary<MethodBase, MethodInfo> dictionary = new Dictionary<MethodBase, MethodInfo>();
Map(dictionary, typeof(StringBuilder), ".ctor", Type.EmptyTypes, "Create");
Map(dictionary, typeof(StringBuilder), "Append", new Type[1] { typeof(string) }, "Append");
Map(dictionary, typeof(StringBuilder), "Append", new Type[1] { typeof(char) }, "AppendChar");
Map(dictionary, typeof(StringBuilder), "AppendFormat", new Type[2]
{
typeof(string),
typeof(object)
}, "AppendFormatOne");
Map(dictionary, typeof(StringBuilder), "AppendFormat", new Type[3]
{
typeof(string),
typeof(object),
typeof(object)
}, "AppendFormatTwo");
Map(dictionary, typeof(StringBuilder), "AppendFormat", new Type[2]
{
typeof(string),
typeof(object[])
}, "AppendFormatMany");
Map(dictionary, typeof(StringBuilder), "ToString", Type.EmptyTypes, "Finish");
Map(dictionary, typeof(object), "ToString", Type.EmptyTypes, "ObjectString");
Map(dictionary, typeof(string), "Format", new Type[2]
{
typeof(string),
typeof(object)
}, "FormatOne");
Map(dictionary, typeof(string), "Concat", new Type[2]
{
typeof(string),
typeof(string)
}, "ConcatTwo");
Map(dictionary, typeof(string), "Concat", new Type[3]
{
typeof(string),
typeof(string),
typeof(string)
}, "ConcatThree");
Map(dictionary, typeof(string), "Concat", new Type[3]
{
typeof(object),
typeof(object),
typeof(object)
}, "ConcatObjects");
Map(dictionary, typeof(string), "Concat", new Type[1] { typeof(object[]) }, "ConcatArray");
return dictionary;
}
private static IEnumerable<MethodBase> TargetMethods()
{
yield return AccessTools.Method(typeof(WeaponData), "GetDisplayName", (Type[])null, (Type[])null);
yield return AccessTools.Method(typeof(WeaponData), "GetDescription", (Type[])null, (Type[])null);
yield return AccessTools.Method(typeof(PropPurchasable), "ShowInfoBox", (Type[])null, (Type[])null);
yield return AccessTools.Method(typeof(GUIStore), "Show", (Type[])null, (Type[])null);
yield return AccessTools.Method(typeof(GUIStoreStatsDisplay), "Show", (Type[])null, (Type[])null);
}
internal static IEnumerable<CodeInstruction> Rewrite(IEnumerable<CodeInstruction> instructions, bool display)
{
foreach (CodeInstruction instruction in instructions)
{
MethodBase methodBase = instruction.operand as MethodBase;
MethodInfo value = null;
if ((object)methodBase != null && (instruction.opcode == OpCodes.Call || instruction.opcode == OpCodes.Callvirt || instruction.opcode == OpCodes.Newobj))
{
replacements.TryGetValue(methodBase, out value);
if (display && methodBase.Equals(AccessTools.PropertySetter(typeof(TextMesh), "text")))
{
value = AccessTools.Method(typeof(ShopDisplay), "SetText", (Type[])null, (Type[])null);
}
if (display && methodBase.Equals(AccessTools.Method(typeof(GUIText), "SetText", (Type[])null, (Type[])null)))
{
value = AccessTools.Method(typeof(ShopDisplay), "SetGuiText", (Type[])null, (Type[])null);
}
}
if ((object)value == null)
{
yield return instruction;
continue;
}
CodeInstruction val = new CodeInstruction(instruction);
val.opcode = OpCodes.Call;
val.operand = value;
yield return val;
}
}
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
{
return Rewrite(instructions, (object)__originalMethod.DeclaringType == typeof(GUIStore) || (object)__originalMethod.DeclaringType == typeof(GUIStoreStatsDisplay));
}
}
[HarmonyPatch(typeof(GuiTutorialMessageBox), "SetContent")]
internal static class TutorialMessagePatch
{
private static void Prefix(GUIText ___m_textMessage)
{
Bind(___m_textMessage);
}
internal static void Bind(GUIText gui)
{
if (!((Object)(object)Plugin.Instance == (Object)null) && !((Object)(object)gui == (Object)null))
{
LocalizedMenuLabel.GetOrCreate(((Component)gui).gameObject);
}
}
}
[HarmonyPatch(typeof(GuiTutorialMessageBox), "ShowButton")]
internal static class TutorialButtonPatch
{
private static void Prefix(GUIText ___m_textButton)
{
TutorialMessagePatch.Bind(___m_textButton);
}
}
[HarmonyPatch(typeof(TextWrapper), "WrapText")]
internal static class TutorialWrapPatch
{
private static bool Prefix(string input, float width, TextMesh ___textMesh, ref string __result)
{
if ((Object)(object)Plugin.Instance == (Object)null || !Plugin.Instance.Language.UsesChineseTextRules || (Object)(object)___textMesh == (Object)null || (Object)(object)___textMesh.font == (Object)null || !ChineseTextWrapper.ContainsChinese(input) || (Object)(object)((Component)___textMesh).GetComponent<LocalizedMenuLabel>() == (Object)null || (Object)(object)((Component)___textMesh).GetComponentInParent<GuiTutorialMessageBox>() == (Object)null)
{
return true;
}
TutorialTextMetrics tutorialTextMetrics = new TutorialTextMetrics(___textMesh);
__result = ChineseTextWrapper.Wrap(input, width, tutorialTextMetrics.Advance, tutorialTextMetrics.TagWidth);
return false;
}
}
internal sealed class TutorialTextMetrics
{
private static readonly Regex Size = new Regex("\\bsize\\s*=\\s*[\"']?([0-9]+(?:\\.[0-9]+)?)", RegexOptions.IgnoreCase);
private readonly Font font;
private readonly float scale;
private readonly int baseSize;
private readonly FontStyle baseStyle;
private readonly Stack<int> sizes = new Stack<int>();
private int size;
private int bold;
private int italic;
internal TutorialTextMetrics(TextMesh mesh)
{
//IL_004a: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
font = mesh.font;
baseSize = (size = ((mesh.fontSize > 0) ? mesh.fontSize : font.fontSize));
baseStyle = mesh.fontStyle;
scale = mesh.characterSize * 0.1f * Mathf.Abs(((Component)mesh).transform.lossyScale.x);
}
internal float Advance(char ch)
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Invalid comparison between Unknown and I4
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Invalid comparison between Unknown and I4
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Invalid comparison between Unknown and I4
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Invalid comparison between Unknown and I4
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_0085: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Unknown result type (might be due to invalid IL or missing references)
bool num = bold > 0 || (int)baseStyle == 1 || (int)baseStyle == 3;
bool flag = italic > 0 || (int)baseStyle == 2 || (int)baseStyle == 3;
FontStyle val = (FontStyle)((!num) ? (flag ? 2 : 0) : ((!flag) ? 1 : 3));
if (font.dynamic)
{
font.RequestCharactersInTexture(ch.ToString(), size, val);
}
CharacterInfo val2 = default(CharacterInfo);
if (!font.GetCharacterInfo(ch, ref val2, size, val))
{
return (float)size * scale;
}
return (float)((CharacterInfo)(ref val2)).advance * scale;
}
internal float TagWidth(string tag)
{
if (tag.StartsWith("<quad", StringComparison.OrdinalIgnoreCase))
{
Match match = Size.Match(tag);
if (!match.Success || !float.TryParse(match.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
{
return (float)size * scale;
}
return result * scale;
}
if (tag.StartsWith("<size=", StringComparison.OrdinalIgnoreCase))
{
Match match2 = Size.Match(tag);
if (match2.Success && int.TryParse(match2.Groups[1].Value, out var result2) && result2 > 0)
{
sizes.Push(size);
size = result2;
}
}
else if (string.Equals(tag, "</size>", StringComparison.OrdinalIgnoreCase))
{
size = ((sizes.Count > 0) ? sizes.Pop() : baseSize);
}
else if (string.Equals(tag, "<b>", StringComparison.OrdinalIgnoreCase))
{
bold++;
}
else if (string.Equals(tag, "</b>", StringComparison.OrdinalIgnoreCase))
{
bold = Math.Max(0, bold - 1);
}
else if (string.Equals(tag, "<i>", StringComparison.OrdinalIgnoreCase))
{
italic++;
}
else if (string.Equals(tag, "</i>", StringComparison.OrdinalIgnoreCase))
{
italic = Math.Max(0, italic - 1);
}
return 0f;
}
}
internal static class BuildVersion
{
internal const string Plugin = "0.13.0";
internal const string Assembly = "0.13.0.0";
}