Decompiled source of MissionLog v0.1.2

plugins/MycopunkMissionLog/MycopunkMissionLog.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using MycopunkShared;
using Pigeon.Movement;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("MycopunkMissionLog")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2+fe8cef1291afd0a7f7ea8a9ddfd28d3607c4cdd5")]
[assembly: AssemblyProduct("MycopunkMissionLog")]
[assembly: AssemblyTitle("MycopunkMissionLog")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace MycopunkMissionLog
{
	internal sealed class FrameStats
	{
		private const float BucketMs = 0.25f;

		private const int Buckets = 1000;

		private readonly int[] _histogram = new int[1000];

		private readonly List<float> _long = new List<float>();

		public int Frames { get; private set; }

		public double TotalMs { get; private set; }

		public float WorstMs { get; private set; }

		public int Over25 { get; private set; }

		public int Over50 { get; private set; }

		public int Over100 { get; private set; }

		public float AverageFps
		{
			get
			{
				if (!(TotalMs > 0.0))
				{
					return 0f;
				}
				return (float)((double)Frames * 1000.0 / TotalMs);
			}
		}

		public float OnePercentLowFps
		{
			get
			{
				if (Frames == 0)
				{
					return 0f;
				}
				int num = Math.Max(1, (int)Math.Ceiling((double)Frames * 0.01));
				if (_long.Count >= num)
				{
					_long.Sort();
					return 1000f / _long[_long.Count - num];
				}
				int num2 = _long.Count;
				for (int num3 = 999; num3 >= 0; num3--)
				{
					num2 += _histogram[num3];
					if (num2 >= num)
					{
						return 1000f / ((float)(num3 + 1) * 0.25f);
					}
				}
				return 0f;
			}
		}

		public void Clear()
		{
			Array.Clear(_histogram, 0, 1000);
			_long.Clear();
			Frames = 0;
			TotalMs = 0.0;
			WorstMs = 0f;
			int num = (Over100 = 0);
			int over = (Over50 = num);
			Over25 = over;
		}

		public void Add(float ms)
		{
			if (!(ms <= 0f) && !float.IsNaN(ms) && !float.IsInfinity(ms))
			{
				Frames++;
				TotalMs += ms;
				if (ms > WorstMs)
				{
					WorstMs = ms;
				}
				if (ms >= 25f)
				{
					Over25++;
				}
				if (ms >= 50f)
				{
					Over50++;
				}
				if (ms >= 100f)
				{
					Over100++;
				}
				int num = (int)(ms / 0.25f);
				if (num < 1000)
				{
					_histogram[num]++;
				}
				else
				{
					_long.Add(ms);
				}
			}
		}
	}
	internal static class MissionRecorder
	{
		private sealed class PendingRow
		{
			public string Row;

			public long Offset = -1L;

			public byte[] Bytes;
		}

		[HarmonyPatch(typeof(MissionManager), "OnMissionStarted_Client")]
		private static class StartedPatch
		{
			[HarmonyPostfix]
			private static void Postfix(MissionManager __instance)
			{
				OnStarted(__instance);
			}
		}

		[HarmonyPatch(typeof(PlayerData), "OnMissionCompleted")]
		private static class CompletedPatch
		{
			[HarmonyPrefix]
			private static void Prefix()
			{
				OnCompleted();
			}
		}

		[HarmonyPatch(typeof(PlayerData), "OnMissionFailed")]
		private static class FailedPatch
		{
			[HarmonyPrefix]
			private static void Prefix()
			{
				OnFailed();
			}
		}

		[HarmonyPatch(typeof(MissionManager), "FinalizeMissionData")]
		private static class FinalizedPatch
		{
			[HarmonyPostfix]
			private static void Postfix(MissionManager __instance)
			{
				OnFinalized(__instance);
			}
		}

		[HarmonyPatch(typeof(MissionManager), "OnDestroy")]
		private static class DestroyedPatch
		{
			[HarmonyPrefix]
			[HarmonyPriority(800)]
			private static void Prefix(MissionManager __instance)
			{
				OnDestroyed(__instance);
			}
		}

		private const int Mates = 3;

		private static readonly string[] StatColumns = new string[10] { "enemies_killed", "targets_killed", "damage_dealt", "damage_taken", "healing", "friendly_fire", "deaths", "repairs", "resources", "upgrades" };

		private static readonly FrameStats Frames = new FrameStats();

		private static readonly Queue<PendingRow> Pending = new Queue<PendingRow>();

		private static float _nextRetry;

		private static bool _active;

		private static string _outcome;

		private static DateTime _startLocal;

		private static float _startReal;

		private static int _startGen2;

		internal static bool Active
		{
			get
			{
				if (!_active)
				{
					return Pending.Count != 0;
				}
				return true;
			}
		}

		internal static string FilePath { get; private set; }

		public static void Setup(Harmony harmony)
		{
			string text = Path.Combine(Paths.BepInExRootPath, "MissionLog");
			Directory.CreateDirectory(text);
			FilePath = Path.Combine(text, "missions-v1.tsv");
			Patch(harmony, typeof(StartedPatch), "MissionManager", "OnMissionStarted_Client");
			Patch(harmony, typeof(CompletedPatch), "PlayerData", "OnMissionCompleted");
			Patch(harmony, typeof(FailedPatch), "PlayerData", "OnMissionFailed");
			Patch(harmony, typeof(FinalizedPatch), "MissionManager", "FinalizeMissionData");
			Patch(harmony, typeof(DestroyedPatch), "MissionManager", "OnDestroy");
		}

		private static void Patch(Harmony harmony, Type patch, string type, string method)
		{
			try
			{
				harmony.CreateClassProcessor(patch).Patch();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not hook " + type + "." + method + ": " + ex.Message));
			}
		}

		public static void Tick()
		{
			if (_active)
			{
				Frames.Add(Time.unscaledDeltaTime * 1000f);
			}
			if (Pending.Count != 0 && Time.unscaledTime >= _nextRetry)
			{
				FlushPending();
			}
		}

		private static void OnStarted(MissionManager __instance)
		{
			if (!((Object)(object)__instance != (Object)(object)MissionManager.Instance))
			{
				_active = true;
				_outcome = null;
				_startLocal = DateTime.Now;
				_startReal = Time.realtimeSinceStartup;
				_startGen2 = GC.CollectionCount(2);
				Frames.Clear();
			}
		}

		private static void OnCompleted()
		{
			if (_active)
			{
				_outcome = "success";
			}
		}

		private static void OnFailed()
		{
			if (_active)
			{
				_outcome = "fail";
			}
		}

		private static void OnFinalized(MissionManager __instance)
		{
			if (_active && !((Object)(object)__instance != (Object)(object)MissionManager.Instance))
			{
				Finish(__instance, _outcome ?? "unknown", finalized: true);
			}
		}

		private static void OnDestroyed(MissionManager __instance)
		{
			if (_active && !((Object)(object)__instance != (Object)(object)MissionManager.Instance))
			{
				Finish(__instance, "left", finalized: false);
			}
		}

		private static void Finish(MissionManager manager, string outcome, bool finalized)
		{
			_active = false;
			try
			{
				Pending.Enqueue(new PendingRow
				{
					Row = BuildRow(manager, outcome, finalized)
				});
				FlushPending();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Mission log row could not be prepared: " + ex));
			}
		}

		internal static void FlushPending()
		{
			_nextRetry = Time.unscaledTime + 5f;
			while (Pending.Count != 0)
			{
				PendingRow pendingRow = Pending.Peek();
				try
				{
					using (FileStream fileStream = new FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read))
					{
						if (pendingRow.Offset < 0)
						{
							pendingRow.Offset = fileStream.Length;
							pendingRow.Bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(((pendingRow.Offset == 0L) ? (Header() + "\n") : "") + pendingRow.Row + "\n");
						}
						if (fileStream.Length < pendingRow.Offset)
						{
							throw new IOException("Mission log was shortened while a row was pending.");
						}
						fileStream.Position = pendingRow.Offset;
						int num = (int)Math.Min(fileStream.Length - pendingRow.Offset, pendingRow.Bytes.Length);
						for (int i = 0; i < num; i++)
						{
							if (fileStream.ReadByte() != pendingRow.Bytes[i])
							{
								throw new IOException("Mission log changed while a row was pending.");
							}
						}
						if (num < pendingRow.Bytes.Length)
						{
							fileStream.Position = pendingRow.Offset + num;
							fileStream.Write(pendingRow.Bytes, num, pendingRow.Bytes.Length - num);
							fileStream.Flush();
						}
					}
					Pending.Dequeue();
					Plugin.Log.LogInfo((object)"Mission log row saved.");
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Mission log row pending; retrying: " + ex.Message));
					break;
				}
			}
		}

		private static string Header()
		{
			List<string> list = new List<string>
			{
				"start", "duration_s", "outcome", "mission_id", "mission", "region_id", "region", "seed", "threat", "intensity",
				"players", "character", "weapon1", "weapon2", "grenade"
			};
			list.AddRange(StatColumns);
			list.Add("my_max_dps");
			for (int i = 1; i <= 3; i++)
			{
				list.AddRange(new string[5]
				{
					"mate" + i + "_name",
					"mate" + i + "_character",
					"mate" + i + "_max_dps",
					"mate" + i + "_kills",
					"mate" + i + "_estimated"
				});
			}
			list.AddRange(new string[8] { "frames", "fps_avg", "fps_1pct_low", "worst_ms", "over25ms", "over50ms", "over100ms", "gen2_gc" });
			return string.Join("\t", list);
		}

		private static string BuildRow(MissionManager manager, string outcome, bool finalized)
		{
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			list.Add(_startLocal.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture));
			list.Add(Num(Time.realtimeSinceStartup - _startReal, "0"));
			list.Add(outcome);
			Safe(list, delegate
			{
				Mission mission = ((MissionData)(ref MissionManager.CurrentMission)).Mission;
				return ((Object)(object)mission != (Object)null) ? new string[2] { mission.ID, mission.MissionName } : new string[2] { "", "" };
			}, 2);
			Safe(list, delegate
			{
				WorldRegion val = (((Object)(object)LevelData.Instance != (Object)null) ? LevelData.Instance.Region : null);
				return ((Object)(object)val != (Object)null) ? new string[2] { val.NameID, val.RegionName } : new string[2] { "", "" };
			}, 2);
			Safe(list, () => new string[1] { MissionManager.MissionSeed.ToString("X8", CultureInfo.InvariantCulture) }, 1);
			Safe(list, () => new string[2]
			{
				GameManager.ThreatLevel.ToString(CultureInfo.InvariantCulture),
				GameManager.IntensityLevel.ToString(CultureInfo.InvariantCulture)
			}, 2);
			List<Player> list2 = Players();
			list.Add(list2.Count.ToString(CultureInfo.InvariantCulture));
			Safe(list, delegate
			{
				Player localPlayer = Player.LocalPlayer;
				return (!((Object)(object)localPlayer == (Object)null)) ? new string[4]
				{
					((Object)(object)localPlayer.Character != (Object)null) ? ((Dude)localPlayer.Character).APIName : "",
					GearName(localPlayer, 0),
					GearName(localPlayer, 1),
					GearName(localPlayer, 3)
				} : new string[4] { "", "", "", "" };
			}, 4);
			AddStats(list, manager, finalized);
			Func<ulong, string> dps = ModChannels.Get<Func<ulong, string>>("MycopunkShared.DpsChecker.Stats.v1");
			ulong id = (((Object)(object)NetworkManager.Singleton != (Object)null) ? NetworkManager.Singleton.LocalClientId : ulong.MaxValue);
			list.Add(DpsText(dps, id, out var _, out var _));
			int num = 0;
			foreach (Player item in list2)
			{
				if (!((Object)(object)item == (Object)null) && !((NetworkBehaviour)item).IsLocalPlayer && num < 3)
				{
					num++;
					string s = "";
					string s2 = "";
					try
					{
						PlayerInfo playerInfo = item.GetPlayerInfo();
						s = playerInfo.name;
						s2 = (((Object)(object)playerInfo.character != (Object)null) ? ((Dude)playerInfo.character).APIName : "");
					}
					catch
					{
					}
					list.Add(Clean(s));
					list.Add(Clean(s2));
					list.Add(DpsText(dps, ((NetworkBehaviour)item).OwnerClientId, out var kills2, out var estimated2));
					list.Add(kills2);
					list.Add(estimated2);
				}
			}
			for (; num < 3; num++)
			{
				list.AddRange(new string[5] { "", "", "", "", "" });
			}
			list.Add(Frames.Frames.ToString(CultureInfo.InvariantCulture));
			list.Add(Num(Frames.AverageFps, "0.0"));
			list.Add(Num(Frames.OnePercentLowFps, "0.0"));
			list.Add(Num(Frames.WorstMs, "0.0"));
			list.Add(Frames.Over25.ToString(CultureInfo.InvariantCulture));
			list.Add(Frames.Over50.ToString(CultureInfo.InvariantCulture));
			list.Add(Frames.Over100.ToString(CultureInfo.InvariantCulture));
			list.Add((GC.CollectionCount(2) - _startGen2).ToString(CultureInfo.InvariantCulture));
			for (int num2 = 0; num2 < list.Count; num2++)
			{
				list[num2] = Clean(list[num2]);
			}
			return string.Join("\t", list);
		}

		private static void AddStats(List<string> c, MissionManager manager, bool finalized)
		{
			//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)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: 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)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			MissionPlayerData missionData = manager.missionData;
			PlayerData val = null;
			try
			{
				val = PlayerData.Instance;
			}
			catch
			{
			}
			double[] array = new double[10] { missionData.enemiesKilled, missionData.targetsKilled, missionData.damageDealt, missionData.damageTaken, missionData.healingDealt, missionData.friendlyFireDamageDealt, missionData.deaths, missionData.playersRepaired, missionData.resourcesCollected, missionData.upgradesCollected };
			double?[] array2 = ((val == null) ? null : new double?[10] { val.enemiesKilled, val.targetsKilled, val.damageDealt, val.damageTaken, val.healingDealt, val.friendlyFireDamageDealt, val.deaths, val.playersRepaired, null, null });
			for (int i = 0; i < array.Length; i++)
			{
				string item = "";
				if (finalized)
				{
					item = Num(array[i], "0.##");
				}
				else if (array2 != null && array2[i].HasValue)
				{
					item = Num(array2[i].Value - array[i], "0.##");
				}
				c.Add(item);
			}
		}

		private static string DpsText(Func<ulong, string> dps, ulong id, out string kills, out string estimated)
		{
			kills = (estimated = "");
			if (dps == null)
			{
				return "";
			}
			string text;
			try
			{
				text = dps(id);
			}
			catch
			{
				return "";
			}
			float num = default(float);
			int num2 = default(int);
			bool flag = default(bool);
			if (!ModChannels.TryParseDps(text, ref num, ref num2, ref flag))
			{
				return "";
			}
			kills = num2.ToString(CultureInfo.InvariantCulture);
			estimated = (flag ? "1" : "0");
			return Num(num, "0");
		}

		private static List<Player> Players()
		{
			List<Player> list = new List<Player>();
			try
			{
				if (GameManager.players != null)
				{
					foreach (Player player in GameManager.players)
					{
						if ((Object)(object)player != (Object)null)
						{
							list.Add(player);
						}
					}
				}
			}
			catch
			{
			}
			return list;
		}

		private static string GearName(Player player, int index)
		{
			IGear[] gear = player.Gear;
			if (gear == null || index >= gear.Length || gear[index] == null)
			{
				return "";
			}
			if (!((Object)(object)((IUpgradable)gear[index]).Info != (Object)null))
			{
				return "";
			}
			return ((IUpgradable)gear[index]).Info.APIName;
		}

		private static void Safe(List<string> c, Func<string[]> get, int count)
		{
			string[] array = null;
			try
			{
				array = get();
			}
			catch
			{
			}
			for (int i = 0; i < count; i++)
			{
				c.Add((array != null && i < array.Length) ? (array[i] ?? "") : "");
			}
		}

		private static string Num(double v, string format)
		{
			return v.ToString(format, CultureInfo.InvariantCulture);
		}

		private static string Clean(string s)
		{
			if (!string.IsNullOrEmpty(s))
			{
				return s.Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' ');
			}
			return "";
		}
	}
	[BepInPlugin("t4t5u0.mycopunk.missionlog", "Mycopunk Mission Log", "0.1.2")]
	[BepInDependency("doku_macaron.mycopunk.macaroncore", "1.1.0")]
	[MycoMod(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "t4t5u0.mycopunk.missionlog";

		public const string Name = "Mycopunk Mission Log";

		public const string Version = "0.1.2";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		public bool CanHotReload => !MissionRecorder.Active;

		private void Awake()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_harmony = new Harmony("t4t5u0.mycopunk.missionlog");
			MissionRecorder.Setup(_harmony);
			Hotkeys.ReportConflicts((BaseUnityPlugin)(object)this);
			Log.LogInfo((object)("Mycopunk Mission Log 0.1.2 loaded. Rows go to " + MissionRecorder.FilePath));
		}

		private void Update()
		{
			MissionRecorder.Tick();
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void OnApplicationQuit()
		{
			MissionRecorder.FlushPending();
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}