Decompiled source of CafeLink v1.0.4

plugins/io.github.nonperforming.cafelink/io.github.nonperforming.cafelink.dll

Decompiled 7 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PulseLib.Cafe;
using PulseLib.Cafe.Models;
using PulseLib.Extensions;
using RDLevelEditor;
using RhythmDoctor.CafeLink.Patches;
using RhythmDoctor.CafeLink.Server;
using RhythmDoctor.CafeLink.Server.Handlers;
using RhythmDoctor.CafeLink.Server.Interfaces;
using RhythmDoctor.CafeLink.Server.Responses;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("io.github.nonperforming.cafelink")]
[assembly: AssemblyConfiguration("Release-BepInEx5")]
[assembly: AssemblyDescription("Additional support for rhythm.cafe")]
[assembly: AssemblyFileVersion("1.0.4.0")]
[assembly: AssemblyInformationalVersion("1.0.4+f0284d4a57a7f447916c333e19424954a18e6870")]
[assembly: AssemblyProduct("RhythmDoctor.CafeLink")]
[assembly: AssemblyTitle("io.github.nonperforming.cafelink")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.4.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
[CompilerGenerated]
internal sealed class <>z__ReadOnlySingleElementList<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>
{
	private sealed class Enumerator : IDisposable, IEnumerator, IEnumerator<T>
	{
		object IEnumerator.Current => _item;

		T IEnumerator<T>.Current => _item;

		public Enumerator(T item)
		{
			_item = item;
		}

		bool IEnumerator.MoveNext()
		{
			if (!_moveNextCalled)
			{
				return _moveNextCalled = true;
			}
			return false;
		}

		void IEnumerator.Reset()
		{
			_moveNextCalled = false;
		}

		void IDisposable.Dispose()
		{
		}
	}

	int ICollection.Count => 1;

	bool ICollection.IsSynchronized => false;

	object ICollection.SyncRoot => this;

	object IList.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	bool IList.IsFixedSize => true;

	bool IList.IsReadOnly => true;

	int IReadOnlyCollection<T>.Count => 1;

	T IReadOnlyList<T>.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
	}

	int ICollection<T>.Count => 1;

	bool ICollection<T>.IsReadOnly => true;

	T IList<T>.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	public <>z__ReadOnlySingleElementList(T item)
	{
		_item = item;
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		return new Enumerator(_item);
	}

	void ICollection.CopyTo(Array array, int index)
	{
		array.SetValue(_item, index);
	}

	int IList.Add(object value)
	{
		throw new NotSupportedException();
	}

	void IList.Clear()
	{
		throw new NotSupportedException();
	}

	bool IList.Contains(object value)
	{
		return EqualityComparer<T>.Default.Equals(_item, (T)value);
	}

	int IList.IndexOf(object value)
	{
		if (!EqualityComparer<T>.Default.Equals(_item, (T)value))
		{
			return -1;
		}
		return 0;
	}

	void IList.Insert(int index, object value)
	{
		throw new NotSupportedException();
	}

	void IList.Remove(object value)
	{
		throw new NotSupportedException();
	}

	void IList.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}

	IEnumerator<T> IEnumerable<T>.GetEnumerator()
	{
		return new Enumerator(_item);
	}

	void ICollection<T>.Add(T item)
	{
		throw new NotSupportedException();
	}

	void ICollection<T>.Clear()
	{
		throw new NotSupportedException();
	}

	bool ICollection<T>.Contains(T item)
	{
		return EqualityComparer<T>.Default.Equals(_item, item);
	}

	void ICollection<T>.CopyTo(T[] array, int arrayIndex)
	{
		array[arrayIndex] = _item;
	}

	bool ICollection<T>.Remove(T item)
	{
		throw new NotSupportedException();
	}

	int IList<T>.IndexOf(T item)
	{
		if (!EqualityComparer<T>.Default.Equals(_item, item))
		{
			return -1;
		}
		return 0;
	}

	void IList<T>.Insert(int index, T item)
	{
		throw new NotSupportedException();
	}

	void IList<T>.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}
}
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RhythmDoctor.CafeLink
{
	[Flags]
	public enum BusyStatus
	{
		None = 0,
		CleaningUpTransientPlay = 1,
		Importing = 2,
		ProcessingTransient = 4
	}
	internal class Configuration
	{
		internal ConfigEntry<bool> AllowRemoteRequests;

		internal Configuration(ConfigFile config)
		{
			Plugin.Logger.LogDebug((object)"[Configuration] Binding configuration");
			AllowRemoteRequests = config.Bind<bool>("Security", "AllowRemoteRequests", false, "Allows other devices on the network to import/play songs.");
			Plugin.Logger.LogDebug((object)"[Configuration] Binding done");
		}
	}
	[BepInPlugin("io.github.nonperforming.cafelink", "RhythmDoctor.CafeLink", "1.0.4")]
	[BepInDependency("io.github.nonperforming.pulse", "0.0.6")]
	[BepInProcess("Rhythm Doctor.exe")]
	public class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Logger;

		internal static Configuration Configuration;

		private static volatile int _status;

		private readonly Harmony _harmony = new Harmony("io.github.nonperforming.cafelink");

		private RhythmDoctor.CafeLink.Server.Server _server;

		[CompilerGenerated]
		private static List<CustomLevelData> <LevelsData>k__BackingField;

		internal static BusyStatus Status => (BusyStatus)_status;

		private static List<CustomLevelData> LevelsData
		{
			get
			{
				scnBase instance = scnBase.instance;
				scnCLS val = (scnCLS)(object)((instance is scnCLS) ? instance : null);
				if (val != null)
				{
					<LevelsData>k__BackingField = new List<CustomLevelData>(val.levelsData);
				}
				return <LevelsData>k__BackingField;
			}
			[CompilerGenerated]
			set
			{
				<LevelsData>k__BackingField = value;
			}
		}

		private void Awake()
		{
			Logger = ((BaseUnityPlugin)this).Logger;
			Logger.LogInfo((object)"[Plugin] Loading RhythmDoctor.CafeLink version 1.0.4");
			Configuration = new Configuration(((BaseUnityPlugin)this).Config);
			Logger.LogInfo((object)"[Plugin] Cleaning up any prior transient plays");
			Task.Run((Action)CleanupTransientPlay);
			Logger.LogInfo((object)"[Plugin] Creating Server");
			_server = new RhythmDoctor.CafeLink.Server.Server(2615);
			Logger.LogInfo((object)"[Plugin] Applying DirectImportPatch");
			_harmony.PatchAll(typeof(DirectImportPatch));
			Logger.LogInfo((object)"[Plugin] Applying HandleCafeSchemePatch");
			_harmony.PatchAll(typeof(HandleCafeSchemePatch));
			Logger.LogInfo((object)"[Plugin] Applying HandleStatusPatch");
			_harmony.PatchAll(typeof(HandleStatusPatch));
			Logger.LogInfo((object)"[Plugin] RhythmDoctor.CafeLink loaded!");
		}

		private void Start()
		{
			Logger.LogInfo((object)"[Plugin] Building LevelsData");
			StubbedScnCLS stubbedScnCLS = ((Component)this).gameObject.AddComponent<StubbedScnCLS>();
			((scnCLS)stubbedScnCLS).levelsData = new List<CustomLevelData>();
			scnBase instance = scnBase._instance;
			scnBase._instance = (scnBase)(object)stubbedScnCLS;
			DesktopLevelLoader.InitializeCacheLevelsDict();
			DesktopLevelLoader.LoadLevelsPreviewFromPath(LevelValidation.CustomLevelsPath);
			LevelsData = new List<CustomLevelData>(((scnCLS)stubbedScnCLS).levelsData);
			scnBase._instance = instance;
			Object.DestroyImmediate((Object)(object)stubbedScnCLS);
			Logger.LogInfo((object)"[Plugin] Built LevelsData");
		}

		internal static async Task PlayTransient(string uri, bool twoPlayer)
		{
			Logger.LogInfo((object)string.Format("[{0}] Playing {1} transiently (2P: {2})...", "Plugin", uri, twoPlayer));
			using HttpClient client = new HttpClient();
			client.DefaultRequestHeaders.UserAgent.Add(ProductInfoHeaderValue.Parse("RhythmDoctor.CafeLink/1.0.4"));
			if (uri.StartsWith("cafe://"))
			{
				try
				{
					string text = uri.Split('/').Last();
					Logger.LogInfo((object)("[Plugin] Getting level data of cafe level " + text + "..."));
					LevelData? val = await Cafe.GetLevelData(text);
					if (!val.HasValue)
					{
						Logger.LogError((object)"[Plugin] Could not get level data (is level ID valid?)");
						throw new ArgumentException("Could not get level data");
					}
					LevelData value = val.Value;
					if (TryFindLevelWithHash(((LevelData)(ref value)).RDMD5, out CustomLevelData customLevelData))
					{
						Logger.LogWarning((object)"[Plugin] Found already imported level, going to that instead");
						Task.Run((Action)CleanupTransientPlay);
						scnBaseExtensions.GoToLevelWithCustomLevelData(customLevelData, twoPlayer);
						scnBase.currentLevelSelect = "scnCLS";
						return;
					}
					value = val.Value;
					uri = ((LevelData)(ref value)).RDZipUrl.AbsoluteUri;
				}
				catch (Exception ex)
				{
					Logger.LogError((object)ex);
					throw;
				}
			}
			if (!TrySetStatusFlag(BusyStatus.ProcessingTransient))
			{
				Logger.LogWarning((object)("[Plugin] Ignoring request to play transient " + uri + " because we are processing one already"));
				return;
			}
			Logger.LogWarning((object)"[Plugin] Waiting for prior transient play to be cleaned up...");
			Task.Run((Action)CleanupTransientPlay);
			await Task.Run(async delegate
			{
				while (Status.HasFlag(BusyStatus.CleaningUpTransientPlay))
				{
					await Task.Delay(500);
				}
			});
			Logger.LogWarning((object)"[Plugin] Prior play cleaned up");
			DirectoryInfo transientDir = null;
			try
			{
				Stream stream = null;
				try
				{
					Logger.LogInfo((object)"[Plugin] Trying to get stream");
					stream = await client.GetStreamAsync(uri);
				}
				catch
				{
					Logger.LogError((object)"[Plugin] Failed to get stream, abandoning");
					if (stream != null)
					{
						await stream.DisposeAsync();
					}
					throw;
				}
				Logger.LogInfo((object)"[Plugin] Got stream");
				string text2 = Path.Combine(LevelValidation.TempLevelsFolder, "cafelink-transient");
				transientDir = Directory.CreateDirectory(text2);
				Logger.LogInfo((object)("[Plugin] Extracting to " + text2));
				using (ZipArchive source = new ZipArchive(stream, ZipArchiveMode.Read))
				{
					source.ExtractToDirectory(text2);
				}
				Logger.LogInfo((object)("[Plugin] Extracted to " + text2));
				Logger.LogDebug((object)"[Plugin] Attempting to get RDLevelData");
				RDLevelData val2 = LevelBase.DecodeLevelData(RDFile.ReadAllText(Path.Join(text2.AsSpan(), "main.rdlevel".AsSpan()), (Encoding)null));
				if (val2 == null)
				{
					Logger.LogError((object)"[Plugin] Could not get RDLevelData");
					Task.Run((Action)CleanupTransientPlay);
					return;
				}
				if (TryGoToLevelWithHash(RDUtils.GetHash(new string[3]
				{
					val2.settings.author,
					val2.settings.artist,
					val2.settings.song
				}), twoPlayer))
				{
					Logger.LogWarning((object)"[Plugin] Found already imported level, going to that instead");
					Task.Run((Action)CleanupTransientPlay);
					scnBase.currentLevelSelect = "scnCLS";
				}
				string text3 = ((twoPlayer && (int)val2.settings.canBePlayedOn == 3 && !string.IsNullOrEmpty(val2.settings.separate2PLevelFilename)) ? Path.Join(text2.AsSpan(), val2.settings.separate2PLevelFilename.AsSpan()) : Path.Join(text2.AsSpan(), "main.rdlevel".AsSpan()));
				Logger.LogInfo((object)("[Plugin] Going to " + text3));
				scnGame.goToScnMenuAfterHelpingHands = true;
				scnBase.GoToLevelWithExternalPath(text3);
			}
			catch (Exception ex2)
			{
				Logger.LogError((object)ex2);
				transientDir?.Delete(recursive: true);
				throw;
			}
			finally
			{
				ClearStatusFlag(BusyStatus.ProcessingTransient);
			}
		}

		internal static void CleanupTransientPlay()
		{
			if (!TrySetStatusFlag(BusyStatus.CleaningUpTransientPlay))
			{
				Logger.LogWarning((object)"[Plugin] Transient play is already being cleaned up");
				return;
			}
			try
			{
				DirectoryInfo directoryInfo = new DirectoryInfo(Path.Combine(LevelValidation.TempLevelsFolder, "cafelink-transient"));
				try
				{
					Logger.LogInfo((object)"[Plugin] Cleaning up cafelink-transient folder");
					directoryInfo.Delete(recursive: true);
					Logger.LogInfo((object)"[Plugin] Cleaned up cafelink-transient folder");
				}
				catch (DirectoryNotFoundException)
				{
				}
			}
			catch (Exception ex2)
			{
				Logger.LogError((object)ex2);
				throw;
			}
			finally
			{
				ClearStatusFlag(BusyStatus.CleaningUpTransientPlay);
			}
		}

		internal static bool TrySetStatusFlag(BusyStatus flag)
		{
			Logger.LogDebug((object)string.Format("[{0}] Setting status flag {1}", "Plugin", flag));
			BusyStatus status;
			BusyStatus value;
			do
			{
				status = (BusyStatus)_status;
				if ((status & flag) != BusyStatus.None)
				{
					return false;
				}
				value = status | flag;
			}
			while (Interlocked.CompareExchange(ref _status, (int)value, (int)status) != (int)status);
			return true;
		}

		internal static void ClearStatusFlag(BusyStatus flag)
		{
			Logger.LogDebug((object)string.Format("[{0}] Clearing status flag {1}", "Plugin", flag));
			BusyStatus status;
			BusyStatus value;
			do
			{
				status = (BusyStatus)_status;
				value = status & ~flag;
			}
			while (Interlocked.CompareExchange(ref _status, (int)value, (int)status) != (int)status);
		}

		internal static bool TryFindLevelWithHash(string hash, [NotNullWhen(true)] out CustomLevelData? customLevelData, List<CustomLevelData>? levelsDataOverride = null)
		{
			customLevelData = ((levelsDataOverride == null) ? LevelsData.Find((CustomLevelData levelData) => levelData.Hash == hash) : levelsDataOverride.Find((CustomLevelData levelData) => levelData.Hash == hash));
			return customLevelData != null;
		}

		internal static bool TryGoToLevelWithHash(string hash, bool twoPlayer = false)
		{
			if (!TryFindLevelWithHash(hash, out CustomLevelData customLevelData))
			{
				return false;
			}
			scnBaseExtensions.GoToLevelWithCustomLevelData(customLevelData, twoPlayer);
			return true;
		}
	}
	internal class StubbedScnCLS : scnCLS
	{
		private void Awake()
		{
		}

		private void Start()
		{
		}

		private void Update()
		{
		}

		private void LateUpdate()
		{
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "io.github.nonperforming.cafelink";

		public const string PLUGIN_NAME = "RhythmDoctor.CafeLink";

		public const string PLUGIN_VERSION = "1.0.4";
	}
}
namespace RhythmDoctor.CafeLink.Server
{
	internal class Server
	{
		internal const uint PROTOCOL_VERSION = 0u;

