using 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.");
}
}
}