Decompiled source of REPO Game Room v1.0.0

REPOPS1.dll

Decompiled 2 weeks ago
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(