		private static readonly string[] ValidHosts = new string[1] { "127.0.0.1" };

		private readonly HttpListener _listener;

		private ushort _port;

		private readonly Dictionary<string, IHandler> _handlers = new Dictionary<string, IHandler>();

		internal Server(ushort port)
		{
			Plugin.Logger.LogInfo((object)string.Format("[{0}] Creating server for port {1}", "Server", port));
			_port = port;
			if (!HttpListener.IsSupported)
			{
				Plugin.Logger.LogError((object)"[Server] HttpListener is not supported");
				throw new NotSupportedException("HttpListener is not supported.");
			}
			_listener = new HttpListener();
			string[] validHosts = ValidHosts;
			foreach (string text in validHosts)
			{
				Plugin.Logger.LogInfo((object)string.Format("[{0}] Binding to http:{1}:{2}/", "Server", text, port));
				_listener.Prefixes.Add($"http://{text}:{port}/");
			}
			IHandler[] array = new IHandler[3]
			{
				new ImportHandler(),
				new PlayHandler(),
				new StatusHandler()
			};
			foreach (IHandler handler in array)
			{
				Plugin.Logger.LogInfo((object)("[Server] Adding handler for " + handler.Endpoint));
				_handlers.Add(handler.Endpoint, handler);
			}
			Plugin.Logger.LogInfo((object)"[Server] Started");
			Task.Run((Func<Task?>)HandleRequest);
		}

