Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PalaCompany v1.0.1
pala_company.dll
Decompiled 8 hours agousing System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net.WebSockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using KickChat; using LethalConfig; using LethalConfig.ConfigItems; using LethalConfig.ConfigItems.Options; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using TMPro; using Unity.Netcode; using UnityEngine; using UnityEngine.AI; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("pala_company")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+0a8a90a57f180d726af3619ae00f80e4f4eb29c0")] [assembly: AssemblyProduct("pala_company")] [assembly: AssemblyTitle("pala_company")] [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 PalaCompany { internal static class LethalConfigSupport { [MethodImpl(MethodImplOptions.NoInlining)] internal static void Register(ConfigEntry<float> userCooldown, ConfigEntry<float> globalCooldown, ConfigEntry<int> maxLive, ConfigEntry<float> spawnRadius, ConfigEntry<string> perEnemyLimits, ConfigEntry<int> defaultPerEnemyMax, ConfigEntry<bool> enableDebugKeys, ConfigEntry<bool> countOnlyModEnemies, ConfigEntry<bool> showSpawnTips, ConfigEntry<bool> kickEnabled, ConfigEntry<string> prefix, ConfigEntry<string> blocklist, ConfigEntry<string> kickChatroomId, ConfigEntry<string> kickAppKey, ConfigEntry<string> kickCluster, Action restartKick) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Expected O, but got Unknown //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Expected O, but got Unknown //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Expected O, but got Unknown //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Expected O, but got Unknown //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Expected O, but got Unknown //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Expected O, but got Unknown //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Expected O, but got Unknown //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Expected O, but got Unknown //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Expected O, but got Unknown //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Expected O, but got Unknown //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Expected O, but got Unknown //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Expected O, but got Unknown //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Expected O, but got Unknown LethalConfigManager.SkipAutoGen(); FloatSliderOptions val = new FloatSliderOptions(); ((BaseRangeOptions<float>)val).Min = 0f; ((BaseRangeOptions<float>)val).Max = 300f; ((BaseOptions)val).RequiresRestart = false; LethalConfigManager.AddConfigItem((BaseConfigItem)new FloatSliderConfigItem(userCooldown, val)); FloatSliderOptions val2 = new FloatSliderOptions(); ((BaseRangeOptions<float>)val2).Min = 0f; ((BaseRangeOptions<float>)val2).Max = 120f; ((BaseOptions)val2).RequiresRestart = false; LethalConfigManager.AddConfigItem((BaseConfigItem)new FloatSliderConfigItem(globalCooldown, val2)); IntSliderOptions val3 = new IntSliderOptions(); ((BaseRangeOptions<int>)val3).Min = 0; ((BaseRangeOptions<int>)val3).Max = 50; ((BaseOptions)val3).RequiresRestart = false; LethalConfigManager.AddConfigItem((BaseConfigItem)new IntSliderConfigItem(maxLive, val3)); IntSliderOptions val4 = new IntSliderOptions(); ((BaseRangeOptions<int>)val4).Min = 0; ((BaseRangeOptions<int>)val4).Max = 10; ((BaseOptions)val4).RequiresRestart = false; LethalConfigManager.AddConfigItem((BaseConfigItem)new IntSliderConfigItem(defaultPerEnemyMax, val4)); FloatSliderOptions val5 = new FloatSliderOptions(); ((BaseRangeOptions<float>)val5).Min = 1f; ((BaseRangeOptions<float>)val5).Max = 50f; ((BaseOptions)val5).RequiresRestart = false; LethalConfigManager.AddConfigItem((BaseConfigItem)new FloatSliderConfigItem(spawnRadius, val5)); LethalConfigManager.AddConfigItem((BaseConfigItem)new BoolCheckBoxConfigItem(enableDebugKeys, false)); LethalConfigManager.AddConfigItem((BaseConfigItem)new BoolCheckBoxConfigItem(countOnlyModEnemies, false)); LethalConfigManager.AddConfigItem((BaseConfigItem)new BoolCheckBoxConfigItem(showSpawnTips, false)); LethalConfigManager.AddConfigItem((BaseConfigItem)new BoolCheckBoxConfigItem(kickEnabled, false)); LethalConfigManager.AddConfigItem((BaseConfigItem)new TextInputFieldConfigItem(prefix, false)); LethalConfigManager.AddConfigItem((BaseConfigItem)new TextInputFieldConfigItem(blocklist, false)); LethalConfigManager.AddConfigItem((BaseConfigItem)new TextInputFieldConfigItem(perEnemyLimits, false)); LethalConfigManager.AddConfigItem((BaseConfigItem)new TextInputFieldConfigItem(kickChatroomId, false)); LethalConfigManager.AddConfigItem((BaseConfigItem)new TextInputFieldConfigItem(kickAppKey, false)); LethalConfigManager.AddConfigItem((BaseConfigItem)new TextInputFieldConfigItem(kickCluster, false)); LethalConfigManager.AddConfigItem((BaseConfigItem)new GenericButtonConfigItem("Kick", "Reconnect Kick", "Stop and restart the Kick chat connection using the current settings.", "Reconnect Kick", (GenericButtonHandler)delegate { restartKick(); })); } } public class ChatMessage { public string User; public string Text; } [BepInPlugin("miskin213.pala_company", "Pala Company", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private static readonly ConcurrentQueue<ChatMessage> Inbox = new ConcurrentQueue<ChatMessage>(); private static readonly Dictionary<string, string> Aliases = new Dictionary<string, string> { { "bracken", "flowerman" }, { "snareflea", "centipede" }, { "flea", "centipede" }, { "thumper", "crawler" }, { "hygrodere", "blob" }, { "ghostgirl", "girl" }, { "ghost", "girl" }, { "eyelessdog", "mouthdog" }, { "dog", "mouthdog" }, { "coilhead", "spring" }, { "sporelizard", "puffer" }, { "lizard", "puffer" }, { "lassoman", "lasso" }, { "spider", "bunkerspider" }, { "hoarder", "hoardingbug" }, { "bug", "hoardingbug" }, { "giant", "forestgiant" }, { "leviathan", "earthleviathan" }, { "baboon", "baboonhawk" }, { "hawk", "baboonhawk" }, { "bees", "redlocustbees" }, { "killerbees", "redlocustbees" } }; private static readonly string[] TestMessages = new string[5] { "!spawn hygrodere", "!spawn baboon", "!spawn notarealenemy", "just chatting", "!spawn spore lizard" }; private ConfigEntry<string> prefix; private ConfigEntry<string> blocklist; private ConfigEntry<string> kickChatroomId; private ConfigEntry<string> kickAppKey; private ConfigEntry<string> kickCluster; private ConfigEntry<string> perEnemyLimits; private ConfigEntry<float> userCooldown; private ConfigEntry<float> globalCooldown; private ConfigEntry<float> spawnRadius; private ConfigEntry<int> maxLive; private ConfigEntry<int> defaultPerEnemyMax; private ConfigEntry<bool> enableDebugKeys; private ConfigEntry<bool> countOnlyModEnemies; private ConfigEntry<bool> showSpawnTips; private ConfigEntry<bool> kickEnabled; private ConfigEntry<bool> logMenuHierarchy; private ConfigEntry<bool> customTitle; private KickChatClient kickChatClient; private bool kickChatStopped; private readonly Dictionary<string, float> lastByUser = new Dictionary<string, float>(); private readonly List<EnemyAI> modSpawnedEnemies = new List<EnemyAI>(); private readonly HashSet<string> loggedInvalidPerEnemyLimitEntries = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private float lastGlobal = -999f; private Dictionary<string, EnemyType> enemyCache; private int testIndex; private int fakeViewer; public static void EnqueueChat(string user, string text) { Inbox.Enqueue(new ChatMessage { User = user, Text = text }); } private void Awake() { ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; prefix = ((BaseUnityPlugin)this).Config.Bind<string>("Commands", "Prefix", "!spawn", "Chat command that triggers a spawn."); userCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("Limits", "UserCooldownSeconds", 30f, "Seconds between spawns per viewer."); globalCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("Limits", "GlobalCooldownSeconds", 5f, "Seconds between any two spawns."); maxLive = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "MaxLiveEnemies", 8, "No spawns while this many enemies are alive."); spawnRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Limits", "SpawnDistance", 9f, "How far from the target player enemies appear."); blocklist = ((BaseUnityPlugin)this).Config.Bind<string>("Limits", "Blocklist", "", "Comma-separated enemy names chat may NOT spawn."); perEnemyLimits = ((BaseUnityPlugin)this).Config.Bind<string>("Limits", "PerEnemyLimits", "", "Per-enemy max alive, e.g. \"Jester=1, Nutcracker=1, Bracken=2\". Names match chat commands incl. aliases. Values must be 1 or higher."); defaultPerEnemyMax = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "DefaultPerEnemyMax", 0, "Default per-enemy max alive; 0 disables the default limit."); enableDebugKeys = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "EnableDebugKeys", false, "Enable F9/F10/F11 debug keys."); countOnlyModEnemies = ((BaseUnityPlugin)this).Config.Bind<bool>("Limits", "CountOnlyModEnemies", true, "Count only enemies spawned by this mod toward the live enemy limit."); showSpawnTips = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowSpawnTips", true, "Show a HUD tip after a successful spawn."); logMenuHierarchy = ((BaseUnityPlugin)this).Config.Bind<bool>("Menu", "LogHierarchy", false, "Log UI hierarchy details when the MainMenu scene loads."); customTitle = ((BaseUnityPlugin)this).Config.Bind<bool>("Menu", "CustomTitle", true, "Replace the MainMenu title image with title.png."); kickEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Kick", "Enabled", false, "Connect to Kick chat."); kickChatroomId = ((BaseUnityPlugin)this).Config.Bind<string>("Kick", "ChatroomId", "0", "Kick chatroom ID."); kickAppKey = ((BaseUnityPlugin)this).Config.Bind<string>("Kick", "AppKey", "34bf7a0ff419a2a775b9", "Kick Pusher app key."); kickCluster = ((BaseUnityPlugin)this).Config.Bind<string>("Kick", "Cluster", "us2", "Kick Pusher cluster."); if (Chainloader.PluginInfos.ContainsKey("ainavt.lc.lethalconfig")) { LethalConfigSupport.Register(userCooldown, globalCooldown, maxLive, spawnRadius, perEnemyLimits, defaultPerEnemyMax, enableDebugKeys, countOnlyModEnemies, showSpawnTips, kickEnabled, prefix, blocklist, kickChatroomId, kickAppKey, kickCluster, RestartKick); } SceneManager.sceneLoaded += OnSceneLoaded; if (kickEnabled.Value) { StartKickClient(); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"Pala Company Phase2 loaded!"); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) ((BaseUnityPlugin)this).Logger.LogInfo((object)("Scene loaded: " + ((Scene)(ref scene)).name)); if (customTitle.Value && ((Scene)(ref scene)).name == "MainMenu") { ((MonoBehaviour)this).StartCoroutine(ApplyCustomTitle(scene)); } if (logMenuHierarchy.Value && ((Scene)(ref scene)).name == "MainMenu") { ((MonoBehaviour)this).StartCoroutine(LogMenuHierarchyAfterDelay(scene)); } } private IEnumerator ApplyCustomTitle(Scene scene) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) yield return (object)new WaitForSecondsRealtime(1f); Image image = null; Sprite customSprite = null; if (!TryApplyCustomTitle(out image, out customSprite)) { yield break; } for (int elapsedMilliseconds = 0; elapsedMilliseconds < 10000; elapsedMilliseconds += 250) { yield return (object)new WaitForSecondsRealtime(0.25f); try { if ((Object)(object)image != (Object)null && (Object)(object)image.sprite != (Object)(object)customSprite) { string replacedName = (((Object)(object)image.sprite != (Object)null) ? ((Object)image.sprite).name : "<none>"); image.sprite = customSprite; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Restored custom menu title after replacing sprite: " + replacedName)); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to restore custom menu title: " + ex)); break; } } } private bool TryApplyCustomTitle(out Image image, out Sprite customSprite) { //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Expected O, but got Unknown //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) image = null; customSprite = null; try { GameObject val = GameObject.Find("Canvas/MenuContainer/MainButtons/HeaderImage"); if ((Object)(object)val == (Object)null) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Custom menu title not applied: HeaderImage was not found."); return false; } image = val.GetComponent<Image>(); if ((Object)(object)image == (Object)null) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Custom menu title not applied: HeaderImage has no Image component."); return false; } byte[] array = null; string path = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "title.png"); if (File.Exists(path)) { array = File.ReadAllBytes(path); } else { Assembly assembly = ((object)this).GetType().Assembly; string text = assembly.GetManifestResourceNames().FirstOrDefault((string name) => name.EndsWith("title.png", StringComparison.OrdinalIgnoreCase)); if (text != null) { using Stream stream = assembly.GetManifestResourceStream(text); using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); array = memoryStream.ToArray(); } } if (array == null) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Custom menu title not applied: title.png was not found beside the plugin DLL or as an embedded resource."); return false; } Texture2D val2 = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val2, array, false)) { Object.Destroy((Object)(object)val2); ((BaseUnityPlugin)this).Logger.LogError((object)"Custom menu title not applied: title.png could not be loaded."); return false; } customSprite = Sprite.Create(val2, new Rect(0f, 0f, (float)((Texture)val2).width, (float)((Texture)val2).height), new Vector2(0.5f, 0.5f)); ((Object)customSprite).name = "PalaCompanyTitle"; Sprite sprite = image.sprite; ManualLogSource logger = ((BaseUnityPlugin)this).Logger; string[] obj = new string[6] { "Replacing menu title sprite: name=", ((Object)(object)sprite != (Object)null) ? ((Object)sprite).name : "<none>", " rectSize=", null, null, null }; object obj2; if (!((Object)(object)sprite != (Object)null)) { obj2 = "<none>"; } else { Rect rect = sprite.rect; obj2 = ((object)((Rect)(ref rect)).size/*cast due to .constrained prefix*/).ToString(); } obj[3] = (string)obj2; obj[4] = " textureSize="; obj[5] = (((Object)(object)sprite != (Object)null) ? (((Texture)sprite.texture).width + "x" + ((Texture)sprite.texture).height) : "<none>"); logger.LogInfo((object)string.Concat(obj)); image.sprite = customSprite; return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to apply custom menu title: " + ex)); return false; } } private unsafe IEnumerator LogMenuHierarchyAfterDelay(Scene scene) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) yield return (object)new WaitForSecondsRealtime(2f); try { GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); foreach (GameObject root in rootGameObjects) { Image[] componentsInChildren = root.GetComponentsInChildren<Image>(true); Rect rect; foreach (Image image in componentsInChildren) { rect = ((Graphic)image).rectTransform.rect; Vector2 rectSize = ((Rect)(ref rect)).size; ManualLogSource logger = ((BaseUnityPlugin)this).Logger; string[] obj = new string[10] { "[menu-ui] path=", GetHierarchyPath(((Component)image).transform), " activeInHierarchy=", ((Component)image).gameObject.activeInHierarchy.ToString(), " component=", ((object)image).GetType().Name, " sprite=", ((Object)(object)image.sprite != (Object)null) ? ((Object)image.sprite).name : "<none>", " rectSize=", null }; Vector2 val = rectSize; obj[9] = ((object)(*(Vector2*)(&val))/*cast due to .constrained prefix*/).ToString(); logger.LogInfo((object)string.Concat(obj)); } TMP_Text[] componentsInChildren2 = root.GetComponentsInChildren<TMP_Text>(true); foreach (TMP_Text text in componentsInChildren2) { rect = text.rectTransform.rect; Vector2 rectSize2 = ((Rect)(ref rect)).size; ManualLogSource logger2 = ((BaseUnityPlugin)this).Logger; string[] obj2 = new string[10] { "[menu-ui] path=", GetHierarchyPath(text.transform), " activeInHierarchy=", ((Component)text).gameObject.activeInHierarchy.ToString(), " component=", ((object)text).GetType().Name, " text=", text.text, " rectSize=", null }; Vector2 val = rectSize2; obj2[9] = ((object)(*(Vector2*)(&val))/*cast due to .constrained prefix*/).ToString(); logger2.LogInfo((object)string.Concat(obj2)); } } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to log MainMenu UI hierarchy: " + ex)); } } private static string GetHierarchyPath(Transform transform) { string text = ((Object)transform).name; while ((Object)(object)transform.parent != (Object)null) { transform = transform.parent; text = ((Object)transform).name + "/" + text; } return text; } public void RestartKick() { try { StopKickChatClient(); kickChatClient = null; kickChatStopped = false; if (kickEnabled.Value) { StartKickClient(); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to restart Kick chat: " + ex)); } } private void StartKickClient() { if (!long.TryParse(kickChatroomId.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result <= 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Kick chat is enabled, but Kick.ChatroomId is empty or invalid; the connection remains disabled."); return; } kickChatClient = new KickChatClient(result, kickAppKey.Value, kickCluster.Value, (Action<string>)((BaseUnityPlugin)this).Logger.LogInfo); kickChatClient.OnMessage += HandleKickMessage; kickChatStopped = false; kickChatClient.Start(); } private void HandleKickMessage(KickMessage msg) { if (msg != null && msg.MessageType == "message") { EnqueueChat(msg.Username, msg.Content); } } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; StopKickChatClient(); } private void OnApplicationQuit() { StopKickChatClient(); } private void StopKickChatClient() { if (kickChatClient != null && !kickChatStopped) { kickChatStopped = true; kickChatClient.Stop(); } } private void Update() { try { Keyboard current = Keyboard.current; if (enableDebugKeys.Value && current != null) { if (((ButtonControl)current.f9Key).wasPressedThisFrame) { ListEnemies(); } if (((ButtonControl)current.f10Key).wasPressedThisFrame) { string text = TestMessages[testIndex++ % TestMessages.Length]; int num = ++fakeViewer; string text2 = "viewer" + num; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[fake] " + text2 + ": " + text)); EnqueueChat(text2, text); } if (((ButtonControl)current.f11Key).wasPressedThisFrame) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"[fake] spammer: !spawn hoarding bug"); EnqueueChat("spammer", "!spawn hoarding bug"); } } int num2 = 0; ChatMessage result; while (num2 < 3 && Inbox.TryDequeue(out result)) { num2++; Handle(result); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)ex); } } private static string Norm(string s) { return new string((s ?? "").Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray()); } private bool TryResolveEnemyType(string name, out EnemyType type) { string key = Norm(name); if (Aliases.TryGetValue(key, out var value)) { key = value; } return GetEnemyTypes().TryGetValue(key, out type); } private Dictionary<EnemyType, int> ParsePerEnemyLimits() { Dictionary<EnemyType, int> dictionary = new Dictionary<EnemyType, int>(); string[] array = perEnemyLimits.Value.Split(','); foreach (string text in array) { string text2 = text.Trim(); if (text2.Length == 0) { continue; } int num = text2.IndexOf('='); if (num <= 0 || num != text2.LastIndexOf('=')) { LogInvalidPerEnemyLimit(text2); continue; } string text3 = text2.Substring(0, num).Trim(); string s = text2.Substring(num + 1).Trim(); if (text3.Length == 0 || !int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 1 || !TryResolveEnemyType(text3, out var type)) { LogInvalidPerEnemyLimit(text2); } else { dictionary[type] = result; } } return dictionary; } private void LogInvalidPerEnemyLimit(string entry) { if (loggedInvalidPerEnemyLimitEntries.Add(entry)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Ignoring invalid Limits.PerEnemyLimits entry: '" + entry + "'.")); } } private Dictionary<string, EnemyType> GetEnemyTypes() { if (enemyCache != null && enemyCache.Count > 0) { return enemyCache; } Dictionary<string, EnemyType> dictionary = new Dictionary<string, EnemyType>(); StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return dictionary; } SelectableLevel[] levels = instance.levels; foreach (SelectableLevel val in levels) { List<SpawnableEnemyWithRarity>[] array = new List<SpawnableEnemyWithRarity>[3] { val.Enemies, val.DaytimeEnemies, val.OutsideEnemies }; foreach (List<SpawnableEnemyWithRarity> list in array) { foreach (SpawnableEnemyWithRarity item in list) { if (item != null && !((Object)(object)item.enemyType == (Object)null)) { dictionary[Norm(item.enemyType.enemyName)] = item.enemyType; } } } } enemyCache = dictionary; return dictionary; } private void ListEnemies() { Dictionary<string, EnemyType> enemyTypes = GetEnemyTypes(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Found " + enemyTypes.Count + " enemy types:")); foreach (EnemyType value in enemyTypes.Values) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Enemy: " + value.enemyName)); } } private void Reject(string user, string reason) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[rejected] " + user + ": " + reason)); } private void Handle(ChatMessage msg) { //IL_0522: Unknown result type (might be due to invalid IL or missing references) //IL_0527: Unknown result type (might be due to invalid IL or missing references) //IL_052b: Unknown result type (might be due to invalid IL or missing references) //IL_053b: Unknown result type (might be due to invalid IL or missing references) //IL_0540: Unknown result type (might be due to invalid IL or missing references) //IL_0549: Unknown result type (might be due to invalid IL or missing references) //IL_054e: Unknown result type (might be due to invalid IL or missing references) //IL_055a: Unknown result type (might be due to invalid IL or missing references) //IL_0561: Unknown result type (might be due to invalid IL or missing references) //IL_0566: Unknown result type (might be due to invalid IL or missing references) //IL_056b: Unknown result type (might be due to invalid IL or missing references) //IL_056d: Unknown result type (might be due to invalid IL or missing references) //IL_05a1: Unknown result type (might be due to invalid IL or missing references) //IL_05b2: Unknown result type (might be due to invalid IL or missing references) //IL_05b7: Unknown result type (might be due to invalid IL or missing references) //IL_06e9: Unknown result type (might be due to invalid IL or missing references) //IL_06f5: Unknown result type (might be due to invalid IL or missing references) //IL_071a: Unknown result type (might be due to invalid IL or missing references) //IL_072b: Unknown result type (might be due to invalid IL or missing references) string text = (msg.Text ?? "").Trim(); string text2 = prefix.Value.Trim(); if (!text.StartsWith(text2 + " ", StringComparison.OrdinalIgnoreCase)) { return; } string text3 = text.Substring(text2.Length).Trim(); if (text3.Length == 0) { return; } RoundManager instance = RoundManager.Instance; StartOfRound instance2 = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null) { Reject(msg.User, "not in a game"); return; } if (!((NetworkBehaviour)instance).IsServer) { Reject(msg.User, "not the host"); return; } if (!instance2.shipHasLanded) { Reject(msg.User, "ship hasn't landed"); return; } if (!TryResolveEnemyType(text3, out var type)) { Reject(msg.User, "unknown enemy '" + text3 + "'"); return; } HashSet<string> hashSet = new HashSet<string>(blocklist.Value.Split(',').Select(Norm)); if (hashSet.Contains(Norm(type.enemyName))) { Reject(msg.User, type.enemyName + " is blocked"); return; } if (type.isOutsideEnemy && (Object)(object)type.nestSpawnPrefab != (Object)null && type.requireNestObjectsToSpawn && !instance.enemyNestSpawnObjects.Any((EnemyAINestSpawnObject n) => (Object)(object)n != (Object)null && (Object)(object)n.enemyType == (Object)(object)type)) { Reject(msg.User, type.enemyName + " needs a nest that doesn't exist on this moon"); return; } float time = Time.time; if (time - lastGlobal < globalCooldown.Value) { Reject(msg.User, "global cooldown"); return; } if (lastByUser.TryGetValue(msg.User, out var value) && time - value < userCooldown.Value) { Reject(msg.User, "your cooldown: " + Mathf.CeilToInt(userCooldown.Value - (time - value)) + "s left"); return; } int num; if (countOnlyModEnemies.Value) { modSpawnedEnemies.RemoveAll((EnemyAI e) => (Object)(object)e == (Object)null || e.isEnemyDead); num = modSpawnedEnemies.Count; } else { num = instance.SpawnedEnemies.Count((EnemyAI e) => (Object)(object)e != (Object)null && !e.isEnemyDead); } if (num >= maxLive.Value) { Reject(msg.User, "too many enemies alive (" + num + ")"); return; } Dictionary<EnemyType, int> dictionary = ParsePerEnemyLimits(); if (!dictionary.TryGetValue(type, out var value2)) { value2 = defaultPerEnemyMax.Value; } if (value2 > 0) { if (countOnlyModEnemies.Value) { modSpawnedEnemies.RemoveAll((EnemyAI e) => (Object)(object)e == (Object)null || e.isEnemyDead); } List<EnemyAI> source = (countOnlyModEnemies.Value ? modSpawnedEnemies : instance.SpawnedEnemies); int num2 = source.Count((EnemyAI e) => (Object)(object)e != (Object)null && !e.isEnemyDead && (Object)(object)e.enemyType == (Object)(object)type); if (num2 >= value2) { Reject(msg.User, type.enemyName + " limit reached (" + num2 + " alive)"); return; } } bool wantInside = !type.isOutsideEnemy; List<PlayerControllerB> list = instance2.allPlayerScripts.Where((PlayerControllerB p) => (Object)(object)p != (Object)null && p.isPlayerControlled && !p.isPlayerDead && !p.isInHangarShipRoom && p.isInsideFactory == wantInside).ToList(); if (list.Count == 0) { Reject(msg.User, type.enemyName + " needs a player " + (wantInside ? "inside the facility" : "outside")); return; } PlayerControllerB val = list[Random.Range(0, list.Count)]; Vector2 insideUnitCircle = Random.insideUnitCircle; Vector2 val2 = ((Vector2)(ref insideUnitCircle)).normalized * spawnRadius.Value; Vector3 val3 = ((Component)val).transform.position + new Vector3(val2.x, 0f, val2.y); NavMeshHit val4 = default(NavMeshHit); if (!NavMesh.SamplePosition(val3, ref val4, 10f, -1)) { Reject(msg.User, "no walkable spot near target"); return; } NetworkObjectReference val5 = instance.SpawnEnemyGameObject(((NavMeshHit)(ref val4)).position, 0f, -1, type); lastGlobal = time; lastByUser[msg.User] = time; NetworkObject val6 = default(NetworkObject); if (!((NetworkObjectReference)(ref val5)).TryGet(ref val6, (NetworkManager)null)) { ((BaseUnityPlugin)this).Logger.LogError((object)("Could not track spawned " + type.enemyName + " for the live enemy cap.")); return; } EnemyAI component = ((Component)val6).GetComponent<EnemyAI>(); if ((Object)(object)component == (Object)null) { ((BaseUnityPlugin)this).Logger.LogError((object)("Spawned " + type.enemyName + " has no EnemyAI component; it cannot be tracked for the live enemy cap.")); return; } modSpawnedEnemies.Add(component); if (showSpawnTips.Value && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.DisplayTip(msg.User, type.enemyName, false, false, "LC_Tip1"); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("[spawned] " + type.enemyName + " for " + msg.User + " | dist=" + Vector3.Distance(((NavMeshHit)(ref val4)).position, ((Component)val).transform.position).ToString("F1") + " dy=" + (((NavMeshHit)(ref val4)).position.y - ((Component)val).transform.position.y).ToString("F1") + " playerInside=" + val.isInsideFactory)); } } } namespace KickChat { public sealed class KickMessage { public string Username { get; private set; } public string Content { get; private set; } public string MessageType { get; private set; } public string[] BadgeTypes { get; private set; } internal KickMessage(string username, string content, string messageType, string[] badgeTypes) { Username = username; Content = content; MessageType = messageType; BadgeTypes = badgeTypes; } } public sealed class KickChatClient { private readonly long _chatroomId; private readonly string _appKey; private readonly string _cluster; private readonly Action<string> _log; private readonly object _sync = new object(); private CancellationTokenSource _cancellation; private Task _worker; private ClientWebSocket _socket; public event Action<KickMessage> OnMessage; public KickChatClient(long chatroomId, string appKey, string cluster, Action<string> log) { if (string.IsNullOrWhiteSpace(appKey)) { throw new ArgumentException("An app key is required.", "appKey"); } if (string.IsNullOrWhiteSpace(cluster)) { throw new ArgumentException("A cluster is required.", "cluster"); } _chatroomId = chatroomId; _appKey = appKey; _cluster = cluster; _log = log ?? throw new ArgumentNullException("log"); } public void Start() { lock (_sync) { if (_worker == null || _worker.IsCompleted) { _cancellation = new CancellationTokenSource(); CancellationToken token = _cancellation.Token; _worker = Task.Run(() => RunAsync(token)); } } } public void Stop() { ClientWebSocket socket; lock (_sync) { if (_cancellation != null) { _cancellation.Cancel(); } socket = _socket; } if (socket != null && (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseReceived)) { try { socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Client stopping", CancellationToken.None).GetAwaiter().GetResult(); } catch (Exception ex) { Log("Could not send the WebSocket close frame: " + ex.Message); } } } private async Task RunAsync(CancellationToken cancellationToken) { int retryDelaySeconds = 2; try { while (!cancellationToken.IsCancellationRequested) { ClientWebSocket socket = null; try { socket = new ClientWebSocket(); lock (_sync) { _socket = socket; } string endpoint = "wss://ws-" + _cluster + ".pusher.com/app/" + _appKey + "?protocol=7&client=js&version=8.4.0&flash=false"; await socket.ConnectAsync(new Uri(endpoint), cancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (await ReadFramesAsync(socket, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)) { break; } if (!cancellationToken.IsCancellationRequested) { Log("Kick chat disconnected; reconnecting."); } } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { break; } catch (Exception ex2) { Exception exception = ex2; Log("Kick chat connection failed: " + exception.Message); } finally { lock (_sync) { if (_socket == socket) { _socket = null; } } if (socket != null) { try { socket.Dispose(); } catch (Exception ex2) { Exception exception2 = ex2; Log("Could not dispose the WebSocket: " + exception2.Message); } } } if (cancellationToken.IsCancellationRequested) { break; } try { await Task.Delay(TimeSpan.FromSeconds(retryDelaySeconds), cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { break; } retryDelaySeconds = Math.Min(retryDelaySeconds * 2, 60); } } catch (Exception ex2) { Exception exception3 = ex2; Log("Kick chat background loop failed: " + exception3.Message); } } private async Task<bool> ReadFramesAsync(ClientWebSocket socket, CancellationToken cancellationToken) { byte[] buffer = new byte[8192]; using (MemoryStream frame = new MemoryStream()) { while (socket.State == WebSocketState.Open) { WebSocketReceiveResult result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (result.MessageType == WebSocketMessageType.Close) { return false; } frame.Write(buffer, 0, result.Count); if (result.EndOfMessage) { string raw = Encoding.UTF8.GetString(frame.GetBuffer(), 0, (int)frame.Length); frame.SetLength(0L); bool flag = result.MessageType == WebSocketMessageType.Text; bool flag2 = flag; if (flag2) { flag2 = await HandleFrameAsync(socket, raw, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } if (flag2) { return true; } } } } return false; } private async Task<bool> HandleFrameAsync(ClientWebSocket socket, string raw, CancellationToken cancellationToken) { JToken root = JToken.Parse(raw); switch (GetStringProperty(root, "event")) { case "pusher:connection_established": { string subscribe = JsonConvert.SerializeObject((object)new { @event = "pusher:subscribe", data = new { auth = "", channel = "chatrooms." + _chatroomId + ".v2" } }); await SendAsync(socket, subscribe, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); break; } case "pusher:ping": await SendAsync(socket, "{\"event\":\"pusher:pong\",\"data\":{}}", cancellationToken).ConfigureAwait(continueOnCapturedContext: false); break; case "pusher:error": { JToken errorData = ParseDataObject(root); JToken code = GetProperty(errorData, "code"); if (GetStringProperty(errorData, "code") == "4001" || (code != null && (int)code.Type == 6 && ((object)code).ToString() == "4001")) { Log("Kick rejected the app key (it was probably rotated). Update AppKey in the config."); return true; } throw new InvalidOperationException("Pusher reported a connection error."); } case "App\\Events\\ChatMessageEvent": { JToken data = ParseDataObject(root); JToken sender = GetProperty(data, "sender"); JToken identity = GetProperty(sender, "identity"); JToken badges = GetProperty(identity, "badges"); List<string> badgeTypes = new List<string>(); JArray badgeArray = (JArray)(object)((badges is JArray) ? badges : null); if (badgeArray != null) { foreach (JToken badge in badgeArray) { string badgeType = GetStringProperty(badge, "type"); if (badgeType != null) { badgeTypes.Add(badgeType); } } } KickMessage chatMessage = new KickMessage(GetStringProperty(sender, "username") ?? "", GetStringProperty(data, "content") ?? "", GetStringProperty(data, "type") ?? "", badgeTypes.ToArray()); Action<KickMessage> handler = this.OnMessage; if (handler != null) { try { handler(chatMessage); } catch (Exception ex) { Exception exception = ex; Log("Kick chat message handler failed: " + exception.Message); } } break; } } return false; } private static JToken ParseDataObject(JToken root) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 JToken property = GetProperty(root, "data"); if (property != null && (int)property.Type == 8) { string text = Extensions.Value<string>((IEnumerable<JToken>)property); if (text == null) { return null; } return JToken.Parse(text); } return property; } private static JToken GetProperty(JToken element, string name) { JObject val = (JObject)(object)((element is JObject) ? element : null); return (val != null) ? val[name] : null; } private static string GetStringProperty(JToken element, string name) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 JToken property = GetProperty(element, name); return (property != null && (int)property.Type == 8) ? Extensions.Value<string>((IEnumerable<JToken>)property) : null; } private static Task SendAsync(ClientWebSocket socket, string message, CancellationToken cancellationToken) { byte[] bytes = Encoding.UTF8.GetBytes(message); return socket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, endOfMessage: true, cancellationToken); } private void Log(string message) { try { _log(message); } catch { } } } }