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.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityEngine.Video; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SmileOsViewer")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("SmileOsViewer")] [assembly: AssemblyTitle("SmileOsViewer")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace SmileosRecordingViewer { [BepInPlugin("com.smileo.recordingviewer", "Smileos Recording Viewer", "1.0.0")] public class MainPlugin : BaseUnityPlugin { public static ManualLogSource ModLogger; public static string ClipsFolder; public static string TempFramesFolder; public static string FFmpegPath; private void Awake() { //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Expected O, but got Unknown ModLogger = ((BaseUnityPlugin)this).Logger; ModLogger.LogInfo((object)"[MainPlugin] Awake() called. Initializing paths..."); string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location); ClipsFolder = Path.Combine(directoryName, "Clips"); TempFramesFolder = Path.Combine(directoryName, "TempFrames"); FFmpegPath = Path.Combine(directoryName, "ffmpeg.exe"); Directory.CreateDirectory(ClipsFolder); Directory.CreateDirectory(TempFramesFolder); if (!File.Exists(FFmpegPath)) { ModLogger.LogError((object)("[MainPlugin] CRITICAL: ffmpeg.exe was not found at '" + FFmpegPath + "'! Video clipping will not work.")); } else { ModLogger.LogInfo((object)"[MainPlugin] ffmpeg.exe found successfully."); } Harmony val = new Harmony("com.smileo.recordingviewer"); val.PatchAll(); ModLogger.LogInfo((object)"[MainPlugin] Harmony patches applied."); SceneManager.sceneLoaded += OnSceneLoaded; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown if ((Object)(object)GameObject.Find("SmileosFrameClipperObj") == (Object)null) { ModLogger.LogInfo((object)("[MainPlugin] Scene loaded (" + ((Scene)(ref scene)).name + "). Spawning FrameClipper object...")); GameObject val = new GameObject("SmileosFrameClipperObj"); Object.DontDestroyOnLoad((Object)(object)val); val.AddComponent(); ModLogger.LogInfo((object)"[MainPlugin] FrameClipper component successfully added!"); } } } public class FrameClipper : MonoBehaviour { private const int MaxFrames = 450; private Queue frameBuffer = new Queue(); private bool isSaving = false; private const int DownscaleDivisor = 4; private Texture2D? fullScreenTex = null; private Texture2D? smallTex = null; private void Start() { MainPlugin.ModLogger.LogInfo((object)"[FrameClipper] Start() triggered. Launching ultra-lightweight CaptureRoutine..."); ((MonoBehaviour)this).StartCoroutine(CaptureRoutine()); } private void Update() { if ((Input.GetKeyDown((KeyCode)96) || Input.GetKeyDown((KeyCode)289)) && !isSaving) { MainPlugin.ModLogger.LogInfo((object)"[FrameClipper] Hotkey triggered! Attempting to save clip..."); ((MonoBehaviour)this).StartCoroutine(SaveClipRoutine()); } } private IEnumerator CaptureRoutine() { MainPlugin.ModLogger.LogInfo((object)"[CaptureRoutine] Started lightweight 15 FPS frame capture loop."); while (Screen.width <= 0 || Screen.height <= 0) { yield return null; } float nextCaptureTime = Time.realtimeSinceStartup; float captureInterval = 1f / 15f; while (true) { yield return (object)new WaitForEndOfFrame(); if (isSaving || Time.realtimeSinceStartup < nextCaptureTime) { continue; } nextCaptureTime += captureInterval; if (Screen.width <= 0 || Screen.height <= 0) { continue; } int targetWidth = Mathf.Max(160, Screen.width / 4); int targetHeight = Mathf.Max(90, Screen.height / 4); if ((Object)(object)fullScreenTex == (Object)null || ((Texture)fullScreenTex).width != Screen.width || ((Texture)fullScreenTex).height != Screen.height) { if ((Object)(object)fullScreenTex != (Object)null) { Object.Destroy((Object)(object)fullScreenTex); } if ((Object)(object)smallTex != (Object)null) { Object.Destroy((Object)(object)smallTex); } fullScreenTex = new Texture2D(Screen.width, Screen.height, (TextureFormat)3, false); smallTex = new Texture2D(targetWidth, targetHeight, (TextureFormat)3, false); } byte[] bytes = null; RenderTexture tempRt = null; try { fullScreenTex.ReadPixels(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), 0, 0, false); fullScreenTex.Apply(); tempRt = RenderTexture.GetTemporary(targetWidth, targetHeight, 0); Graphics.Blit((Texture)(object)fullScreenTex, tempRt); RenderTexture.active = tempRt; smallTex.ReadPixels(new Rect(0f, 0f, (float)targetWidth, (float)targetHeight), 0, 0, false); smallTex.Apply(); RenderTexture.active = null; bytes = ImageConversion.EncodeToJPG(smallTex, 40); } catch (Exception ex) { Exception ex2 = ex; MainPlugin.ModLogger.LogError((object)("[CaptureRoutine] Exception during frame capture: " + ex2.Message)); } finally { if ((Object)(object)tempRt != (Object)null) { RenderTexture.ReleaseTemporary(tempRt); } } if (bytes != null) { frameBuffer.Enqueue(bytes); if (frameBuffer.Count > 450) { frameBuffer.Dequeue(); } } } } private IEnumerator SaveClipRoutine() { if (!File.Exists(MainPlugin.FFmpegPath)) { MainPlugin.ModLogger.LogError((object)"[SaveClipRoutine] Cannot save clip: ffmpeg.exe is missing!"); yield break; } isSaving = true; MainPlugin.ModLogger.LogInfo((object)"[SaveClipRoutine] Saving clip, dumping frames to disk..."); byte[][] framesToDump = frameBuffer.ToArray(); MainPlugin.ModLogger.LogInfo((object)$"[SaveClipRoutine] Total frames to dump: {framesToDump.Length}"); for (int i = 0; i < framesToDump.Length; i++) { string filePath = Path.Combine(MainPlugin.TempFramesFolder, $"frame_{i:D4}.jpg"); File.WriteAllBytes(filePath, framesToDump[i]); } string outputFile = Path.Combine(path2: "Clip_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".mp4", path1: MainPlugin.ClipsFolder); string normalizedTempFolder = MainPlugin.TempFramesFolder.Replace("\\", "/"); ProcessStartInfo startInfo = new ProcessStartInfo { FileName = MainPlugin.FFmpegPath, Arguments = "-framerate 15 -i \"" + normalizedTempFolder + "/frame_%04d.jpg\" -r 15 -c:v libx264 -pix_fmt yuv420p \"" + outputFile + "\"", UseShellExecute = false, CreateNoWindow = true }; MainPlugin.ModLogger.LogInfo((object)"[SaveClipRoutine] Starting FFmpeg process..."); Process process; try { process = Process.Start(startInfo); } catch (Exception ex) { Exception ex2 = ex; MainPlugin.ModLogger.LogError((object)("[SaveClipRoutine] Failed to start FFmpeg: " + ex2.Message)); isSaving = false; yield break; } if (process != null) { while (!process.HasExited) { yield return null; } MainPlugin.ModLogger.LogInfo((object)"[SaveClipRoutine] FFmpeg encoding finished."); } MainPlugin.ModLogger.LogInfo((object)"[SaveClipRoutine] Cleaning up temp frame files..."); string[] files = Directory.GetFiles(MainPlugin.TempFramesFolder); foreach (string file in files) { try { File.Delete(file); } catch { } } isSaving = false; MainPlugin.ModLogger.LogInfo((object)("[SaveClipRoutine] Clip saved successfully to: " + outputFile)); } private void OnDestroy() { if ((Object)(object)fullScreenTex != (Object)null) { Object.Destroy((Object)(object)fullScreenTex); } if ((Object)(object)smallTex != (Object)null) { Object.Destroy((Object)(object)smallTex); } } } [HarmonyPatch] public static class TerminalInjectionPatch { private static MethodBase? TargetMethod() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = assembly.GetType("ShopZone"); if (type != null) { MainPlugin.ModLogger.LogInfo((object)"Found ShopZone type in assembly!"); return type.GetMethod("Start", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } } MainPlugin.ModLogger.LogError((object)"Could not find ShopZone type!"); return null; } private static Transform? FindChildRecursive(Transform parent, string exactName) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown if (((Object)parent).name == exactName) { return parent; } foreach (Transform item in parent) { Transform parent2 = item; Transform val = FindChildRecursive(parent2, exactName); if ((Object)(object)val != (Object)null) { return val; } } return null; } private static Transform? FindFirstButton(Transform parent) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown foreach (Transform item in parent) { Transform val = item; if (((Object)val).name != "RecordingsButton" && (Object)(object)((Component)val).GetComponent