		private async Task HandleRequest()
		{
			Plugin.Logger.LogInfo((object)"[Server] Waiting for request...");
			_listener.Start();
			HttpListenerContext httpListenerContext = await _listener.GetContextAsync();
			HttpListenerRequest request = httpListenerContext.Request;
			HttpListenerResponse httpResponse = httpListenerContext.Response;
			httpResponse.KeepAlive = false;
			httpResponse.ContentEncoding = Encoding.UTF8;
			Plugin.Logger.LogInfo((object)"[Server] Handling request...");
			if (!request.IsLocal && !Plugin.Configuration.AllowRemoteRequests.Value)
			{
				Plugin.Logger.LogWarning((object)string.Format("[{0}] Blocked request from remote {1}", "Server", request.RemoteEndPoint));
				httpResponse.StatusCode = 403;
			}
			else if (request.AcceptTypes == null || (!request.AcceptTypes.Contains("*/*") && !request.AcceptTypes.Contains("application/json")))
			{
				Plugin.Logger.LogWarning((object)("[Server] Unacceptable Accept types: " + string.Join(",", request.AcceptTypes ?? new string[1] { "(null)" })));
				httpResponse.StatusCode = 406;
			}
			else
			{
				string text = null;
				string[] segments = request.Url.Segments;
				foreach (string text2 in segments)
				{
					if (!(text2 == "/"))
					{
						text = text2.Replace("/", "");
						break;
					}
				}
				IResponse response;
				if (string.IsNullOrEmpty(text))
				{
					Plugin.Logger.LogWarning((object)"[Server] No endpoint specified in request.");
					response = new ErrorResponse(HttpStatusCode.BadRequest, "No endpoint specified.");
				}
				else
				{
					Plugin.Logger.LogInfo((object)("[Server] Handling '" + text + "' request..."));
					if (_handlers.TryGetValue(text, out IHandler value))
					{
						IResponse response2;
						if (!(value.AcceptedMethod.Method != request.HttpMethod))
						{
							response2 = value.HandleRequest(httpListenerContext.Request.QueryString);
						}
						else
						{
							IResponse response3 = new ErrorResponse(HttpStatusCode.MethodNotAllowed, "Expected " + value.AcceptedMethod.Method + " method.");
							response2 = response3;
						}
						response = response2;
					}
					else
					{
						Plugin.Logger.LogWarning((object)("[Server] Unknown endpoint '" + text + "'."));
						response = new ErrorResponse(HttpStatusCode.NotFound, "Unknown endpoint.");
					}
				}
				IReadOnlyCollection<byte> data = response.Data;
				httpResponse.AddHeader("Access-Control-Allow-Origin", "https://rhythm.cafe");
				httpResponse.StatusCode = (int)response.StatusCode;
				httpResponse.ContentType = response.ContentType;
				httpResponse.ContentLength64 = data.Count;
				await httpResponse.OutputStream.WriteAsync(data.ToArray(), 0, data.Count);
			}
			httpResponse.Close();
			_listener.Stop();
			Task.Run((Func<Task?>)HandleRequest);
		}
	}
}
namespace RhythmDoctor.CafeLink.Server.Responses
{
	internal class ErrorResponse : IResponse
	{
		public HttpStatusCode StatusCode { get; } = HttpStatusCode.BadRequest;

