Decompiled source of RepoMusicPlayer v1.2.0

BepInEx/plugins/MusicPlayer/MusicPlayer.dll

Decompiled 9 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using Photon.Voice.Unity;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 RepoMusicPlayer
{
	internal static class FlacDecoder
	{
		public sealed class Result
		{
			public float[] Samples;

			public int Channels;

			public int SampleRate;

			public int BitsPerSample;

			public int FrameCount;
		}

		public static Result Decode(byte[] data)
		{
			if (data == null || data.Length < 8)
			{
				throw new InvalidOperationException("文件太小");
			}
			int num = 0;
			if (data[0] != 102 || data[1] != 76 || data[2] != 97 || data[3] != 67)
			{
				if (data.Length <= 10 || data[0] != 73 || data[1] != 68 || data[2] != 51)
				{
					throw new InvalidOperationException("不是 FLAC 文件");
				}
				int num2 = ((data[6] & 0x7F) << 21) | ((data[7] & 0x7F) << 14) | ((data[8] & 0x7F) << 7) | (data[9] & 0x7F);
				num = 10 + num2;
				if (num + 4 > data.Length || data[num] != 102 || data[num + 1] != 76 || data[num + 2] != 97 || data[num + 3] != 67)
				{
					throw new InvalidOperationException("不是 FLAC 文件");
				}
			}
			num += 4;
			int num3 = 44100;
			int channels = 2;
			int bitsPerSample = 16;
			while (num + 4 <= data.Length)
			{
				byte b = data[num];
				bool flag = (b & 0x80) != 0;
				int num4 = b & 0x7F;
				int num5 = (data[num + 1] << 16) | (data[num + 2] << 8) | data[num + 3];
				num += 4;
				if (num4 == 0 && num5 >= 34 && num + 34 <= data.Length)
				{
					int num6 = num + 10;
					byte b2 = data[num6];
					byte b3 = data[num6 + 1];
					byte b4 = data[num6 + 2];
					byte b5 = data[num6 + 3];
					num3 = (b2 << 12) | (b3 << 4) | (b4 >> 4);
					channels = ((b4 >> 1) & 7) + 1;
					bitsPerSample = (((b4 & 1) << 4) | (b5 >> 4)) + 1;
				}
				num += num5;
				if (flag)
				{
					break;
				}
			}
			if (num >= data.Length)
			{
				throw new InvalidOperationException("没有音频帧");
			}
			if (channels <= 0 || channels > 8)
			{
				throw new InvalidOperationException($"声道数异常:{channels}");
			}
			BitReader bitReader = new BitReader(data, num);
			List<float[]>[] array = new List<float[]>[channels];
			for (int i = 0; i < channels; i++)
			{
				array[i] = new List<float[]>();
			}
			int num7 = 0;
			while (bitReader.RemainingBits > 32 && TryDecodeFrame(bitReader, ref channels, array, bitsPerSample, num3))
			{
				num7++;
			}
			int num8 = 0;
			foreach (float[] item in array[0])
			{
				num8 += item.Length;
			}
			if (num8 == 0)
			{
				throw new InvalidOperationException("没有解出音频数据");
			}
			float[] array2 = new float[num8 * channels];
			int num9 = 0;
			for (int j = 0; j < array[0].Count; j++)
			{
				int num10 = array[0][j].Length;
				for (int k = 0; k < num10; k++)
				{
					for (int l = 0; l < channels; l++)
					{
						array2[(num9 + k) * channels + l] = array[l][j][k];
					}
				}
				num9 += num10;
			}
			return new Result
			{
				Samples = array2,
				Channels = channels,
				SampleRate = num3,
				BitsPerSample = bitsPerSample,
				FrameCount = num7
			};
		}

		private static bool TryDecodeFrame(BitReader reader, ref int channels, List<float[]>[] chunks, int bitsPerSample, int streamSampleRate)
		{
			int num = reader.ReadBits(14);
			if (num != 16382)
			{
				return false;
			}
			reader.SkipBits(1);
			reader.ReadBits(1);
			int num2 = reader.ReadBits(4);
			int num3 = reader.ReadBits(4);
			int num4 = reader.ReadBits(4);
			int code = reader.ReadBits(3);
			reader.SkipBits(1);
			if (num2 == 0 || num3 == 15 || num4 > 10)
			{
				return false;
			}
			ReadUtf8Number(reader);
			int num5 = ResolveBlockSize(reader, num2);
			ResolveSampleRate(reader, num3, streamSampleRate);
			int num6 = ResolveBitsPerSample(code, bitsPerSample);
			reader.SkipBits(8);
			int num7 = ((num4 < 8) ? (num4 + 1) : 2);
			if (num7 != channels)
			{
				return false;
			}
			int[][] array = new int[num7][];
			for (int i = 0; i < num7; i++)
			{
				int num8 = num6;
				if ((num4 == 8 && i == 1) || (num4 == 9 && i == 0) || (num4 == 10 && i == 1))
				{
					num8++;
				}
				array[i] = DecodeSubframe(reader, num5, num8);
				if (array[i] == null)
				{
					return false;
				}
			}
			reader.AlignToByte();
			reader.SkipBits(16);
			ApplyChannelDecorrelation(num4, array, num5);
			float num9 = 1f / (float)(1 << num6 - 1);
			for (int j = 0; j < num7; j++)
			{
				float[] array2 = new float[num5];
				int[] array3 = array[j];
				for (int k = 0; k < num5; k++)
				{
					float num10 = (float)array3[k] * num9;
					array2[k] = ((num10 > 1f) ? 1f : ((num10 < -1f) ? (-1f) : num10));
				}
				chunks[j].Add(array2);
			}
			return true;
		}

		private static int[] DecodeSubframe(BitReader reader, int blockSize, int bitsPerSample)
		{
			reader.SkipBits(1);
			int num = reader.ReadBits(6);
			int num2 = reader.ReadBits(1);
			int num3 = 0;
			if (num2 != 0)
			{
				num3 = reader.ReadUnary() + 1;
			}
			int num4 = bitsPerSample - num3;
			if (num4 <= 0 || num4 > 32)
			{
				return null;
			}
			int[] array;
			if (num == 0)
			{
				int num5 = reader.ReadSignedBits(num4);
				array = new int[blockSize];
				for (int i = 0; i < blockSize; i++)
				{
					array[i] = num5;
				}
			}
			else if (num == 1)
			{
				array = new int[blockSize];
				for (int j = 0; j < blockSize; j++)
				{
					array[j] = reader.ReadSignedBits(num4);
				}
			}
			else if (num >= 8 && num <= 12)
			{
				int num6 = num - 8;
				array = new int[blockSize];
				for (int k = 0; k < num6; k++)
				{
					array[k] = reader.ReadSignedBits(num4);
				}
				int[] array2 = DecodeResidual(reader, blockSize, num6);
				if (array2 == null)
				{
					return null;
				}
				for (int l = num6; l < blockSize; l++)
				{
					array[l] = (int)(num6 switch
					{
						0 => array2[l], 
						1 => (long)array[l - 1] + (long)array2[l], 
						2 => 2L * (long)array[l - 1] - array[l - 2] + array2[l], 
						3 => 3L * (long)array[l - 1] - 3L * (long)array[l - 2] + array[l - 3] + array2[l], 
						_ => 4L * (long)array[l - 1] - 6L * (long)array[l - 2] + 4L * (long)array[l - 3] - array[l - 4] + array2[l], 
					});
				}
			}
			else
			{
				if (num < 32)
				{
					return null;
				}
				int num7 = (num & 0x1F) + 1;
				array = new int[blockSize];
				for (int m = 0; m < num7; m++)
				{
					array[m] = reader.ReadSignedBits(num4);
				}
				int num8 = reader.ReadBits(4) + 1;
				if (num8 == 16)
				{
					return null;
				}
				int num9 = reader.ReadSignedBits(5);
				int[] array3 = new int[num7];
				for (int n = 0; n < num7; n++)
				{
					array3[n] = reader.ReadSignedBits(num8);
				}
				int[] array4 = DecodeResidual(reader, blockSize, num7);
				if (array4 == null)
				{
					return null;
				}
				for (int num10 = num7; num10 < blockSize; num10++)
				{
					long num11 = 0L;
					for (int num12 = 0; num12 < num7; num12++)
					{
						num11 += (long)array3[num12] * (long)array[num10 - 1 - num12];
					}
					array[num10] = (int)((num11 >> num9) + array4[num10]);
				}
			}
			if (num3 > 0)
			{
				for (int num13 = 0; num13 < blockSize; num13++)
				{
					array[num13] <<= num3;
				}
			}
			return array;
		}

		private static int[] DecodeResidual(BitReader reader, int blockSize, int predictorOrder)
		{
			int num = reader.ReadBits(2);
			if (num > 1)
			{
				return null;
			}
			int count = ((num == 0) ? 4 : 5);
			int num2 = ((num == 0) ? 15 : 31);
			int num3 = reader.ReadBits(4);
			int num4 = 1 << num3;
			int num5 = blockSize >> num3;
			if (num5 == 0)
			{
				return null;
			}
			int[] array = new int[blockSize];
			int num6 = predictorOrder;
			for (int i = 0; i < num4; i++)
			{
				int num7 = num5;
				if (i == 0)
				{
					num7 -= predictorOrder;
				}
				int num8 = reader.ReadBits(count);
				if (num8 == num2)
				{
					int count2 = reader.ReadBits(5);
					for (int j = 0; j < num7; j++)
					{
						array[num6++] = reader.ReadSignedBits(count2);
					}
				}
				else
				{
					for (int k = 0; k < num7; k++)
					{
						array[num6++] = ReadRice(reader, num8);
					}
				}
			}
			return array;
		}

		private static int ReadRice(BitReader reader, int parameter)
		{
			int num = reader.ReadUnary();
			int num2 = ((parameter > 0) ? reader.ReadBits(parameter) : 0);
			int num3 = (num << parameter) | num2;
			return (num3 >> 1) ^ -(num3 & 1);
		}

		private static void ApplyChannelDecorrelation(int assignment, int[][] subframes, int blockSize)
		{
			if (assignment < 8 || subframes.Length < 2)
			{
				return;
			}
			int[] array = subframes[0];
			int[] array2 = subframes[1];
			switch (assignment)
			{
			case 8:
			{
				for (int j = 0; j < blockSize; j++)
				{
					int num3 = array[j];
					int num4 = array2[j];
					array[j] = num3;
					array2[j] = num3 - num4;
				}
				break;
			}
			case 9:
			{
				for (int k = 0; k < blockSize; k++)
				{
					int num5 = array[k];
					int num6 = array2[k];
					array[k] = num6 + num5;
					array2[k] = num6;
				}
				break;
			}
			case 10:
			{
				for (int i = 0; i < blockSize; i++)
				{
					int num = array[i];
					int num2 = array2[i];
					num = (num << 1) | (num2 & 1);
					array[i] = num + num2 >> 1;
					array2[i] = num - num2 >> 1;
				}
				break;
			}
			}
		}

		private static int ResolveBlockSize(BitReader reader, int code)
		{
			if (code == 1)
			{
				return 192;
			}
			if (code >= 2 && code <= 5)
			{
				return 576 << code - 2;
			}
			return code switch
			{
				6 => reader.ReadBits(8) + 1, 
				7 => reader.ReadBits(16) + 1, 
				_ => 256 << code - 8, 
			};
		}

		private static int ResolveSampleRate(BitReader reader, int code, int streamRate)
		{
			return code switch
			{
				0 => streamRate, 
				1 => 88200, 
				2 => 176400, 
				3 => 192000, 
				4 => 8000, 
				5 => 16000, 
				6 => 22050, 
				7 => 24000, 
				8 => 32000, 
				9 => 44100, 
				10 => 48000, 
				11 => 96000, 
				12 => reader.ReadBits(8) * 1000, 
				13 => reader.ReadBits(16), 
				_ => reader.ReadBits(16) * 10, 
			};
		}

		private static int ResolveBitsPerSample(int code, int streamBits)
		{
			return code switch
			{
				1 => 8, 
				2 => 12, 
				4 => 16, 
				5 => 20, 
				6 => 24, 
				7 => 32, 
				_ => streamBits, 
			};
		}

		private static void ReadUtf8Number(BitReader reader)
		{
			int num = reader.ReadBits(8);
			if ((num & 0x80) != 0)
			{
				int num2 = (((num & 0xE0) == 192) ? 1 : (((num & 0xF0) == 224) ? 2 : (((num & 0xF8) == 240) ? 3 : (((num & 0xFC) == 248) ? 4 : (((num & 0xFE) != 252) ? 6 : 5)))));
				for (int i = 0; i < num2; i++)
				{
					reader.SkipBits(8);
				}
			}
		}
	}
	internal sealed class BitReader
	{
		private readonly byte[] _data;

		private int _bitPosition;

		public int RemainingBits => _data.Length * 8 - _bitPosition;

		public BitReader(byte[] data, int byteOffset)
		{
			_data = data;
			_bitPosition = byteOffset * 8;
		}

		public int ReadBits(int count)
		{
			if (count <= 0)
			{
				return 0;
			}
			int num = 0;
			int num2 = count;
			while (num2 > 0)
			{
				int num3 = _bitPosition >> 3;
				if (num3 >= _data.Length)
				{
					throw new InvalidOperationException("FLAC 数据意外结束");
				}
				int num4 = _bitPosition & 7;
				int num5 = 8 - num4;
				int num6 = ((num5 < num2) ? num5 : num2);
				int num7 = num5 - num6;
				int num8 = (1 << num6) - 1;
				int num9 = (_data[num3] >> num7) & num8;
				num = (num << num6) | num9;
				_bitPosition += num6;
				num2 -= num6;
			}
			return num;
		}

		public int ReadSignedBits(int count)
		{
			if (count <= 0)
			{
				return 0;
			}
			int num = ReadBits(count);
			int num2 = 1 << count - 1;
			return ((num & num2) != 0) ? (num - (num2 << 1)) : num;
		}

		public int ReadUnary()
		{
			int num = 0;
			int num4;
			while (true)
			{
				int num2 = _bitPosition >> 3;
				if (num2 >= _data.Length)
				{
					throw new InvalidOperationException("FLAC 数据意外结束");
				}
				int num3 = _bitPosition & 7;
				num4 = (_data[num2] & (255 >> num3)) << num3;
				if (num4 != 0)
				{
					break;
				}
				int num5 = 8 - num3;
				num += num5;
				_bitPosition += num5;
			}
			int num6 = 0;
			while ((num4 & 0x80) == 0)
			{
				num6++;
				num4 <<= 1;
			}
			_bitPosition += num6 + 1;
			return num + num6;
		}

		public void SkipBits(int count)
		{
			_bitPosition += Math.Max(0, count);
			int num = _data.Length * 8;
			if (_bitPosition > num)
			{
				_bitPosition = num;
			}
		}

		public void AlignToByte()
		{
			int num = _bitPosition & 7;
			if (num != 0)
			{
				_bitPosition += 8 - num;
			}
		}
	}
	internal static class Playlist
	{
		private static readonly string[] Extensions = new string[4] { ".mp3", ".ogg", ".wav", ".flac" };

		private const string Header = "# R.E.P.O. 音乐播放器播放列表\n# 每行一个本地文件或 http(s) 直链;相对路径相对于本文件同目录下的 music 文件夹。\n# 支持格式:mp3 / ogg / wav / flac。\n# 例:\n#   song.mp3\n#   D:\\Music\\album\\track01.ogg\n#   https://example.com/radio.mp3\n#\n# 列表为空时会自动扫描 music 文件夹里的音频文件。\n";

		public static string Root { get; } = Path.Combine(Paths.ConfigPath, "RepoMusicPlayer");

		public static string FilePath { get; } = Path.Combine(Root, "playlist.txt");

		public static string MusicDir { get; } = Path.Combine(Root, "music");

		public static List<string> Load()
		{
			List<string> list = new List<string>();
			try
			{
				Directory.CreateDirectory(Root);
				Directory.CreateDirectory(MusicDir);
				if (!File.Exists(FilePath))
				{
					File.WriteAllText(FilePath, "# R.E.P.O. 音乐播放器播放列表\n# 每行一个本地文件或 http(s) 直链;相对路径相对于本文件同目录下的 music 文件夹。\n# 支持格式:mp3 / ogg / wav / flac。\n# 例:\n#   song.mp3\n#   D:\\Music\\album\\track01.ogg\n#   https://example.com/radio.mp3\n#\n# 列表为空时会自动扫描 music 文件夹里的音频文件。\n", new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				}
				string[] array = File.ReadAllLines(FilePath, Encoding.UTF8);
				foreach (string text in array)
				{
					string text2 = text.Trim();
					if (text2.Length != 0 && text2[0] != '#')
					{
						string text3 = Resolve(text2);
						if (text3 != null)
						{
							list.Add(text3);
						}
					}
				}
				if (list.Count == 0)
				{
					string[] files = Directory.GetFiles(MusicDir);
					foreach (string text4 in files)
					{
						string value = Path.GetExtension(text4).ToLowerInvariant();
						if (Array.IndexOf(Extensions, value) >= 0)
						{
							list.Add(text4);
						}
					}
					list.Sort(StringComparer.OrdinalIgnoreCase);
				}
			}
			catch (Exception ex)
			{
				MusicPlayerPlugin.Log.LogError((object)("读取播放列表失败:" + ex.Message));
			}
			return list;
		}

		private static string Resolve(string line)
		{
			if (line.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || line.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
			{
				return line;
			}
			try
			{
				string text = (Path.IsPathRooted(line) ? line : Path.Combine(MusicDir, line));
				return File.Exists(text) ? text : null;
			}
			catch
			{
				return null;
			}
		}
	}
	[BepInPlugin("xingKong.RepoMusicPlayer", "REPO Music Player", "1.0.0")]
	[BepInProcess("REPO.exe")]
	public class MusicPlayerPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "xingKong.RepoMusicPlayer";

		public const string PluginName = "REPO Music Player";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		internal static MusicConfig Cfg;

		private readonly List<string> _tracks = new List<string>();

		private AudioSource _source;

		private Coroutine _loadRoutine;

		private UnityWebRequest _request;

		private int _index = -1;

		private bool _loading;

		private bool _playing;

		private bool _paused;

		private string _toastText = string.Empty;

		private float _toastUntil;

		private GUIStyle _style;

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Cfg = new MusicConfig(((BaseUnityPlugin)this).Config);
			_source = ((Component)this).gameObject.AddComponent<AudioSource>();
			_source.playOnAwake = false;
			_source.loop = false;
			_source.spatialBlend = 0f;
			_source.volume = Mathf.Clamp01(Cfg.Volume.Value);
			ReloadPlaylist();
			Log.LogInfo((object)string.Format("{0} v{1} 已加载:{2} 首,热键 F7/F8/F9,音量 F6/F10。", "REPO Music Player", "1.0.0", _tracks.Count));
			Log.LogInfo((object)("播放列表:" + Playlist.FilePath));
		}

		private void Update()
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: 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)
			if (Cfg != null && Cfg.Enabled.Value)
			{
				if (Input.GetKeyDown(Cfg.KeyNext.Value))
				{
					Next();
				}
				else if (Input.GetKeyDown(Cfg.KeyPrevious.Value))
				{
					Previous();
				}
				else if (Input.GetKeyDown(Cfg.KeyPlayPause.Value))
				{
					TogglePause();
				}
				else if (Input.GetKeyDown(Cfg.KeyVolumeUp.Value))
				{
					ChangeVolume(0.05f);
				}
				else if (Input.GetKeyDown(Cfg.KeyVolumeDown.Value))
				{
					ChangeVolume(-0.05f);
				}
				else if (Input.GetKeyDown(Cfg.KeyReload.Value))
				{
					ReloadPlaylist();
					Next();
				}
				if (_playing && !_paused && !_loading && (Object)(object)_source != (Object)null && !_source.isPlaying)
				{
					OnTrackEnded();
				}
			}
		}

		private void OnDestroy()
		{
			VoiceBroadcast.Stop();
		}

		private void OnGUI()
		{
			//IL_009a: 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_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Expected O, but got Unknown
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			if (Cfg == null || !Cfg.Enabled.Value)
			{
				return;
			}
			bool flag = _toastUntil > Time.unscaledTime;
			if (flag || Cfg.AlwaysShow.Value)
			{
				if (_style == null)
				{
					_style = new GUIStyle(GUI.skin.label)
					{
						fontSize = 20,
						alignment = (TextAnchor)4,
						richText = false
					};
				}
				_style.normal.textColor = Color.white;
				string text = (flag ? _toastText : CurrentStatus());
				if (!string.IsNullOrEmpty(text))
				{
					Rect val = default(Rect);
					((Rect)(ref val))..ctor((float)Screen.width / 2f - 400f, 36f, 800f, 32f);
					GUI.Label(new Rect(((Rect)(ref val)).x + 1f, ((Rect)(ref val)).y + 1f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), text, ShadowStyle());
					GUI.Label(val, text, _style);
				}
			}
		}

		private GUIStyle ShadowStyle()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Expected O, but got Unknown
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			GUIStyle val = new GUIStyle(_style);
			val.normal.textColor = new Color(0f, 0f, 0f, 0.75f);
			return val;
		}

		private string CurrentStatus()
		{
			if (_tracks.Count == 0)
			{
				return "♪ 播放列表为空";
			}
			string arg = Title(_tracks[Mathf.Clamp(_index, 0, _tracks.Count - 1)]);
			string arg2 = (_loading ? "加载中" : (_paused ? "已暂停" : (_playing ? "播放中" : "已停止")));
			return $"♪ {arg}  [{arg2}]  {Mathf.RoundToInt(_source.volume * 100f)}%";
		}

		public void Next()
		{
			if (_tracks.Count == 0)
			{
				Toast("播放列表为空,见 BepInEx/config/RepoMusicPlayer/playlist.txt");
				return;
			}
			CancelLoad();
			if (Cfg.Shuffle.Value)
			{
				int num = _index;
				if (_tracks.Count > 1)
				{
					while (num == _index)
					{
						num = Random.Range(0, _tracks.Count);
					}
				}
				_index = num;
			}
			else
			{
				_index = (_index + 1) % _tracks.Count;
			}
			_loadRoutine = ((MonoBehaviour)this).StartCoroutine(LoadTrack(_tracks[_index]));
		}

		public void Previous()
		{
			if (_tracks.Count != 0)
			{
				CancelLoad();
				_index = (_index - 1 + _tracks.Count) % _tracks.Count;
				_loadRoutine = ((MonoBehaviour)this).StartCoroutine(LoadTrack(_tracks[_index]));
			}
		}

		public void TogglePause()
		{
			if ((Object)(object)_source == (Object)null || (Object)(object)_source.clip == (Object)null)
			{
				Next();
			}
			else if (_paused)
			{
				_source.UnPause();
				_paused = false;
				if ((Object)(object)_source.clip != (Object)null)
				{
					VoiceBroadcast.Start(_source.clip);
				}
				Toast("继续播放");
			}
			else if (_source.isPlaying)
			{
				_source.Pause();
				_paused = true;
				VoiceBroadcast.Stop();
				Toast("已暂停");
			}
			else
			{
				Next();
			}
		}

		private void ChangeVolume(float delta)
		{
			float num = Mathf.Clamp01(Cfg.Volume.Value + delta);
			Cfg.Volume.Value = num;
			if ((Object)(object)_source != (Object)null)
			{
				_source.volume = num;
			}
			Toast($"音量 {Mathf.RoundToInt(num * 100f)}%");
		}

		private void OnTrackEnded()
		{
			if (!Cfg.LoopPlaylist.Value)
			{
				_playing = false;
				VoiceBroadcast.Stop();
				Toast("播放结束");
			}
			else
			{
				Next();
			}
		}

		private void ReloadPlaylist()
		{
			_tracks.Clear();
			_tracks.AddRange(Playlist.Load());
			_index = -1;
			Log.LogInfo((object)$"[音乐] 播放列表已加载:{_tracks.Count} 首。");
		}

		private void CancelLoad()
		{
			VoiceBroadcast.Stop();
			if (_loadRoutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_loadRoutine);
				_loadRoutine = null;
			}
			try
			{
				UnityWebRequest request = _request;
				if (request != null)
				{
					request.Dispose();
				}
			}
			catch
			{
			}
			_request = null;
			_loading = false;
		}

		private IEnumerator LoadTrack(string track)
		{
			_loading = true;
			_playing = false;
			_paused = false;
			Toast("加载中:" + Title(track));
			bool isUrl = track.Contains("://");
			string ext = Path.GetExtension(track).ToLowerInvariant();
			if (ext == ".flac")
			{
				yield return LoadFlac(track, isUrl);
				yield break;
			}
			string uri = (isUrl ? track : new Uri(track).AbsoluteUri);
			AudioType type = (AudioType)((ext == ".ogg") ? 14 : ((ext == ".wav") ? 20 : 13));
			UnityWebRequest request = (_request = UnityWebRequestMultimedia.GetAudioClip(uri, type));
			yield return request.SendWebRequest();
			_loading = false;
			_loadRoutine = null;
			_request = null;
			if ((int)request.result != 1)
			{
				string error = request.error;
				request.Dispose();
				Toast("加载失败:" + error);
				Log.LogWarning((object)("[音乐] 加载失败 " + track + ":" + error));
				yield break;
			}
			AudioClip clip = DownloadHandlerAudioClip.GetContent(request);
			request.Dispose();
			if ((Object)(object)clip == (Object)null)
			{
				Toast("无法解码该音频");
			}
			else
			{
				Play(clip, track);
			}
		}

		private IEnumerator LoadFlac(string track, bool isUrl)
		{
			Toast("解码 FLAC:" + Title(track));
			byte[] bytes;
			if (isUrl)
			{
				UnityWebRequest request = UnityWebRequest.Get(track);
				try
				{
					_request = request;
					yield return request.SendWebRequest();
					_request = null;
					if ((int)request.result != 1)
					{
						_loading = false;
						_loadRoutine = null;
						Toast("加载失败:" + request.error);
						Log.LogWarning((object)("[音乐] FLAC 下载失败 " + track + ":" + request.error));
						yield break;
					}
					bytes = request.downloadHandler.data;
				}
				finally
				{
					((IDisposable)request)?.Dispose();
				}
			}
			else
			{
				try
				{
					bytes = File.ReadAllBytes(track);
				}
				catch (Exception ex)
				{
					Exception e = ex;
					_loading = false;
					_loadRoutine = null;
					Toast("读取失败:" + e.Message);
					yield break;
				}
			}
			FlacDecoder.Result decoded = null;
			Exception failure = null;
			Task task = Task.Run(delegate
			{
				try
				{
					decoded = FlacDecoder.Decode(bytes);
				}
				catch (Exception ex2)
				{
					failure = ex2;
				}
			});
			while (!task.IsCompleted)
			{
				yield return null;
			}
			_loading = false;
			_loadRoutine = null;
			if (failure != null || decoded == null || decoded.Samples == null || decoded.Samples.Length == 0)
			{
				Toast("FLAC 解码失败:" + (failure?.Message ?? "无音频数据"));
				Log.LogWarning((object)$"[音乐] FLAC 解码失败 {track}:{failure}");
				yield break;
			}
			int frames = decoded.Samples.Length / decoded.Channels;
			if (frames <= 0)
			{
				Toast("FLAC 没有音频帧");
				yield break;
			}
			if ((long)frames * (long)decoded.Channels > 60000000)
			{
				Toast("FLAC 文件过大,已跳过");
				Log.LogWarning((object)$"[音乐] FLAC 过大跳过 {track}:{frames} 帧 × {decoded.Channels} 声道");
				yield break;
			}
			AudioClip clip = AudioClip.Create(Title(track), frames, decoded.Channels, decoded.SampleRate, false);
			clip.SetData(decoded.Samples, 0);
			Log.LogInfo((object)$"[音乐] FLAC 解码完成:{clip.frequency}Hz / {clip.channels}ch / {clip.length:F0}s");
			Play(clip, track);
		}

		private void Play(AudioClip clip, string track)
		{
			_source.Stop();
			_source.clip = clip;
			_source.volume = Mathf.Clamp01(Cfg.Volume.Value);
			_source.Play();
			VoiceBroadcast.Start(clip);
			_playing = true;
			_paused = false;
			Toast("♪ " + Title(track));
			Log.LogInfo((object)("[音乐] 播放:" + Title(track)));
		}

		private static string Title(string track)
		{
			if (string.IsNullOrEmpty(track))
			{
				return "<空>";
			}
			if (track.Contains("://"))
			{
				return track;
			}
			return Path.GetFileNameWithoutExtension(track);
		}

		private void Toast(string text)
		{
			_toastText = text;
			_toastUntil = Time.unscaledTime + 3.5f;
		}
	}
	internal sealed class MusicConfig
	{
		public readonly ConfigEntry<bool> Enabled;

		public readonly ConfigEntry<float> Volume;

		public readonly ConfigEntry<bool> Shuffle;

		public readonly ConfigEntry<bool> LoopPlaylist;

		public readonly ConfigEntry<bool> AlwaysShow;

		public readonly ConfigEntry<bool> Broadcast;

		public readonly ConfigEntry<bool> HostOnlyBroadcast;

		public readonly ConfigEntry<KeyCode> KeyNext;

		public readonly ConfigEntry<KeyCode> KeyPrevious;

		public readonly ConfigEntry<KeyCode> KeyPlayPause;

		public readonly ConfigEntry<KeyCode> KeyVolumeUp;

		public readonly ConfigEntry<KeyCode> KeyVolumeDown;

		public readonly ConfigEntry<KeyCode> KeyReload;

		public MusicConfig(ConfigFile cfg)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			Enabled = cfg.Bind<bool>("1. 通用", "启用", true, "总开关。");
			Volume = cfg.Bind<float>("2. 播放", "音量", 0.6f, new ConfigDescription("0 ~ 1,热键可实时调整。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			Shuffle = cfg.Bind<bool>("2. 播放", "随机播放", false, "下一首时随机选曲。");
			LoopPlaylist = cfg.Bind<bool>("2. 播放", "循环播放", true, "一首播完自动下一首;关闭则播完停止。");
			AlwaysShow = cfg.Bind<bool>("2. 播放", "常驻显示状态", false, "在屏幕上方一直显示当前曲目与音量。");
			Broadcast = cfg.Bind<bool>("4. 广播", "广播给所有玩家", true, "借用游戏语音通道,把当前歌曲当成麦克风播出去,其它玩家不装插件也能听到。关闭则只在自己客户端播放。");
			HostOnlyBroadcast = cfg.Bind<bool>("4. 广播", "仅房主可广播", true, "开启时只有主机(MasterClient)会广播,避免多个客户端同时抢占语音通道。");
			KeyNext = cfg.Bind<KeyCode>("3. 热键", "下一首", (KeyCode)290, "切到下一首。");
			KeyPrevious = cfg.Bind<KeyCode>("3. 热键", "上一首", (KeyCode)288, "切到上一首。");
			KeyPlayPause = cfg.Bind<KeyCode>("3. 热键", "暂停/继续", (KeyCode)289, "暂停、继续,或没在播放时开始。");
			KeyVolumeUp = cfg.Bind<KeyCode>("3. 热键", "音量 +", (KeyCode)291, "每次 +5%。");
			KeyVolumeDown = cfg.Bind<KeyCode>("3. 热键", "音量 -", (KeyCode)287, "每次 -5%。");
			KeyReload = cfg.Bind<KeyCode>("3. 热键", "重载列表", (KeyCode)286, "重新读取 playlist.txt 并播放。");
		}
	}
	internal static class VoiceBroadcast
	{
		private static readonly FieldInfo InstanceField = typeof(PlayerVoiceChat).GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

		private static readonly FieldInfo RecorderField = typeof(PlayerVoiceChat).GetField("recorder", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		private static Recorder _recorder;

		private static bool _active;

		private static bool _saved;

		private static InputSourceType _originalSource;

		private static bool _originalTransmit;

		private static bool _originalVoiceDetection;

		private static AudioClip _originalClip;

		private static bool _originalLoop;

		private static bool _warned;

		public static bool IsActive => _active;

		public static bool CanBroadcast()
		{
			if (!MusicPlayerPlugin.Cfg.Broadcast.Value)
			{
				return false;
			}
			if (MusicPlayerPlugin.Cfg.HostOnlyBroadcast.Value)
			{
				try
				{
					if (!SemiFunc.IsMasterClientOrSingleplayer())
					{
						return false;
					}
				}
				catch
				{
					return false;
				}
			}
			return true;
		}

		public static void Start(AudioClip clip)
		{
			if ((Object)(object)clip == (Object)null || !CanBroadcast())
			{
				return;
			}
			Recorder recorder = GetRecorder();
			if ((Object)(object)recorder == (Object)null)
			{
				return;
			}
			try
			{
				if (!_active)
				{
					SaveOriginals(recorder);
				}
				recorder.SourceType = (InputSourceType)1;
				recorder.AudioClip = clip;
				recorder.LoopAudioClip = true;
				recorder.VoiceDetection = false;
				recorder.TransmitEnabled = true;
				recorder.RestartRecording();
				_active = true;
				MusicPlayerPlugin.Log.LogInfo((object)"[音乐] 已通过语音通道广播当前歌曲(其它玩家无需插件)。");
			}
			catch (Exception ex)
			{
				MusicPlayerPlugin.Log.LogWarning((object)("[音乐] 广播失败:" + ex.Message));
				Restore();
			}
		}

		public static void Stop()
		{
			if (_active)
			{
				Restore();
				MusicPlayerPlugin.Log.LogInfo((object)"[音乐] 已停止广播,恢复麦克风。");
			}
		}

		private static void Restore()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)_recorder != (Object)null && _saved)
				{
					_recorder.SourceType = _originalSource;
					_recorder.AudioClip = _originalClip;
					_recorder.LoopAudioClip = _originalLoop;
					_recorder.VoiceDetection = _originalVoiceDetection;
					_recorder.TransmitEnabled = _originalTransmit;
					_recorder.RestartRecording();
				}
			}
			catch (Exception ex)
			{
				MusicPlayerPlugin.Log.LogDebug((object)("[音乐] 恢复麦克风失败:" + ex.Message));
			}
			_active = false;
			_saved = false;
		}

		private static void SaveOriginals(Recorder recorder)
		{
			//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)
			_recorder = recorder;
			_originalSource = recorder.SourceType;
			_originalClip = recorder.AudioClip;
			_originalLoop = recorder.LoopAudioClip;
			_originalVoiceDetection = recorder.VoiceDetection;
			_originalTransmit = recorder.TransmitEnabled;
			_saved = true;
		}

		private static Recorder GetRecorder()
		{
			try
			{
				object? obj = InstanceField?.GetValue(null);
				PlayerVoiceChat val = (PlayerVoiceChat)((obj is PlayerVoiceChat) ? obj : null);
				if ((Object)(object)val == (Object)null)
				{
					if (!_warned)
					{
						_warned = true;
						MusicPlayerPlugin.Log.LogInfo((object)"[音乐] 未找到 PlayerVoiceChat,广播模式不可用(仅本地播放)。");
					}
					return null;
				}
				object? obj2 = RecorderField?.GetValue(val);
				Recorder val2 = (Recorder)((obj2 is Recorder) ? obj2 : null);
				if ((Object)(object)val2 == (Object)null && !_warned)
				{
					_warned = true;
					MusicPlayerPlugin.Log.LogInfo((object)"[音乐] PlayerVoiceChat 上没有 Recorder,广播模式不可用(仅本地播放)。");
				}
				return val2;
			}
			catch (Exception ex)
			{
				MusicPlayerPlugin.Log.LogDebug((object)("[音乐] 获取 Recorder 失败:" + ex.Message));
				return null;
			}
		}
	}
}