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 HeadcamReplay v1.1.3
HeadcamReplay.dll
Decompiled 7 months agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Photon.Pun; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("RepoStarterMod")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("RepoStarterMod")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("97d352f5-4c2d-4aec-a09b-d82de3937eea")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] [BepInPlugin("com.yourname.headcamreplay", "Headcam Replay", "1.1.3")] public sealed class HeadcamReplay : BaseUnityPlugin { [CompilerGenerated] private sealed class <>c__DisplayClass34_0 { public Texture2D screenshot; public HeadcamReplay <>4__this; internal void <CaptureCurrentScreenFrame>b__0() { byte[] item = ImageConversion.EncodeToPNG(screenshot); Object.Destroy((Object)(object)screenshot); int num = Mathf.RoundToInt(<>4__this.cfgReplaySeconds.Value * (float)<>4__this.cfgBufferFPS.Value); if (<>4__this.frameBuffer.Count >= num) { <>4__this.frameBuffer.Dequeue(); } <>4__this.frameBuffer.Enqueue(item); } } [CompilerGenerated] private sealed class <>c__DisplayClass39_0 { public string execPath; public HeadcamReplay <>4__this; } [CompilerGenerated] private sealed class <>c__DisplayClass39_1 { public string args; public bool finished; public <>c__DisplayClass39_0 CS$<>8__locals1; internal void <FinalizeExportCoroutine>b__0() { try { ProcessStartInfo startInfo = new ProcessStartInfo { FileName = CS$<>8__locals1.execPath, Arguments = args, UseShellExecute = false, CreateNoWindow = true }; Process process = new Process { StartInfo = startInfo }; process.Start(); process.WaitForExit(); finished = true; } catch (Exception ex) { CS$<>8__locals1.<>4__this.PublicLogger.LogError((object)("Failed to run FFmpeg: ApplicationName='" + CS$<>8__locals1.execPath + "', Arguments='" + args + "', Error= " + ex.Message)); finished = true; } } } [CompilerGenerated] private sealed class <CaptureCurrentScreenFrame>d__34 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public HeadcamReplay <>4__this; private <>c__DisplayClass34_0 <>8__1; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <CaptureCurrentScreenFrame>d__34(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>8__1 = null; <>1__state = -2; } private bool MoveNext() { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>8__1 = new <>c__DisplayClass34_0(); <>8__1.<>4__this = <>4__this; if (<>4__this.isCapturingFrame) { return false; } <>4__this.isCapturingFrame = true; <>2__current = (object)new WaitForEndOfFrame(); <>1__state = 1; return true; case 1: <>1__state = -1; <>8__1.screenshot = ScreenCapture.CaptureScreenshotAsTexture(); <>2__current = Task.Run(delegate { byte[] item = ImageConversion.EncodeToPNG(<>8__1.screenshot); Object.Destroy((Object)(object)<>8__1.screenshot); int num = Mathf.RoundToInt(<>8__1.<>4__this.cfgReplaySeconds.Value * (float)<>8__1.<>4__this.cfgBufferFPS.Value); if (<>8__1.<>4__this.frameBuffer.Count >= num) { <>8__1.<>4__this.frameBuffer.Dequeue(); } <>8__1.<>4__this.frameBuffer.Enqueue(item); }); <>1__state = 2; return true; case 2: <>1__state = -1; <>4__this.isCapturingFrame = false; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <ExportCoroutine>d__36 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public HeadcamReplay <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <ExportCoroutine>d__36(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>4__this.PrepareExport(); <>2__current = <>4__this.FinalizeExportCoroutine(); <>1__state = 1; return true; case 1: <>1__state = -1; <>4__this.PublicLogger.LogInfo((object)"Pre-death footage export complete."); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <FinalizeExportCoroutine>d__39 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public HeadcamReplay <>4__this; private <>c__DisplayClass39_0 <>8__1; private bool <useSystemPath>5__2; private int <i>5__3; private string <path>5__4; private <>c__DisplayClass39_1 <>8__5; private float <playbackFPS>5__6; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <FinalizeExportCoroutine>d__39(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>8__1 = null; <path>5__4 = null; <>8__5 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; } else { <>1__state = -1; <>8__1 = new <>c__DisplayClass39_0(); <>8__1.<>4__this = <>4__this; <i>5__3 = 0; while (<i>5__3 < <>4__this.framePngsToExport.Count) { <path>5__4 = Path.Combine(<>4__this.tempPngFolder, $"{<i>5__3:D6}.png"); File.WriteAllBytes(<path>5__4, <>4__this.framePngsToExport[<i>5__3]); <path>5__4 = null; <i>5__3++; } <>4__this.framePngsToExport.Clear(); <useSystemPath>5__2 = <>4__this.ffmpegPath == "ffmpeg"; <>8__1.execPath = <>4__this.ffmpegPath; if (!<useSystemPath>5__2 && !File.Exists(<>8__1.execPath)) { <>4__this.PublicLogger.LogError((object)("FFmpeg not found at the expected path: " + <>4__this.ffmpegPath + ". Export skipped.")); goto IL_02db; } <>8__5 = new <>c__DisplayClass39_1(); <>8__5.CS$<>8__locals1 = <>8__1; if (!<useSystemPath>5__2) { <>8__5.CS$<>8__locals1.execPath = "\"" + <>8__5.CS$<>8__locals1.execPath + "\""; } <playbackFPS>5__6 = (float)<>4__this.cfgBufferFPS.Value * <>4__this.cfgPlaybackSpeed.Value; if (<playbackFPS>5__6 < 1f) { <playbackFPS>5__6 = 1f; } <>8__5.args = $"-framerate {<playbackFPS>5__6} -i \"{<>4__this.tempPngFolder}/%06d.png\" -c:v libx264 -pix_fmt yuv420p -y \"{<>4__this.exportFilePath}\""; <>4__this.PublicLogger.LogInfo((object)$"Starting FFmpeg process. Playback FPS: {<playbackFPS>5__6}, Arguments: {<>8__5.args}"); <>8__5.finished = false; Task.Run(delegate { try { ProcessStartInfo startInfo = new ProcessStartInfo { FileName = <>8__5.CS$<>8__locals1.execPath, Arguments = <>8__5.args, UseShellExecute = false, CreateNoWindow = true }; Process process = new Process { StartInfo = startInfo }; process.Start(); process.WaitForExit(); <>8__5.finished = true; } catch (Exception ex) { <>8__5.CS$<>8__locals1.<>4__this.PublicLogger.LogError((object)("Failed to run FFmpeg: ApplicationName='" + <>8__5.CS$<>8__locals1.execPath + "', Arguments='" + <>8__5.args + "', Error= " + ex.Message)); <>8__5.finished = true; } }); } if (!<>8__5.finished) { <>2__current = null; <>1__state = 1; return true; } <>4__this.PublicLogger.LogInfo((object)"FFmpeg finished. Cleaning up."); <>8__5 = null; goto IL_02db; IL_02db: try { Directory.Delete(<>4__this.tempPngFolder, recursive: true); } catch { } <>4__this.PublicLogger.LogInfo((object)("Pre-death footage export complete. Saved to: " + <>4__this.exportFilePath)); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string PLUGIN_GUID = "com.yourname.headcamreplay"; public const string PLUGIN_NAME = "Headcam Replay"; public const string PLUGIN_VERSION = "1.1.3"; private ConfigEntry<float> cfgReplaySeconds; private ConfigEntry<int> cfgBufferFPS; private ConfigEntry<string> cfgExportFolder; private ConfigEntry<float> cfgPlaybackSpeed; private Queue<byte[]> frameBuffer; internal float timeOfDeath = 0f; private const float POST_DEATH_DELAY = 2f; private List<byte[]> framePngsToExport; private string exportFilePath; private string tempPngFolder; private string ffmpegPath; private bool isCapturingFrame = false; internal static HeadcamReplay Instance { get; private set; } internal static Camera StaticPlayerCam { get; set; } internal static CameraNoise StaticCameraNoise { get; set; } public ManualLogSource PublicLogger => ((BaseUnityPlugin)this).Logger; private Camera PlayerCam => StaticPlayerCam; private void Awake() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Expected O, but got Unknown //IL_011b: Unknown result type (might be due to invalid IL or missing references) Instance = this; frameBuffer = new Queue<byte[]>(); framePngsToExport = new List<byte[]>(); cfgReplaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ReplaySeconds", 10f, new ConfigDescription("Seconds of pre-death footage", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 30f), Array.Empty<object>())); cfgBufferFPS = ((BaseUnityPlugin)this).Config.Bind<int>("Performance", "BufferFPS", 30, new ConfigDescription("Recording FPS", (AcceptableValueBase)(object)new AcceptableValueRange<int>(24, 60), Array.Empty<object>())); string text = Path.Combine(Application.dataPath, "..", "DeathVids"); cfgExportFolder = ((BaseUnityPlugin)this).Config.Bind<string>("Export", "ExportFolder", text, "Folder for MP4s (full path or relative to game dir)"); cfgPlaybackSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Export", "PlaybackSpeedMultiplier", 1f, new ConfigDescription("Playback speed multiplier (1.0 = normal, 0.5 = half speed/slow motion)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 4f), Array.Empty<object>())); Directory.CreateDirectory(cfgExportFolder.Value); new Harmony("com.yourname.headcamreplay").PatchAll(); PublicLogger.LogInfo((object)"Headcam Replay v1.1.3 loaded. Delayed full screen capture enabled."); } private void Update() { if ((Object)(object)PlayerCam == (Object)null) { TryFindPlayerComponentsFallback(); } if ((Object)(object)PlayerCam != (Object)null && !isCapturingFrame && Time.frameCount % (60 / cfgBufferFPS.Value) == 0) { ((MonoBehaviour)this).StartCoroutine(CaptureCurrentScreenFrame()); } if (timeOfDeath > 0f && Time.time >= timeOfDeath + 2f) { StartExport(); timeOfDeath = 0f; } } private void TryFindPlayerComponentsFallback() { if ((Object)(object)StaticCameraNoise == (Object)null) { StaticCameraNoise = Object.FindObjectOfType<CameraNoise>(); } if ((Object)(object)StaticCameraNoise != (Object)null && (Object)(object)StaticPlayerCam == (Object)null) { StaticPlayerCam = ((Component)StaticCameraNoise).GetComponentInChildren<Camera>(true); } } [IteratorStateMachine(typeof(<CaptureCurrentScreenFrame>d__34))] private IEnumerator CaptureCurrentScreenFrame() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <CaptureCurrentScreenFrame>d__34(0) { <>4__this = this }; } internal void StartExport() { if ((Object)(object)PlayerCam == (Object)null) { TryFindPlayerComponentsFallback(); if ((Object)(object)PlayerCam == (Object)null) { PublicLogger.LogError((object)"Cannot start export: Player Camera is null. Export aborted."); return; } } framePngsToExport.Clear(); foreach (byte[] item in frameBuffer) { framePngsToExport.Add(item); } frameBuffer.Clear(); ((MonoBehaviour)this).StartCoroutine(ExportCoroutine()); PublicLogger.LogInfo((object)$"Pre-death footage export initiated. Capturing {framePngsToExport.Count} frames."); } [IteratorStateMachine(typeof(<ExportCoroutine>d__36))] private IEnumerator ExportCoroutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ExportCoroutine>d__36(0) { <>4__this = this }; } private void PrepareExport() { string text = "Player"; if ((Object)(object)StaticCameraNoise != (Object)null) { text = ((Object)((Component)StaticCameraNoise).gameObject.transform.root).name.Replace("(Clone)", "").Trim(); } string text2 = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string path = text + "_" + text2 + ".mp4"; exportFilePath = Path.Combine(cfgExportFolder.Value, path); tempPngFolder = Path.Combine(Path.GetTempPath(), "HeadcamReplay_" + Guid.NewGuid().ToString("N").Substring(0, 8)); Directory.CreateDirectory(tempPngFolder); DownloadFFmpeg(); } private void DownloadFFmpeg() { string directoryName = Path.GetDirectoryName(typeof(HeadcamReplay).Assembly.Location); ffmpegPath = Path.Combine(directoryName, "ffmpeg.exe"); if (File.Exists(ffmpegPath)) { PublicLogger.LogInfo((object)("FFmpeg found locally: " + ffmpegPath)); return; } PublicLogger.LogWarning((object)"FFmpeg not found next to mod DLL. Attempting to use system PATH (command: 'ffmpeg')."); ffmpegPath = "ffmpeg"; } [IteratorStateMachine(typeof(<FinalizeExportCoroutine>d__39))] private IEnumerator FinalizeExportCoroutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <FinalizeExportCoroutine>d__39(0) { <>4__this = this }; } } [HarmonyPatch(typeof(PlayerLocalCamera), "Update")] public static class CameraReferencePatch { private static readonly FieldInfo PhotonViewField = AccessTools.Field(typeof(PlayerLocalCamera), "photonView"); public static void Postfix(PlayerLocalCamera __instance) { if ((Object)(object)HeadcamReplay.Instance == (Object)null || (Object)(object)HeadcamReplay.StaticPlayerCam != (Object)null) { return; } ManualLogSource publicLogger = HeadcamReplay.Instance.PublicLogger; object? obj = PhotonViewField?.GetValue(__instance); PhotonView val = (PhotonView)((obj is PhotonView) ? obj : null); if (!((Object)(object)val == (Object)null) && val.IsMine && (Object)(object)CameraNoise.Instance != (Object)null) { HeadcamReplay.StaticCameraNoise = CameraNoise.Instance; HeadcamReplay.StaticPlayerCam = ((Component)CameraNoise.Instance).GetComponentInChildren<Camera>(true); if ((Object)(object)HeadcamReplay.StaticPlayerCam == (Object)null) { publicLogger.LogError((object)"Camera component not found on CameraNoise children."); } else { publicLogger.LogInfo((object)"Successfully cached Camera references using CameraNoise."); } } } } [HarmonyPatch(typeof(PlayerHealth), "Death")] public static class PlayerDeathPatch { public static void Postfix() { if ((Object)(object)HeadcamReplay.Instance != (Object)null && HeadcamReplay.Instance.timeOfDeath == 0f) { HeadcamReplay.Instance.timeOfDeath = Time.time; HeadcamReplay.Instance.PublicLogger.LogInfo((object)"Death detected. Recording will continue for 2 seconds."); } } }