		public IReadOnlyCollection<byte> Data { get; }

		internal ErrorResponse()
		{
			Data = (IReadOnlyCollection<byte>)(object)"{\"status\":\"error\",\"reason\":\"null\"}"u8.ToArray();
		}

		internal ErrorResponse(HttpStatusCode statusCode, string reason)
		{
			StatusCode = statusCode;
			Data = (IReadOnlyCollection<byte>)(object)Encoding.UTF8.GetBytes("{\"status\":\"error\",\"reason\":\"" + reason + "\"}");
		}

		internal ErrorResponse(string reason)
		{
			Data = (IReadOnlyCollection<byte>)(object)Encoding.UTF8.GetBytes("{\"status\":\"error\",\"reason\":\"" + reason + "\"}");
		}
	}
	public class ImportResponse : IResponse
	{
		private readonly IList<IResponse> _results = new List<IResponse>();

		private int _badCount;

		public HttpStatusCode StatusCode
		{
			get
			{
				if (_results.Count != _badCount)
				{
					return HttpStatusCode.Accepted;
				}
				return HttpStatusCode.BadRequest;
			}
		}

		public IReadOnlyCollection<byte> Data
		{
			get
			{
				ReadOnlySpan<byte> readOnlySpan = "["u8;
				List<byte> list = new List<byte>(readOnlySpan.Length);
				ReadOnlySpan<byte>.Enumerator enumerator = readOnlySpan.GetEnumerator();
				while (enumerator.MoveNext())
				{
					byte current = enumerator.Current;
					list.Add(current);
				}
				List<byte> list2 = list;
				foreach (IResponse result in _results)
				{
					list2.AddRange(result.Data);
					list2.AddRange(","u8.ToArray());
				}
				list2[list2.Count - 1] = 93;
				return list2;
			}
		}

		internal void AddResult(IResponse result)
		{
			_results.Add(result);
			if (result is ErrorResponse)
			{
				_badCount++;
			}
		}
	}
	internal class StatusResponse : IResponse
	{
		public HttpStatusCode StatusCode => HttpStatusCode.OK;

		public required IReadOnlyCollection<byte> Data { get; init; }
	}
	internal class SuccessResponse : IResponse
	{
		public HttpStatusCode StatusCode => HttpStatusCode.OK;

		public IReadOnlyCollection<byte> Data => (IReadOnlyCollection<byte>)(object)"{\"status\":\"ok\"}"u8.ToArray();
	}
}
namespace RhythmDoctor.CafeLink.Server.Interfaces
{
	internal interface IHandler
	{
		internal string Endpoint { get; }

		HttpMethod AcceptedMethod { get; }

		internal IResponse HandleRequest(NameValueCollection parameters);
	}
	public interface IResponse
	{
		HttpStatusCode StatusCode { get; }

		string ContentType => "application/json";

		IReadOnlyCollection<byte> Data { get; }
	}
}
namespace RhythmDoctor.CafeLink.Server.Handlers
{
	internal class ImportHandler : IHandler
	{
		public string Endpoint => "import";

		public HttpMethod AcceptedMethod => HttpMethod.Post;

		public IResponse HandleRequest(NameValueCollection parameters)
		{
			if (!RDStartup.hasInitialized)
			{
				return new ErrorResponse(HttpStatusCode.ServiceUnavailable, "Not initialized.");
			}
			if (Plugin.Status.HasFlag(BusyStatus.Importing))
			{
				return new ErrorResponse(HttpStatusCode.ServiceUnavailable, "Already importing something.");
			}
			if (Plugin.Status.HasFlag(BusyStatus.ProcessingTransient))
			{
				return new ErrorResponse(HttpStatusCode.ServiceUnavailable, "Currently processing prior request to play level transiently.");
			}
			string[] array = parameters["uri"].Split(',');
			ImportResponse importResponse = new ImportResponse();
			string[] array2 = array;
			foreach (string text in array2)
			{
				Plugin.Logger.LogDebug((object)("[Server] Checking URI " + text));
				if (string.IsNullOrEmpty(text))
				{
					Plugin.Logger.LogError((object)("[Server] URI " + text + " is empty"));
					importResponse.AddResult(new ErrorResponse(HttpStatusCode.BadRequest, "URI is empty."));
					continue;
				}
				if (!Uri.TryCreate(text, UriKind.Absolute, out Uri result))
				{
					Plugin.Logger.LogError((object)("[Server] URI " + text + " is bad"));
					importResponse.AddResult(new ErrorResponse(HttpStatusCode.BadRequest, "Bad URI given."));
					continue;
				}
				bool flag;
				switch (result.Scheme)
				{
				case "http":
				case "https":
				case "cafe":
					flag = true;
					break;
				default:
					flag = false;
					break;
				}
				if (!flag)
				{
					Plugin.Logger.LogError((object)("[Server] URI " + text + "'s scheme is not http[s]/cafe"));
					importResponse.AddResult(new ErrorResponse(HttpStatusCode.BadRequest, "Bad URI scheme."));
				}
				else
				{
					Plugin.Logger.LogDebug((object)("[Server] URI " + text + " is OK"));
					DirectImportPatch.AddUrisToImport(new <>z__ReadOnlySingleElementList<Uri>(result));
					importResponse.AddResult(new SuccessResponse());
				}
			}
			if (importResponse.StatusCode == HttpStatusCode.Accepted)
			{
				Plugin.TrySetStatusFlag(BusyStatus.Importing);
				scnBase.GoToCustomLevelSelect();
			}
			return importResponse;
		}
	}
	internal class PlayHandler : IHandler
	{
		public string Endpoint => "play";

		public HttpMethod AcceptedMethod => HttpMethod.Post;

		public IResponse HandleRequest(NameValueCollection parameters)
		{
			if (!RDStartup.hasInitialized)
			{
				return new ErrorResponse(HttpStatusCode.ServiceUnavailable, "Not initialized.");
			}
			if (Plugin.Status.HasFlag(BusyStatus.Importing))
			{
				return new ErrorResponse(HttpStatusCode.ServiceUnavailable, "Currently importing something.");
			}
			bool transient = parameters.AllKeys.Contains<string>("transient");
			string text = parameters["uri"];
			bool twoPlayer = parameters.AllKeys.Contains<string>("2p");
			if (Plugin.Status.HasFlag(BusyStatus.ProcessingTransient))
			{
				return new ErrorResponse(HttpStatusCode.ServiceUnavailable, "Currently processing prior request to play level transiently.");
			}
			if (string.IsNullOrWhiteSpace(text))
			{
				return new ErrorResponse(HttpStatusCode.BadRequest, "No URI given.");
			}
			if (!Uri.TryCreate(text, UriKind.Absolute, out Uri result))
			{
				return new ErrorResponse(HttpStatusCode.BadRequest, "Bad URI given.");
			}
			bool flag;
			switch (result.Scheme)
			{
			case "http":
			case "https":
			case "cafe":
				flag = true;
				break;
			default:
				flag = false;
				break;
			}
			if (!flag)
			{
				return new ErrorResponse(HttpStatusCode.BadRequest, "Bad URI scheme.");
			}
			DirectImportPatch.SetUriToPlay(result, transient, twoPlayer);
			return new SuccessResponse();
		}
	}
	internal class StatusHandler : IHandler
	{
		public string Endpoint => "status";

		public HttpMethod AcceptedMethod => HttpMethod.Get;

		public IResponse HandleRequest(NameValueCollection parameters)
		{
			string text = "ok";
			string text2 = "";
			if (!RDStartup.hasInitialized)
			{
				text = "starting";
			}
			else if (Plugin.Status != BusyStatus.None)
			{
				text = "busy";
				foreach (BusyStatus value in Enum.GetValues(typeof(BusyStatus)))
				{
					text2 = text2 + "\"" + Enum.GetName(typeof(BusyStatus), value).ToLowerInvariant() + "\",";
				}
			}
			text2 = text2.TrimEnd(',');
			StatusResponse statusResponse = new StatusResponse();
			statusResponse.Data = (IReadOnlyCollection<byte>)(object)Encoding.UTF8.GetBytes(string.Format("{{\"status\":\"{0}\",\"work\":[{1}],\"protocol\":{2},\"version\":\"{3}\"}}", text, text2, 0u, "1.0.4"));
			return statusResponse;
		}
	}
}
namespace RhythmDoctor.CafeLink.Patches
{
	[HarmonyPatch]
	internal static class DirectImportPatch
	{
		private enum DirectPlayStatus
		{
			None,
			ImportAndPlay
		}

		private static List<string> _urisToImport = new List<string>();

		private static DirectPlayStatus _directPlayStatus = DirectPlayStatus.None;

		private static bool _directPlayTwoPlayer = false;

		[HarmonyPatch(typeof(scnCLS), "LateUpdate")]
		[HarmonyPostfix]
		private static void HandleDirectImportAndPlayPatch(scnCLS __instance)
		{
			if (_urisToImport.Count != 0)
			{
				Plugin.Logger.LogInfo((object)"[DirectImportPatch] Handling direct import and play");
				__instance.ChangeToImportOption();
				__instance.WardOptionSelectedFeedback(true);
				__instance.levelImporter.Showing = true;
			}
		}

		[HarmonyPatch(typeof(LevelImporter), "LateUpdate")]
		[HarmonyPostfix]
		private static void AutoImportLevelsPatch(LevelImporter __instance)
		{
			if (_urisToImport.Count != 0)
			{
				__instance.urlInput.text = string.Join('\n', _urisToImport);
				__instance.ValidateUrl();
				__instance.Install_Public();
				_urisToImport.Clear();
			}
		}

		[HarmonyPatch(typeof(LevelImporter), "Install")]
		[HarmonyPostfix]
		private static IEnumerator DirectPlayAfterInstallPatch(IEnumerator __result, LevelImporter __instance)
		{
			while (__result.MoveNext())
			{
				yield return __result.Current;
			}
			if (_directPlayStatus != DirectPlayStatus.ImportAndPlay)
			{
				yield break;
			}
			_directPlayStatus = DirectPlayStatus.None;
			Plugin.Logger.LogInfo((object)"[DirectImportPatch] Handling direct play - after install");
			if (__instance.errorsIS.levels.Count == 1 && __instance.installedIS.levels.Count == 0)
			{
				if (__instance.errorsIS.levels[0].errorToken == RDString.Get("customLevelSelect.import.sameHashError"))
				{
					Plugin.Logger.LogWarning((object)"[DirectImportPatch] Going to level that had same hash...");
					Plugin.TryGoToLevelWithHash(__instance.errorsIS.levels[0].customLevel.Hash, _directPlayTwoPlayer);
				}
			}
			else if (__instance.errorsIS.levels.Count == 0 && __instance.installedIS.levels.Count == 1)
			{
				Plugin.Logger.LogInfo((object)"[DirectImportPatch] Going to just imported level...");
				scnBaseExtensions.GoToLevelWithImportLevel(__instance.installedIS.levels[0], _directPlayTwoPlayer);
			}
			else
			{
				Plugin.Logger.LogError((object)string.Format("[{0}] More than one imported/error: {1}, {2}", "DirectImportPatch", __instance.errorsIS.levels.Count, __instance.installedIS.levels.Count));
			}
		}

		[HarmonyPatch(typeof(scnBase), "GoToScene")]
		[HarmonyPostfix]
		internal static void CleanupAfterTransientPlayPatch(string name)
		{
			if (!(name == "scnGame"))
			{
				Task.Run((Action)Plugin.CleanupTransientPlay);
			}
		}

		internal static void AddUrisToImport([ParamCollection] IEnumerable<string> uris)
		{
			foreach (string uri in uris)
			{
				Plugin.Logger.LogInfo((object)("[DirectImportPatch] Adding " + uri + " to import list"));
				_urisToImport.Add(uri);
			}
		}

		internal static void AddUrisToImport([ParamCollection] IEnumerable<Uri> uris)
		{
			AddUrisToImport(uris.Select((Uri uri) => uri.OriginalString));
		}

		internal static void AddUrisToImportImmediate([ParamCollection] IEnumerable<string> uris)
		{
			AddUrisToImport(uris);
			Plugin.Logger.LogDebug((object)"[DirectImportPatch] Import immediate - going to custom level select");
			scnBase.GoToCustomLevelSelect();
		}

		internal static void AddUrisToImportImmediate([ParamCollection] IEnumerable<Uri> uris)
		{
			AddUrisToImportImmediate(uris.Select((Uri uri) => uri.OriginalString));
		}

		internal static void SetUriToPlay(string uri, bool transient = false, bool twoPlayer = false)
		{
			Plugin.Logger.LogInfo((object)string.Format("[{0}] Setting to play {1} (t: {2}, 2p: {3})", "DirectImportPatch", uri, transient, twoPlayer));
			if (transient)
			{
				Task.Run(() => Plugin.PlayTransient(uri, twoPlayer));
				return;
			}
			if (_urisToImport.Count != 0 && _urisToImport.Count > 1)
			{
				Plugin.Logger.LogWarning((object)("[DirectImportPatch] _urisToPlay already exists - overwriting with " + uri));
				_urisToImport.Clear();
			}
			AddUrisToImport(new <>z__ReadOnlySingleElementList<string>(uri));
			_directPlayStatus = DirectPlayStatus.ImportAndPlay;
			_directPlayTwoPlayer = twoPlayer;
			scnBase.GoToCustomLevelSelect();
		}

		internal static void SetUriToPlay(Uri uri, bool transient = false, bool twoPlayer = false)
		{
			SetUriToPlay(uri.OriginalString, transient, twoPlayer);
		}
	}
	[HarmonyPatch(typeof(LevelImporter))]
	internal static class HandleCafeSchemePatch
	{
		private static bool _hasPlayedInstallSound;

		[HarmonyPatch("Install")]
		[HarmonyPostfix]
		private static IEnumerator FixupCafeSchemePatch(IEnumerator __result, LevelImporter __instance)
		{
			((RDBase)__instance).cls.CLSPlaySound("sndImportInstallButtonClick", 1f, "CustomLevelSelect", 1f, 0f);
			_hasPlayedInstallSound = true;
			__instance.ToggleInsertUrlContainer(false, true, false);
			__instance.CurrentContentName = (ContentName)2;
			__instance.stoppedInstallCoroutine = false;
			__instance.CanToggleClearButton = false;
			foreach (ImportLevel level in __instance.toInstallIS.levels)
			{
				level.canToggleRemoveButton = false;
			}
			Plugin.Logger.LogDebug((object)"[HandleCafeSchemePatch] Fixing up cafe schemes");
			for (int i = 0; i < __instance.toInstallIS.levels.Count; i++)
			{
				ImportLevel importLevel = __instance.toInstallIS.levels[i];
				if (!importLevel.isUrl || !importLevel.path.StartsWith("cafe://"))
				{
					continue;
				}
				string id = importLevel.path.Split('/')[2];
				if (!id.All(char.IsLetterOrDigit))
				{
					Plugin.Logger.LogError((object)("[HandleCafeSchemePatch] Invalid ID " + id));
					continue;
				}
				Plugin.Logger.LogInfo((object)("[HandleCafeSchemePatch] Getting level data for ID " + id));
				Task<LevelData?> getLevelDataTask = Cafe.GetLevelData(id);
				yield return (object)new WaitUntil((Func<bool>)(() => getLevelDataTask.IsCompleted));
				if (getLevelDataTask.IsFaulted || getLevelDataTask.IsCanceled || !getLevelDataTask.Result.HasValue)
				{
					Plugin.Logger.LogError((object)string.Format("[{0}] Failed to get level data for ID {1} (faulted: {2} - {3}, cancelled: {4}, value: {5})", "HandleCafeSchemePatch", id, getLevelDataTask.IsFaulted, getLevelDataTask.Exception, getLevelDataTask.IsCanceled, getLevelDataTask.Result.HasValue));
					continue;
				}
				LevelData value = getLevelDataTask.Result.Value;
				if (Plugin.TryFindLevelWithHash(((LevelData)(ref value)).RDMD5, out CustomLevelData _))
				{
					Plugin.Logger.LogWarning((object)("[HandleCafeSchemePatch] Found duplicated hash for ID " + id));
					CustomLevelData customLevel = importLevel.customLevel;
					value = getLevelDataTask.Result.Value;
					customLevel._hash = ((LevelData)(ref value)).RDMD5;
					__instance.AddLevelToErrorSection(importLevel, RDString.Get("customLevelSelect.import.sameHashError"));
					__instance.toInstallIS.Remove(importLevel);
					i--;
				}
				else
				{
					value = getLevelDataTask.Result.Value;
					string text = ((LevelData)(ref value)).RDZipUrl.ToString();
					Plugin.Logger.LogInfo((object)("[HandleCafeSchemePatch] Resolved RDZip url to " + text + " for ID " + id));
					importLevel.path = text;
				}
			}
			Plugin.Logger.LogDebug((object)"[HandleCafeSchemePatch] Running original Install coroutine");
			while (__result.MoveNext())
			{
				yield return __result.Current;
			}
		}

		[HarmonyPatch(typeof(scnCLS), "CLSPlaySound")]
		[HarmonyPrefix]
		private static void DontDuplicateInstallButtonClickSoundPatch(string sound, ref bool __runOriginal)
		{
			if (sound == "sndImportInstallButtonClick" && _hasPlayedInstallSound)
			{
				__runOriginal = false;
				_hasPlayedInstallSound = false;
			}
		}
	}
	[HarmonyPatch]
	internal static class HandleStatusPatch
	{
		[HarmonyPatch(typeof(LevelImporter), "Install")]
		[HarmonyPostfix]
		[HarmonyPriority(700)]
		private static void SetImportingStatusPatch()
		{
			Plugin.TrySetStatusFlag(BusyStatus.Importing);
		}

		[HarmonyPatch(typeof(LevelImporter), "Install")]
		[HarmonyPostfix]
		[HarmonyPriority(100)]
		private static void ClearImportingStatusPatch()
		{
			Plugin.ClearStatusFlag(BusyStatus.Importing);
		}
	}
}
namespace System.Diagnostics.CodeAnalysis
{
	[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ConstantExpectedAttribute : Attribute
	{
		public object? Min { get; set; }

		public object? Max { get; set; }
	}
	[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ExperimentalAttribute : Attribute
	{
		public string DiagnosticId { get; }

		public string? UrlFormat { get; set; }

		public ExperimentalAttribute(string diagnosticId)
		{
			DiagnosticId = diagnosticId;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
	[ExcludeFromCodeCoverage]
	internal sealed class MemberNotNullAttribute : Attribute
	{
		public string[] Members { get; }

		public MemberNotNullAttribute(string member)
		{
			Members = new string[1] { member };
		}

		public MemberNotNullAttribute(params string[] members)
		{
			Members = members;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
	[ExcludeFromCodeCoverage]
	internal sealed class MemberNotNullWhenAttribute : Attribute
	{
		public bool ReturnValue { get; }

		public string[] Members { get; }

		public MemberNotNullWhenAttribute(bool returnValue, string member)
		{
			ReturnValue = returnValue;
			Members = new string[1] { member };
		}

		public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
		{
			ReturnValue = returnValue;
			Members = members;
		}
	}
	[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class SetsRequiredMembersAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class StringSyntaxAttribute : Attribute
	{
		public const string CompositeFormat = "CompositeFormat";

		public const string DateOnlyFormat = "DateOnlyFormat";

		public const string DateTimeFormat = "DateTimeFormat";

		public const string EnumFormat = "EnumFormat";

		public const string GuidFormat = "GuidFormat";

		public const string Json = "Json";

		public const string NumericFormat = "NumericFormat";

		public const string Regex = "Regex";

		public const string TimeOnlyFormat = "TimeOnlyFormat";

		public const string TimeSpanFormat = "TimeSpanFormat";

		public const string Uri = "Uri";

		public const string Xml = "Xml";

		public string Syntax { get; }

		public object?[] Arguments { get; }

		public StringSyntaxAttribute(string syntax)
		{
			Syntax = syntax;
			Arguments = new object[0];
		}

		public StringSyntaxAttribute(string syntax, params object?[] arguments)
		{
			Syntax = syntax;
			Arguments = arguments;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class UnscopedRefAttribute : Attribute
	{
	}
}
namespace System.Runtime.Versioning
{
	[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class RequiresPreviewFeaturesAttribute : Attribute
	{
		public string? Message { get; }

		public string? Url { get; set; }

		public RequiresPreviewFeaturesAttribute()
		{
		}

		public RequiresPreviewFeaturesAttribute(string? message)
		{
			Message = message;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
	[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class CallerArgumentExpressionAttribute : Attribute
	{
		public string ParameterName { get; }

		public CallerArgumentExpressionAttribute(string parameterName)
		{
			ParameterName = parameterName;
		}
	}
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class CollectionBuilderAttribute : Attribute
	{
		public Type BuilderType { get; }

		public string MethodName { get; }

		public CollectionBuilderAttribute(Type builderType, string methodName)
		{
			BuilderType = builderType;
			MethodName = methodName;
		}
	}
	[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class CompilerFeatureRequiredAttribute : Attribute
	{
		public const string RefStructs = "RefStructs";

		public const string RequiredMembers = "RequiredMembers";

		public string FeatureName { get; }

		public bool IsOptional { get; set; }

		public CompilerFeatureRequiredAttribute(string featureName)
		{
			FeatureName = featureName;
		}
	}
	[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute
	{
		public string[] Arguments { get; }

		public InterpolatedStringHandlerArgumentAttribute(string argument)
		{
			Arguments = new string[1] { argument };
		}

		public InterpolatedStringHandlerArgumentAttribute(params string[] arguments)
		{
			Arguments = arguments;
		}
	}
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class InterpolatedStringHandlerAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Method, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ModuleInitializerAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class OverloadResolutionPriorityAttribute : Attribute
	{
		public int Priority { get; }

		public OverloadResolutionPriorityAttribute(int priority)
		{
			Priority = priority;
		}
	}
	[AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ParamCollectionAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class RequiredMemberAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
	[EditorBrowsable(EditorBrowsableState.Never)]
	[ExcludeFromCodeCoverage]
	internal sealed class RequiresLocationAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class SkipLocalsInitAttribute : Attribute
	{
	}
}