Decompiled source of MediaDrop v1.0.3

Mods\MediaDrop.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BoneLib.BoneMenu;
using BoneLib.Notifications;
using HarmonyLib;
using LabFusion.Network;
using LabFusion.Network.Serialization;
using LabFusion.Player;
using LabFusion.SDK.Metadata;
using LabFusion.SDK.Modules;
using LabFusion.Safety;
using MediaDrop;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(MediaDropMod), "MediaDrop", "1.3.1", "ChappieStudios", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("MediaDrop")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("MediaDrop")]
[assembly: AssemblyTitle("MediaDrop")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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 MediaDrop
{
	public sealed class MediaDropMod : MelonMod
	{
		private static readonly HashSet<string> VideoExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".mp4", ".m4v", ".mov", ".webm", ".avi", ".wmv", ".mkv" };

		private static readonly HashSet<string> AudioExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".mp3", ".wav", ".ogg", ".m4a", ".aac", ".flac" };

		private static readonly HttpClient HttpClient = new HttpClient
		{
			Timeout = TimeSpan.FromMinutes(10.0)
		};

		private const int PreferredPort = 28472;

		private const int MaxMenuNameLength = 52;

		private const float FusionPresenceUpdateSeconds = 5f;

		private const string FusionPresenceKey = "MediaDropMod";

		private const string FusionPresenceValue = "1.3.1";

		private const string CloudflaredDownloadUrl = "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe";

		private readonly object _lock = new object();

		private readonly Queue<Action> _mainThreadActions = new Queue<Action>();

		private readonly Dictionary<string, string> _fusionLinks = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private readonly HashSet<string> _allowedTunnelHosts = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private MelonPreferences_Category _preferences;

		private MelonPreferences_Entry<bool> _autoCopyPreference;

		private MelonPreferences_Entry<bool> _shareToFusionPreference;

		private Page _menuPage;

		private Page _peopleWithoutModPage;

		private string _mediaFolder;

		private string _cloudflaredPath;

		private bool _autoCopyNewFiles = true;

		private bool _shareToFusion = true;

		private TcpListener _listener;

		private Thread _serverThread;

		private volatile bool _serverRunning;

		private int _port;

		private Process _tunnelProcess;

		private string _publicBaseUrl;

		private bool _tunnelStarting;

		private FileSystemWatcher _watcher;

		private string _pendingAutoCopyPath;

		private float _nextFusionPresenceUpdateTime;

		private float _nextMissingModMenuRefreshTime;

		internal static MediaDropMod Instance { get; private set; }

		public override void OnInitializeMelon()
		{
			Instance = this;
			SetupPreferences();
			RegisterFusionNetworking();
			((MelonBase)this).HarmonyInstance.PatchAll(typeof(MediaDropMod).Assembly);
			_mediaFolder = Path.Combine(MelonEnvironment.UserDataDirectory, "MediaDrop");
			_cloudflaredPath = Path.Combine(_mediaFolder, "cloudflared.exe");
			Directory.CreateDirectory(_mediaFolder);
			StartServer();
			StartWatcher();
			BuildMenu();
			RestartPublicTunnel();
			((MelonBase)this).LoggerInstance.Msg("MediaDrop loaded. Folder: " + _mediaFolder);
		}

		public override void OnDeinitializeMelon()
		{
			StopWatcher();
			StopTunnel();
			StopServer();
			Instance = null;
		}

		public override void OnUpdate()
		{
			float time = Time.time;
			UpdateFusionPresence(time);
			RefreshPeopleWithoutModPage(time);
			while (true)
			{
				Action action = null;
				lock (_lock)
				{
					if (_mainThreadActions.Count > 0)
					{
						action = _mainThreadActions.Dequeue();
					}
				}
				if (action != null)
				{
					try
					{
						action();
					}
					catch (Exception ex)
					{
						((MelonBase)this).LoggerInstance.Warning("MediaDrop main-thread action failed: " + ex.Message);
					}
					continue;
				}
				break;
			}
		}

		private void SetupPreferences()
		{
			_preferences = MelonPreferences.CreateCategory("MediaDrop");
			_autoCopyPreference = _preferences.CreateEntry<bool>("AutoCopyNewFiles", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			_shareToFusionPreference = _preferences.CreateEntry<bool>("ShareToFusion", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			_autoCopyNewFiles = _autoCopyPreference.Value;
			_shareToFusion = _shareToFusionPreference.Value;
		}

		private void RegisterFusionNetworking()
		{
			try
			{
				ModuleMessageManager.RegisterHandler<MediaDropLinkMessage>();
				((MelonBase)this).LoggerInstance.Msg("MediaDrop Fusion networking registered.");
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("MediaDrop Fusion networking registration skipped: " + ex.Message);
			}
		}

		private void BuildMenu()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_022b: Unknown result type (might be due to invalid IL or missing references)
			if (_menuPage == null)
			{
				_menuPage = Page.Root.CreatePage("MediaDrop", Color.cyan, 0, true);
			}
			else
			{
				_menuPage.RemoveAll();
			}
			List<string> mediaFiles = GetMediaFiles();
			string text = ((!string.IsNullOrWhiteSpace(_publicBaseUrl)) ? "Tunnel Ready" : (_tunnelStarting ? "Tunnel Starting" : "No Public Tunnel"));
			_menuPage.CreateBool("Auto Copy New Files", Color.yellow, _autoCopyNewFiles, (Action<bool>)SetAutoCopy);
			_menuPage.CreateBool("Share To Fusion", Color.cyan, _shareToFusion, (Action<bool>)SetShareToFusion);
			_menuPage.CreateFunction(text, string.IsNullOrWhiteSpace(_publicBaseUrl) ? Color.red : Color.green, (Action)CopyTunnelBaseUrl);
			_menuPage.CreateFunction("Restart Public Tunnel", Color.yellow, (Action)RestartPublicTunnel);
			_menuPage.CreateFunction($"Refresh Files ({mediaFiles.Count})", Color.yellow, (Action)BuildMenu);
			_menuPage.CreateFunction("Open Media Folder", Color.green, (Action)OpenMediaFolder);
			_menuPage.CreateFunction("Copy Latest Public Link", Color.white, (Action)CopyLatestPublicLink);
			AddFilePage("Videos", Color.red, mediaFiles.Where(IsVideoFile).ToList());
			AddFilePage("Audio", Color.magenta, mediaFiles.Where(IsAudioFile).ToList());
			AddFilePage("All Files", Color.white, mediaFiles);
			AddFusionLinksPage();
			_peopleWithoutModPage = _menuPage.CreatePage("People Without Mod", Color.cyan, 0, true);
			BuildPeopleWithoutModPage();
		}

		private void AddFilePage(string name, Color color, List<string> files)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			Page val = _menuPage.CreatePage($"{name} ({files.Count})", color, 0, true);
			if (files.Count == 0)
			{
				val.CreateFunction("No Files Found", Color.gray, (Action)delegate
				{
				});
				return;
			}
			foreach (string item in files.OrderBy(Path.GetFileName))
			{
				string capturedFile = item;
				val.CreateFunction(TrimMenuName(Path.GetFileName(capturedFile)), Color.white, (Action)delegate
				{
					CopyOrQueuePublicUrl(capturedFile);
				});
			}
		}

		private void SetAutoCopy(bool value)
		{
			_autoCopyNewFiles = value;
			_autoCopyPreference.Value = value;
			_preferences.SaveToFile(false);
		}

		private void SetShareToFusion(bool value)
		{
			_shareToFusion = value;
			_shareToFusionPreference.Value = value;
			_preferences.SaveToFile(false);
		}

		private void AddFusionLinksPage()
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			Page val = _menuPage.CreatePage($"Fusion Links ({_fusionLinks.Count})", Color.cyan, 0, true);
			if (_fusionLinks.Count == 0)
			{
				val.CreateFunction("No Links Received", Color.gray, (Action)delegate
				{
				});
				return;
			}
			foreach (KeyValuePair<string, string> item in _fusionLinks.OrderBy((KeyValuePair<string, string> pair) => pair.Key))
			{
				string name = item.Key;
				string url = item.Value;
				val.CreateFunction(TrimMenuName(name), Color.white, (Action)delegate
				{
					CopyReceivedFusionUrl(name, url);
				});
			}
		}

		private void UpdateFusionPresence(float now)
		{
			if (now < _nextFusionPresenceUpdateTime)
			{
				return;
			}
			_nextFusionPresenceUpdateTime = now + 5f;
			try
			{
				if (NetworkInfo.HasServer && PlayerIDManager.LocalID != null && PlayerIDManager.LocalID.IsValid && PlayerIDManager.LocalID.Metadata.Metadata.GetMetadata("MediaDropMod") != "1.3.1")
				{
					PlayerIDManager.LocalID.Metadata.Metadata.TrySetMetadata("MediaDropMod", "1.3.1");
				}
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("MediaDrop Fusion mod check failed: " + ex.Message);
			}
		}

		private void RefreshPeopleWithoutModPage(float now)
		{
			if (!(now < _nextMissingModMenuRefreshTime))
			{
				_nextMissingModMenuRefreshTime = now + 5f;
				BuildPeopleWithoutModPage();
			}
		}

		private void BuildPeopleWithoutModPage()
		{
			//IL_018b: 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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			if (_peopleWithoutModPage == null)
			{
				return;
			}
			_peopleWithoutModPage.RemoveAll();
			_peopleWithoutModPage.CreateFunction("Refresh", Color.yellow, (Action)BuildPeopleWithoutModPage);
			try
			{
				if (!NetworkInfo.HasServer)
				{
					_peopleWithoutModPage.CreateFunction("Not In Fusion Lobby", Color.gray, (Action)delegate
					{
					});
					return;
				}
				List<PlayerID> list = new List<PlayerID>(PlayerIDManager.PlayerIDs);
				list.Sort((PlayerID left, PlayerID right) => string.Compare(GetFusionPlayerName(left), GetFusionPlayerName(right), StringComparison.OrdinalIgnoreCase));
				int num = 0;
				foreach (PlayerID item in list)
				{
					if (item != null && item.IsValid && !item.IsMe && string.IsNullOrWhiteSpace(item.Metadata.Metadata.GetMetadata("MediaDropMod")))
					{
						num++;
						string fusionPlayerName = GetFusionPlayerName(item);
						_peopleWithoutModPage.CreateFunction(TrimMenuName(fusionPlayerName), Color.red, (Action)delegate
						{
						});
					}
				}
				if (num == 0)
				{
					_peopleWithoutModPage.CreateFunction("Everyone Has Mod", Color.green, (Action)delegate
					{
					});
				}
			}
			catch (Exception ex)
			{
				_peopleWithoutModPage.CreateFunction("Could Not Read Lobby", Color.red, (Action)delegate
				{
				});
				((MelonBase)this).LoggerInstance.Warning("MediaDrop people-without-mod menu failed: " + ex.Message);
			}
		}

		private static string GetFusionPlayerName(PlayerID player)
		{
			if (player == null)
			{
				return "Unknown Player";
			}
			PlayerMetadata metadata = player.Metadata;
			object obj;
			if (metadata == null)
			{
				obj = null;
			}
			else
			{
				MetadataVariable nickname = metadata.Nickname;
				obj = ((nickname != null) ? nickname.GetValueOrEmpty() : null);
			}
			string text = (string)obj;
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text;
			}
			PlayerMetadata metadata2 = player.Metadata;
			object obj2;
			if (metadata2 == null)
			{
				obj2 = null;
			}
			else
			{
				MetadataVariable username = metadata2.Username;
				obj2 = ((username != null) ? username.GetValueOrEmpty() : null);
			}
			string text2 = (string)obj2;
			if (!string.IsNullOrWhiteSpace(text2))
			{
				return text2;
			}
			return $"Player {player.SmallID}";
		}

		private void OpenMediaFolder()
		{
			try
			{
				Process.Start(new ProcessStartInfo
				{
					FileName = _mediaFolder,
					UseShellExecute = true
				});
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Could not open MediaDrop folder: " + ex.Message);
			}
		}

		private void CopyTunnelBaseUrl()
		{
			if (string.IsNullOrWhiteSpace(_publicBaseUrl))
			{
				Notify("MediaDrop", "Public tunnel is not ready yet.");
				return;
			}
			GUIUtility.systemCopyBuffer = _publicBaseUrl;
			Notify("MediaDrop", "Copied public tunnel link.");
		}

		private void CopyLatestPublicLink()
		{
			string text = GetMediaFiles().OrderByDescending(File.GetLastWriteTimeUtc).FirstOrDefault();
			if (string.IsNullOrWhiteSpace(text))
			{
				Notify("MediaDrop", "No media files found.");
			}
			else
			{
				CopyOrQueuePublicUrl(text);
			}
		}

		private void CopyOrQueuePublicUrl(string path)
		{
			if (!File.Exists(path))
			{
				return;
			}
			if (string.IsNullOrWhiteSpace(_publicBaseUrl))
			{
				_pendingAutoCopyPath = path;
				Notify("MediaDrop", "Waiting for public tunnel, then link will copy.");
				if (!_tunnelStarting)
				{
					RestartPublicTunnel();
				}
			}
			else
			{
				string publicUrlForFile = GetPublicUrlForFile(path);
				AllowTunnelUrl(publicUrlForFile);
				GUIUtility.systemCopyBuffer = publicUrlForFile;
				Notify("MediaDrop", "Copied public link: " + Path.GetFileName(path));
				((MelonBase)this).LoggerInstance.Msg("Copied public MediaDrop link: " + publicUrlForFile);
				SharePublicUrlWithFusion(Path.GetFileName(path), publicUrlForFile);
			}
		}

		private string GetPublicUrlForFile(string path)
		{
			string text = Uri.EscapeDataString(Path.GetFileName(path));
			return _publicBaseUrl.TrimEnd('/') + "/media/" + text;
		}

		private void StartWatcher()
		{
			_watcher = new FileSystemWatcher(_mediaFolder);
			_watcher.IncludeSubdirectories = false;
			_watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite;
			_watcher.Created += OnMediaFolderChanged;
			_watcher.Renamed += OnMediaFolderRenamed;
			_watcher.Deleted += OnMediaFolderChanged;
			_watcher.EnableRaisingEvents = true;
		}

		private void StopWatcher()
		{
			if (_watcher != null)
			{
				_watcher.EnableRaisingEvents = false;
				_watcher.Dispose();
				_watcher = null;
			}
		}

		private void OnMediaFolderChanged(object sender, FileSystemEventArgs args)
		{
			QueueMainThread(BuildMenu);
			if (_autoCopyNewFiles && File.Exists(args.FullPath) && IsMediaFile(args.FullPath))
			{
				QueueMainThread(delegate
				{
					CopyOrQueuePublicUrl(args.FullPath);
				});
			}
		}

		private void OnMediaFolderRenamed(object sender, RenamedEventArgs args)
		{
			QueueMainThread(BuildMenu);
			if (_autoCopyNewFiles && File.Exists(args.FullPath) && IsMediaFile(args.FullPath))
			{
				QueueMainThread(delegate
				{
					CopyOrQueuePublicUrl(args.FullPath);
				});
			}
		}

		private void StartServer()
		{
			try
			{
				_listener = new TcpListener(IPAddress.Loopback, 28472);
				_listener.Start();
			}
			catch
			{
				_listener = new TcpListener(IPAddress.Loopback, 0);
				_listener.Start();
			}
			_port = ((IPEndPoint)_listener.LocalEndpoint).Port;
			_serverRunning = true;
			_serverThread = new Thread(ServerLoop)
			{
				IsBackground = true,
				Name = "MediaDrop HTTP Server"
			};
			_serverThread.Start();
		}

		private void StopServer()
		{
			_serverRunning = false;
			try
			{
				_listener?.Stop();
			}
			catch
			{
			}
			_listener = null;
		}

		private void RestartPublicTunnel()
		{
			if (_tunnelStarting)
			{
				Notify("MediaDrop", "Public tunnel is already starting.");
				return;
			}
			StopTunnel();
			_publicBaseUrl = null;
			_tunnelStarting = true;
			BuildMenu();
			Notify("MediaDrop", "Starting public tunnel.");
			Task.Run((Func<Task?>)StartTunnelWorker);
		}

		private async Task StartTunnelWorker()
		{
			try
			{
				await EnsureCloudflared();
				ProcessStartInfo startInfo = new ProcessStartInfo
				{
					FileName = _cloudflaredPath,
					Arguments = $"tunnel --no-autoupdate --url http://127.0.0.1:{_port}",
					UseShellExecute = false,
					RedirectStandardOutput = true,
					RedirectStandardError = true,
					CreateNoWindow = true
				};
				Process process = new Process
				{
					StartInfo = startInfo,
					EnableRaisingEvents = true
				};
				process.OutputDataReceived += OnTunnelOutput;
				process.ErrorDataReceived += OnTunnelOutput;
				process.Exited += OnTunnelExited;
				lock (_lock)
				{
					_tunnelProcess = process;
				}
				process.Start();
				process.BeginOutputReadLine();
				process.BeginErrorReadLine();
			}
			catch (Exception ex2)
			{
				Exception ex = ex2;
				((MelonBase)this).LoggerInstance.Warning("Could not start MediaDrop public tunnel: " + ex.Message);
				QueueMainThread(delegate
				{
					_tunnelStarting = false;
					Notify("MediaDrop Tunnel Failed", ex.Message);
					BuildMenu();
				});
			}
		}

		private async Task EnsureCloudflared()
		{
			if (!File.Exists(_cloudflaredPath))
			{
				QueueMainThread(delegate
				{
					Notify("MediaDrop", "Downloading public tunnel helper.");
				});
				byte[] bytes = await HttpClient.GetByteArrayAsync("https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe");
				File.WriteAllBytes(_cloudflaredPath, bytes);
			}
		}

		private void OnTunnelOutput(object sender, DataReceivedEventArgs args)
		{
			if (string.IsNullOrWhiteSpace(args.Data))
			{
				return;
			}
			Match match = Regex.Match(args.Data, "https://[-a-zA-Z0-9.]+\\.trycloudflare\\.com");
			if (!match.Success)
			{
				return;
			}
			string url = match.Value;
			QueueMainThread(delegate
			{
				_publicBaseUrl = url;
				AllowTunnelUrl(url);
				_tunnelStarting = false;
				Notify("MediaDrop", "Public tunnel ready.");
				BuildMenu();
				if (!string.IsNullOrWhiteSpace(_pendingAutoCopyPath) && File.Exists(_pendingAutoCopyPath))
				{
					string pendingAutoCopyPath = _pendingAutoCopyPath;
					_pendingAutoCopyPath = null;
					CopyOrQueuePublicUrl(pendingAutoCopyPath);
				}
			});
		}

		private void OnTunnelExited(object sender, EventArgs args)
		{
			QueueMainThread(delegate
			{
				((MelonBase)this).LoggerInstance.Warning("MediaDrop public tunnel stopped.");
				_publicBaseUrl = null;
				_tunnelStarting = false;
				BuildMenu();
			});
		}

		private void SharePublicUrlWithFusion(string fileName, string url)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			if (!_shareToFusion || !NetworkInfo.HasServer)
			{
				return;
			}
			try
			{
				MessageRelay.RelayModule<MediaDropLinkMessage, MediaDropLinkData>(new MediaDropLinkData
				{
					FileName = fileName,
					Url = url
				}, CommonMessageRoutes.ReliableToOtherClients);
				Notify("MediaDrop Fusion", "Sent link to Fusion players.");
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Could not share MediaDrop link through Fusion: " + ex.Message);
			}
		}

		internal void ReceiveFusionLink(string fileName, string url)
		{
			if (!string.IsNullOrWhiteSpace(url))
			{
				if (string.IsNullOrWhiteSpace(fileName))
				{
					fileName = "MediaDrop Link";
				}
				AllowTunnelUrl(url);
				_fusionLinks[fileName] = url;
				GUIUtility.systemCopyBuffer = url;
				Notify("MediaDrop Fusion", "Received link: " + fileName);
				BuildMenu();
			}
		}

		private void CopyReceivedFusionUrl(string name, string url)
		{
			if (!string.IsNullOrWhiteSpace(url))
			{
				AllowTunnelUrl(url);
				GUIUtility.systemCopyBuffer = url;
				Notify("MediaDrop Fusion", "Copied link: " + name);
			}
		}

		private void AllowTunnelUrl(string url)
		{
			if (Uri.TryCreate(url, UriKind.Absolute, out Uri result))
			{
				_allowedTunnelHosts.Add(result.Host);
			}
		}

		internal static bool IsAllowedMediaDropUrl(string url)
		{
			if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result))
			{
				return false;
			}
			if (result.Scheme != Uri.UriSchemeHttp && result.Scheme != Uri.UriSchemeHttps)
			{
				return false;
			}
			if (!result.AbsolutePath.StartsWith("/media/", StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			MediaDropMod instance = Instance;
			if (instance != null && instance._allowedTunnelHosts.Contains(result.Host))
			{
				return true;
			}
			return result.Host.EndsWith(".trycloudflare.com", StringComparison.OrdinalIgnoreCase);
		}

		private void StopTunnel()
		{
			Process process = null;
			lock (_lock)
			{
				process = _tunnelProcess;
				_tunnelProcess = null;
			}
			if (process == null)
			{
				return;
			}
			try
			{
				if (!process.HasExited)
				{
					process.Kill();
				}
			}
			catch
			{
			}
		}

		private void ServerLoop()
		{
			while (_serverRunning)
			{
				try
				{
					TcpClient client = _listener.AcceptTcpClient();
					ThreadPool.QueueUserWorkItem(delegate
					{
						HandleClient(client);
					});
				}
				catch
				{
					if (_serverRunning)
					{
						Thread.Sleep(50);
					}
				}
			}
		}

		private void HandleClient(TcpClient client)
		{
			using (client)
			{
				using NetworkStream stream = client.GetStream();
				try
				{
					string text = ReadRequestHeader(stream);
					if (string.IsNullOrWhiteSpace(text))
					{
						return;
					}
					string[] array = text.Split(new string[1] { "\r\n" }, StringSplitOptions.None);
					string[] array2 = array[0].Split(' ');
					if (array2.Length < 2)
					{
						WriteStatus(stream, "400 Bad Request", "Bad request.");
						return;
					}
					string text2 = array2[0];
					string text3 = array2[1];
					if (text2 != "GET" && text2 != "HEAD")
					{
						WriteStatus(stream, "405 Method Not Allowed", "Only GET and HEAD are supported.");
						return;
					}
					if (!text3.StartsWith("/media/", StringComparison.OrdinalIgnoreCase))
					{
						WriteStatus(stream, "404 Not Found", "Media file not found.");
						return;
					}
					string path = Uri.UnescapeDataString(text3.Substring("/media/".Length).Split('?')[0]);
					path = Path.GetFileName(path);
					string text4 = Path.Combine(_mediaFolder, path);
					if (!File.Exists(text4) || !IsMediaFile(text4))
					{
						WriteStatus(stream, "404 Not Found", "Media file not found.");
					}
					else
					{
						ServeFile(stream, text4, array, text2 == "HEAD");
					}
				}
				catch
				{
				}
			}
		}

		private static string ReadRequestHeader(NetworkStream stream)
		{
			List<byte> list = new List<byte>();
			byte[] array = new byte[1];
			while (list.Count < 16384 && stream.Read(array, 0, 1) > 0)
			{
				list.Add(array[0]);
				int count = list.Count;
				if (count >= 4 && list[count - 4] == 13 && list[count - 3] == 10 && list[count - 2] == 13 && list[count - 1] == 10)
				{
					break;
				}
			}
			return Encoding.ASCII.GetString(list.ToArray());
		}

		private static void ServeFile(NetworkStream stream, string filePath, string[] requestLines, bool headOnly)
		{
			long length = new FileInfo(filePath).Length;
			long num = 0L;
			long num2 = length - 1;
			bool flag = false;
			string text = requestLines.FirstOrDefault((string line) => line.StartsWith("Range:", StringComparison.OrdinalIgnoreCase));
			if (!string.IsNullOrWhiteSpace(text))
			{
				string text2 = text.Substring("Range:".Length).Trim();
				if (text2.StartsWith("bytes=", StringComparison.OrdinalIgnoreCase))
				{
					string[] array = text2.Substring("bytes=".Length).Split('-');
					if (array.Length == 2)
					{
						if (long.TryParse(array[0], out var result))
						{
							num = Math.Max(0L, result);
						}
						if (long.TryParse(array[1], out var result2))
						{
							num2 = Math.Min(length - 1, result2);
						}
						if (num <= num2)
						{
							flag = true;
						}
					}
				}
			}
			long num3 = num2 - num + 1;
			string value = (flag ? "206 Partial Content" : "200 OK");
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("HTTP/1.1 ").Append(value).Append("\r\n");
			stringBuilder.Append("Content-Type: ").Append(GetContentType(filePath)).Append("\r\n");
			stringBuilder.Append("Accept-Ranges: bytes\r\n");
			stringBuilder.Append("Content-Length: ").Append(num3).Append("\r\n");
			if (flag)
			{
				stringBuilder.Append("Content-Range: bytes ").Append(num).Append("-")
					.Append(num2)
					.Append("/")
					.Append(length)
					.Append("\r\n");
			}
			stringBuilder.Append("Connection: close\r\n\r\n");
			byte[] bytes = Encoding.ASCII.GetBytes(stringBuilder.ToString());
			stream.Write(bytes, 0, bytes.Length);
			if (headOnly)
			{
				return;
			}
			byte[] array2 = new byte[81920];
			using FileStream fileStream = File.OpenRead(filePath);
			fileStream.Position = num;
			long num4 = num3;
			while (num4 > 0)
			{
				int num5 = fileStream.Read(array2, 0, (int)Math.Min(array2.Length, num4));
				if (num5 <= 0)
				{
					break;
				}
				stream.Write(array2, 0, num5);
				num4 -= num5;
			}
		}

		private static void WriteStatus(NetworkStream stream, string status, string message)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(message);
			string s = $"HTTP/1.1 {status}\r\nContent-Type: text/plain\r\nContent-Length: {bytes.Length}\r\nConnection: close\r\n\r\n";
			byte[] bytes2 = Encoding.ASCII.GetBytes(s);
			stream.Write(bytes2, 0, bytes2.Length);
			stream.Write(bytes, 0, bytes.Length);
		}

		private List<string> GetMediaFiles()
		{
			if (!Directory.Exists(_mediaFolder))
			{
				return new List<string>();
			}
			return Directory.EnumerateFiles(_mediaFolder, "*", SearchOption.TopDirectoryOnly).Where(IsMediaFile).OrderBy(Path.GetFileName)
				.ToList();
		}

		private static bool IsMediaFile(string path)
		{
			if (!IsVideoFile(path))
			{
				return IsAudioFile(path);
			}
			return true;
		}

		private static bool IsVideoFile(string path)
		{
			return VideoExtensions.Contains(Path.GetExtension(path));
		}

		private static bool IsAudioFile(string path)
		{
			return AudioExtensions.Contains(Path.GetExtension(path));
		}

		private static string GetContentType(string path)
		{
			switch (Path.GetExtension(path).ToLowerInvariant())
			{
			case ".m4v":
			case ".mp4":
				return "video/mp4";
			case ".mov":
				return "video/quicktime";
			case ".webm":
				return "video/webm";
			case ".avi":
				return "video/x-msvideo";
			case ".wmv":
				return "video/x-ms-wmv";
			case ".mkv":
				return "video/x-matroska";
			case ".mp3":
				return "audio/mpeg";
			case ".wav":
				return "audio/wav";
			case ".ogg":
				return "audio/ogg";
			case ".m4a":
				return "audio/mp4";
			case ".aac":
				return "audio/aac";
			case ".flac":
				return "audio/flac";
			default:
				return "application/octet-stream";
			}
		}

		private static string TrimMenuName(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return "Unnamed File";
			}
			if (value.Length > 52)
			{
				return value.Substring(0, 49) + "...";
			}
			return value;
		}

		private void QueueMainThread(Action action)
		{
			lock (_lock)
			{
				_mainThreadActions.Enqueue(action);
			}
		}

		private void Notify(string title, string message)
		{
			//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_0007: 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_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			try
			{
				Notifier.Send(new Notification
				{
					Title = new NotificationText(title),
					Message = new NotificationText(message),
					ShowTitleOnPopup = true,
					PopupLength = 5f,
					Type = (NotificationType)0
				});
			}
			catch
			{
			}
		}
	}
	public sealed class MediaDropLinkData : INetSerializable
	{
		public string FileName;

		public string Url;

		public int? GetSize()
		{
			return SizeExtensions.GetSize(FileName) + SizeExtensions.GetSize(Url);
		}

		public void Serialize(INetSerializer serializer)
		{
			serializer.SerializeValue(ref FileName);
			serializer.SerializeValue(ref Url);
		}
	}
	public sealed class MediaDropLinkMessage : ModuleMessageHandler
	{
		protected override void OnHandleMessage(ReceivedMessage received)
		{
			MediaDropLinkData mediaDropLinkData = ((ReceivedMessage)(ref received)).ReadData<MediaDropLinkData>();
			MediaDropMod.Instance?.ReceiveFusionLink(mediaDropLinkData.FileName, mediaDropLinkData.Url);
		}
	}
	[HarmonyPatch(typeof(URLWhitelistManager), "IsURLWhitelisted")]
	internal static class FusionUrlWhitelistPatch
	{
		private static void Postfix(string url, ref bool __result)
		{
			if (!__result && MediaDropMod.IsAllowedMediaDropUrl(url))
			{
				__result = true;
			}
		}
	}
}