using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Discord; using HarmonyLib; using LethalConfig; using LethalConfig.ConfigItems; using LethalLetdownBot.Client; using LethalLetdownBot.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Steamworks; using Steamworks.Data; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("packsolite.lethalletdownbot")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0+bb9a837a2900004f9e5c17e2214be18e77af65d7")] [assembly: AssemblyProduct("LethalLetdownBot")] [assembly: AssemblyTitle("packsolite.lethalletdownbot")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LethalLetdownBot { [BepInPlugin("packsolite.lethalletdownbot", "LethalLetdownBot", "1.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class LethalLetdownBot : BaseUnityPlugin { public static LethalLetdownBot Instance { get; private set; } = null; internal static ManualLogSource Logger { get; private set; } = null; internal static Harmony Harmony { get; set; } = null; internal static string Token { get; set; } = null; internal static string URL { get; set; } = null; internal static bool Enabled { get; set; } = true; private void Awake() { Logger = ((BaseUnityPlugin)this).Logger; Instance = this; LoadConfig(); Token = UserUtil.GetOrCreateUserId(); Patch(); Logger.LogInfo((object)"packsolite.lethalletdownbot v1.1.0 has loaded!"); } internal static void Patch() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown if (Harmony == null) { Harmony = new Harmony("packsolite.lethalletdownbot"); } Harmony.PatchAll(); } internal static void Unpatch() { Harmony harmony = Harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void LoadConfig() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown ConfigEntry enableEntry = ((BaseUnityPlugin)this).Config.Bind("General", "Enable bot", true, "Controls wether the bot announces the lobby on discord. Can be used to quickly toggle the bot while the game is running."); BoolCheckBoxConfigItem val = new BoolCheckBoxConfigItem(enableEntry, false); ConfigEntry endpointEntry = ((BaseUnityPlugin)this).Config.Bind("Configuration", "Endpoint", "http://localhost:8080/", "Endpoint to use for the LethalLetdown-Bot"); TextInputFieldConfigItem val2 = new TextInputFieldConfigItem(endpointEntry, false); endpointEntry.SettingChanged += ApplyConfig; enableEntry.SettingChanged += ApplyConfig; LethalConfigManager.AddConfigItem((BaseConfigItem)(object)val2); LethalConfigManager.AddConfigItem((BaseConfigItem)(object)val); ApplyConfig(null, null); void ApplyConfig(object? obj, EventArgs? args) { URL = endpointEntry.Value.TrimEnd('/') + "/"; Enabled = enableEntry.Value; if (Enabled) { LobbyManager.Instance.CreateLobby(); } else { LobbyManager.Instance.DeleteLobby(); } } } } public class LobbyManager { private static readonly Lazy _instance = new Lazy(() => new LobbyManager()); public static LobbyManager Instance => _instance.Value; private LobbyManager() { } public void CreateLobby() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) StartOfRound instance = StartOfRound.Instance; if (instance == null || !((NetworkBehaviour)instance).IsHost) { return; } string lobbyName = GameNetworkManager.Instance.lobbyHostSettings.lobbyName; GameNetworkManager instance2 = GameNetworkManager.Instance; object obj; if (instance2 == null) { obj = null; } else { Lobby? currentLobby = instance2.currentLobby; if (!currentLobby.HasValue) { obj = null; } else { Lobby valueOrDefault = currentLobby.GetValueOrDefault(); SteamId id = ((Lobby)(ref valueOrDefault)).Id; obj = ((object)(SteamId)(ref id)).ToString(); } } if (obj == null) { obj = string.Empty; } string steamLobbyId = (string)obj; bool isPublic = GameNetworkManager.Instance?.lobbyHostSettings.isLobbyPublic ?? false; LobbyStatus currentLobbyStatus = GetCurrentLobbyStatus(); LobbyOwner currentLobbyOwner = GetCurrentLobbyOwner(); RestClient.Instance.CreateLobby(currentLobbyOwner, currentLobbyStatus, lobbyName, steamLobbyId, isPublic).ContinueWith(delegate(Task t) { if (t.IsFaulted) { LogHelper.Error(t.Exception?.GetBaseException(), "Failed creating lobby"); } }); } public void UpdateLobby() { LobbyStatus status = GetCurrentLobbyStatus(); RestClient.Instance.UpdateLobbyStatus(status).ContinueWith(delegate(Task t) { LogHelper.LogFaulted(t, $"Failed updating lobby status {status}"); }); } public void DeleteLobby() { RestClient.Instance.DestroyLobby().ContinueWith(delegate(Task t) { LogHelper.LogFaulted(t, "Failed deleting lobby"); }); } private static LobbyStatus GetCurrentLobbyStatus() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) StartOfRound instance = StartOfRound.Instance; SelectableLevel currentLevel = instance.currentLevel; CurrentMoon currentMoon = (((Object)(object)currentLevel != (Object)null) ? CurrentMoon.FromSelectableLevel(currentLevel) : null); int connectedPlayers = GameNetworkManager.Instance.connectedPlayers; GameNetworkManager instance2 = GameNetworkManager.Instance; int? obj; if (instance2 == null) { obj = null; } else { Lobby? currentLobby = instance2.currentLobby; if (!currentLobby.HasValue) { obj = null; } else { Lobby valueOrDefault = currentLobby.GetValueOrDefault(); obj = ((Lobby)(ref valueOrDefault)).MaxMembers; } } int? num = obj; int valueOrDefault2 = num.GetValueOrDefault(4); ShipStatus shipStatus = (instance.firingPlayersCutsceneRunning ? ShipStatus.Fired : (instance.shipIsLeaving ? ShipStatus.Leaving : (instance.beganLoadingNewLevel ? ShipStatus.Landing : ((!instance.inShipPhase) ? ((!instance.currentLevel.planetHasTime && instance.currentLevel.riskLevel.Equals("Safe")) ? ShipStatus.Selling : ShipStatus.Landed) : ShipStatus.Orbiting)))); return new LobbyStatus(shipStatus, currentMoon, connectedPlayers, valueOrDefault2); } private static LobbyOwner GetCurrentLobbyOwner() { //IL_0081: Unknown result type (might be due to invalid IL or missing references) string name = GameNetworkManager.Instance.localPlayerController?.playerUsername ?? "unknown"; string id = GameNetworkManager.Instance.localPlayerController?.playerSteamId.ToString() ?? string.Empty; DiscordController instance = DiscordController.Instance; long? obj; if (instance == null) { obj = null; } else { Discord discord = instance.discord; if (discord == null) { obj = null; } else { UserManager userManager = discord.GetUserManager(); obj = ((userManager != null) ? new long?(userManager.GetCurrentUser().Id) : null); } } long? num = obj; long valueOrDefault = num.GetValueOrDefault(); return new LobbyOwner(name, id, valueOrDefault); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "packsolite.lethalletdownbot"; public const string PLUGIN_NAME = "LethalLetdownBot"; public const string PLUGIN_VERSION = "1.1.0"; } } namespace LethalLetdownBot.Utils { internal static class LogHelper { public static void Info(string message) { LethalLetdownBot.Logger.LogInfo((object)message); } public static void Warn(string message) { LethalLetdownBot.Logger.LogWarning((object)message); } public static void Error(string message) { LethalLetdownBot.Logger.LogError((object)message); } public static void Error(Exception ex, string context = "Unhandled exception") { string text = ex?.GetBaseException().Message ?? "Unknown error"; LethalLetdownBot.Logger.LogError((object)(context + ": " + text)); } public static void LogFaulted(Task task, string context) { if (task.IsFaulted) { string text = task.Exception?.GetBaseException().Message ?? "Unknown error"; LethalLetdownBot.Logger.LogError((object)(context + ": " + text)); } } } public static class UserUtil { private static readonly string FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "LethalLetdown", "user-secret.txt"); public static string GetOrCreateUserId() { if (File.Exists(FilePath)) { return File.ReadAllText(FilePath).Trim(); } string text = Guid.NewGuid().ToString(); Directory.CreateDirectory(Path.GetDirectoryName(FilePath)); File.WriteAllText(FilePath, text); return text; } } } namespace LethalLetdownBot.Patches { [HarmonyPatch(typeof(StartOfRound))] public class StartOfRoundPatch { [CompilerGenerated] private sealed class d__10 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__10(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } if ((Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null) { <>2__current = (object)new WaitForSeconds(1f); <>1__state = 1; return true; } LobbyManager.Instance.CreateLobby(); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [HarmonyPatch("Start")] [HarmonyPrefix] private static void OnCreateLobby(StartOfRound __instance) { if (((NetworkBehaviour)__instance).IsHost && LethalLetdownBot.Enabled) { ((MonoBehaviour)__instance).StartCoroutine(CreateLobbyDelayed(__instance)); } } [HarmonyPatch("OnLocalDisconnect")] [HarmonyPrefix] private static void OnDestroyLobby(StartOfRound __instance) { if (((NetworkBehaviour)__instance).IsHost && LethalLetdownBot.Enabled) { LobbyManager.Instance.DeleteLobby(); } } [HarmonyPatch("StartGame")] [HarmonyPostfix] private static void OnStartGame(StartOfRound __instance) { UpdateStatus(__instance); } [HarmonyPatch("OnShipLandedMiscEvents")] [HarmonyPostfix] private static void OnShipLanded(StartOfRound __instance) { UpdateStatus(__instance); } [HarmonyPatch("ShipLeave")] [HarmonyPostfix] private static void OnShipLeave(StartOfRound __instance) { UpdateStatus(__instance); } [HarmonyPatch("SetShipReadyToLand")] [HarmonyPostfix] private static void OnUpdateDiscordStatus(StartOfRound __instance) { UpdateStatus(__instance); } [HarmonyPatch("FirePlayersAfterDeadlineClientRpc")] [HarmonyPostfix] private static void OnPlayersFired(StartOfRound __instance) { UpdateStatus(__instance); } [HarmonyPatch("OnClientConnect")] [HarmonyPostfix] private static void OnClientConnected(StartOfRound __instance) { UpdateStatus(__instance); } [HarmonyPatch("OnClientDisconnect")] [HarmonyPostfix] private static void OnClientDisconnected(StartOfRound __instance) { UpdateStatus(__instance); } private static void UpdateStatus(StartOfRound startOfRound) { LobbyManager.Instance.UpdateLobby(); } [IteratorStateMachine(typeof(d__10))] private static IEnumerator CreateLobbyDelayed(StartOfRound __instance) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__10(0); } } } namespace LethalLetdownBot.Client { public enum ShipStatus { Orbiting, Landing, Landed, Leaving, Selling, Fired } public class CurrentMoon { public string Name { get; set; } public string PlanetName { get; set; } public string CurrentWeather { get; set; } public string RiskLevel { get; set; } public CurrentMoon() { } public CurrentMoon(string name, string planetName, string currentWeather, string riskLevel) { Name = name; PlanetName = planetName; CurrentWeather = currentWeather; RiskLevel = riskLevel; } public static CurrentMoon FromSelectableLevel(SelectableLevel level) { return new CurrentMoon(((Object)level).name, level.PlanetName, ((object)(LevelWeatherType)(ref level.currentWeather)).ToString(), level.riskLevel); } } public class LobbyStatus { public ShipStatus ShipStatus { get; set; } public CurrentMoon? CurrentMoon { get; set; } public int PlayerCount { get; set; } public int MaxPlayerCount { get; set; } public LobbyStatus() { } public LobbyStatus(ShipStatus shipStatus, CurrentMoon currentMoon, int playerCount, int maxPlayerCount) { ShipStatus = shipStatus; CurrentMoon = currentMoon; PlayerCount = playerCount; MaxPlayerCount = maxPlayerCount; } } public class LobbyOwner { public string Name { get; set; } public string ID { get; set; } public long DiscordId { get; set; } public LobbyOwner() { } public LobbyOwner(string name, string id, long discordId) { Name = name; ID = id; DiscordId = discordId; } } public class Lobby { public LobbyStatus Status { get; set; } public LobbyOwner Owner { get; set; } } internal class LobbyStatusUpdateRequest { public LobbyStatus Status { get; set; } } internal class LobbyCreateRequest { public string Token { get; set; } public string Name { get; set; } public string SteamLobby { get; set; } public LobbyOwner Owner { get; set; } public LobbyStatus Status { get; set; } public bool IsPublic { get; set; } } internal class LobbyCreateResponse { public bool Successful { get; set; } public string LobbyId { get; set; } } public class RestClient { private static readonly Lazy _instance = new Lazy(() => new RestClient()); private readonly HttpClient _httpClient = new HttpClient(); private string _lobbyId; public static RestClient Instance => _instance.Value; private RestClient() { StartHeartbeatTimer(); } private void StartHeartbeatTimer() { Timer timer = new Timer(delegate { SendHeartbeat().ContinueWith(delegate(Task t) { LogHelper.LogFaulted(t, "Failed sending heartbeat"); }); }, null, TimeSpan.Zero, TimeSpan.FromMinutes(1.0)); } public async Task SendHeartbeat() { if (_lobbyId != null) { await _httpClient.PostAsync(LethalLetdownBot.URL + "lobby/" + _lobbyId + "/heartbeat", new ByteArrayContent(new byte[0])); } } public async Task CreateLobby(LobbyOwner owner, LobbyStatus status, string lobbyName, string steamLobbyId, bool isPublic) { LobbyCreateRequest lobbyCreateRequest = new LobbyCreateRequest { Token = LethalLetdownBot.Token, Name = lobbyName, SteamLobby = steamLobbyId, Owner = owner, Status = status, IsPublic = isPublic }; StringContent content = new StringContent(JsonConvert.SerializeObject((object)lobbyCreateRequest), Encoding.UTF8, "application/json"); HttpResponseMessage httpResponseMessage = await _httpClient.PostAsync(LethalLetdownBot.URL + "lobby", content); if (!httpResponseMessage.IsSuccessStatusCode) { object arg = httpResponseMessage.StatusCode; LogHelper.Error($"Failed to post: {arg} - {await httpResponseMessage.Content.ReadAsStringAsync()}"); return; } string text = await httpResponseMessage.Content.ReadAsStringAsync(); try { LobbyCreateResponse lobbyCreateResponse = JsonConvert.DeserializeObject(text); if (lobbyCreateResponse == null || !lobbyCreateResponse.Successful) { LogHelper.Error("Failed to create lobby: " + (lobbyCreateResponse?.LobbyId ?? ("invalid response: " + text))); return; } _lobbyId = lobbyCreateResponse.LobbyId; LethalLetdownBot.Logger.LogInfo((object)("Successfully created lobby with id '" + _lobbyId + "'")); } catch (Exception ex) { LogHelper.Error(ex, "Failed deserializing response"); } } public async Task DestroyLobby() { if (_lobbyId == null) { LogHelper.Error("Could not delete lobby: _lobbyId is null"); return; } await _httpClient.DeleteAsync(LethalLetdownBot.URL + "lobby/" + _lobbyId); LethalLetdownBot.Logger.LogInfo((object)("Successfully deleted lobby '" + _lobbyId + "'")); _lobbyId = null; } public async Task UpdateLobbyStatus(LobbyStatus status) { if (_lobbyId == null) { LogHelper.Error("Could not update lobby status: _lobbyId is null"); return; } StringContent content = new StringContent(JsonConvert.SerializeObject((object)new LobbyStatusUpdateRequest { Status = status }), Encoding.UTF8, "application/json"); HttpResponseMessage httpResponseMessage = await _httpClient.PutAsync(LethalLetdownBot.URL + "lobby/" + _lobbyId + "/status", content); if (httpResponseMessage.IsSuccessStatusCode) { LogHelper.Info($"Successfully updated lobby (status={status.ShipStatus}, players={status.PlayerCount})"); return; } object arg = httpResponseMessage.StatusCode; LogHelper.Error($"Failed to post: {arg} - {await httpResponseMessage.Content.ReadAsStringAsync()}"); } } }