using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HG; using Microsoft.CodeAnalysis; using RoR2; using RoR2.Stats; using UnityEngine; using UnityEngine.Networking; [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("GsStatsEmitter")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.8.1.0")] [assembly: AssemblyInformationalVersion("0.8.1+08294f4a1aab1e5fe5cdfc4482b23ad7cd703bba")] [assembly: AssemblyProduct("GsStatsEmitter")] [assembly: AssemblyTitle("GsStatsEmitter")] [assembly: AssemblyVersion("0.8.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace GsStatsEmitter { [BepInPlugin("net.cproudlock.gsstatsemitter", "GS Stats Emitter", "0.10.0")] public class Plugin : BaseUnityPlugin { private class StageEntry { public int index; public string scene; public double startedAtSec; public double endedAtSec; public Dictionary> playerDeltas = new Dictionary>(); public Dictionary> playerItemPicks = new Dictionary>(); } public const string GUID = "net.cproudlock.gsstatsemitter"; public const string NAME = "GS Stats Emitter"; public const string VERSION = "0.10.0"; private readonly Dictionary> finalReportStatsByName = new Dictionary>(); private readonly Dictionary> finalReportBossKillsByName = new Dictionary>(); private readonly Dictionary> lastBodySnapshot = new Dictionary>(); private const float HeartbeatIntervalSec = 30f; private float lastHeartbeatTime; private static readonly string[] StatNames = new string[29] { "totalKills", "totalEliteKills", "totalDeaths", "totalDamageDealt", "totalDamageTaken", "totalHealthHealed", "totalGoldCollected", "totalItemsCollected", "highestLevel", "totalDistanceTraveled", "totalTimeAlive", "totalStagesCompleted", "totalPurchases", "totalMinionDamageDealt", "totalMinionKills", "totalDamageBlocked", "totalGoldPurchases", "totalTeleporterBossKillsWitnessed", "highestDamageDealt", "highestCollected", "highestPurchases", "highestGoldPurchases", "highestLunarPurchases", "highestBloodPurchases", "highestStagesCompleted", "highestItemsCollected", "highestTier1Purchases", "highestTier2Purchases", "highestTier3Purchases" }; private static readonly string[] StagedStatNames = new string[8] { "totalKills", "totalEliteKills", "totalDeaths", "totalDamageDealt", "totalDamageTaken", "totalHealthHealed", "totalGoldCollected", "totalItemsCollected" }; private static readonly string[] BossBodies = new string[26] { "TitanGoldBody", "TitanBlackBody", "TitanBody", "BeetleQueen2Body", "ClayBossBody", "VagrantBody", "GravekeeperBody", "RoboBallBossBody", "MagmaWormBody", "ElectricWormBody", "ImpBossBody", "GrandparentBody", "MithrixBody", "BrotherBody", "BrotherHurtBody", "VoidRaidCrabBody", "VoidRaidCrabJointBody", "ScavLunar1Body", "ScavLunar2Body", "ScavLunar3Body", "ScavLunar4Body", "ArtifactShellBody", "FalseSonBoss1Body", "FalseSonBoss2Body", "FalseSonBossLightningBody", "HalcyoniteBody" }; internal static ManualLogSource Log; private static readonly HttpClient http = new HttpClient { Timeout = TimeSpan.FromSeconds(5.0) }; private ConfigEntry ingestUrlCfg; private ConfigEntry ingestTokenCfg; private DateTime runStartUtc; private string runIdLocal; private readonly List stages = new List(); private StageEntry currentStage; private readonly Dictionary> lastSnapshot = new Dictionary>(); private readonly Dictionary> lastItemSnapshot = new Dictionary>(); private readonly Dictionary lastAttackerByPlayer = new Dictionary(); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; ingestUrlCfg = ((BaseUnityPlugin)this).Config.Bind("Ingest", "Url", "http://localhost:3001/api/ingest", "Endpoint to POST run summaries to. Set this if the gs dashboard runs on a different host/port."); ingestTokenCfg = ((BaseUnityPlugin)this).Config.Bind("Ingest", "Token", "", "Bearer token required by the gs dashboard. Get this from the dashboard owner."); Run.onRunStartGlobal += OnRunStart; Run.onRunDestroyGlobal += OnRunDestroy; Run.onClientGameOverGlobal += OnClientGameOver; Stage.onServerStageBegin += OnServerStageBegin; GlobalEventManager.onCharacterDeathGlobal += OnCharacterDeath; } private void Update() { if (Time.unscaledTime - lastHeartbeatTime < 30f) { return; } lastHeartbeatTime = Time.unscaledTime; try { if (NetworkServer.active) { Run instance = Run.instance; if (!((Object)(object)instance == (Object)null)) { SendHeartbeat(instance); } } } catch (Exception ex) { Log.LogWarning((object)("[gs] heartbeat: " + ex.Message)); } } private void SendHeartbeat(Run run) { //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(512); stringBuilder.Append('{'); J(stringBuilder, "schemaVersion", 1); C(stringBuilder); J(stringBuilder, "game", "ror2"); C(stringBuilder); J(stringBuilder, "runIdLocal", runIdLocal ?? ""); C(stringBuilder); string val = ""; string val2 = ""; try { foreach (PlayerCharacterMasterController instance in PlayerCharacterMasterController.instances) { if (!((Object)(object)((instance != null) ? instance.networkUser : null) == (Object)null) && ((NetworkBehaviour)instance.networkUser).isLocalPlayer) { val = instance.GetDisplayName() ?? ""; CharacterMaster master = instance.master; object obj; if (master == null) { obj = null; } else { GameObject bodyPrefab = master.bodyPrefab; obj = ((bodyPrefab != null) ? bodyPrefab.GetComponent() : null); } CharacterBody val3 = (CharacterBody)obj; if ((Object)(object)val3 != (Object)null) { val2 = val3.baseNameToken ?? ""; } break; } } } catch { } J(stringBuilder, "hostName", val); C(stringBuilder); J(stringBuilder, "character", val2); C(stringBuilder); DifficultyIndex selectedDifficulty = run.selectedDifficulty; J(stringBuilder, "difficulty", ((object)(DifficultyIndex)(ref selectedDifficulty)).ToString()); C(stringBuilder); string val4 = "unknown"; try { SceneDef sceneDefForCurrentScene = SceneCatalog.GetSceneDefForCurrentScene(); val4 = ((sceneDefForCurrentScene != null) ? sceneDefForCurrentScene.cachedName : null) ?? "unknown"; } catch { } J(stringBuilder, "scene", val4); C(stringBuilder); J(stringBuilder, "startedAtUtc", runStartUtc.ToString("O")); C(stringBuilder); J(stringBuilder, "elapsedSec", Mathf.RoundToInt(run.GetRunStopwatch())); C(stringBuilder); J(stringBuilder, "stageClearCount", run.stageClearCount); C(stringBuilder); J(stringBuilder, "eclipseLevel", GetEclipseLevel(run)); stringBuilder.Append('}'); try { StringContent content = new StringContent(stringBuilder.ToString(), Encoding.UTF8, "application/json"); string requestUri = ingestUrlCfg.Value.Replace("/api/ingest", "/api/heartbeat"); HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri) { Content = content }; if (!string.IsNullOrEmpty(ingestTokenCfg.Value)) { httpRequestMessage.Headers.TryAddWithoutValidation("Authorization", "Bearer " + ingestTokenCfg.Value); } http.SendAsync(httpRequestMessage).ContinueWith(delegate(Task t) { if (t.IsFaulted) { Log.LogWarning((object)("[gs] heartbeat send: " + t.Exception?.GetBaseException().Message)); } }); } catch (Exception ex) { Log.LogWarning((object)("[gs] heartbeat post: " + ex.Message)); } Log.LogInfo((object)("GS Stats Emitter 0.10.0 loaded, posting to " + ingestUrlCfg.Value + " (token " + (string.IsNullOrEmpty(ingestTokenCfg.Value) ? "MISSING" : "set") + ")")); } private void OnRunStart(Run run) { runStartUtc = DateTime.UtcNow; runIdLocal = Guid.NewGuid().ToString("N"); stages.Clear(); currentStage = null; lastSnapshot.Clear(); lastItemSnapshot.Clear(); lastAttackerByPlayer.Clear(); lastBodySnapshot.Clear(); finalReportStatsByName.Clear(); finalReportBossKillsByName.Clear(); Log.LogInfo((object)$"[gs] run started, seed={run.seed}, host={NetworkServer.active}, runId={runIdLocal}"); } private void OnClientGameOver(Run run, RunReport report) { if (report == null) { Log.LogInfo((object)"[gs] game over: null report"); return; } try { int playerInfoCount = report.playerInfoCount; Log.LogInfo((object)$"[gs] game over, report has {playerInfoCount} player(s)"); List allStatDefs = StatDef.allStatDefs; for (int i = 0; i < playerInfoCount; i++) { PlayerInfo playerInfo = report.GetPlayerInfo(i); if (playerInfo == null) { continue; } string text = SafeReportName(playerInfo); StatSheet val = TryGetStatSheet(playerInfo); if (val == null) { Log.LogInfo((object)("[gs] '" + text + "': no statSheet")); continue; } Dictionary dictionary = new Dictionary(); string[] statNames = StatNames; foreach (string text2 in statNames) { StatDef val2 = null; for (int k = 0; k < allStatDefs.Count; k++) { if (allStatDefs[k] != null && allStatDefs[k].name == text2) { val2 = allStatDefs[k]; break; } } if (val2 != null) { double num = ReadStat(val, val2); if (num > 0.0) { dictionary[text2] = num; } } } finalReportStatsByName[text] = dictionary; Dictionary dictionary2 = new Dictionary(); statNames = BossBodies; foreach (string text3 in statNames) { string text4 = "killsAgainst." + text3; StatDef val3 = null; for (int l = 0; l < allStatDefs.Count; l++) { if (allStatDefs[l] != null && allStatDefs[l].name == text4) { val3 = allStatDefs[l]; break; } } if (val3 != null) { ulong num2 = (ulong)ReadStat(val, val3); if (num2 != 0) { dictionary2[text3] = num2; } } } finalReportBossKillsByName[text] = dictionary2; Log.LogInfo((object)$"[gs] '{text}': {dictionary.Count} stats, {dictionary2.Count} boss-kill entries"); } } catch (Exception arg) { Log.LogError((object)$"[gs] game over capture: {arg}"); } } private static string SafeReportName(PlayerInfo pi) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) try { if (!string.IsNullOrEmpty(pi.name)) { return pi.name; } } catch { } try { NetworkUser networkUser = pi.networkUser; if ((Object)(object)networkUser != (Object)null) { try { string userName = networkUser.userName; if (!string.IsNullOrEmpty(userName)) { return userName; } } catch { } try { NetworkPlayerName networkPlayerName = networkUser.GetNetworkPlayerName(); string resolvedName = ((NetworkPlayerName)(ref networkPlayerName)).GetResolvedName(); if (!string.IsNullOrEmpty(resolvedName)) { return resolvedName; } } catch { } } } catch { } return "unknown"; } private static StatSheet TryGetStatSheet(PlayerInfo pi) { try { return pi.statSheet; } catch { return null; } } private static double ReadStat(StatSheet sheet, StatDef def) { if (sheet == null || def == null) { return 0.0; } try { ulong statValueULong = sheet.GetStatValueULong(def); if (statValueULong != 0L) { return statValueULong; } } catch { } try { return sheet.GetStatValueDouble(def); } catch { return 0.0; } } private void SnapshotBody(string playerName, CharacterBody body) { Dictionary snap; if (!((Object)(object)body == (Object)null) && !string.IsNullOrEmpty(playerName)) { snap = new Dictionary(); Try("level", () => body.level); Try("maxHealth", () => body.maxHealth); Try("maxShield", () => body.maxShield); Try("regen", () => body.regen); Try("damage", () => body.damage); Try("baseDamage", () => body.baseDamage); Try("attackSpeed", () => body.attackSpeed); Try("moveSpeed", () => body.moveSpeed); Try("jumpPower", () => body.jumpPower); Try("crit", () => body.crit); Try("armor", () => body.armor); Try("maxJumpCount", () => body.maxJumpCount); lastBodySnapshot[playerName] = snap; } void Try(string k, Func read) { try { snap[k] = read(); } catch { } } } private void OnCharacterDeath(DamageReport report) { if (report == null || (Object)(object)report.victim == (Object)null) { return; } try { CharacterMaster victimMaster = report.victimMaster; if ((Object)(object)victimMaster == (Object)null) { return; } PlayerCharacterMasterController playerCharacterMasterController = victimMaster.playerCharacterMasterController; if ((Object)(object)playerCharacterMasterController == (Object)null) { return; } string key = SafePlayerName(playerCharacterMasterController); string value = "environment"; try { if ((Object)(object)report.attackerBody != (Object)null) { value = report.attackerBody.baseNameToken ?? ((Object)report.attackerBody).name ?? "unknown"; } else if (report.damageInfo != null && (Object)(object)report.damageInfo.attacker != (Object)null) { value = report.damageInfo.attacker.GetComponent()?.baseNameToken ?? "unknown"; } } catch { } lastAttackerByPlayer[key] = value; } catch (Exception ex) { Log.LogWarning((object)("[gs] death track: " + ex.Message)); } } private void OnServerStageBegin(Stage stage) { if (!NetworkServer.active) { return; } Run instance = Run.instance; if (!((Object)(object)instance == (Object)null)) { double num = 0.0; try { num = instance.GetRunStopwatch(); } catch { } string text = "unknown"; try { SceneDef sceneDefForCurrentScene = SceneCatalog.GetSceneDefForCurrentScene(); text = ((sceneDefForCurrentScene != null) ? sceneDefForCurrentScene.cachedName : null) ?? "unknown"; } catch { } if (currentStage != null) { currentStage.endedAtSec = num; FillStageDeltas(currentStage); stages.Add(currentStage); } currentStage = new StageEntry { index = stages.Count, scene = text, startedAtSec = num }; SnapshotAllPlayers(); Log.LogInfo((object)$"[gs] stage {currentStage.index} ({text}) at {num:0.0}s"); } } private void SnapshotAllPlayers() { //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Invalid comparison between Unknown and I4 //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) lastSnapshot.Clear(); lastItemSnapshot.Clear(); ReadOnlyCollection instances = PlayerCharacterMasterController.instances; if (instances == null) { return; } List allStatDefs = StatDef.allStatDefs; foreach (PlayerCharacterMasterController item in instances) { if ((Object)(object)((item != null) ? item.master : null) == (Object)null) { continue; } string text = SafePlayerName(item); try { SnapshotBody(text, item.master.GetBody()); } catch { } StatSheet val = item.master.playerStatsComponent?.currentStats; if (val != null) { Dictionary dictionary = new Dictionary(); string[] stagedStatNames = StagedStatNames; foreach (string text2 in stagedStatNames) { StatDef val2 = null; for (int j = 0; j < allStatDefs.Count; j++) { if (allStatDefs[j] != null && allStatDefs[j].name == text2) { val2 = allStatDefs[j]; break; } } if (val2 != null) { dictionary[text2] = ReadStat(val, val2); } } lastSnapshot[text] = dictionary; } Inventory inventory = item.master.inventory; if (!((Object)(object)inventory != (Object)null)) { continue; } Dictionary dictionary2 = new Dictionary(); try { Enumerator enumerator2 = ItemCatalog.allItemDefs.GetEnumerator(); try { while (enumerator2.MoveNext()) { ItemDef current2 = enumerator2.Current; if ((Object)(object)current2 == (Object)null) { continue; } bool flag = false; try { flag = current2.hidden; } catch { } if (flag) { continue; } bool flag2 = false; try { flag2 = (int)current2.tier == 5; } catch { } if (!flag2) { int itemCountPermanent = inventory.GetItemCountPermanent(current2.itemIndex); if (itemCountPermanent > 0) { dictionary2[((Object)current2).name] = itemCountPermanent; } } } } finally { ((IDisposable)enumerator2).Dispose(); } } catch { } lastItemSnapshot[text] = dictionary2; } } private void FillStageDeltas(StageEntry stage) { //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Invalid comparison between Unknown and I4 //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) ReadOnlyCollection instances = PlayerCharacterMasterController.instances; if (instances == null) { return; } List allStatDefs = StatDef.allStatDefs; foreach (PlayerCharacterMasterController item in instances) { if ((Object)(object)((item != null) ? item.master : null) == (Object)null) { continue; } string key = SafePlayerName(item); StatSheet val = item.master.playerStatsComponent?.currentStats; if (val != null) { lastSnapshot.TryGetValue(key, out var value); Dictionary dictionary = new Dictionary(); string[] stagedStatNames = StagedStatNames; foreach (string text in stagedStatNames) { StatDef val2 = null; for (int j = 0; j < allStatDefs.Count; j++) { if (allStatDefs[j] != null && allStatDefs[j].name == text) { val2 = allStatDefs[j]; break; } } if (val2 != null) { double num = ReadStat(val, val2); double value2; double num2 = ((value != null && value.TryGetValue(text, out value2)) ? value2 : 0.0); double num3 = num - num2; if (num3 > 0.0) { dictionary[text] = num3; } } } stage.playerDeltas[key] = dictionary; } Inventory inventory = item.master.inventory; if (!((Object)(object)inventory != (Object)null)) { continue; } lastItemSnapshot.TryGetValue(key, out var value3); Dictionary dictionary2 = new Dictionary(); try { Enumerator enumerator2 = ItemCatalog.allItemDefs.GetEnumerator(); try { while (enumerator2.MoveNext()) { ItemDef current2 = enumerator2.Current; if ((Object)(object)current2 == (Object)null) { continue; } bool flag = false; try { flag = current2.hidden; } catch { } if (flag) { continue; } bool flag2 = false; try { flag2 = (int)current2.tier == 5; } catch { } if (!flag2) { int itemCountPermanent = inventory.GetItemCountPermanent(current2.itemIndex); int value4; int num4 = ((value3 != null && value3.TryGetValue(((Object)current2).name, out value4)) ? value4 : 0); int num5 = itemCountPermanent - num4; if (num5 > 0) { dictionary2[((Object)current2).name] = num5; } } } } finally { ((IDisposable)enumerator2).Dispose(); } } catch { } if (dictionary2.Count > 0) { stage.playerItemPicks[key] = dictionary2; } } } private static string SafePlayerName(PlayerCharacterMasterController pcmc) { try { string displayName = pcmc.GetDisplayName(); if (!string.IsNullOrEmpty(displayName)) { return displayName; } } catch { } try { return ((Object)pcmc.master).name ?? "unknown"; } catch { } return "unknown"; } private void OnRunDestroy(Run run) { if ((Object)(object)run == (Object)null) { return; } if (!NetworkServer.active) { Log.LogInfo((object)"[gs] not host, skipping emit"); return; } try { if (currentStage != null) { double endedAtSec = 0.0; try { endedAtSec = run.GetRunStopwatch(); } catch { } currentStage.endedAtSec = endedAtSec; FillStageDeltas(currentStage); stages.Add(currentStage); currentStage = null; } string text = BuildJson(run); Log.LogInfo((object)$"[gs] emit ({text.Length} bytes, {stages.Count} stages)"); StringContent content = new StringContent(text, Encoding.UTF8, "application/json"); HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, ingestUrlCfg.Value) { Content = content }; if (!string.IsNullOrEmpty(ingestTokenCfg.Value)) { httpRequestMessage.Headers.TryAddWithoutValidation("Authorization", "Bearer " + ingestTokenCfg.Value); } http.SendAsync(httpRequestMessage).ContinueWith(delegate(Task t) { if (t.IsFaulted) { Log.LogError((object)("[gs] ingest failed: " + t.Exception?.GetBaseException().Message)); } else { Log.LogInfo((object)$"[gs] ingest status: {(int)t.Result.StatusCode}"); } }); } catch (Exception arg) { Log.LogError((object)$"[gs] emit error: {arg}"); } } private string BuildJson(Run run) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(2048); stringBuilder.Append('{'); J(stringBuilder, "schemaVersion", 1); C(stringBuilder); J(stringBuilder, "game", "ror2"); C(stringBuilder); J(stringBuilder, "runIdLocal", runIdLocal); C(stringBuilder); J(stringBuilder, "seed", run.seed.ToString()); C(stringBuilder); DifficultyIndex selectedDifficulty = run.selectedDifficulty; J(stringBuilder, "difficulty", ((object)(DifficultyIndex)(ref selectedDifficulty)).ToString()); C(stringBuilder); J(stringBuilder, "stageClearCount", run.stageClearCount); C(stringBuilder); J(stringBuilder, "runTimeSeconds", Mathf.RoundToInt(run.GetRunStopwatch())); C(stringBuilder); J(stringBuilder, "startedAtUtc", runStartUtc.ToString("O")); C(stringBuilder); J(stringBuilder, "endedAtUtc", DateTime.UtcNow.ToString("O")); C(stringBuilder); string val = "unknown"; try { SceneDef sceneDefForCurrentScene = SceneCatalog.GetSceneDefForCurrentScene(); val = ((sceneDefForCurrentScene != null) ? sceneDefForCurrentScene.cachedName : null) ?? "unknown"; } catch { } J(stringBuilder, "endedOnScene", val); C(stringBuilder); J(stringBuilder, "eclipseLevel", GetEclipseLevel(run)); C(stringBuilder); stringBuilder.Append("\"artifacts\":["); AppendArtifacts(stringBuilder); stringBuilder.Append("],"); stringBuilder.Append("\"itemTiers\":{"); AppendItemTiers(stringBuilder); stringBuilder.Append("},"); stringBuilder.Append("\"mods\":["); AppendMods(stringBuilder); stringBuilder.Append("],"); stringBuilder.Append("\"stages\":["); AppendStages(stringBuilder); stringBuilder.Append("],"); stringBuilder.Append("\"players\":["); bool flag = true; ReadOnlyCollection instances = PlayerCharacterMasterController.instances; if (instances != null) { foreach (PlayerCharacterMasterController item in instances) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item.master == (Object)null)) { if (!flag) { stringBuilder.Append(','); } flag = false; AppendPlayer(stringBuilder, item); } } } stringBuilder.Append(']'); stringBuilder.Append('}'); return stringBuilder.ToString(); } private void AppendPlayer(StringBuilder sb, PlayerCharacterMasterController pcmc) { //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Invalid comparison between Unknown and I4 //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) string text = "unknown"; try { text = pcmc.GetDisplayName() ?? ((Object)pcmc.master).name; } catch { try { text = ((Object)pcmc.master).name; } catch { } } string val = "unknown"; try { string text2 = ((!((Object)(object)pcmc.master.bodyPrefab != (Object)null)) ? null : pcmc.master.bodyPrefab.GetComponent()?.baseNameToken); if (!string.IsNullOrEmpty(text2)) { val = text2; } } catch { } bool flag = false; try { flag = pcmc.master.IsDeadAndOutOfLivesServer(); } catch { } bool val2 = false; try { val2 = (Object)(object)pcmc.networkUser != (Object)null && ((NetworkBehaviour)pcmc.networkUser).isLocalPlayer; } catch { } string val3 = null; if (flag && lastAttackerByPlayer.TryGetValue(text, out var value)) { val3 = value; } sb.Append('{'); J(sb, "name", text); C(sb); J(sb, "character", val); C(sb); J(sb, "dead", flag); C(sb); J(sb, "isHost", val2); C(sb); J(sb, "killedBy", val3); C(sb); sb.Append("\"items\":{"); bool flag2 = true; try { Inventory inventory = pcmc.master.inventory; if ((Object)(object)inventory != (Object)null) { Enumerator enumerator = ItemCatalog.allItemDefs.GetEnumerator(); try { while (enumerator.MoveNext()) { ItemDef current = enumerator.Current; if ((Object)(object)current == (Object)null) { continue; } bool flag3 = false; try { flag3 = current.hidden; } catch { } if (flag3) { continue; } bool flag4 = false; try { flag4 = (int)current.tier == 5; } catch { } if (flag4) { continue; } int itemCountPermanent = inventory.GetItemCountPermanent(current.itemIndex); if (itemCountPermanent > 0) { if (!flag2) { sb.Append(','); } flag2 = false; sb.Append('"').Append(Escape(((Object)current).name)).Append("\":") .Append(itemCountPermanent); } } } finally { ((IDisposable)enumerator).Dispose(); } } } catch (Exception ex) { Log.LogWarning((object)("[gs] item enum: " + ex.Message)); } sb.Append('}'); AppendPlayerStats(sb, pcmc); AppendBossKills(sb, pcmc); AppendBodySnapshot(sb, pcmc); AppendLoadout(sb, pcmc); sb.Append('}'); } private void AppendLoadout(StringBuilder sb, PlayerCharacterMasterController pcmc) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Invalid comparison between Unknown and I4 //IL_004d: Unknown result type (might be due to invalid IL or missing references) CharacterBody val = null; try { CharacterMaster master = pcmc.master; val = ((master != null) ? master.GetBody() : null); } catch { } string text = null; try { CharacterMaster master2 = pcmc.master; Inventory val2 = ((master2 != null) ? master2.inventory : null); if ((Object)(object)val2 != (Object)null) { EquipmentIndex equipmentIndex = val2.GetEquipmentIndex(); if ((int)equipmentIndex != -1) { EquipmentDef equipmentDef = EquipmentCatalog.GetEquipmentDef(equipmentIndex); if ((Object)(object)equipmentDef != (Object)null) { text = ((Object)equipmentDef).name; } } } } catch { } sb.Append(",\"equipment\":"); if (text != null) { sb.Append('"').Append(Escape(text)).Append('"'); } else { sb.Append("null"); } sb.Append(",\"skills\":{"); try { SkillLocator val3 = ((val != null) ? val.skillLocator : null); bool firstSlot; if ((Object)(object)val3 != (Object)null) { firstSlot = true; EmitSlot("primary", val3.primary); EmitSlot("secondary", val3.secondary); EmitSlot("utility", val3.utility); EmitSlot("special", val3.special); } void EmitSlot(string slot, GenericSkill gs) { if (!((Object)(object)gs == (Object)null)) { string text2 = null; try { text2 = gs.skillDef?.skillName ?? gs.skillDef?.skillNameToken; } catch { } if (!string.IsNullOrEmpty(text2)) { if (!firstSlot) { sb.Append(','); } firstSlot = false; sb.Append('"').Append(slot).Append("\":\"") .Append(Escape(text2)) .Append('"'); } } } } catch (Exception ex) { Log.LogWarning((object)("[gs] skill enum: " + ex.Message)); } sb.Append('}'); } private void AppendBodySnapshot(StringBuilder sb, PlayerCharacterMasterController pcmc) { sb.Append(",\"finalStats\":{"); string text = SafePlayerName(pcmc); try { CharacterMaster master = pcmc.master; SnapshotBody(text, (master != null) ? master.GetBody() : null); } catch { } if (!lastBodySnapshot.TryGetValue(text, out var value) || value == null || value.Count == 0) { sb.Append('}'); return; } bool flag = true; foreach (KeyValuePair item in value) { double value2 = item.Value; if (!double.IsNaN(value2) && !double.IsInfinity(value2)) { if (!flag) { sb.Append(','); } flag = false; sb.Append('"').Append(item.Key).Append("\":"); if (Math.Floor(value2) == value2 && Math.Abs(value2) < 1000000000000000.0) { sb.Append((long)value2); } else { sb.Append(value2.ToString("0.##", CultureInfo.InvariantCulture)); } } } sb.Append('}'); } private void AppendStages(StringBuilder sb) { bool flag = true; foreach (StageEntry stage in stages) { if (!flag) { sb.Append(','); } flag = false; sb.Append('{'); J(sb, "index", stage.index); C(sb); J(sb, "scene", stage.scene ?? "unknown"); C(sb); sb.Append("\"startedAtSec\":").Append(stage.startedAtSec.ToString("0.##", CultureInfo.InvariantCulture)); C(sb); sb.Append("\"endedAtSec\":").Append(stage.endedAtSec.ToString("0.##", CultureInfo.InvariantCulture)); C(sb); sb.Append("\"playerDeltas\":{"); bool flag2 = true; foreach (KeyValuePair> playerDelta in stage.playerDeltas) { if (!flag2) { sb.Append(','); } flag2 = false; sb.Append('"').Append(Escape(playerDelta.Key)).Append("\":{"); bool flag3 = true; foreach (KeyValuePair item in playerDelta.Value) { if (!flag3) { sb.Append(','); } flag3 = false; sb.Append('"').Append(Escape(item.Key)).Append("\":"); double value = item.Value; if (Math.Floor(value) == value && Math.Abs(value) < 1000000000000000.0) { sb.Append((long)value); } else { sb.Append(value.ToString("0.##", CultureInfo.InvariantCulture)); } } sb.Append('}'); } sb.Append('}'); sb.Append(",\"playerItemPicks\":{"); bool flag4 = true; foreach (KeyValuePair> playerItemPick in stage.playerItemPicks) { if (!flag4) { sb.Append(','); } flag4 = false; sb.Append('"').Append(Escape(playerItemPick.Key)).Append("\":{"); bool flag5 = true; foreach (KeyValuePair item2 in playerItemPick.Value) { if (!flag5) { sb.Append(','); } flag5 = false; sb.Append('"').Append(Escape(item2.Key)).Append("\":") .Append(item2.Value); } sb.Append('}'); } sb.Append('}'); sb.Append('}'); } } private void AppendMods(StringBuilder sb) { bool flag = true; try { Dictionary pluginInfos = Chainloader.PluginInfos; if (pluginInfos == null) { return; } foreach (KeyValuePair item in pluginInfos) { PluginInfo value = item.Value; if (value == null || value.Metadata == null) { continue; } string text = ""; string val = ""; try { string directoryName = Path.GetDirectoryName(value.Location ?? ""); if (!string.IsNullOrEmpty(directoryName)) { text = Path.GetFileName(directoryName); int num = text.IndexOf('-'); if (num > 0) { val = text.Substring(0, num); } } } catch { } if (!flag) { sb.Append(','); } flag = false; sb.Append('{'); J(sb, "guid", value.Metadata.GUID ?? ""); C(sb); J(sb, "name", value.Metadata.Name ?? ""); C(sb); J(sb, "version", value.Metadata.Version?.ToString() ?? ""); C(sb); J(sb, "author", val); C(sb); J(sb, "folder", text); sb.Append('}'); } } catch (Exception ex) { Log.LogWarning((object)("[gs] mods enum: " + ex.Message)); } } private void AppendItemTiers(StringBuilder sb) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: 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) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) bool flag = true; try { Enumerator enumerator = ItemCatalog.allItemDefs.GetEnumerator(); try { while (enumerator.MoveNext()) { ItemDef current = enumerator.Current; if ((Object)(object)current == (Object)null) { continue; } bool flag2 = false; try { flag2 = current.hidden; } catch { } if (flag2) { continue; } string text = null; try { ItemTier tier = current.tier; text = ((object)(ItemTier)(ref tier)).ToString(); } catch { } if (!string.IsNullOrEmpty(text) && !(text == "NoTier")) { if (!flag) { sb.Append(','); } flag = false; sb.Append('"').Append(Escape(((Object)current).name)).Append("\":\"") .Append(Escape(text)) .Append('"'); } } } finally { ((IDisposable)enumerator).Dispose(); } } catch (Exception ex) { Log.LogWarning((object)("[gs] item tiers: " + ex.Message)); } } private void AppendArtifacts(StringBuilder sb) { bool flag = true; try { if ((Object)(object)RunArtifactManager.instance == (Object)null) { return; } int artifactCount = ArtifactCatalog.artifactCount; for (int i = 0; i < artifactCount; i++) { ArtifactDef artifactDef = ArtifactCatalog.GetArtifactDef((ArtifactIndex)i); if (!((Object)(object)artifactDef == (Object)null) && RunArtifactManager.instance.IsArtifactEnabled(artifactDef)) { if (!flag) { sb.Append(','); } flag = false; sb.Append('"').Append(Escape(artifactDef.cachedName)).Append('"'); } } } catch (Exception ex) { Log.LogWarning((object)("[gs] artifact enum: " + ex.Message)); } } private int GetEclipseLevel(Run run) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) try { DifficultyIndex selectedDifficulty = run.selectedDifficulty; string text = ((object)(DifficultyIndex)(ref selectedDifficulty)).ToString(); if (text.StartsWith("Eclipse", StringComparison.OrdinalIgnoreCase) && int.TryParse(text.Substring("Eclipse".Length), out var result)) { return result; } } catch { } return 0; } private void AppendPlayerStats(StringBuilder sb, PlayerCharacterMasterController pcmc) { sb.Append(",\"stats\":{"); bool flag = true; string key = SafePlayerName(pcmc); if (finalReportStatsByName.TryGetValue(key, out var value) && value.Count > 0) { foreach (KeyValuePair item in value) { if (!flag) { sb.Append(','); } flag = false; double value2 = item.Value; sb.Append('"').Append(Escape(item.Key)).Append("\":"); if (Math.Floor(value2) == value2 && Math.Abs(value2) < 1000000000000000.0) { sb.Append((long)value2); } else { sb.Append(value2.ToString("0.##", CultureInfo.InvariantCulture)); } } sb.Append('}'); return; } try { CharacterMaster master = pcmc.master; StatSheet val = ((master == null) ? null : master.playerStatsComponent?.currentStats); if (val == null) { sb.Append('}'); return; } List allStatDefs = StatDef.allStatDefs; string[] statNames = StatNames; foreach (string text in statNames) { StatDef val2 = null; for (int j = 0; j < allStatDefs.Count; j++) { if (allStatDefs[j] != null && allStatDefs[j].name == text) { val2 = allStatDefs[j]; break; } } if (val2 == null) { continue; } double num = ReadStat(val, val2); if (!(num <= 0.0)) { if (!flag) { sb.Append(','); } flag = false; sb.Append('"').Append(Escape(text)).Append("\":"); if (Math.Floor(num) == num && Math.Abs(num) < 1000000000000000.0) { sb.Append((long)num); } else { sb.Append(num.ToString("0.##", CultureInfo.InvariantCulture)); } } } } catch (Exception ex) { Log.LogWarning((object)("[gs] stats enum: " + ex.Message)); } sb.Append('}'); } private void AppendBossKills(StringBuilder sb, PlayerCharacterMasterController pcmc) { sb.Append(",\"bossKills\":{"); bool flag = true; string key = SafePlayerName(pcmc); if (finalReportBossKillsByName.TryGetValue(key, out var value) && value.Count > 0) { foreach (KeyValuePair item in value) { if (!flag) { sb.Append(','); } flag = false; sb.Append('"').Append(Escape(item.Key)).Append("\":") .Append(item.Value); } sb.Append('}'); return; } try { CharacterMaster master = pcmc.master; StatSheet val = ((master == null) ? null : master.playerStatsComponent?.currentStats); if (val == null) { sb.Append('}'); return; } List allStatDefs = StatDef.allStatDefs; string[] bossBodies = BossBodies; foreach (string text in bossBodies) { string text2 = "killsAgainst." + text; StatDef val2 = null; for (int j = 0; j < allStatDefs.Count; j++) { if (allStatDefs[j] != null && allStatDefs[j].name == text2) { val2 = allStatDefs[j]; break; } } if (val2 == null) { continue; } ulong num = (ulong)ReadStat(val, val2); if (num != 0L) { if (!flag) { sb.Append(','); } flag = false; sb.Append('"').Append(Escape(text)).Append("\":") .Append(num); } } } catch (Exception ex) { Log.LogWarning((object)("[gs] boss enum: " + ex.Message)); } sb.Append('}'); } private static void J(StringBuilder sb, string key, string val) { sb.Append('"').Append(Escape(key)).Append("\":"); if (val == null) { sb.Append("null"); } else { sb.Append('"').Append(Escape(val)).Append('"'); } } private static void J(StringBuilder sb, string key, int val) { sb.Append('"').Append(Escape(key)).Append("\":") .Append(val); } private static void J(StringBuilder sb, string key, bool val) { sb.Append('"').Append(Escape(key)).Append("\":") .Append(val ? "true" : "false"); } private static void C(StringBuilder sb) { sb.Append(','); } private static string Escape(string s) { if (string.IsNullOrEmpty(s)) { return s ?? ""; } StringBuilder stringBuilder = new StringBuilder(s.Length + 8); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4")); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } } }