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 GsStatsEmitter v0.8.1
GsStatsEmitter.dll
Decompiled 6 hours agousing 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<string, Dictionary<string, double>> playerDeltas = new Dictionary<string, Dictionary<string, double>>(); public Dictionary<string, Dictionary<string, int>> playerItemPicks = new Dictionary<string, Dictionary<string, int>>(); } public const string GUID = "net.cproudlock.gsstatsemitter"; public const string NAME = "GS Stats Emitter"; public const string VERSION = "0.10.0"; private readonly Dictionary<string, Dictionary<string, double>> finalReportStatsByName = new Dictionary<string, Dictionary<string, double>>(); private readonly Dictionary<string, Dictionary<string, ulong>> finalReportBossKillsByName = new Dictionary<string, Dictionary<string, ulong>>(); private readonly Dictionary<string, Dictionary<string, double>> lastBodySnapshot = new Dictionary<string, Dictionary<string, double>>(); 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<string> ingestUrlCfg; private ConfigEntry<string> ingestTokenCfg; private DateTime runStartUtc; private string runIdLocal; private readonly List<StageEntry> stages = new List<StageEntry>(); private StageEntry currentStage; private readonly Dictionary<string, Dictionary<string, double>> lastSnapshot = new Dictionary<string, Dictionary<string, double>>(); private readonly Dictionary<string, Dictionary<string, int>> lastItemSnapshot = new Dictionary<string, Dictionary<string, int>>(); private readonly Dictionary<string, string> lastAttackerByPlayer = new Dictionary<string, string>(); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; ingestUrlCfg = ((BaseUnityPlugin)this).Config.Bind<string>("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<string>("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<CharacterBody>() : 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<HttpResponseMessage> 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<StatDef> 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<string, double> dictionary = new Dictionary<string, double>(); 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<string, ulong> dictionary2 = new Dictionary<string, ulong>(); 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<string, double> snap; if (!((Object)(object)body == (Object)null) && !string.IsNullOrEmpty(playerName)) { snap = new Dictionary<string, double>(); 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<double> 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<CharacterBody>()?.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<PlayerCharacterMasterController> instances = PlayerCharacterMasterController.instances; if (instances == null) { return; } List<StatDef> 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<string, double> dictionary = new Dictionary<string, double>(); 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<string, int> dictionary2 = new Dictionary<string, int>(); try { Enumerator<ItemDef> 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<PlayerCharacterMasterController> instances = PlayerCharacterMasterController.instances; if (instances == null) { return; } List<StatDef> 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<string, double> dictionary = new Dictionary<string, double>(); 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<string, int> dictionary2 = new Dictionary<string, int>(); try { Enumerator<ItemDef> 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<HttpResponseMessage> 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<PlayerCharacterMasterController> 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<CharacterBody>()?.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<ItemDef> 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<string, double> 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<string, Dictionary<string, double>> playerDelta in stage.playerDeltas) { if (!flag2) { sb.Append(','); } flag2 = false; sb.Append('"').Append(Escape(playerDelta.Key)).Append("\":{"); bool flag3 = true; foreach (KeyValuePair<string, double> 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<string, Dictionary<string, int>> playerItemPick in stage.playerItemPicks) { if (!flag4) { sb.Append(','); } flag4 = false; sb.Append('"').Append(Escape(playerItemPick.Key)).Append("\":{"); bool flag5 = true; foreach (KeyValuePair<string, int> 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<string, PluginInfo> pluginInfos = Chainloader.PluginInfos; if (pluginInfos == null) { return; } foreach (KeyValuePair<string, PluginInfo> 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<ItemDef> 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<string, double> 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<StatDef> 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<string, ulong> 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<StatDef> 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(); } } }