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)
{
}
}
}