using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using CMRLib.Core; using ComputerysModdingUtilities; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: StraftatMod(true)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("CMRLib")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+035ee42632884b38ea937935eb423d7c7e886381")] [assembly: AssemblyProduct("CMRLib")] [assembly: AssemblyTitle("CMRLib")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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 CMRLib { [BepInPlugin("cmr.core", "CMRLib", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class CMRLibPlugin : BaseUnityPlugin { private void Awake() { ((BaseUnityPlugin)this).Logger.LogInfo((object)"CMRLib 1.0.0 - starting"); Install("host", delegate { CmrHost.Install(((BaseUnityPlugin)this).Logger); }); Install("text patch", delegate { TextPatch.Install(((BaseUnityPlugin)this).Logger); }); Install("Mod menu card (optional)", delegate { ModMenuDecoration.Apply(typeof(CMRLibPlugin), "CMRLib.Resources.icon.png", "Shared runtime for the CMR mods. Does nothing on its own.", ((BaseUnityPlugin)this).Logger); }); ((BaseUnityPlugin)this).Logger.LogInfo((object)"CMRLib 1.0.0 - ready"); } private void Install(string name, Action install) { try { install(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[Init] " + name + ": installed")); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("[Init] " + name + ": NOT installed - " + ex.GetType().Name + ": " + ex.Message)); } } } } namespace CMRLib.Core { public sealed class CmrHost : MonoBehaviour { private static GameObject _go; private static ManualLogSource _log; private static bool _installed; public static event Action Tick; public static event Action GuiDraw; public static void Install(ManualLogSource log) { _log = log; if (!_installed) { _installed = true; SceneManager.sceneLoaded += delegate { Ensure(); }; Ensure(); } } private static void Ensure() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (!((Object)(object)_go != (Object)null)) { _go = new GameObject("CMR_Host"); Object.DontDestroyOnLoad((Object)(object)_go); _go.AddComponent(); _log.LogInfo((object)"[Host] CMR_Host created"); } } private void Update() { Action tick = CmrHost.Tick; if (tick == null) { return; } try { tick(); } catch (Exception ex) { _log.LogError((object)("[Host] Tick failed: " + ex.GetType().Name + ": " + ex.Message)); } } private void OnGUI() { Action guiDraw = CmrHost.GuiDraw; if (guiDraw == null) { return; } try { guiDraw(); } catch (Exception ex) { _log.LogError((object)("[Host] GuiDraw failed: " + ex.GetType().Name + ": " + ex.Message)); } } private void OnDestroy() { if (_log != null) { _log.LogInfo((object)"[Host] CMR_Host destroyed - will be recreated on the next scene"); } _go = null; } } public static class EmbeddedAudio { private struct WavInfo { public int FormatTag; public int Channels; public int SampleRate; public int Bits; public int DataOffset; public int DataLength; } private static readonly Dictionary<(string Asm, string Res), AudioClip> _cache = new Dictionary<(string, string), AudioClip>(); public static AudioClip Load(Assembly owner, string resourceName, ManualLogSource log) { if (owner == null) { throw new ArgumentNullException("owner"); } if (string.IsNullOrEmpty(resourceName)) { throw new ArgumentNullException("resourceName"); } (string, string) key = (owner.GetName().Name, resourceName); if (_cache.TryGetValue(key, out var value)) { return value; } AudioClip val = null; try { val = LoadInternal(owner, resourceName, log); } catch (Exception ex) { log.LogError((object)("[Audio] " + owner.GetName().Name + ":" + resourceName + ": " + ex.GetType().Name + ": " + ex.Message)); } _cache[key] = val; return val; } private static AudioClip LoadInternal(Assembly asm, string resourceName, ManualLogSource log) { byte[] array; using (Stream stream = asm.GetManifestResourceStream(resourceName)) { if (stream == null) { log.LogError((object)("[Audio] resource not found: " + resourceName + ". Available: " + string.Join(", ", asm.GetManifestResourceNames()))); return null; } array = new byte[stream.Length]; int num; for (int i = 0; i < array.Length; i += num) { num = stream.Read(array, i, array.Length - i); if (num <= 0) { break; } } } if (!TryParseWav(array, out var info, out var error)) { log.LogError((object)("[Audio] " + resourceName + ": " + error)); return null; } float[] array2 = Decode(array, info); if (array2 == null) { log.LogError((object)$"[Audio] {resourceName}: unsupported format, {info.Bits}-bit (tag {info.FormatTag})"); return null; } int num2 = array2.Length / Math.Max(1, info.Channels); AudioClip val = AudioClip.Create(Path.GetFileNameWithoutExtension(resourceName), num2, info.Channels, info.SampleRate, false); val.SetData(array2, 0); log.LogInfo((object)($"[Audio] {resourceName}: {info.Channels}ch {info.SampleRate}Hz {info.Bits}bit, " + $"{val.length:0.###}s")); return val; } private static bool TryParseWav(byte[] b, out WavInfo info, out string error) { info = default(WavInfo); error = null; if (b == null || b.Length < 12) { error = "file shorter than 12 bytes"; return false; } if (Tag(b, 0) != "RIFF") { error = "no RIFF signature"; return false; } if (Tag(b, 8) != "WAVE") { error = "not a WAVE file"; return false; } bool flag = false; bool flag2 = false; int num = 12; while (num + 8 <= b.Length) { string text = Tag(b, num); int num2 = BitConverter.ToInt32(b, num + 4); int num3 = num + 8; if (num2 < 0 || num3 + num2 > b.Length) { num2 = b.Length - num3; } if (text == "fmt " && num2 >= 16) { info.FormatTag = BitConverter.ToInt16(b, num3); info.Channels = BitConverter.ToInt16(b, num3 + 2); info.SampleRate = BitConverter.ToInt32(b, num3 + 4); info.Bits = BitConverter.ToInt16(b, num3 + 14); flag = true; } else if (text == "data") { info.DataOffset = num3; info.DataLength = num2; flag2 = true; } num = num3 + num2 + (num2 & 1); } if (!flag) { error = "no fmt chunk"; return false; } if (!flag2) { error = "no data chunk"; return false; } if (info.Channels <= 0 || info.SampleRate <= 0) { error = $"bad parameters: {info.Channels}ch {info.SampleRate}Hz"; return false; } return true; } private static float[] Decode(byte[] b, WavInfo i) { int dataOffset = i.DataOffset; int dataLength = i.DataLength; switch (i.Bits) { case 8: { float[] array2 = new float[dataLength]; for (int l = 0; l < dataLength; l++) { array2[l] = (float)(b[dataOffset + l] - 128) / 128f; } return array2; } case 16: { int num2 = dataLength / 2; float[] array3 = new float[num2]; for (int m = 0; m < num2; m++) { array3[m] = (float)BitConverter.ToInt16(b, dataOffset + m * 2) / 32768f; } return array3; } case 24: { int num3 = dataLength / 3; float[] array4 = new float[num3]; for (int n = 0; n < num3; n++) { int num4 = dataOffset + n * 3; int num5 = b[num4] | (b[num4 + 1] << 8) | (b[num4 + 2] << 16); if ((num5 & 0x800000) != 0) { num5 |= -16777216; } array4[n] = (float)num5 / 8388608f; } return array4; } case 32: { int num = dataLength / 4; float[] array = new float[num]; if (i.FormatTag == 3) { for (int j = 0; j < num; j++) { array[j] = BitConverter.ToSingle(b, dataOffset + j * 4); } } else { for (int k = 0; k < num; k++) { array[k] = (float)BitConverter.ToInt32(b, dataOffset + k * 4) / 2.1474836E+09f; } } return array; } default: return null; } } private static string Tag(byte[] b, int off) { char c = (char)b[off]; string text = c.ToString(); c = (char)b[off + 1]; string text2 = c.ToString(); c = (char)b[off + 2]; string text3 = c.ToString(); c = (char)b[off + 3]; return text + text2 + text3 + c; } } public static class FieldSnapshot { private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly Dictionary _cache = new Dictionary(); private static bool IsScalar(Type t) { if (!(t == typeof(float)) && !(t == typeof(int)) && !(t == typeof(bool)) && !(t == typeof(string)) && !(t == typeof(uint))) { return t == typeof(double); } return true; } private static FieldInfo[] ScalarFields(Type t) { if (_cache.TryGetValue(t, out var value)) { return value; } List list = new List(); FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (IsScalar(fieldInfo.FieldType)) { list.Add(fieldInfo); } } FieldInfo[] array = list.ToArray(); _cache[t] = array; return array; } public static Dictionary Take(Component c) { Dictionary dictionary = new Dictionary(); if ((Object)(object)c == (Object)null) { return dictionary; } FieldInfo[] array = ScalarFields(((object)c).GetType()); foreach (FieldInfo fieldInfo in array) { try { dictionary[fieldInfo.Name] = fieldInfo.GetValue(c); } catch { } } return dictionary; } public static (bool Changed, string Text) Diff(Dictionary before, Component c) { if ((Object)(object)c == (Object)null) { return (Changed: false, Text: "component is gone"); } List list = new List(); FieldInfo[] array = ScalarFields(((object)c).GetType()); foreach (FieldInfo fieldInfo in array) { object value; try { value = fieldInfo.GetValue(c); } catch { continue; } if (before.TryGetValue(fieldInfo.Name, out var value2) && !object.Equals(value2, value)) { list.Add(fieldInfo.Name + ": " + Fmt(value2) + " -> " + Fmt(value)); } } if (list.Count != 0) { return (Changed: true, Text: string.Join("; ", list.ToArray())); } return (Changed: false, Text: "no changes"); } public static string Fmt(object v) { if (v != null) { if (!(v is float num)) { if (!(v is double num2)) { if (!(v is bool)) { if (v is string text) { return "\"" + text + "\""; } return Convert.ToString(v, CultureInfo.InvariantCulture); } if (!(bool)v) { return "false"; } return "true"; } return num2.ToString("0.#####", CultureInfo.InvariantCulture); } return num.ToString("0.#####", CultureInfo.InvariantCulture); } return "null"; } } public static class ModMenuDecoration { private const string ModMenuGuid = "kestrel.straftat.modmenu"; private const string ApiType = "ModMenu.Api.ModMenuCustomisation"; public static void Apply(Type ownerType, string iconResource, string description, ManualLogSource log, params ConfigEntryBase[] hide) { if (ownerType == null) { throw new ArgumentNullException("ownerType"); } if (!Chainloader.PluginInfos.TryGetValue("kestrel.straftat.modmenu", out var value)) { log.LogInfo((object)"[ModMenu] not installed - settings are available via the console and the .cfg"); return; } Type type = AccessTools.TypeByName("ModMenu.Api.ModMenuCustomisation"); if (type == null) { log.LogWarning((object)(string.Format("[ModMenu] plugin {0} found but type {1} is missing - ", value.Metadata.Version, "ModMenu.Api.ModMenuCustomisation") + "the API changed, decoration skipped")); return; } log.LogInfo((object)$"[ModMenu] detected {value.Metadata.Version}, settings will appear in the menu automatically"); TrySetDescription(type, ownerType, description, log); if (iconResource != null) { TrySetIcon(type, ownerType, iconResource, log); } if (hide != null) { foreach (ConfigEntryBase entry in hide) { TryHide(type, entry, log); } } } private static bool TryRegister(Type api, string property, string guid, object value, ManualLogSource log) { PropertyInfo propertyInfo = AccessTools.Property(api, property); if (propertyInfo == null) { log.LogWarning((object)("[ModMenu] " + property + " is absent - the API changed")); return false; } if (!(propertyInfo.GetValue(null, null) is IDictionary dictionary)) { log.LogWarning((object)("[ModMenu] " + property + " is not a dictionary - the API changed")); return false; } dictionary[guid] = value; return true; } private static string GuidOf(Type ownerType) { return (((MemberInfo)ownerType).GetCustomAttribute() ?? throw new ArgumentException(ownerType.Name + " has no [BepInPlugin] - pass the mod's plugin type", "ownerType")).GUID; } private static void TrySetDescription(Type api, Type ownerType, string description, ManualLogSource log) { try { string text = GuidOf(ownerType); if (TryRegister(api, "Descriptions", text, description, log)) { log.LogInfo((object)("[ModMenu] description registered for " + text)); } } catch (Exception ex) { log.LogWarning((object)("[ModMenu] description not set: " + ex.GetType().Name + ": " + ex.Message)); } } private static void TrySetIcon(Type api, Type ownerType, string iconResource, ManualLogSource log) { try { string text = GuidOf(ownerType); Sprite val = LoadIcon(ownerType, iconResource, log); if (!((Object)(object)val == (Object)null) && TryRegister(api, "Icons", text, val, log)) { log.LogInfo((object)("[ModMenu] icon registered for " + text)); } } catch (Exception ex) { log.LogWarning((object)("[ModMenu] icon not set: " + ex.GetType().Name + ": " + ex.Message)); } } private static Sprite LoadIcon(Type ownerType, string iconResource, ManualLogSource log) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) Assembly assembly = ownerType.Assembly; byte[] array; using (Stream stream = assembly.GetManifestResourceStream(iconResource)) { if (stream == null) { log.LogWarning((object)("[ModMenu] icon resource " + iconResource + " not found in " + assembly.GetName().Name)); return null; } array = new byte[stream.Length]; int num; for (int i = 0; i < array.Length; i += num) { num = stream.Read(array, i, array.Length - i); if (num <= 0) { break; } } } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, array)) { log.LogWarning((object)"[ModMenu] icon PNG could not be decoded"); return null; } ((Object)val).hideFlags = (HideFlags)61; ((Object)val).name = assembly.GetName().Name + "_Icon"; Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f)); ((Object)obj).hideFlags = (HideFlags)61; ((Object)obj).name = ((Object)val).name; log.LogInfo((object)$"[ModMenu] icon loaded for {assembly.GetName().Name}: {((Texture)val).width}x{((Texture)val).height}"); return obj; } private static void TryHide(Type api, ConfigEntryBase entry, ManualLogSource log) { if (entry == null) { return; } try { MethodInfo methodInfo = null; MethodInfo[] methods = api.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2.Name != "HideEntry") && methodInfo2.IsGenericMethodDefinition && methodInfo2.GetParameters().Length == 1) { methodInfo = methodInfo2; break; } } if (methodInfo == null) { MethodInfo methodInfo3 = AccessTools.Method(api, "HideEntry", new Type[1] { typeof(ConfigEntryBase) }, (Type[])null); if (methodInfo3 == null) { log.LogInfo((object)"[ModMenu] HideEntry is absent, skipped"); return; } methodInfo3.Invoke(null, new object[1] { entry }); return; } Type type = EntryValueType(entry); if (type == null) { log.LogWarning((object)("[ModMenu] could not hide '" + entry.Definition.Key + "': value type is unknown")); return; } methodInfo.MakeGenericMethod(type).Invoke(null, new object[1] { entry }); } catch (Exception ex) { log.LogWarning((object)("[ModMenu] could not hide '" + entry.Definition.Key + "': " + ex.GetType().Name + ": " + ex.Message)); } } private static Type EntryValueType(ConfigEntryBase entry) { Type type = ((object)entry).GetType(); while (type != null) { if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ConfigEntry<>)) { return type.GetGenericArguments()[0]; } type = type.BaseType; } return null; } } public sealed class PrefabRef { public Component Owner; public FieldInfo Field; public Component Value; public GameObject TargetRoot; public override string ToString() { return $"{((object)Owner).GetType().Name}.{Field.Name} -> {((Object)TargetRoot).name}#{((Object)TargetRoot).GetInstanceID()}"; } } public static class PrefabRebinder { private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; public static List FindExternalRefs(GameObject root) { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if ((Object)(object)root == (Object)null) { return list; } Component[] componentsInChildren = root.GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } Type type = ((object)val).GetType(); FieldInfo[] fields; try { fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } catch { continue; } FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { if (!typeof(Component).IsAssignableFrom(fieldInfo.FieldType)) { continue; } Component val2; try { object? value = fieldInfo.GetValue(val); val2 = (Component)((value is Component) ? value : null); } catch { continue; } if ((Object)(object)val2 == (Object)null) { continue; } GameObject gameObject = ((Component)val2.transform.root).gameObject; if (gameObject != root) { Scene scene = gameObject.scene; if (!((Scene)(ref scene)).IsValid()) { list.Add(new PrefabRef { Owner = val, Field = fieldInfo, Value = val2, TargetRoot = gameObject }); } } } } return list; } public static void ReportSharing(IEnumerable owners, ManualLogSource log) { Dictionary> dictionary = new Dictionary>(); Dictionary dictionary2 = new Dictionary(); foreach (GameObject owner in owners) { if ((Object)(object)owner == (Object)null) { continue; } List list = FindExternalRefs(owner); log.LogInfo((object)$"[Rebind] {((Object)owner).name}: {list.Count} external references"); foreach (PrefabRef item in list) { log.LogInfo((object)$"[Rebind] {item}"); int instanceID = ((Object)item.TargetRoot).GetInstanceID(); dictionary2[instanceID] = ((Object)item.TargetRoot).name; if (!dictionary.TryGetValue(instanceID, out var value)) { value = (dictionary[instanceID] = new List()); } if (!value.Contains(((Object)owner).name)) { value.Add(((Object)owner).name); } } } foreach (KeyValuePair> item2 in dictionary) { if (item2.Value.Count >= 2) { log.LogWarning((object)($"[Rebind] SHARED prefab {dictionary2[item2.Key]}#{item2.Key} " + "used by: " + string.Join(", ", item2.Value.ToArray()))); } } } } public enum PrefabScope { Any, Pickup, Spawned } public static class PrefabRegistry { public const string WeaponsResourcePath = "RandomWeapons"; private static readonly string[] SpawnedMarkerTypes = new string[8] { "PredictedProjectile", "ShrapnelBallistic", "PhysicsGrenade", "ProximityMine", "Claymore", "Obus", "HandGrenadeTwo", "RebondBalle" }; private static bool _warm; private static GameObject[] _pickups = (GameObject[])(object)new GameObject[0]; private static List _spawned = new List(); private static ManualLogSource _log; private static ManualLogSource Log => _log ?? (_log = Logger.CreateLogSource("CMRLib.Registry")); public static IReadOnlyList Pickups { get { EnsureWarm(); return _pickups; } } public static IReadOnlyList Spawned { get { EnsureWarm(); return _spawned; } } public static bool IsWarm => _warm; public static void EnsureWarm() { if (!_warm) { _pickups = (GameObject[])(((object)Resources.LoadAll("RandomWeapons")) ?? ((object)new GameObject[0])); Log.LogInfo((object)string.Format("[Registry] LoadAll(\"{0}\") -> {1} pickups", "RandomWeapons", _pickups.Length)); if (_pickups.Length == 0) { Log.LogWarning((object)"[Registry] Empty. Did the Resources path change? Everything below will find nothing."); } CollectSpawned(); Log.LogInfo((object)$"[Registry] spawned prefabs: {_spawned.Count}"); _warm = true; } } private static void CollectSpawned() { //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) HashSet hashSet = new HashSet(from p in _pickups where (Object)(object)p != (Object)null select ((Object)p).GetInstanceID()); Dictionary dictionary = new Dictionary(); string[] spawnedMarkerTypes = SpawnedMarkerTypes; foreach (string text in spawnedMarkerTypes) { Type type = AccessTools.TypeByName(text); if (type == null) { Log.LogWarning((object)("[Registry] type " + text + " not found - did the game update?")); continue; } Object[] array; try { array = Resources.FindObjectsOfTypeAll(type); } catch (Exception ex) { Log.LogWarning((object)("[Registry] " + text + ": " + ex.Message)); continue; } Object[] array2 = array; foreach (Object obj in array2) { Component val = (Component)(object)((obj is Component) ? obj : null); if (val != null) { GameObject gameObject = ((Component)val.transform.root).gameObject; Scene scene = gameObject.scene; if (!((Scene)(ref scene)).IsValid() && !hashSet.Contains(((Object)gameObject).GetInstanceID())) { dictionary[((Object)gameObject).GetInstanceID()] = gameObject; } } } } _spawned = dictionary.Values.ToList(); } public static GameObject[] Resolve(PrefabQuery query) { EnsureWarm(); return (query.Scope switch { PrefabScope.Pickup => _pickups, PrefabScope.Spawned => _spawned, _ => _pickups.Concat(_spawned), }).Where((GameObject go) => (Object)(object)go != (Object)null && Matches(go, query)).ToArray(); } private static bool Matches(GameObject go, PrefabQuery q) { if (!string.IsNullOrEmpty(q.Name) && !(q.Exact ? string.Equals(((Object)go).name, q.Name, StringComparison.OrdinalIgnoreCase) : (((Object)go).name.IndexOf(q.Name, StringComparison.OrdinalIgnoreCase) >= 0))) { return false; } if (!string.IsNullOrEmpty(q.Layer)) { int num = LayerMask.NameToLayer(q.Layer); if (num < 0) { return false; } if (go.layer != num) { return false; } } if (!string.IsNullOrEmpty(q.RequireComponent)) { Type type = AccessTools.TypeByName(q.RequireComponent); if (type == null) { return false; } if ((Object)(object)go.GetComponentInChildren(type, true) == (Object)null) { return false; } } return true; } } public sealed class PrefabQuery { public string Name; public bool Exact = true; public string Layer; public string RequireComponent; public PrefabScope Scope; public override string ToString() { List list = new List(); if (!string.IsNullOrEmpty(Name)) { list.Add(Exact ? ("name==" + Name) : ("name~" + Name)); } if (!string.IsNullOrEmpty(Layer)) { list.Add("layer=" + Layer); } if (!string.IsNullOrEmpty(RequireComponent)) { list.Add("has=" + RequireComponent); } if (Scope != PrefabScope.Any) { list.Add($"scope={Scope}"); } return string.Join(", ", list); } } public sealed class TextPatch { private const int RetryEveryFrames = 30; private static readonly List _pending = new List(); private static ManualLogSource _log; private static bool _installed; private readonly string _path; private Func _transform; private float? _fontSize; private bool _keep; private string _applied; private string _original; private int _lastTry = -999; private bool _done; private TextPatch(string path) { _path = path; } public static void Install(ManualLogSource log) { _log = log; if (!_installed) { _installed = true; CmrHost.Tick += TickAll; } } public static TextPatch At(string path) { TextPatch textPatch = new TextPatch(path); _pending.Add(textPatch); return textPatch; } public TextPatch Set(string text) { return Transform((string _) => text); } public TextPatch Append(string suffix) { return Transform((string orig) => orig + suffix); } public TextPatch Prepend(string prefix) { return Transform((string orig) => prefix + orig); } public TextPatch Transform(Func f) { _transform = f; return this; } public TextPatch FontSize(float size) { _fontSize = size; return this; } public TextPatch Keep() { _keep = true; return this; } private static void TickAll() { for (int num = _pending.Count - 1; num >= 0; num--) { TextPatch textPatch = _pending[num]; if (textPatch.TryApply() && textPatch._done) { _pending.RemoveAt(num); } } } private bool TryApply() { if (Time.frameCount - _lastTry < 30) { return false; } _lastTry = Time.frameCount; Transform val = UiFind.Path(_path); if ((Object)(object)val == (Object)null) { return false; } TMP_Text val2 = ((Component)val).GetComponent() ?? ((Component)val).GetComponentInChildren(true); if ((Object)(object)val2 == (Object)null) { _log.LogWarning((object)("[Text] " + _path + ": object found but it has no TMP_Text")); _done = true; return true; } if (_original == null) { _original = val2.text ?? ""; _log.LogInfo((object)("[Text] " + _path + ": original = \"" + Escape(_original) + "\"")); } if (_applied != null && val2.text != _applied) { _original = val2.text ?? ""; if (_keep) { _log.LogInfo((object)("[Text] " + _path + ": game overwrote it, restoring ours")); } } string text = ((_transform != null) ? _transform(_original) : _original); if (val2.text != text) { val2.text = text; _applied = text; _log.LogInfo((object)("[Text] " + _path + ": \"" + Escape(_original) + "\" -> \"" + Escape(text) + "\"")); } else { _applied = text; } if (_fontSize.HasValue && Math.Abs(val2.fontSize - _fontSize.Value) > 0.01f) { val2.fontSize = _fontSize.Value; _log.LogInfo((object)$"[Text] {_path}: fontSize -> {_fontSize.Value}"); } if (!_keep) { _done = true; } return true; } private static string Escape(string s) { if (s != null) { return s.Replace("\r", "\\r").Replace("\n", "\\n"); } return ""; } } public sealed class Tweak { private sealed class Step { public string Label; public Func Run; } internal readonly struct StepResult { public readonly bool Changed; public readonly string Text; public StepResult(bool changed, string text) { Changed = changed; Text = text; } } private readonly List _steps = new List(); public string Id { get; private set; } public PrefabQuery Query { get; private set; } public bool AllowMultiple { get; private set; } public bool Optional { get; private set; } public bool IsPinned { get; private set; } public string PinReason { get; private set; } internal int StepCount => _steps.Count; private Tweak(string id, PrefabQuery query) { Id = id; Query = query; } public static Tweak Pickup(string name) { return new Tweak(name, new PrefabQuery { Name = name, Exact = true, Scope = PrefabScope.Pickup }); } public static Tweak Spawned(string name) { return new Tweak(name, new PrefabQuery { Name = name, Exact = true, Scope = PrefabScope.Spawned }); } public static Tweak Any(string name) { return new Tweak(name, new PrefabQuery { Name = name, Exact = true, Scope = PrefabScope.Any }); } public static Tweak Where(string id, PrefabQuery query) { return new Tweak(id, query); } public Tweak OnLayer(string layer) { Query.Layer = layer; return this; } public Tweak WithComponent(string typeName) { Query.RequireComponent = typeName; return this; } public Tweak Multiple() { AllowMultiple = true; return this; } public Tweak IfPresent() { Optional = true; return this; } public Tweak Pin(string reason = null) { IsPinned = true; PinReason = reason; return this; } public Tweak Weapon(Action apply) { return this.Component(apply, "Weapon"); } public Tweak Melee(Action apply) { return this.Component(apply, "MeleeWeapon"); } public Tweak Item(Action apply) { return this.Component(apply, "ItemBehaviour"); } public Tweak Rename(string weaponName) { _steps.Add(new Step { Label = "Rename -> \"" + weaponName + "\"", Run = delegate(GameObject root) { ItemBehaviour componentInChildren = root.GetComponentInChildren(true); if ((Object)(object)componentInChildren == (Object)null) { throw new TweakException("no ItemBehaviour"); } string weaponName2 = componentInChildren.weaponName; componentInChildren.weaponName = weaponName; return (!(weaponName2 == weaponName)) ? new StepResult(changed: true, "weaponName: \"" + weaponName2 + "\" -> \"" + weaponName + "\"") : new StepResult(changed: false, "weaponName: already \"" + weaponName + "\""); } }); return this; } public Tweak Component(Action apply, string label = null) where T : Component { string name = label ?? typeof(T).Name; _steps.Add(new Step { Label = name, Run = delegate(GameObject root) { T componentInChildren = root.GetComponentInChildren(true); if ((Object)(object)componentInChildren == (Object)null) { throw new TweakException("no " + name); } Dictionary before = FieldSnapshot.Take((Component)(object)componentInChildren); apply(componentInChildren); var (changed, text) = FieldSnapshot.Diff(before, (Component)(object)componentInChildren); return new StepResult(changed, name + " { " + text + " }"); } }); return this; } public Tweak Private(string field, object value) where T : Component { _steps.Add(new Step { Label = typeof(T).Name + "." + field, Run = delegate(GameObject root) { T componentInChildren = root.GetComponentInChildren(true); if ((Object)(object)componentInChildren == (Object)null) { throw new TweakException("no " + typeof(T).Name); } return SetField(componentInChildren, typeof(T), field, value); } }); return this; } public Tweak PrivateOn(string typeName, string field, object value) { _steps.Add(new Step { Label = typeName + "." + field, Run = delegate(GameObject root) { Type type = AccessTools.TypeByName(typeName); if (type == null) { throw new TweakException("type " + typeName + " not found"); } Component componentInChildren = root.GetComponentInChildren(type, true); if ((Object)(object)componentInChildren == (Object)null) { throw new TweakException("no " + typeName); } return SetField(componentInChildren, type, field, value); } }); return this; } public Tweak HideChild(string childName) { _steps.Add(new Step { Label = "HideChild(" + childName + ")", Run = delegate(GameObject root) { List list = new List(); int num = 0; Transform val = root.transform.Find(childName); if ((Object)(object)val != (Object)null) { if (((Component)val).gameObject.activeSelf) { ((Component)val).gameObject.SetActive(false); list.Add(((Object)val).name); } else { num++; } } else { Transform[] componentsInChildren = root.GetComponentsInChildren(true); foreach (Transform val2 in componentsInChildren) { if (string.Equals(((Object)val2).name, childName, StringComparison.OrdinalIgnoreCase)) { if (((Component)val2).gameObject.activeSelf) { ((Component)val2).gameObject.SetActive(false); list.Add(((Object)val2).name); } else { num++; } } } } if (list.Count == 0 && num == 0) { throw new TweakException("no child named '" + childName + "'"); } return (list.Count <= 0) ? new StepResult(changed: false, "HideChild(" + childName + "): already hidden") : new StepResult(changed: true, "HideChild(" + childName + "): hidden" + ((list.Count > 1) ? (" " + list.Count) : "")); } }); return this; } public Tweak Raw(string describe, Action action) { _steps.Add(new Step { Label = describe, Run = delegate(GameObject root) { action(root); return new StepResult(changed: true, describe); } }); return this; } internal int ApplyTo(GameObject root, List applied) { int num = 0; foreach (Step step in _steps) { StepResult stepResult = step.Run(root); applied.Add(stepResult.Text); if (stepResult.Changed) { num++; } } return num; } private static StepResult SetField(object target, Type declaring, string field, object value) { FieldInfo fieldInfo = AccessTools.Field(declaring, field); if (fieldInfo == null) { throw new TweakException("no field " + declaring.Name + "." + field); } object obj = value; if (value != null && !fieldInfo.FieldType.IsInstanceOfType(value)) { try { obj = Convert.ChangeType(value, fieldInfo.FieldType); } catch { throw new TweakException(declaring.Name + "." + field + ": " + value?.GetType().Name + " is not convertible to " + fieldInfo.FieldType.Name); } } object value2 = fieldInfo.GetValue(target); fieldInfo.SetValue(target, obj); string text = declaring.Name + "." + field; if (!object.Equals(value2, obj)) { return new StepResult(changed: true, text + ": " + FieldSnapshot.Fmt(value2) + " -> " + FieldSnapshot.Fmt(obj)); } return new StepResult(changed: false, text + ": already " + FieldSnapshot.Fmt(obj)); } } public sealed class TweakException : Exception { public TweakException(string message) : base(message) { } } public static class TweakRunner { public sealed class Report { public int Applied; public int Missing; public int Ambiguous; public int Failed; public int NoOp; public int Pinned; public readonly List Problems = new List(); public bool AllGood { get { if (Missing == 0 && Ambiguous == 0 && Failed == 0) { return NoOp == 0; } return false; } } } public static Report Run(string groupName, IEnumerable tweaks, ManualLogSource log) { Report report = new Report(); IList list = (tweaks as IList) ?? tweaks.ToList(); log.LogInfo((object)$"[Tweaks] === {groupName}: {list.Count} tweaks ==="); foreach (Tweak item in list) { try { RunOne(item, log, report); } catch (Exception ex) { report.Failed++; string text = item.Id + ": unexpected failure - " + ex.GetType().Name + ": " + ex.Message; report.Problems.Add(text); log.LogError((object)("[Tweaks] " + text)); } } log.LogInfo((object)($"[Tweaks] === {groupName}: applied {report.Applied}, " + $"pinned-unchanged {report.Pinned}, no-op {report.NoOp}, " + $"missing {report.Missing}, ambiguous {report.Ambiguous}, " + $"failed {report.Failed} ===")); return report; } private static void RunOne(Tweak tweak, ManualLogSource log, Report report) { GameObject[] array = PrefabRegistry.Resolve(tweak.Query); if (array.Length == 0) { if (!tweak.Optional) { report.Missing++; string text = $"{tweak.Id}: not found ({tweak.Query})"; report.Problems.Add(text); log.LogWarning((object)("[Tweaks] " + text)); } return; } if (array.Length > 1 && !tweak.AllowMultiple) { report.Ambiguous++; string arg = string.Join(", ", array.Select((GameObject h) => $"{((Object)h).name}#{((Object)h).GetInstanceID()}")); string text2 = $"{tweak.Id}: {array.Length} matches ({arg}) - narrow the selector " + "(layer/component) or allow .Multiple()"; report.Problems.Add(text2); log.LogWarning((object)("[Tweaks] " + text2)); return; } GameObject[] array2 = array; foreach (GameObject val in array2) { List list = new List(); try { int num2 = tweak.ApplyTo(val, list); report.Applied++; string text3 = string.Join("; ", list); if (num2 == 0 && tweak.IsPinned) { report.Pinned++; string text4 = (string.IsNullOrEmpty(tweak.PinReason) ? "" : (" - " + tweak.PinReason)); log.LogInfo((object)("[Tweaks] " + tweak.Id + " -> " + ((Object)val).name + ": pin holds current value" + text4 + " (" + text3 + ")")); } else if (num2 == 0) { report.NoOp++; string text5 = tweak.Id + " -> " + ((Object)val).name + ": NOTHING CHANGED (" + text3 + ")"; report.Problems.Add(text5); log.LogWarning((object)("[Tweaks] " + text5)); } else { log.LogInfo((object)("[Tweaks] " + tweak.Id + " -> " + ((Object)val).name + ": " + text3)); } } catch (TweakException ex) { report.Failed++; string text6 = $"{tweak.Id} -> {((Object)val).name}: {ex.Message} (completed {list.Count}/{tweak.StepCount})"; report.Problems.Add(text6); log.LogWarning((object)("[Tweaks] " + text6)); } catch (Exception ex2) { report.Failed++; string text7 = tweak.Id + " -> " + ((Object)val).name + ": " + ex2.GetType().Name + ": " + ex2.Message; report.Problems.Add(text7); log.LogError((object)("[Tweaks] " + text7)); } } } } public static class UiFind { public static Transform Root(string name) { //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) { continue; } GameObject[] rootGameObjects = ((Scene)(ref sceneAt)).GetRootGameObjects(); foreach (GameObject val in rootGameObjects) { if (((Object)val).name == name) { return val.transform; } } } GameObject val2 = GameObject.Find(name); if (!((Object)(object)val2 != (Object)null)) { return null; } return val2.transform; } public static Transform Descendant(Transform parent, string name) { if ((Object)(object)parent == (Object)null) { return null; } Queue queue = new Queue(); queue.Enqueue(parent); while (queue.Count > 0) { Transform val = queue.Dequeue(); if (((Object)val).name == name) { return val; } for (int i = 0; i < val.childCount; i++) { queue.Enqueue(val.GetChild(i)); } } return null; } public static Transform Path(string path) { if (string.IsNullOrEmpty(path)) { return null; } string[] array = path.Split(new char[1] { '/' }); Transform val = Root(array[0]); for (int i = 1; i < array.Length; i++) { if (!((Object)(object)val != (Object)null)) { break; } val = Descendant(val, array[i]); } return val; } public static string FullPath(Transform t) { if ((Object)(object)t == (Object)null) { return ""; } string text = ((Object)t).name; Transform parent = t.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } } }