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 REPO Game Room v1.0.0
REPOPS1.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.Win32.SafeHandles; using Photon.Pun; using Photon.Realtime; using Steamworks; using Steamworks.Data; using Unity.Collections; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.Utilities; using UnityEngine.Networking; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("REPOPS1")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("REPOPS1")] [assembly: AssemblyTitle("REPOPS1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] namespace REPOPS1; internal static class CoverDownload { private static readonly HashSet<string> Tried = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly Queue<(GameEntry game, Action done)> Waiting = new Queue<(GameEntry, Action)>(); private static readonly Dictionary<string, List<string>> Index = new Dictionary<string, List<string>>(); private static bool _busy; public static void Want(GameEntry game, Action done) { if (REPOPS1Plugin.DownloadCovers.Value && game != null && Tried.Add(game.Path)) { Waiting.Enqueue((game, done)); Next(); } } private static void Next() { while (!_busy && Waiting.Count > 0) { (GameEntry, Action) tuple = Waiting.Dequeue(); GameEntry game = tuple.Item1; Action done = tuple.Item2; string system = SystemFor(game); if (system == null) { continue; } string name = Path.GetFileNameWithoutExtension(game.Path); _busy = true; Fetch(PictureUrl(system, name), delegate(byte[] data) { if (data != null) { Save(game, name, data, name, done); Finish(); } else { WithIndex(system, delegate(List<string> list) { string match = ((list != null) ? BestMatch(name, list) : null); if (match == null) { REPOPS1Plugin.Log.LogInfo((object)("No cover in the library for \"" + name + "\".")); Finish(); } else { Fetch(PictureUrl(system, match), delegate(byte[] found) { if (found != null) { Save(game, name, found, match, done); } else { REPOPS1Plugin.Log.LogInfo((object)("Cover \"" + match + "\" for \"" + name + "\" could not be fetched.")); } Finish(); }); } }); } }); } } private static void Finish() { _busy = false; Next(); } private static void Save(GameEntry game, string name, byte[] data, string libraryName, Action done) { try { string text = GameShelf.CoversFolder(game.Path); Directory.CreateDirectory(text); File.WriteAllBytes(Path.Combine(text, name + ".png"), data); REPOPS1Plugin.Log.LogInfo((object)((libraryName == name) ? ("Cover saved: " + name + ".png") : ("Cover saved: " + name + ".png (the library's \"" + libraryName + "\")"))); done?.Invoke(); } catch (Exception ex) { REPOPS1Plugin.Log.LogWarning((object)("Cover for \"" + name + "\": " + ex.Message)); } } private static string PictureUrl(string system, string name) { return "https://thumbnails.libretro.com/" + Uri.EscapeDataString(system) + "/Named_Boxarts/" + Uri.EscapeDataString(Regex.Replace(name, "[&*/:`<>?\\\\|\"]", "_")) + ".png"; } private static void Fetch(string url, Action<byte[]> then) { UnityWebRequest req = UnityWebRequest.Get(url); req.timeout = 30; ((AsyncOperation)req.SendWebRequest()).completed += delegate { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 byte[] obj = null; try { if ((int)req.result == 1 && req.downloadHandler.data.Length > 1000) { obj = req.downloadHandler.data; } } catch (Exception) { } finally { req.Dispose(); } then(obj); }; } private static void WithIndex(string system, Action<List<string>> then) { if (Index.TryGetValue(system, out var value)) { then(value); return; } UnityWebRequest req = UnityWebRequest.Get("https://thumbnails.libretro.com/" + Uri.EscapeDataString(system) + "/Named_Boxarts/"); req.timeout = 60; ((AsyncOperation)req.SendWebRequest()).completed += delegate { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 List<string> list = null; try { if ((int)req.result == 1) { list = new List<string>(); foreach (Match item in Regex.Matches(req.downloadHandler.text, "href=\"([^\"]+)\\.png\"")) { list.Add(Uri.UnescapeDataString(item.Groups[1].Value)); } REPOPS1Plugin.Log.LogInfo((object)$"Cover library for {system}: {list.Count} pictures."); } } catch (Exception ex) { REPOPS1Plugin.Log.LogWarning((object)("Cover library list: " + ex.Message)); } finally { req.Dispose(); } Index[system] = list; then(list); }; } private static string BestMatch(string fileName, List<string> library) { string text = Normalize(fileName); if (text.Length == 0) { return null; } HashSet<string> hashSet = Tags(fileName); string result = null; int num = int.MaxValue; foreach (string item in library) { if (Normalize(item) != text) { continue; } HashSet<string> hashSet2 = Tags(item); int num2 = 50; foreach (string item2 in hashSet) { if (hashSet2.Contains(item2)) { num2 -= 20; } } if (hashSet2.Contains("usa")) { num2 -= 8; } else if (hashSet2.Contains("world")) { num2 -= 6; } else if (hashSet2.Contains("europe")) { num2 -= 4; } else if (hashSet2.Contains("japan")) { num2 -= 2; } num2 += hashSet2.Count; if (num2 < num) { num = num2; result = item; } } return result; } private static string Normalize(string name) { string text = Regex.Replace(name ?? "", "\\([^)]*\\)|\\[[^\\]]*\\]", " "); StringBuilder stringBuilder = new StringBuilder(); string text2 = text.ToLowerInvariant(); foreach (char c in text2) { stringBuilder.Append(char.IsLetterOrDigit(c) ? c : ' '); } return Regex.Replace(stringBuilder.ToString(), "\\s+", " ").Trim(); } private static HashSet<string> Tags(string name) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (Match item in Regex.Matches(name ?? "", "\\(([^)]*)\\)")) { string[] array = item.Groups[1].Value.Split(','); foreach (string text in array) { if (text.Trim().Length > 0) { hashSet.Add(text.Trim().ToLowerInvariant()); } } } return hashSet; } private static string SystemFor(GameEntry game) { string fileName = Path.GetFileName(Path.GetDirectoryName(game.CorePath) ?? ""); string[] array = (REPOPS1Plugin.CoverSystems.Value ?? "").Split(';'); foreach (string text in array) { int num = text.IndexOf('='); if (num > 0 && text.Substring(0, num).Trim().Equals(fileName, StringComparison.OrdinalIgnoreCase)) { return text.Substring(num + 1).Trim(); } } return null; } } internal static class D3D11Host { [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int QueryInterfaceFn(IntPtr self, ref Guid iid, out IntPtr obj); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate uint ReleaseFn(IntPtr self); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void GetDeviceFn(IntPtr self, out IntPtr device); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetFeatureLevelFn(IntPtr self); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void GetImmediateContextFn(IntPtr self, out IntPtr context); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private unsafe delegate int CreateDeviceContextStateFn(IntPtr self, uint flags, int* levels, uint count, uint sdk, ref Guid emulated, int* chosen, out IntPtr state); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void SwapDeviceContextStateFn(IntPtr self, IntPtr state, out IntPtr previous); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void GetResourceFn(IntPtr self, out IntPtr resource); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private unsafe delegate void PSGetShaderResourcesFn(IntPtr ctx, uint start, uint count, IntPtr* views); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private unsafe delegate void OMGetRenderTargetsFn(IntPtr ctx, uint count, IntPtr* views, IntPtr depth); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void Tex2DGetDescFn(IntPtr self, out Texture2DDesc desc); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void CopySubresourceRegionFn(IntPtr ctx, IntPtr dst, uint dstSub, uint x, uint y, uint z, IntPtr src, uint srcSub, ref Box box); private struct Texture2DDesc { public uint Width; public uint Height; public uint MipLevels; public uint ArraySize; public uint Format; public uint SampleCount; public uint SampleQuality; public uint Usage; public uint BindFlags; public uint CpuAccess; public uint Misc; } private struct Box { public uint Left; public uint Top; public uint Front; public uint Right; public uint Bottom; public uint Back; } [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void RenderEventFn(int eventId); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int FeatureLevelFn(IntPtr self); public const uint ContextDirect3D = 7u; private const int InterfaceD3D11 = 3; private const uint D3D11SdkVersion = 7u; private static readonly Guid IidDevice1 = new Guid("a04bfb29-08ef-43d6-a49c-a9bdbdcbe686"); private static readonly Guid IidContext1 = new Guid("bb2c6faa-b5fb-4082-8e6b-388b8cfa90e1"); private static IntPtr _device; private static IntPtr _context; private static IntPtr _device1; private static IntPtr _context1; private static IntPtr _coreState; private static IntPtr _compile; private static int _featureLevel; private static bool _tried; private static bool _warnedState; private static volatile bool _loggedFrame; private static int _calls; private static int _shownCalls; private static RenderEventFn _renderEvent; private static IntPtr _renderEventPtr; private const int EvRun = 1; private const int EvShutdown = 2; private const int EvCapture = 3; private static LibretroCore _core; private static volatile int _runs; private static volatile bool _resetPending; private static volatile bool _shutdownDone; private static readonly ConcurrentQueue<Action<LibretroCore>> Jobs = new ConcurrentQueue<Action<LibretroCore>>(); private static FeatureLevelFn _level110; private static IntPtr _originalLevelFn; private static IntPtr _dst; private static volatile int _dstW; private static volatile int _dstH; private static volatile int _wantW; private static volatile int _wantH; private static volatile uint _wantFormat; private static volatile int _copied; private static Texture2D _tex; private static int _seenCopies; public static bool Available { get; private set; } public static IntPtr Interface { get; private set; } internal static IntPtr Device => _device; public static bool ShutdownDone => _shutdownDone; private static IntPtr Slot(IntPtr com, int index) { return Marshal.ReadIntPtr(Marshal.ReadIntPtr(com), index * IntPtr.Size); } private static T Call<T>(IntPtr com, int slot) where T : Delegate { return Marshal.GetDelegateForFunctionPointer<T>(Slot(com, slot)); } private static void Release(IntPtr com) { if (com != IntPtr.Zero) { Call<ReleaseFn>(com, 2)(com); } } public static bool Init() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (_tried) { return Available; } _tried = true; try { if ((int)SystemInfo.graphicsDeviceType != 2) { REPOPS1Plugin.Log.LogWarning((object)$"GPU rendering needs Direct3D 11; the game runs on {SystemInfo.graphicsDeviceType}. Using software."); return false; } Texture2D val = new Texture2D(4, 4, (TextureFormat)4, false); val.Apply(false); IntPtr nativeTexturePtr = ((Texture)val).GetNativeTexturePtr(); Call<GetDeviceFn>(nativeTexturePtr, 3)(nativeTexturePtr, out _device); Object.Destroy((Object)val); if (_device == IntPtr.Zero) { return false; } Call<GetImmediateContextFn>(_device, 40)(_device, out _context); _featureLevel = Call<GetFeatureLevelFn>(_device, 37)(_device); Guid iid = IidDevice1; if (Call<QueryInterfaceFn>(_device, 0)(_device, ref iid, out _device1) < 0) { REPOPS1Plugin.Log.LogWarning((object)"No ID3D11Device1: using software."); return false; } iid = IidContext1; if (Call<QueryInterfaceFn>(_context, 0)(_context, ref iid, out _context1) < 0) { REPOPS1Plugin.Log.LogWarning((object)"No ID3D11DeviceContext1: using software."); return false; } IntPtr intPtr = Kernel32.LoadLibraryW("d3dcompiler_47.dll"); _compile = ((intPtr != IntPtr.Zero) ? Kernel32.GetProcAddress(intPtr, "D3DCompile") : IntPtr.Zero); Interface = Marshal.AllocHGlobal(48); Marshal.WriteInt32(Interface, 0, 3); Marshal.WriteInt32(Interface, 4, 1); Marshal.WriteIntPtr(Interface, 8, IntPtr.Zero); Marshal.WriteIntPtr(Interface, 16, _device); Marshal.WriteIntPtr(Interface, 24, _context); Marshal.WriteInt32(Interface, 32, _featureLevel); Marshal.WriteIntPtr(Interface, 40, _compile); _renderEvent = OnRenderEvent; _renderEventPtr = Marshal.GetFunctionPointerForDelegate(_renderEvent); Available = true; REPOPS1Plugin.Log.LogInfo((object)$"GPU rendering ready (D3D11, feature level 0x{_featureLevel:X})."); } catch (Exception ex) { REPOPS1Plugin.Log.LogError((object)("GPU rendering unavailable: " + ex.Message)); Available = false; } return Available; } public static void IssueCapture() { if (Init()) { GL.IssuePluginEvent(_renderEventPtr, 3); } } public static void Enqueue(Action<LibretroCore> job) { Jobs.Enqueue(job); if (_core != null) { GL.IssuePluginEvent(_renderEventPtr, 1); } } private static int Level110(IntPtr self) { return 45056; } private static void PatchFeatureLevel(bool on) { if (_device == IntPtr.Zero || (!REPOPS1Plugin.D3D11Level110.Value && on)) { return; } try { IntPtr intPtr = Marshal.ReadIntPtr(_device) + 37 * IntPtr.Size; if (on && _originalLevelFn == IntPtr.Zero) { if (_level110 == null) { _level110 = Level110; } _originalLevelFn = Marshal.ReadIntPtr(intPtr); WriteSlot(intPtr, Marshal.GetFunctionPointerForDelegate(_level110)); Marshal.WriteInt32(Interface, 32, 45056); } else if (!on && _originalLevelFn != IntPtr.Zero) { WriteSlot(intPtr, _originalLevelFn); _originalLevelFn = IntPtr.Zero; Marshal.WriteInt32(Interface, 32, _featureLevel); } } catch (Exception ex) { REPOPS1Plugin.Log.LogWarning((object)("Feature level switch failed: " + ex.Message)); } } private static void WriteSlot(IntPtr slot, IntPtr fn) { if (!Kernel32.VirtualProtect(slot, (UIntPtr)(ulong)IntPtr.Size, 4u, out var oldProtect)) { throw new InvalidOperationException("VirtualProtect failed"); } Marshal.WriteIntPtr(slot, fn); Kernel32.VirtualProtect(slot, (UIntPtr)(ulong)IntPtr.Size, oldProtect, out var _); } public static void BeforeLoad() { PatchFeatureLevel(on: true); } public static void AfterCore() { PatchFeatureLevel(on: false); } public static void Begin(LibretroCore core) { _loggedFrame = false; _calls = (_shownCalls = 0); _core = core; _resetPending = true; _shutdownDone = false; } public static void Run(int frames) { if (_core != null && (frames > 0 || _resetPending || !Jobs.IsEmpty)) { _runs = frames; GL.IssuePluginEvent(_renderEventPtr, 1); } } public static void Shutdown() { if (_core == null) { _shutdownDone = true; } else { GL.IssuePluginEvent(_renderEventPtr, 2); } } private unsafe static void OnRenderEvent(int id) { if (id == 3) { try { WindowCapture.RenderTick(); return; } catch (Exception ex) { REPOPS1Plugin.Log.LogError((object)("Window capture failed: " + ex.Message)); return; } } LibretroCore core = _core; if (core == null) { return; } IntPtr previous = IntPtr.Zero; try { if (_coreState == IntPtr.Zero) { int featureLevel = _featureLevel; Guid emulated = IidDevice1; CreateDeviceContextStateFn createDeviceContextStateFn = Call<CreateDeviceContextStateFn>(_device1, 47); int num = default(int); if (createDeviceContextStateFn(_device1, 0u, &featureLevel, 1u, 7u, ref emulated, &num, out _coreState) < 0 || _coreState == IntPtr.Zero) { createDeviceContextStateFn(_device1, 1u, &featureLevel, 1u, 7u, ref emulated, &num, out _coreState); } if (_coreState == IntPtr.Zero && !_warnedState) { _warnedState = true; REPOPS1Plugin.Log.LogWarning((object)"No separate GPU state for the core: R.E.P.O's picture may glitch. Set Video/Renderer = Software if it does."); } } if (_coreState != IntPtr.Zero) { Call<SwapDeviceContextStateFn>(_context1, 131)(_context1, _coreState, out previous); } if (id == 2) { Action<LibretroCore> result; while (Jobs.TryDequeue(out result)) { try { result(core); } catch (Exception) { } } core.ShutdownOnRenderThread(); _core = null; _shutdownDone = true; return; } if (_resetPending) { _resetPending = false; core.ContextReset(); } Action<LibretroCore> result2; while (Jobs.TryDequeue(out result2)) { try { result2(core); } catch (Exception ex3) { REPOPS1Plugin.Log.LogError((object)("Core job failed: " + ex3.Message)); } } for (int i = 0; i < _runs; i++) { core.Run(); } _runs = 0; } catch (Exception ex4) { REPOPS1Plugin.Log.LogError((object)("GPU frame failed: " + ex4.Message)); } finally { if (previous != IntPtr.Zero) { Call<SwapDeviceContextStateFn>(_context1, 131)(_context1, previous, out var previous2); Release(previous2); Release(previous); } } } public unsafe static void OnFrame(IntPtr srv, int width, int height) { bool flag = srv == IntPtr.Zero || width <= 0; if (_calls < 3 || (!flag && _shownCalls < 3)) { _calls++; if (!flag) { _shownCalls++; } REPOPS1Plugin.Log.LogInfo((object)$"GPU video callback #{_calls}: data 0x{srv.ToInt64():X}, {width}x{height}."); } if (srv == IntPtr.Zero || width <= 0 || height <= 0) { return; } IntPtr resource; if (srv == new IntPtr(-1)) { IntPtr* ptr = stackalloc IntPtr[1]; *ptr = IntPtr.Zero; Call<PSGetShaderResourcesFn>(_context, 73)(_context, 0u, 1u, ptr); if (*ptr == IntPtr.Zero) { Call<OMGetRenderTargetsFn>(_context, 89)(_context, 1u, ptr, IntPtr.Zero); } if (*ptr == IntPtr.Zero) { if (!_loggedFrame) { _loggedFrame = true; REPOPS1Plugin.Log.LogWarning((object)"GPU frame: \"ready\" signal but nothing bound to read it from."); } return; } Call<GetResourceFn>(*ptr, 7)(*ptr, out resource); Release(*ptr); } else { Call<GetResourceFn>(srv, 7)(srv, out resource); } if (resource == IntPtr.Zero) { return; } try { Call<Tex2DGetDescFn>(resource, 10)(resource, out var desc); if (_loggedFrame && (width != _wantW || height != _wantH)) { REPOPS1Plugin.Log.LogInfo((object)$"GPU frame size now {width}x{height}."); } _wantW = width; _wantH = height; _wantFormat = desc.Format; if (!_loggedFrame) { _loggedFrame = true; REPOPS1Plugin.Log.LogInfo((object)$"GPU frame: {width}x{height} shown of a {desc.Width}x{desc.Height} texture, DXGI format {desc.Format}, {desc.SampleCount} sample(s)."); } IntPtr dst = _dst; if (!(dst == IntPtr.Zero) && _dstW == width && _dstH == height) { Box box = new Box { Left = 0u, Top = 0u, Front = 0u, Right = (uint)Math.Min(width, (int)desc.Width), Bottom = (uint)Math.Min(height, (int)desc.Height), Back = 1u }; Call<CopySubresourceRegionFn>(_context, 46)(_context, dst, 0u, 0u, 0u, 0u, resource, 0u, ref box); _copied++; } } finally { Release(resource); } } internal static void CopyIn(IntPtr tex, int x, int y, int width, int height) { Call<Tex2DGetDescFn>(tex, 10)(tex, out var desc); width = Math.Min(width, (int)desc.Width - x); height = Math.Min(height, (int)desc.Height - y); if (width > 0 && height > 0) { if (!_loggedFrame) { _loggedFrame = true; REPOPS1Plugin.Log.LogInfo((object)$"Captured frame: {width}x{height} of a {desc.Width}x{desc.Height} window picture, DXGI format {desc.Format}."); } _wantW = width; _wantH = height; _wantFormat = desc.Format; IntPtr dst = _dst; if (!(dst == IntPtr.Zero) && _dstW == width && _dstH == height) { Box box = new Box { Left = (uint)x, Top = (uint)y, Front = 0u, Right = (uint)(x + width), Bottom = (uint)(y + height), Back = 1u }; Call<CopySubresourceRegionFn>(_context, 46)(_context, dst, 0u, 0u, 0u, 0u, tex, 0u, ref box); _copied++; } } } public static void ResetFrameLog() { _loggedFrame = false; } public static bool UpdateTexture(ref Texture2D texture) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_00c8: Expected O, but got Unknown int wantW = _wantW; int wantH = _wantH; if (wantW > 0 && wantH > 0 && ((Object)(object)_tex == (Object)null || ((Texture)_tex).width != wantW || ((Texture)_tex).height != wantH || (Object)(object)_tex != (Object)(object)texture)) { TextureFormat val = (TextureFormat)((_wantFormat == 87 || _wantFormat == 91) ? 14 : 4); if ((Object)(object)_tex != (Object)null && (Object)(object)_tex != (Object)(object)texture) { Object.Destroy((Object)(object)_tex); } if ((Object)(object)texture != (Object)null) { Object.Destroy((Object)(object)texture); } Texture2D val2 = new Texture2D(wantW, wantH, val, false) { name = "REPOPS1 Screen (GPU)", wrapMode = (TextureWrapMode)1 }; Texture2D tex = val2; texture = val2; _tex = tex; texture.Apply(false, true); _dst = ((Texture)texture).GetNativeTexturePtr(); _dstW = wantW; _dstH = wantH; } int copied = _copied; if (copied == _seenCopies) { return false; } _seenCopies = copied; return true; } public static void ForgetTexture() { _dst = IntPtr.Zero; _dstW = (_dstH = 0); _wantW = (_wantH = 0); _tex = null; } } internal sealed class EmuAudio : MonoBehaviour { private const int Capacity = 32768; private const double MaxLatencySeconds = 0.12; private readonly object _lock = new object(); private readonly float[] _ring = new float[65536]; private int _read; private int _write; private int _count; private double _pos; private double _srcRate = 44100.0; private double _outRate = 48000.0; private AudioSource _source; public float Volume = 1f; private void Awake() { _outRate = AudioSettings.outputSampleRate; _source = ((Component)this).gameObject.AddComponent<AudioSource>(); _source.clip = AudioClip.Create("PS1 Audio", (int)_outRate, 2, (int)_outRate, false); _source.loop = true; _source.spatialBlend = 0f; _source.playOnAwake = false; _source.Play(); } public void SetSpatial(bool spatial) { float num = (spatial ? 1f : 0f); if (_source.spatialBlend != num) { _source.spatialBlend = num; _source.rolloffMode = (AudioRolloffMode)1; _source.minDistance = 1.5f; _source.maxDistance = 12f; _source.dopplerLevel = 0f; } } private void OnDestroy() { if ((Object)(object)_source != (Object)null && (Object)(object)_source.clip != (Object)null) { Object.Destroy((Object)(object)_source.clip); } } public void Push(byte[] pcm, int offset, int length) { lock (_lock) { for (int i = offset; i + 3 < offset + length; i += 4) { Write((short)(pcm[i] | (pcm[i + 1] << 8)), (short)(pcm[i + 2] | (pcm[i + 3] << 8))); } } } public void SetSourceRate(double rate) { if (rate > 0.0) { lock (_lock) { _srcRate = rate; } } } public void Clear() { lock (_lock) { _read = (_write = (_count = 0)); _pos = 0.0; } } public unsafe void Push(IntPtr data, int frames) { short* ptr = (short*)(void*)data; lock (_lock) { for (int i = 0; i < frames; i++) { Write(ptr[i * 2], ptr[i * 2 + 1]); } } } public void Push(short left, short right) { lock (_lock) { Write(left, right); } } private void Write(short left, short right) { if (_count == 32768) { _read = (_read + 1) % 32768; _count--; } _ring[_write * 2] = (float)left / 32768f; _ring[_write * 2 + 1] = (float)right / 32768f; _write = (_write + 1) % 32768; _count++; } private void OnAudioFilterRead(float[] data, int channels) { float volume = Volume; lock (_lock) { double num = _srcRate / _outRate; int num2 = (int)(_srcRate * 0.12); if (_count > num2) { int num3 = _count - num2 / 2; _read = (_read + num3) % 32768; _count -= num3; } for (int i = 0; i < data.Length; i += channels) { if (_count < 2) { Array.Clear(data, i, data.Length - i); break; } int num4 = _read * 2; int num5 = (_read + 1) % 32768 * 2; float num6 = (float)_pos; data[i] = (_ring[num4] + (_ring[num5] - _ring[num4]) * num6) * volume; if (channels > 1) { data[i + 1] = (_ring[num4 + 1] + (_ring[num5 + 1] - _ring[num4 + 1]) * num6) * volume; } for (int j = 2; j < channels; j++) { data[i + j] = 0f; } _pos += num; while (_pos >= 1.0 && _count > 1) { _pos -= 1.0; _read = (_read + 1) % 32768; _count--; } } } } } internal struct PadState { public ushort Buttons; public short Lx; public short Ly; public short Rx; public short Ry; public const int Size = 10; public short Read(uint device, uint index, uint id) { switch (device) { case 1u: if (id == 256) { return (short)Buttons; } return (short)((id < 16 && (Buttons & (1 << (int)id)) != 0) ? 1 : 0); default: return 0; case 5u: if (index == 2) { return (short)((id < 16 && (Buttons & (1 << (int)id)) != 0) ? 32767 : 0); } if (id > 1) { return 0; } switch (index) { case 0u: if (id != 0) { return Ly; } return Lx; case 1u: if (id != 0) { return Ry; } return Rx; default: return 0; } } } public void Write(byte[] b, int at) { b[at] = (byte)Buttons; b[at + 1] = (byte)(Buttons >> 8); Put(b, at + 2, Lx); Put(b, at + 4, Ly); Put(b, at + 6, Rx); Put(b, at + 8, Ry); } public static PadState From(byte[] b, int at) { return new PadState { Buttons = (ushort)(b[at] | (b[at + 1] << 8)), Lx = Get(b, at + 2), Ly = Get(b, at + 4), Rx = Get(b, at + 6), Ry = Get(b, at + 8) }; } private static void Put(byte[] b, int at, short v) { b[at] = (byte)v; b[at + 1] = (byte)(v >> 8); } private static short Get(byte[] b, int at) { return (short)(b[at] | (b[at + 1] << 8)); } } internal static class EmuInput { private static int _stickHeldX; private static int _stickHeld; private static readonly HashSet<int> Virtual = new HashSet<int>(); public static PadState Sample(bool active) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) PadState s = default(PadState); if (!active || PS1Identity.ChatActive) { return s; } for (uint num = 0u; num < 16; num++) { KeyCode val = REPOPS1Plugin.PadKey(num); if ((int)val != 0 && Input.GetKey(val)) { s.Buttons |= (ushort)(1 << (int)num); } } ReadOnlyArray<Gamepad> all = Gamepad.all; for (int i = 0; i < all.Count; i++) { AddPad(ref s, all[i]); } return s; } private static void AddPad(ref PadState s, Gamepad gp) { //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) if (gp != null && ((InputDevice)gp).enabled && !Ours((InputDevice)(object)gp)) { Or(ref s, 0u, gp.buttonSouth.isPressed); Or(ref s, 8u, gp.buttonEast.isPressed); Or(ref s, 1u, gp.buttonWest.isPressed); Or(ref s, 9u, gp.buttonNorth.isPressed); Or(ref s, 10u, gp.leftShoulder.isPressed); Or(ref s, 11u, gp.rightShoulder.isPressed); Or(ref s, 12u, gp.leftTrigger.isPressed); Or(ref s, 13u, gp.rightTrigger.isPressed); Or(ref s, 14u, gp.leftStickButton.isPressed); Or(ref s, 15u, gp.rightStickButton.isPressed); Or(ref s, 3u, gp.startButton.isPressed); Or(ref s, 2u, gp.selectButton.isPressed); Or(ref s, 4u, gp.dpad.up.isPressed); Or(ref s, 5u, gp.dpad.down.isPressed); Or(ref s, 6u, gp.dpad.left.isPressed); Or(ref s, 7u, gp.dpad.right.isPressed); Vector2 val = ((InputControl<Vector2>)(object)gp.leftStick).ReadValue(); Vector2 val2 = ((InputControl<Vector2>)(object)gp.rightStick).ReadValue(); Stronger(ref s.Lx, Axis(val.x)); Stronger(ref s.Ly, Axis(0f - val.y)); Stronger(ref s.Rx, Axis(val2.x)); Stronger(ref s.Ry, Axis(0f - val2.y)); } } private static void Stronger(ref short current, short v) { if (Mathf.Abs((int)v) > Mathf.Abs((int)current)) { current = v; } } public static bool PadPressed(Func<Gamepad, ButtonControl> button) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) ReadOnlyArray<Gamepad> all = Gamepad.all; for (int i = 0; i < all.Count; i++) { if (all[i] != null && !Ours((InputDevice)(object)all[i]) && button(all[i]).wasPressedThisFrame) { return true; } } return false; } public static int StickStepX() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) float num = 0f; ReadOnlyArray<Gamepad> all = Gamepad.all; for (int i = 0; i < all.Count; i++) { if (!Ours((InputDevice)(object)all[i])) { float x = ((InputControl<Vector2>)(object)all[i].leftStick).ReadValue().x; if (Mathf.Abs(x) > Mathf.Abs(num)) { num = x; } } } int num2 = ((num > 0.6f) ? 1 : ((num < -0.6f) ? (-1) : 0)); if (num2 == _stickHeldX) { return 0; } _stickHeldX = num2; return num2; } public static bool ComboPressed() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) ReadOnlyArray<Gamepad> all = Gamepad.all; for (int i = 0; i < all.Count; i++) { Gamepad val = all[i]; if (!Ours((InputDevice)(object)val) && val.leftStickButton.isPressed && val.rightStickButton.isPressed && (val.leftStickButton.wasPressedThisFrame || val.rightStickButton.wasPressedThisFrame)) { return true; } } return false; } public static int StickStep() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) float num = 0f; ReadOnlyArray<Gamepad> all = Gamepad.all; for (int i = 0; i < all.Count; i++) { if (!Ours((InputDevice)(object)all[i])) { float y = ((InputControl<Vector2>)(object)all[i].leftStick).ReadValue().y; if (Mathf.Abs(y) > Mathf.Abs(num)) { num = y; } } } int num2 = ((num > 0.6f) ? (-1) : ((num < -0.6f) ? 1 : 0)); if (num2 == _stickHeld) { return 0; } _stickHeld = num2; return num2; } public static int RealPadCount() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) int num = 0; Enumerator<Gamepad> enumerator = Gamepad.all.GetEnumerator(); try { while (enumerator.MoveNext()) { Gamepad current = enumerator.Current; if (((InputDevice)current).enabled && !Ours((InputDevice)(object)current)) { num++; } } return num; } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } private static bool Ours(InputDevice d) { if (Virtual.Contains(d.deviceId)) { return true; } if (!VirtualPad.IsVirtual(d)) { return false; } Virtual.Add(d.deviceId); if (d.enabled) { InputSystem.DisableDevice(d, false); } REPOPS1Plugin.Log.LogInfo((object)("Controller " + ((InputControl)d).displayName + " is the mod's virtual pad: ignored.")); return true; } public static void WatchDevices() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) Enumerator<Gamepad> enumerator = Gamepad.all.GetEnumerator(); try { while (enumerator.MoveNext()) { Gamepad current = enumerator.Current; REPOPS1Plugin.Log.LogInfo((object)("Controller found: " + ((InputControl)current).displayName + " (" + ((InputControl)current).layout + ").")); } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } InputSystem.onDeviceChange += delegate(InputDevice device, InputDeviceChange change) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (device is Gamepad && (int)change == 0 && !Ours(device)) { InputDeviceDescription description = device.description; if (((InputDeviceDescription)(ref description)).interfaceName == "XInput" && Time.unscaledTime - VirtualPad.PluggedAt < 3f) { Virtual.Add(device.deviceId); InputSystem.DisableDevice(device, false); REPOPS1Plugin.Log.LogInfo((object)("Controller " + ((InputControl)device).displayName + " is the mod's virtual pad: ignored.")); } } if (device is Gamepad && ((int)change == 0 || (int)change == 1)) { REPOPS1Plugin.Log.LogInfo((object)("Controller " + (((int)change == 0) ? "connected" : "removed") + ": " + ((InputControl)device).displayName + " (" + ((InputControl)device).layout + ").")); } }; } private static void Or(ref PadState s, uint id, bool pressed) { if (pressed) { s.Buttons |= (ushort)(1 << (int)id); } } private static short Axis(float v) { return (short)Mathf.Clamp(Mathf.RoundToInt(v * 32767f), -32767, 32767); } } internal sealed class MenuItem { public string Title; public string System; public string Folder; public GameEntry Local; public int RemoteIndex = -1; public bool Join; public bool Running; } internal sealed class EmuSession : MonoBehaviour { public enum ChooseResult { Started, NeedsConfirm } public const int Ports = 4; private static readonly List<InputKey> NoKeys = new List<InputKey>(); private LibretroCore _core; private ExternalEmulator _ext; private string _extProblem; private readonly EmuVideo _video = new EmuVideo(); private EmuAudio _audio; private readonly int[] _portOwner = new int[4] { -1, -1, -1, -1 }; private readonly PadState[] _pads = new PadState[4]; private readonly Dictionary<int, PadState> _remotePads = new Dictionary<int, PadState>(); private GameObject _screen; private RawImage _picture; private AspectRatioFitter _fitter; private string _error; private string _note; private float _noteUntil; private List<GameEntry> _games = new List<GameEntry>(); private bool _scanned; private readonly List<MenuItem> _menu = new List<MenuItem>(); private int _sel; private string _playing; private string _playingPath; private GUIStyle _titleStyle; private GUIStyle _rowStyle; private GUIStyle _smallStyle; private double _acc; private float _helpUntil; private CursorLockMode _prevLock; private bool _prevVisible; private QuickMenu _quick; private EmuThread _thread; private static readonly HashSet<string> NoGpuCores = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static Material _opaque; private LibretroCore _dying; private float _dyingSince; private GameEntry _playingEntry; private GameEntry _pendingStart; public static int WantedPad = -1; private int _menuVersion = -1; private float _padAskAt; private float _playedFor; private float _libraryAt; private string _confirmKey; private float _confirmAt; private bool _menuOpen; private readonly GameShelf _shelf = new GameShelf(); private List<MenuItem> _view = new List<MenuItem>(); public static EmuSession Instance { get; private set; } public static bool Open { get; private set; } public static bool RemoteMode { get; private set; } public Texture2D ScreenTexture => _video.Texture; public int FrameCount => _video.FrameCount; public bool HasGame => Loaded; public string GameTitle { get { if (!Loaded) { return ""; } return _playing; } } public double SampleRate { get { if (_core == null) { return 44100.0; } return _core.AvInfo.Timing.SampleRate; } } public float Aspect { get { if (!((Object)(object)_fitter != (Object)null)) { return 1.3333334f; } return _fitter.aspectRatio; } } public List<GameEntry> Library => _games; private bool Loaded { get { if (_core == null) { return _ext != null; } return true; } } public static int LocalActor { get { if (!PhotonNetwork.InRoom || PhotonNetwork.LocalPlayer == null) { return 0; } return PhotonNetwork.LocalPlayer.ActorNumber; } } private static Material OpaqueMaterial { get { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_0071: Expected O, but got Unknown if ((Object)(object)_opaque != (Object)null) { return _opaque; } Shader val = Shader.Find("Unlit/Texture") ?? Shader.Find("Hidden/BlitCopy") ?? Shader.Find("UI/Default"); REPOPS1Plugin.Log.LogInfo((object)("Fullscreen picture shader: " + (((Object)(object)val != (Object)null) ? ((Object)val).name : "none"))); Material val2 = new Material(val); _opaque = val2; return val2; } } public bool HwActive { get { if (_core != null) { return _core.HwActive; } return false; } } public string GameTitleForMenu { get { if (!RemoteMode) { return _playing; } if (!((Object)(object)PS1Stream.Instance != (Object)null)) { return ""; } return PS1Stream.Instance.RemoteTitle; } } public bool Running { get; private set; } public string RunningTitle { get { if (!Loaded) { if (!RemoteMode) { return null; } return PS1Stream.Instance?.RemoteTitle; } return _playing; } } public int DiscCount { get { if (_ext != null || _core == null) { return 0; } return _core.DiscCount; } } public int LocalPad { get { if (RemoteMode) { if (!((Object)(object)PS1Stream.Instance != (Object)null)) { return -1; } return PS1Stream.Instance.MyPad; } int localActor = LocalActor; for (int i = 0; i < 4; i++) { if (_portOwner[i] == localActor) { return i; } } return -1; } } public int PortOwner(int port) { return _portOwner[port]; } private void Awake() { Instance = this; _quick = new QuickMenu(this); _audio = ((Component)this).gameObject.AddComponent<EmuAudio>(); BuildScreen(); EmuInput.WatchDevices(); } private void Update() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) FinishDying(); int num; if (Open) { if (RemoteMode) { num = 1; goto IL_0031; } if (Loaded) { num = ((!_menuOpen) ? 1 : 0); if (num != 0) { goto IL_0031; } } else { num = 0; } } else { num = 0; } goto IL_0077; IL_0098: if (num != 0 && Input.GetKeyDown((KeyCode)291)) { SaveFrame(); } else if (Input.GetKeyDown(REPOPS1Plugin.ToggleKey.Value) && (Open || PS1Room.Inside || REPOPS1Plugin.PlayAnywhere.Value) && !PS1Identity.ChatActive) { if (Open) { Close(); } else { OpenScreen(); } } else if (Open && Input.GetKeyDown((KeyCode)27)) { Close(); } if (num == 0 && _quick.IsOpen) { _quick.Close(); } if (Open) { BlockGameInput(); Cursor.lockState = (CursorLockMode)1; Cursor.visible = false; if (Input.GetKeyDown(REPOPS1Plugin.ChangeGameKey.Value) && !PS1Identity.ChatActive) { if (RemoteMode) { LeaveRemote(); } else if (Loaded) { _menuOpen = !_menuOpen; _confirmKey = null; if (_menuOpen) { RefreshMenu(rescan: true); } } } if (_quick.IsOpen) { _quick.Tick(); } else if (RemoteMode) { RemoteTick(); } else if (!Loaded || _menuOpen) { MenuInput(); } if (RemoteMode && _quick.IsOpen) { RemoteTick(); } } RunConsole(); RouteAudio(); return; IL_0077: if (_quick.IsOpen && Input.GetKeyDown((KeyCode)27)) { _quick.Close(); } goto IL_0098; IL_0031: if (PS1Identity.ChatActive || (!Input.GetKeyDown(REPOPS1Plugin.QuickMenuKey.Value) && !EmuInput.ComboPressed())) { goto IL_0077; } if (_quick.IsOpen) { _quick.Close(); } else { _quick.Open(); } goto IL_0098; } private void RunConsole() { Running = false; if (_ext != null) { RunExternal(); } else { if (_core == null) { return; } int localActor = LocalActor; if (Open && !RemoteMode && !_menuOpen && !_quick.IsOpen) { TakePort(localActor); } else { ReleasePort(localActor); } DropGonePlayers(); bool flag = false; for (int i = 0; i < 4; i++) { int num = _portOwner[i]; if (num < 0) { _pads[i] = default(PadState); continue; } flag = true; _pads[i] = ((num == localActor) ? EmuInput.Sample(Application.isFocused) : (_remotePads.TryGetValue(num, out var value) ? value : default(PadState))); } if (_thread != null) { _thread.Allow = flag; } if (!flag) { _acc = 0.0; return; } Running = true; if (_playingPath != null && ((Open && !RemoteMode) || HasRemotePlayers())) { _playedFor += Time.unscaledDeltaTime; if (_playedFor >= 5f) { PlayStats.Played(_playingPath, _playedFor); _playedFor = 0f; } } PlayStats.Tick(); double num2 = _core.AvInfo.Timing.Fps; if (num2 < 1.0) { num2 = 60.0; } double num3 = 1.0 / num2; _acc += Time.unscaledDeltaTime; int num4 = 0; while (_acc >= num3 && num4 < 4) { _acc -= num3; num4++; } if (_acc > num3 * 4.0) { _acc = 0.0; } if (_core.HwActive) { D3D11Host.Run(num4); } else if (_thread == null) { for (int j = 0; j < num4; j++) { _core.Run(); } } if (_core.ShutdownRequested) { Unload(); RefreshMenu(rescan: false); return; } if (_core.HwActive && LibretroCore.GpuFailed && _playingEntry != null) { NoGpuCores.Add(_playingEntry.CorePath); REPOPS1Plugin.Log.LogWarning((object)"This core can't draw with the GPU here; starting the game again with its software renderer."); Note("This emulator can't use the GPU here: switching it to its software renderer..."); GameEntry playingEntry = _playingEntry; LibretroCore.GpuFailed = false; Unload(); StartGame(playingEntry); return; } if (_core.AvChanged) { ApplyAvInfo(); } ApplyLayout(); _video.Smooth = REPOPS1Plugin.Smooth.Value; if (_core.HwActive) { _video.UploadHw(); } else { _video.Upload(); } if (!RemoteMode) { _picture.texture = _video.Display; ((Behaviour)_picture).enabled = (Object)(object)_video.Texture != (Object)null; ((Graphic)_picture).material = (_core.HwActive ? OpaqueMaterial : null); } } } private void RunExternal() { int localActor = LocalActor; if (Open && !RemoteMode && !_menuOpen && !_quick.IsOpen) { TakePort(localActor); } else { ReleasePort(localActor); } DropGonePlayers(); bool flag = false; PadState[] array = new PadState[4]; bool flag2 = false; for (int i = 0; i < 4; i++) { int num = _portOwner[i]; flag = flag || num >= 0; PadState value; if (num == localActor) { flag2 = true; } else if (num >= 0 && _remotePads.TryGetValue(num, out value)) { array[i] = value; } } if (flag2) { array[0] = Merge(array[0], EmuInput.Sample(Application.isFocused)); } ExternalEmulator.SetPads(array); Running = flag; if (_ext.Exited) { Note("The emulator closed."); Unload(); RefreshMenu(rescan: false); return; } _ext.Tick(flag); if (_ext.Problem != null && _ext.Problem != _extProblem) { _extProblem = _ext.Problem; Note(_extProblem); REPOPS1Plugin.Log.LogWarning((object)_extProblem); } if (flag && _playingPath != null && (_playedFor += Time.unscaledDeltaTime) >= 5f) { PlayStats.Played(_playingPath, _playedFor); _playedFor = 0f; } PlayStats.Tick(); ApplyLayout(); _video.Smooth = REPOPS1Plugin.Smooth.Value; _video.UploadHw(); Texture2D texture = _video.Texture; if ((Object)(object)texture != (Object)null && ((Texture)texture).height > 0) { _fitter.aspectRatio = (float)((Texture)texture).width / (float)((Texture)texture).height; } if (!RemoteMode) { _picture.texture = _video.Display; ((Behaviour)_picture).enabled = (Object)(object)_video.Texture != (Object)null; ((Graphic)_picture).material = OpaqueMaterial; } } public static int PlayerOfPort(uint port) { switch (port) { default: return -1; case 3u: case 4u: return (int)(port - 1); case 1u: case 2u: return 1; case 0u: return 0; } } private static PadState Merge(PadState a, PadState b) { a.Buttons |= b.Buttons; if (Math.Abs((int)b.Lx) > Math.Abs((int)a.Lx)) { a.Lx = b.Lx; } if (Math.Abs((int)b.Ly) > Math.Abs((int)a.Ly)) { a.Ly = b.Ly; } if (Math.Abs((int)b.Rx) > Math.Abs((int)a.Rx)) { a.Rx = b.Rx; } if (Math.Abs((int)b.Ry) > Math.Abs((int)a.Ry)) { a.Ry = b.Ry; } return a; } public void TakePort(int actor) { for (int i = 0; i < 4; i++) { if (_portOwner[i] == actor) { return; } } int num = ((actor == LocalActor) ? WantedPad : PS1Presence.WantOf(actor)); if (num >= 0 && num < 4 && _portOwner[num] < 0) { _portOwner[num] = actor; REPOPS1Plugin.Log.LogInfo((object)$"Pad {num + 1}: player {actor}."); return; } for (int j = 0; j < 4; j++) { if (_portOwner[j] < 0) { _portOwner[j] = actor; REPOPS1Plugin.Log.LogInfo((object)$"Pad {j + 1}: player {actor}."); break; } } } public void ReleasePort(int actor) { for (int i = 0; i < 4; i++) { if (_portOwner[i] == actor) { _portOwner[i] = -1; REPOPS1Plugin.Log.LogInfo((object)$"Pad {i + 1}: free."); } } if (actor != LocalActor) { _remotePads.Remove(actor); } } private void DropGonePlayers() { int localActor = LocalActor; for (int i = 0; i < 4; i++) { int num = _portOwner[i]; if (num >= 0 && num != localActor && ((Object)(object)PS1Stream.Instance == (Object)null || !PS1Stream.Instance.IsWatching(num))) { ReleasePort(num); } } } public void RemotePad(int actor, PadState pad) { _remotePads[actor] = pad; } private bool HasRemotePlayers() { int localActor = LocalActor; for (int i = 0; i < 4; i++) { if (_portOwner[i] >= 0 && _portOwner[i] != localActor) { return true; } } return false; } public void RemoteStart(int actor, int index) { if (Open && !RemoteMode && Loaded) { TakePort(actor); return; } EnsureLibrary(); if (index < 0 || index >= _games.Count) { return; } if (Loaded && SamePath(_games[index].Path, _playingPath)) { TakePort(actor); return; } REPOPS1Plugin.Log.LogInfo((object)$"Player {actor} started {_games[index].Title} on this PC."); StartGame(_games[index]); if (Loaded) { TakePort(actor); } } public void RemoteStop(int actor) { bool flag = false; for (int i = 0; i < 4; i++) { flag |= _portOwner[i] == actor; } if (flag && Loaded) { REPOPS1Plugin.Log.LogInfo((object)$"Player {actor} stopped the game on this PC."); Unload(); if (Open && !RemoteMode) { RefreshMenu(rescan: false); } } } public void EnsureLibrary() { if (!_scanned) { _scanned = true; } } private void RouteAudio() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) Transform tv = PS1Room.Tv; bool flag = !Open && PS1Room.Inside && (Object)(object)tv != (Object)null; _audio.SetSpatial(flag); if (flag) { ((Component)_audio).transform.position = tv.position; } _audio.Volume = ((_core != null && ((Open && !RemoteMode) || flag)) ? REPOPS1Plugin.Volume.Value : 0f); } private void EnterRemote(int startIndex) { if (!HasRemotePlayers()) { Unload(); } RemoteMode = true; if (startIndex >= 0) { PS1Stream.Instance.SendStart(startIndex); } else { PS1Stream.Instance.SendTakePad(); } _helpUntil = Time.unscaledTime + 5f; } private void LeaveRemote() { if (RemoteMode) { RemoteMode = false; PS1Stream.Instance?.SendReleasePad(); _picture.texture = null; ((Behaviour)_picture).enabled = false; if (Open) { RefreshMenu(rescan: false); } } } private void RemoteTick() { PS1Stream instance = PS1Stream.Instance; if ((Object)(object)instance == (Object)null || !instance.Connected) { LeaveRemote(); Note("The game's PC is gone."); return; } if (!instance.RemoteHasGame && Time.unscaledTime - _helpUntil > 0f) { LeaveRemote(); Note("The game was closed."); return; } instance.SendPad(_quick.IsOpen ? default(PadState) : EmuInput.Sample(Application.isFocused)); if (instance.MyPad < 0 && Time.unscaledTime >= _padAskAt) { _padAskAt = Time.unscaledTime + 2f; instance.SendTakePad(); } _picture.texture = (Texture)(object)(instance.HasViewFrame ? instance.ViewTexture : null); ((Behaviour)_picture).enabled = instance.HasViewFrame; _fitter.aspectRatio = instance.RemoteAspect; ApplyLayout(); } public void OpenScreen() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (!Open) { Open = true; _prevLock = Cursor.lockState; _prevVisible = Cursor.visible; _screen.SetActive(true); _helpUntil = Time.unscaledTime + 5f; _menuOpen = false; if (!Loaded) { RefreshMenu(rescan: true); } } } public void Close() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (Open) { LeaveRemote(); Open = false; _screen.SetActive(false); Cursor.lockState = _prevLock; Cursor.visible = _prevVisible; } } private static void BlockGameInput() { InputManager instance = InputManager.instance; if ((Object)(object)instance != (Object)null) { instance.DisableMovement(0.2f); instance.DisableAiming(0.2f); instance.DisableControlsExcept(0.2f, NoKeys); } PlayerController instance2 = PlayerController.instance; if (instance2 != null) { instance2.InputDisable(0.25f); } } public void Note(string text) { _note = text; _noteUntil = Time.unscaledTime + 4f; } private void OnGUI() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0359: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_0314: Unknown result type (might be due to invalid IL or missing references) if (!Open) { return; } if (_titleStyle == null) { _titleStyle = new GUIStyle(GUI.skin.label) { fontSize = 34, alignment = (TextAnchor)4, richText = true }; _rowStyle = new GUIStyle(GUI.skin.label) { fontSize = 22, alignment = (TextAnchor)3, richText = true, clipping = (TextClipping)1 }; _smallStyle = new GUIStyle(GUI.skin.label) { fontSize = 18, alignment = (TextAnchor)4, richText = true, wordWrap = true }; GUIStyleState normal = _titleStyle.normal; GUIStyleState normal2 = _rowStyle.normal; Color val = (_smallStyle.normal.textColor = Color.white); Color textColor = (normal2.textColor = val); normal.textColor = textColor; } if (Time.unscaledTime < _noteUntil) { GUI.Label(new Rect(0f, (float)Screen.height * 0.02f, (float)Screen.width, 30f), "<color=#FFD080>" + Escape(_note) + "</color>", _smallStyle); } string text = $"{REPOPS1Plugin.ToggleKey.Value} / Esc: back to the room {REPOPS1Plugin.ChangeGameKey.Value}: other game"; if (RemoteMode) { PS1Stream instance = PS1Stream.Instance; if (_quick.IsOpen) { _quick.Draw(); return; } if ((Object)(object)instance != (Object)null && !instance.HasViewFrame) { GUI.Label(new Rect(0f, (float)Screen.height * 0.45f, (float)Screen.width, 40f), "Connecting to the game...", _smallStyle); } if (Time.unscaledTime < _helpUntil || ((Object)(object)instance != (Object)null && instance.MyPad < 0)) { GUI.Label(new Rect(0f, 40f, (float)Screen.width, 40f), (((Object)(object)instance != (Object)null && instance.MyPad >= 0) ? $"Pad {instance.MyPad + 1}" : "All pads taken: watching") + " " + text, _smallStyle); } } else if (Loaded && !_menuOpen) { if (_quick.IsOpen) { _quick.Draw(); } else if (Time.unscaledTime < _helpUntil) { GUI.Label(new Rect(0f, 40f, (float)Screen.width, 40f), Escape(_playing) + " (" + ((_ext != null) ? "PCSX2" : (HwActive ? ("GPU " + REPOPS1Plugin.ResolutionScale.Value + "x") : "Software")) + ") " + text + $" {REPOPS1Plugin.QuickMenuKey.Value} / L3 + R3: menu", _smallStyle); } } else if (_dying != null) { GUI.Label(new Rect(0f, (float)Screen.height * 0.45f, (float)Screen.width, 40f), "Closing the game...", _smallStyle); } else { DrawMenu(); } } private void RefreshMenu(bool rescan) { if (rescan) { ((BaseUnityPlugin)REPOPS1Plugin.Instance).Config.Reload(); _scanned = false; _shelf.Refresh(); } EnsureLibrary(); _menu.Clear(); PS1Stream instance = PS1Stream.Instance; if ((Object)(object)instance != (Object)null && instance.Connected) { if (instance.RemoteHasGame) { _menu.Add(new MenuItem { Title = "Join: " + instance.RemoteTitle, System = "on " + instance.RemoteOwnerName, Join = true }); } List<RemoteGame> remoteLibrary = instance.RemoteLibrary; for (int i = 0; i < remoteLibrary.Count; i++) { _menu.Add(new MenuItem { Title = remoteLibrary[i].Title, System = remoteLibrary[i].System + " (" + instance.RemoteOwnerName + ")", Folder = remoteLibrary[i].Folder, RemoteIndex = i }); } } foreach (GameEntry game in _games) { _menu.Add(new MenuItem { Title = game.Title, System = game.System, Folder = GameLibrary.SystemFolder(game), Local = game, Running = (Loaded && SamePath(game.Path, _playingPath)) }); } _sel = Mathf.Clamp(_sel, 0, Math.Max(0, _menu.Count - 1)); } private void MenuInput() { PS1Stream instance = PS1Stream.Instance; int num = (((Object)(object)instance != (Object)null) ? instance.RemoteVersion : 0); if (num != _menuVersion) { _menuVersion = num; RefreshMenu(rescan: false); } int num2 = 0; if (Input.GetKeyDown((KeyCode)101) || EmuInput.PadPressed((Gamepad g) => g.rightShoulder)) { num2 = 1; } if (Input.GetKeyDown((KeyCode)113) || EmuInput.PadPressed((Gamepad g) => g.leftShoulder)) { num2 = -1; } if (num2 != 0) { _shelf.Tab = (_shelf.Tab + num2 + GameShelf.Tabs.Length) % GameShelf.Tabs.Length; _sel = 0; _confirmKey = null; } if (Loaded && EmuInput.PadPressed((Gamepad g) => g.buttonEast)) { _menuOpen = false; return; } _view = _shelf.Filter(_menu); if (_view.Count != 0) { int num3 = EmuInput.StickStepX(); if (Input.GetKeyDown((KeyCode)275) || Input.GetKeyDown((KeyCode)100) || EmuInput.PadPressed((Gamepad g) => g.dpad.right)) { num3 = 1; } if (Input.GetKeyDown((KeyCode)276) || Input.GetKeyDown((KeyCode)97) || EmuInput.PadPressed((Gamepad g) => g.dpad.left)) { num3 = -1; } if (num3 != 0) { _confirmKey = null; } _sel = Mathf.Clamp(_sel + num3, 0, _view.Count - 1); if ((Input.GetKeyDown((KeyCode)102) || EmuInput.PadPressed((Gamepad g) => g.buttonNorth)) && _view[_sel].Local != null) { PlayStats.ToggleFavourite(_view[_sel].Local.Path); } if ((Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271) || EmuInput.PadPressed((Gamepad g) => g.buttonSouth) || EmuInput.PadPressed((Gamepad g) => g.startButton)) && Choose(_view[_sel]) == ChooseResult.NeedsConfirm) { Note("This closes the game that is running (unsaved progress is lost). Press Enter again to start it."); } } } public ChooseResult Choose(MenuItem item) { PS1Stream instance = PS1Stream.Instance; if (item.Local != null && Loaded && SamePath(item.Local.Path, _playingPath)) { _menuOpen = false; return ChooseResult.Started; } if ((item.Join || (item.RemoteIndex >= 0 && item.Title == instance?.RemoteTitle)) && (Object)(object)instance != (Object)null && instance.RemoteHasGame) { _menuOpen = false; EnterRemote(-1); return ChooseResult.Started; } bool num = (Loaded && (item.Local != null || !HasRemotePlayers())) || (item.RemoteIndex >= 0 && (Object)(object)instance != (Object)null && instance.RemoteHasGame); string text = ((item.Local != null) ? item.Local.Path : ("remote:" + item.RemoteIndex)); if (num && (_confirmKey != text || Time.unscaledTime - _confirmAt > 6f)) { _confirmKey = text; _confirmAt = Time.unscaledTime; return ChooseResult.NeedsConfirm; } _confirmKey = null; _menuOpen = false; if (item.Local != null) { StartGame(item.Local); } else if (item.RemoteIndex >= 0) { EnterRemote(item.RemoteIndex); } return ChooseResult.Started; } public List<MenuItem> ShelfItems() { if (Time.unscaledTime - _libraryAt > 10f) { _libraryAt = Time.unscaledTime; _scanned = false; } if (!Open) { RefreshMenu(rescan: false); } List<MenuItem> list = new List<MenuItem>(); foreach (MenuItem item in _menu) { if (!item.Join) { list.Add(item); } } return list; } public Texture2D CoverFor(MenuItem m, out bool real) { return _shelf.Cover(m, out real); } private static bool SamePath(string a, string b) { if (a != null && b != null) { return string.Equals(Path.GetFullPath(a), Path.GetFullPath(b), StringComparison.OrdinalIgnoreCase); } return false; } private void DrawMenu() { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) _view = _shelf.Filter(_menu); _shelf.Draw(_view, Mathf.Clamp(_sel, 0, Math.Max(0, _view.Count - 1)), (_menu.Count == 0) ? ((_error ?? "No games.") + " Edit BepInEx/config/repops1.platform.cfg.") : null, _playing); if (_error != null && _menu.Count > 0) { GUI.Label(new Rect(0f, (float)Screen.height * 0.88f, (float)Screen.width, 30f), "<color=#FF9090>" + Escape(_error) + "</color>", _smallStyle); } } private static string Escape(string s) { return (s ?? "").Replace("<", "‹").Replace(">", "›"); } private void OnApplicationQuit() { if (_ext != null) { _ext.Stop(); _ext = null; } if (_core != null && !_core.HwActive) { Unload(); } } private void StartGame(GameEntry game) { string text = GameLibrary.MissingTrack(game.Path); if (text != null) { _error = game.Title + ": the file \"" + text + "\" is missing next to the .cue (unzip all of the game's files)."; Note(_error); return; } string text2 = ExternalFor(game); if (text2 != null && _dying == null && (_core == null || !_core.HwActive)) { Unload(); try { _ext = ExternalEmulator.Start(text2, game.Path); _extProblem = null; _error = null; _playing = game.Title; _playingPath = game.Path; _playingEntry = game; PlayStats.Played(game.Path, 0f); PlayStats.Save(); _helpUntil = Time.unscaledTime + 5f; Note("Starting " + Path.GetFileNameWithoutExtension(text2) + "..."); return; } catch (Exception ex) { REPOPS1Plugin.Log.LogError((object)("Could not start " + text2 + ": " + ex)); _error = game.Title + ": could not start " + text2 + " (" + ex.Message + ")"; Note(_error); return; } } if ((_core != null && _core.HwActive) || _dying != null) { _pendingStart = game; Unload(); return; } Unload(); string text3 = REPOPS1Plugin.SystemDirectory.Value.Trim().Trim('"'); if (text3.Length == 0) { text3 = Path.Combine(Paths.ConfigPath, "REPOPS1", "system"); } string fileName = Path.GetFileName(Path.GetDirectoryName(game.CorePath) ?? ""); string text4 = Path.Combine(text3, fileName); if (text4 != text3 && Directory.Exists(text4)) { text3 = text4; } if (ParseOptions(REPOPS1Plugin.SystemFolders.Value).TryGetValue(fileName, out var value) && Directory.Exists(value.Trim('"'))) { text3 = value.Trim('"'); } string text5 = Path.Combine(Paths.ConfigPath, "REPOPS1", "saves"); Directory.CreateDirectory(text3); Directory.CreateDirectory(text5); LibretroCore.LastCoreError = null; LibretroCore.GpuFailed = false; LibretroCore core = new LibretroCore(ParseOptions(REPOPS1Plugin.CoreOptions.Value)); core.Video = delegate(IntPtr data, int w, int h, int pitch) { _video.OnFrame(data, w, h, pitch, core.PixelFormat); }; core.AudioBatch = delegate(IntPtr data, int frames) { _audio.Push(data, frames); PS1Stream.HostAudio(data, frames); }; core.AudioSample = delegate(short l, short r) { _audio.Push(l, r); PS1Stream.HostAudio(l, r); }; core.InputState = delegate(uint port, uint device, uint index, uint id) { int num = PlayerOfPort(port); return (short)((num >= 0) ? _pads[num].Read(device, index, id) : 0); }; core.Message = Note; core.HwWanted = REPOPS1Plugin.Renderer.Value == "GPU" && !NoGpuCores.Contains(game.CorePath) && D3D11Host.Init(); if (core.HwWanted) { D3D11Host.BeforeLoad(); } core.Scale = REPOPS1Plugin.ResolutionScale.Value; core.Enhance = REPOPS1Plugin.Enhancements.Value; core.Widescreen = REPOPS1Plugin.Widescreen.Value; try { core.Load(game.CorePath, game.Path, text3, text5); _core = core; _error = null; _playing = game.Title; _playingPath = game.Path; _playingEntry = game; PlayStats.Played(game.Path, 0f); PlayStats.Save(); if (core.HwActive) { D3D11Host.Begin(core); } else { D3D11Host.AfterCore(); if (REPOPS1Plugin.EmulationThread.Value) { _thread = new EmuThread(core); } } _acc = 0.0; _audio.Clear(); _helpUntil = Time.unscaledTime + 5f; REPOPS1Plugin.Log.LogInfo((object)("Running " + Path.GetFileName(game.Path) + " on " + core.LibraryName)); } catch (Exception ex2) { REPOPS1Plugin.Log.LogError((object)("Could not start the emulator: " + ex2)); D3D11Host.AfterCore(); core.Dispose(); _error = game.Title + ": " + (LibretroCore.LastCoreError ?? ex2.Message); Note(_error); } } private static string ExternalFor(GameEntry game) { if (!ParseOptions(REPOPS1Plugin.ExternalEmulators.Value).TryGetValue(GameLibrary.SystemFolder(game), out var value)) { return null; } value = value.Trim().Trim('"'); if (File.Exists(value)) { return value; } REPOPS1Plugin.Log.LogWarning((object)("ExternalEmulators: " + value + " not found; using the core.")); return null; } private void Unload() { _playingPath = null; _menuOpen = false; if (_ext != null) { _ext.Stop(); _ext = null; } if (_core != null && _core.HwActive) { _dying = _core; _dyingSince = Time.unscaledTime; D3D11Host.Shutdown(); } else if (_core != null) { if (_thread == null || _thread.Stop(3000)) { _core.Dispose(); } else { REPOPS1Plugin.Log.LogError((object)"The emulator thread did not stop in 3 s; leaving the core loaded."); } } _thread = null; _core = null; _video.Dispose(); for (int i = 0; i < 4; i++) { _portOwner[i] = -1; } _remotePads.Clear(); _audio.Clear(); if (!RemoteMode) { _picture.texture = null; ((Behaviour)_picture).enabled = false; } } private void OnCore(Action<LibretroCore> job) { if (_core != null) { if (_core.HwActive) { D3D11Host.Enqueue(job); } else if (_thread != null) { _thread.Enqueue(job); } else { job(_core); } } } private string SlotPath(int slot) { string text = ((_playingEntry != null) ? Path.GetFileNameWithoutExtension(_playingEntry.Path) : "game"); char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { text = text.Replace(oldChar, '_'); } return Path.Combine(Paths.ConfigPath, "REPOPS1", "states", $"{text}.slot{slot}.state"); } public string SlotInfo(int slot) { string path = SlotPath(slot); if (!File.Exists(path)) { return "empty"; } return File.GetLastWriteTime(path).ToString("dd/MM HH:mm"); } public void SaveSlot(int slot) { if (_ext != null) { _ext.SaveState(); Note("Saved (PCSX2's current slot)."); return; } string path = SlotPath(slot); OnCore(delegate(LibretroCore c) { byte[] array = c.SaveState(); if (array == null) { Note("This game can't be saved right now."); } else { Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllBytes(path, array); Note($"Saved to slot {slot}."); } }); } public bool LoadSlot(int slot) { if (_ext != null) { _ext.LoadState(); Note("Loaded (PCSX2's current slot)."); return true; } string path = SlotPath(slot); if (!File.Exists(path)) { Note($"Slot {slot} is empty."); return false; } byte[] state = File.ReadAllBytes(path); OnCore(delegate(LibretroCore c) { Note(c.LoadState(state) ? $"Loaded slot {slot}." : $"Slot {slot} doesn't fit this game or core."); }); return true; } public void ResetGame() { OnCore(delegate(LibretroCore c) { c.Reset(); }); } public void NextDisc() { OnCore(delegate(LibretroCore c) { int num = c.NextDisc(); Note((num < 0) ? "Couldn't change the disc." : $"Disc {num + 1} of {c.DiscCount} is in."); }); } public void ApplyVideoSettings() { if (_core == null) { return; } Dictionary<string, string> dictionary = ParseOptions(REPOPS1Plugin.CoreOptions.Value); bool flag = false; foreach (string item in new List<string>(dictionary.Keys)) { if (LibretroCore.IsPolicyKey(item)) { dictionary.Remove(item); flag = true; } } if (flag) { REPOPS1Plugin.CoreOptions.Value = JoinOptions(dictionary); } _core.Scale = REPOPS1Plugin.ResolutionScale.Value; _core.Enhance = REPOPS1Plugin.Enhancements.Value; _core.Widescreen = REPOPS1Plugin.Widescreen.Value; _ = _core; OnCore(delegate(LibretroCore c) { c.ClearPolicyOverrides(); c.RefreshPolicy(); }); } public string CoreScaleValue() { if (_core == null) { return null; } foreach (string item in _core.OptionKeys()) { string text = item.ToLowerInvariant(); if (text.Contains("resolutionscale") || text.Contains("upscale") || text.Contains("internal_resolution")) { return _core.OptionValue(item); } } return null; } public void PowerOff() { if (RemoteMode) { LeaveRemote(); } else if (Loaded) { Note("Game turned off."); Unload(); if (Open) { RefreshMenu(rescan: true); } } } public List<string> CoreOptionKeys() { if (_core == null) { return new List<string>(); } return _core.OptionKeys(); } public string CoreOptionValue(string key) { return _core?.OptionValue(key); } public string[] CoreOptionChoices(string key) { if (_core == null) { return new string[0]; } return _core.Choices(key); } public void SetCoreOption(string key, string value) { if (_core == null) { return; } if (key.IndexOf("renderer", StringComparison.OrdinalIgnoreCase) >= 0) { Note("The renderer changes with the next game you start (Picture: Renderer). This one keeps running."); return; } OnCore(delegate(LibretroCore c) { c.SetOption(key, value); }); Dictionary<string, string> dictionary = ParseOptions(REPOPS1Plugin.CoreOptions.Value); dictionary[key] = value; REPOPS1Plugin.CoreOptions.Value = JoinOptions(dictionary); } private static string JoinOptions(Dictionary<string, string> options) { List<string> list = new List<string>(); foreach (KeyValuePair<string, string> option in options) { list.Add(option.Key + "=" + option.Value); } return string.Join("; ", list); } public void ShowGameList() { if (RemoteMode) { LeaveRemote(); return; } _menuOpen = true; _confirmKey = null; RefreshMenu(rescan: true); } private void FinishDying() { if (_dying != null) { if (D3D11Host.ShutdownDone) { _dying.Dispose(); _dying = null; D3D11Host.AfterCore(); } else if (Time.unscaledTime - _dyingSince > 10f) { REPOPS1Plugin.Log.LogError((object)"The GPU core did not shut down in 10 s; leaving it loaded."); _dying = null; } } if (_dying == null && _pendingStart != null) { GameEntry pendingStart = _pendingStart; _pendingStart = null; StartGame(pendingStart); } } private void ApplyAvInfo() { RetroSystemAvInfo avInfo = _core.AvInfo; _core.AvChanged = false; _audio.SetSourceRate(avInfo.Timing.SampleRate); RetroGameGeometry geometry = avInfo.Geometry; _fitter.aspectRatio = ((geometry.AspectRatio > 0f) ? geometry.AspectRatio : ((geometry.BaseHeight != 0) ? ((float)geometry.BaseWidth / (float)geometry.BaseHeight) : 1.3333334f)); } private void ApplyLayout() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) AspectMode val = (AspectMode)((!REPOPS1Plugin.Stretch.Value) ? 3 : 0); if (_fitter.aspectMode != val) { _fitter.aspectMode = val; Fill(((Graphic)_picture).rectTransform); } } private static Dictionary<string, string> ParseOptions(string text) { Dictionary<string, string> dictionary = new Dictionary<string, string>(); string[] array = (text ?? "").Split(';'); foreach (string text2 in array) { int num = text2.IndexOf('='); if (num > 0) { dictionary[text2.Substring(0, num).Trim()] = text2.Substring(num + 1).Trim(); } } return dictionary; } private void SaveFrame() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) Texture2D val = _video?.Texture; if ((Object)(object)val == (Object)null) { Note("No frame to save."); return; } RenderTexture temporary = RenderTexture.GetTemporary(((Texture)val).width, ((Texture)val).height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)2); RenderTexture active = RenderTexture.active; try { Graphics.Blit((Texture)(object)val, temporary); RenderTexture.active = temporary; Texture2D val2 = new Texture2D(((Texture)val).width, ((Texture)val).height, (TextureFormat)3, false); val2.ReadPixels(new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), 0, 0); val2.Apply(); string text = Path.Combine(Paths.BepInExRootPath, "REPOPS1 frame.png"); File.WriteAllBytes(text, ImageConversion.EncodeToPNG(val2)); Object.Destroy((Object)(object)val2); REPOPS1Plugin.Log.LogInfo((object)$"Frame saved: {text} ({((Texture)val).width}x{((Texture)val).height}, {((Texture)val).graphicsFormat}, colour space {QualitySettings.activeColorSpace})."); Note("Frame saved next to the log."); } catch (Exception ex) { REPOPS1Plugin.Log.LogWarning((object)("Frame save: " + ex.Message)); } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); } } private void BuildScreen() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_00f0: Unknown result type (might be due to invalid IL or missing references) _screen = new GameObject("REPOPS1 Screen"); _screen.transform.SetParent(((Component)this).transform, false); Canvas obj = _screen.AddComponent<Canvas>(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 32000; GameObject val = new GameObject("Background", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(_screen.transform, false); RawImage obj2 = val.AddComponent<RawImage>(); ((Graphic)obj2).color = Color.black; ((Graphic)obj2).raycastTarget = false; Fill(((Graphic)obj2).rectTransform); GameObject val2 = new GameObject("Picture", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(val.transform, false); _picture = val2.AddComponent<RawImage>(); _picture.uvRect = new Rect(0f, 1f, 1f, -1f); ((Graphic)_picture).raycastTarget = false; ((Behaviour)_picture).enabled = false; Fill(((Graphic)_picture).rectTransform); _fitter = val2.AddComponent<AspectRatioFitter>(); _fitter.aspectRatio = 1.3333334f; _fitter.aspectMode = (AspectMode)3; _screen.SetActive(false); } private static void Fill(RectTransform rt) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one; Vector2 offsetMin = (rt.offsetMax = Vector2.zero); rt.offsetMin = offsetMin; } } internal sealed class EmuThread { private readonly LibretroCore _core; private readonly Thread _thread; private readonly ConcurrentQueue<Action<LibretroCore>> _jobs = new ConcurrentQueue<Action<LibretroCore>>(); private volatile bool _stop; public volatile bool Allow; public EmuThread(LibretroCore core) { _core = core; _thread = new Thread(Loop) { IsBackground = true, Name = "REPOPS1 Emulator", Priority = ThreadPriority.AboveNormal }; _thread.Start(); } public void Enqueue(Action<LibretroCore> job) { _jobs.Enqueue(job); } public bool Stop(int timeoutMs) { _stop = true; return _thread.Join(timeoutMs); } private void Loop() { Stopwatch stopwatch = Stopwatch.StartNew(); double num = 0.0; while (!_stop) { Action<LibretroCore> result; while (_jobs.TryDequeue(out result)) { try { result(_core); } catch (Exception ex) { REPOPS1Plugin.Log.LogError((object)("Core job failed: " + ex.Message)); } } if (!Allow || _core.ShutdownRequested) { Thread.Sleep(4); num = stopwatch.Elapsed.TotalSeconds; continue; } double fps = _core.AvInfo.Timing.Fps; double num2 = 1.0 / ((fps > 1.0) ? fps : 60.0); double totalSeconds = stopwatch.Elapsed.TotalSeconds; if (totalSeconds < num) { double num3 = (num - totalSeconds) * 1000.0; if (num3 > 2.0) { Thread.Sleep((int)(num3 - 1.5)); } while (stopwatch.Elapsed.TotalSeconds < num) { Thread.SpinWait(64); } } try { _core.Run(); } catch (Exception ex2) { REPOPS1Plugin.Log.LogError((object)("Emulator frame failed: " + ex2.Message)); break; } num += num2; if (stopwatch.Elapsed.TotalSeconds - num > 0.25) { num = stopwatch.Elapsed.TotalSeconds; } } } } internal sealed class EmuVideo { public bool Smooth; private readonly object _lock = new object(); private byte[] _buf; private int _w; private int _h; private bool _dirty; private RenderTexture _mips; public Texture2D Texture { get; private set; } public int FrameCount { get; private set; } public Texture Display { get { if (!((Object)(object)_mips != (Object)null)) { return (Texture)(object)Texture; } return (Texture)(object)_mips; } } public void OnFrame(IntPtr data, int width, int height, int pitch, int pixelFormat) { if (data == IntPtr.Zero || width <= 0 || height <= 0) { return; } lock (_lock) { Fill(data, width, height, pitch, pixelFormat); } } private unsafe void Fill(IntPtr data, int width, int height, int pitch, int pixelFormat) { if (_buf == null || width != _w || height != _h) { _buf = new byte[width * height * 4]; _w = width; _h = height; } byte* ptr = (byte*)(void*)data; fixed (byte* buf = _buf) { for (int i = 0; i < height; i++) { byte* ptr2 = buf + i * width * 4; switch (pixelFormat) { case 1: { uint* ptr4 = (uint*)(ptr + i * pitch); int num6 = 0; while (num6 < width) { uint num7 = ptr4[num6]; *ptr2 = (byte)(num7 >> 16); ptr2[1] = (byte)(num7 >> 8); ptr2[2] = (byte)num7; ptr2[3] = byte.MaxValue; num6++; ptr2 += 4; } break; } case 2: { ushort* ptr5 = (ushort*)(ptr + i * pitch); int num8 = 0; while (num8 < width) { ushort num9 = ptr5[num8]; int num10 = num9 >> 11; int num11 = (num9 >> 5) & 0x3F; int num12 = num9 & 0x1F; *ptr2 = (byte)((num10 << 3) | (num10 >> 2)); ptr2[1] = (byte)((num11 << 2) | (num11 >> 4)); ptr2[2] = (byte)((num12 << 3) | (num12 >> 2)); ptr2[3] = byte.MaxValue; num8++; ptr2 += 4; } break; } default: { ushort* ptr3 = (ushort*)(ptr + i * pitch); int num = 0; while (num < width) { ushort num2 = ptr3[num]; int num3 = (num2 >> 10) & 0x1F; int num4 = (num2 >> 5) & 0x1F; int num5 = num2 & 0x1F; *ptr2 = (byte)((num3 << 3) | (num3 >> 2)); ptr2[1] = (byte)((num4 << 3) | (num4 >> 2)); ptr2[2] = (byte)((num5 << 3) | (num5 >> 2)); ptr2[3] = byte.MaxValue; num++; ptr2 += 4; } break; } } } } _dirty = true; } public void UploadHw() { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown Texture2D texture = Texture; if (D3D11Host.UpdateTexture(ref texture)) { FrameCount++; } Texture = texture; if ((Object)(object)Texture == (Object)null) { return; } ((Texture)Texture).filterMode = (FilterMode)(Smooth ? 1 : 0); if (((Texture)Texture).height < Screen.height * 2) { ((Texture)Texture).filterMode = (FilterMode)1; FreeMips(); return; } if ((Object)(object)_mips == (Object)null || ((Texture)_mips).width != ((Texture)Texture).width || ((Texture)_mips).height != ((Texture)Texture).height) { FreeMips(); _mips = new RenderTexture(((Texture)Texture).width, ((Texture)Texture).height, 0, (RenderTextureFormat)0) { useMipMap = true, autoGenerateMips = true, filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1, name = "REPOPS1 Screen (mips)" }; _mips.Create(); } Graphics.Blit((Texture)(object)Texture, _mips); } private void FreeMips() { if (!((Object)(object)_mips == (Object)null)) { _mips.Release(); Object.Destroy((Object)(object)_mips); _mips = null; } } public void Upload() { //IL_0068: 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) //IL_0084: Expected O, but got Unknown lock (_lock) { if (!_dirty || _buf == null) { if ((Object)(object)Texture != (Object)null) { ((Texture)Texture).filterMode = (FilterMode)(Smooth ? 1 : 0); } return; } if ((Object)(object)Texture == (Object)null) { Texture = new Texture2D(_w, _h, (TextureFormat)4, false) { name = "REPOPS1 Screen", wrapMode = (TextureWrapMode)1 }; } else if (((Texture)Texture).width != _w || ((Texture)Texture).height != _h || !((Texture)Texture).isReadable) { Texture.Reinitialize(_w, _h, (TextureFormat)4, false); } Texture.LoadRawTextureData(_buf); _dirty = false; } ((Texture)Texture).filterMode = (FilterMode)(Smooth ? 1 : 0); Texture.Apply(false); FrameCount++; } public void Dispose() { if ((Object)(object)Texture != (Object)null) { Object.Destroy((Object)(object)Texture); } Texture = null; FreeMips(); lock (_lock) { _w = (_h = 0); _buf = null; _dirty = false; } D3D11Host.ForgetTexture(); } } internal sealed class ExternalEmulator { private struct Rect { public int Left; public int Top; public int Right; public int Bottom; } private struct Point { public int X; public int Y; } private delegate bool EnumProc(IntPtr hwnd, IntPtr param); private const int VkSpace = 32; private const int VkF1 = 112; private const int VkF3 = 114; private readonly Process _process; private readonly IntPtr _repoWindow; private readonly float _startedAt; private IntPtr _window; private bool _paused; private bool _placed; private float _lastPlayed = Time.unscaledTime; private float _nextLook; private static readonly VirtualPad[] Pads = new VirtualPad[4]; private static bool _padsTried; public string Problem { get; private set; } public bool HasPicture => WindowCapture.Frames > 0; public bool Exited => _process.HasExited; private ExternalEmulator(Process p, IntPtr repo) { _process = p; _repoWindow = repo; _startedAt = Time.unscaledTime; } public static ExternalEmulator Start(string exe, string game) { IntPtr foregroundWindow = GetForegroundWindow(); try { if (!_padsTried) { _padsTried = true; for (int i = 0; i < Pads.Length; i++) { Pads[i] = VirtualPad.Create(); } } for (int j = 0; j < Pads.Length; j++) { if (Pads[j] == null || Pads[j].XInputSlot < 0) { REPOPS1Plugin.Log.LogWarning((object)$"Virtual pad {j + 1}: no XInput slot, so P{j + 1} can't play PS2."); continue; } int[] array = j switch { 1 => new int[2] { 2, 3 }, 0 => new int[1] { 1 }, _ => new int[1] { j + 2 }, }; foreach (int n in array) { BindPad(exe, n, Pads[j].XInputSlot); } } } catch (Exception ex) { REPOPS1Plugin.Log.LogWarning((object)("Virtual pad: " + ex.Message)); } Process process = Process.Start(new ProcessStartInfo(exe, "-batch -nogui -- \"" + game + "\"") { UseShellExecute = false, WorkingDirectory = (Path.GetDirectoryName(exe) ?? "") }); try { process.PriorityClass = ProcessPriorityClass.AboveNormal; } catch { } REPOPS1Plugin.Log.LogInfo((object)$"Started {Path.GetFileName(exe)} (process {process.Id}) for {Path.GetFileName(game)}."); WindowCapture.Frames = 0; WindowCapture.Failed = false; return new ExternalEmulator(process, foregroundWindow); } public void Tick(bool playing) { if (_process.HasExited) { WindowCapture.Window = IntPtr.Zero; return; } if (_window == IntPtr.Zero || !IsWindow(_window)) { _window = IntPtr.Zero; if (Time.unscaledTime >= _nextLook) { _nextLook = Time.unscaledTime + 0.25f; _window = FindWindow(_process.Id); if (_window != IntPtr.Zero) { Place(); } else if (Time.unscaledTime - _startedAt > 30f && Problem == null) { Problem = "The emulator's window never appeared."; } } } if (!(_window == IntPtr.Zero)) { if (!_placed && Time.unscaledTime - _startedAt > 3f) { Place(); } UpdateCrop(); WindowCapture.Window = _window; if (WindowCapture.Failed) { Problem = "Windows could not capture the emulator's window (see the log)."; } D3D11Host.IssueCapture(); if (playing) { _lastPlayed = Time.unscaledTime; } bool flag = Time.unscaledTime - _lastPlayed > 5f; if (HasPicture && flag != _paused) { _paused = flag; Key(32); } } } public void SaveState() { Key(112); } public void LoadState() { Key(114); } public void Stop() { WindowCapture.Window = IntPtr.Zero; D3D11Host.IssueCapture(); try { if (!_process.HasExited) { if (_window != IntPtr.Zero) { PostMessage(_window, 16u, IntPtr.Zero, IntPtr.Zero); } if (!_process.WaitForExit(3000)) { _process.Kill(); REPOPS1Plugin.Log.LogInfo((object)"The emulator did not close in 3 s: ended it."); } } } catch (Exception ex) { REPOPS1Plugin.Log.LogWarning((object)("Closing the emulator: " + ex.Message)); } finally { SetPads(null); } } public static void SetPads(PadState[] pads) { for (int i = 0; i < Pads.Length; i++) { Pads[i]?.Send((pads != null && i < pads.Length) ? pads[i] : default(PadState)); } } private static string Pcsx2Ini(string exe) { string path = Path.GetDirectoryName(exe) ?? ""; if (File.Exists(Path.Combine(path, "portable.ini")) || File.Exists(Path.Combine(path, "portable.txt"))) { return Path.Combine(path, "inis", "PCSX2.ini"); } return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "PCSX2", "inis", "PCSX2.ini"); } private static void BindPad(string exe, int n, int slot) { string text = Pcsx2Ini(exe); if (!File.Exists(text)) { REPOPS1Plugin.Log.LogWarning((object)("Virtual pad: PCSX2's settings not found at " + text)); return; } string text2 = $"XInput-{slot}/"; string text3 = $"[Pad{n}]"; string[] collection = new string[27] { text3, "Type = DualShock2", "Up = " + text2 + "DPadUp", "Right = " + text2 + "DPadRight", "Down = " + text2 + "DPadDown", "Left = " + text2 + "DPadLeft", "Triangle = " + text2 + "Y", "Circle = " + text2 + "B", "Cross = " + text2 + "A", "Square = " + text2 + "X", "Select = " + text2 + "Back", "Start = " + text2 + "Start", "L1 = " + text2 + "LeftShoulder", "L2 = " + text2 + "+LeftTrigger", "R1 = " + text2 + "RightShoulder", "R2 = " + text2 + "+RightTrigger", "L3 = " + text2 + "LeftStick", "R3 = " + text2 + "RightStick", "LUp = " + text2 + "-LeftY", "LRight = " + text2 + "+LeftX", "LDown = " + text2 + "+LeftY", "LLeft = " + text2 + "-LeftX", "RUp = " + text2 + "-RightY", "RRight = " + text2 + "+RightX", "RDown = " + text2 + "+RightY", "RLeft = " + text2 + "-RightX", "" }; List<string> list = new List<string>(); string text4 = null; bool flag = false; string[] array = File.ReadAllLines(text); foreach (string text5 in array) { string text6 = text5.Trim(); if (text6.StartsWith("[") && text6.EndsWith("]")) { text4 = text6; if (text4 == text3) { list.AddRange(collection); flag = true; } } if (!(text4 == text3)) { if (text4 == "[Pad]" && text6.StartsWith("MultitapPort1")) { list.Add("MultitapPort1 = true"); } else if ((text4 == "[InputSources]" && text6.StartsWith("XInput ")) || (text4 == "[InputSources]" && text6.StartsWith("XInput="))) { list.Add("XInput = true"); } else { list.Add(text5); } } } if (!flag) { list.AddRange(collection); } File.WriteAllLines(text, list); REPOPS1Plugin.Log.LogInfo((object)$"PCSX2 pad {n} set to virtual pad (XInput {slot})."); } private void Place() { _placed = Time.unscaledTime - _startedAt > 3f; long num = GetWindowLongPtr(_window, -16).ToInt64(); num &= -13565953; num |= 0x80000000u; SetWindowLongPtr(_window, -16, new IntPtr(num)); IntPtr after = ((_repoWindow != IntPtr.Zero) ? _repoWindow : new IntPtr(1)); SetWindowPos(_window, after, 0, 0, Screen.width, Screen.height, 48u); if (_repoWindow != IntPtr.Zero) { SetForegroundWindow(_repoWindow); } } private void UpdateCrop() { if (GetClientRect(_window, out var rect)) { Point p = default(Point); ClientToScreen(_window, ref p); if (DwmGetWindowAttribute(_window, 9, out var value, Marshal.SizeOf<Rect>()) != 0) { GetWindowRect(_window, out value); } WindowCapture.CropX = Math.Max(0, p.X - value.Left); WindowCapture.CropY = Math.Max(0, p.Y - value.Top); WindowCapture.CropW = rect.Right - rect.Left; WindowCapture.CropH = rect.Bottom - rect.Top; } } private void Key(int vk) { if (!(_window == IntPtr.Zero)) { uint num = MapVirtualKey((uint)vk, 0u); PostMessage(_window, 256u, new IntPtr(vk), new IntPtr(1 | (num << 16))); PostMessage(_window, 257u, new IntPtr(vk), new IntPtr((int)(1 | (num << 16)) | -1073741824)); } } private static IntPtr FindWindow(int pid) { IntPtr best = IntPtr.Zero; long bestArea = 0L; EnumWindows(delegate(IntPtr h, IntPtr _) { GetWindowThreadProcessId(h, out var pid2); if (pid2 != pid || !IsWindowVisible(h)) { return true; } if (!GetWindowRect(h, out var rect)) { return true; } long num = (long)(rect.Right - rect.Left) * (long)(rect.Bottom - rect.Top); if (num > bestArea && num > 40000) { best = h; bestArea = num; } return true; }, IntPtr.Zero); return best; } [DllImport("user32")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32")] private static extern bool SetForegroundWindow(IntPtr hwnd); [DllImport("user32")] private static extern bool EnumWindows(EnumProc proc, IntPtr param); [DllImport("user32")] private static extern uint GetWindowThreadProcessId(IntPtr hwnd, out int pid); [DllImport("user32")] private static extern bool IsWindowVisible(IntPtr hwnd); [DllImport("user32")] private static extern bool IsWindow(IntPtr hwnd); [DllImport("user32")] private static extern bool GetWindowRect(IntPtr hwnd, out Rect rect); [DllImport("user32")] private static extern bool GetClientRect(IntPtr hwnd, out Rect rect); [DllImport("user32")] private static extern bool ClientToScreen(IntPtr hwnd, ref Point p); [DllImport("user32", CharSet = CharSet.Unicode)] private static extern int GetWindowText(IntPtr hwnd, StringBuilder text, int max); [DllImport("user32")] private static extern IntPtr GetWindowLongPtr(IntPtr hwnd, int index); [DllImport("user32")] private static extern IntPtr SetWindowLongPtr(IntPtr hwnd, int index, IntPtr value); [DllImport("user32")] private static extern bool SetWindowPos(IntPtr hwnd, IntPtr after, int x, int y, int w, int h, uint flags); [DllImport("user32")] private static extern bool PostMessage(IntPtr hwnd, uint msg, IntPtr w, IntPtr l); [DllImport("user32")] private static extern uint MapVirtualKey(uint code, uint type); [DllImport("dwmapi")] private static extern int DwmGetWindowAttribute(IntPtr hwnd, int attr, out Rect value, int size); } internal sealed class GameEntry { public string Path; public string Title; public string CorePath; public string System; } internal static class GameLibrary { private sealed class CoreInfo { public string Path; public string Name; public HashSet<string> Extensions; } private const int MaxGames = 1000; private const int MaxDepth = 4; private static readonly string[] IndexExtensions = new string[4] { ".cue", ".m3u", ".ccd", ".gdi" }; private static readonly Dictionary<string, CoreInfo> CoreCache = new Dictionary<string, CoreInfo>(StringComparer.OrdinalIgnoreCase); private static readonly Regex DiscName = new Regex("^(.*?)\\s*\\(Disc (\\d+)\\)(.*)$", RegexOptions.IgnoreCase); public static List<GameEntry> Scan(out string problem) { problem = null; string problem2; List<CoreInfo> cores = LoadCores(out problem2); List<GameEntry> list = new List<GameEntry>(); string text = Clean(REPOPS1Plugin.GamePath.Value); if (text.Length > 0 && File.Exists(text)) { Add(list, text, cores); } List<string> list2 = GameRoots(); foreach (string item in list2) { Walk(item, 0, list, cores); } string text2 = ((list2.Count > 0) ? list2[0] : ""); list = (from g in list.GroupBy<GameEntry, string>((GameEntry g) => g.Path, StringComparer.OrdinalIgnoreCase) select g.First()).ToList(); list = (from g in JoinDiscs(list, cores) orderby g.System select g).ThenBy<GameEntry, string>((GameEntry g) => g.Title, StringComparer.OrdinalIgnoreCase).ToList(); if (list.Count == 0) { problem = problem2 ?? ((text2.Length == 0 && text.Length == 0) ? "Set GamesFolder (your own games) and CoresFolder (libretro cores) in the config." : "No games found that a core in CoresFolder can play."); } return list; } private static List<GameEntry> JoinDiscs(List<GameEntry> games, List<CoreInfo> cores) { List<GameEntry> list = new List<GameEntry>(); foreach (IGrouping<string, GameEntry> item in games.GroupBy<GameEntry, string>(delegate(GameEntry g) { Match match2 = DiscName.Match(g.Title); return (!match2.Success) ? ("\0" + g.Path) : (Path.GetDirectoryName(g.Path) + "|" + g.CorePath + "|" + (match2.Groups[1].Value + match2.Groups[3].Value).Trim()); }, StringComparer.OrdinalIgnoreCase)) { List<GameEntry> list2 = item.ToList(); CoreInfo coreInfo = cores.FirstOrDefault((CoreInfo c) => string.Equals(c.Path, list2[0].CorePath, StringComparison.OrdinalIgnoreCase)); if (list2.Count < 2 || item.Key[0] == '\0' || coreInfo == null || !coreInfo.Extensions.Contains("m3u")) { list.AddRange(list2); continue; } list2 = list2.OrderBy((GameEntry g) => int.Parse(DiscName.Match(g.Title).Groups[2].Value)).ToList(); Match match = DiscName.Match(