Decompiled source of CustomSong v1.1.2

BepInEx/plugins/CustomSong/CustomSong.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using BepInEx;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using CustomSong.Auto;
using HarmonyLib;
using Il2CppInterop.Common;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.2.0")]
[module: UnverifiableCode]
[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 CustomSong
{
	internal static class BackgroundCatalog
	{
		internal struct Entry
		{
			internal IntPtr Material;

			internal string Video;

			internal bool Said
			{
				get
				{
					if (!(Material != IntPtr.Zero))
					{
						return Video != null;
					}
					return true;
				}
			}
		}

		private static readonly Dictionary<string, Entry[]> ByBase = new Dictionary<string, Entry[]>(StringComparer.Ordinal);

		internal static int Count => ByBase.Count;

		internal static void Reset()
		{
			ByBase.Clear();
		}

		internal static bool Add(ChartInfo info, out string reason)
		{
			reason = null;
			if (info.BackgroundPaths == null)
			{
				return true;
			}
			Entry[] array = new Entry[4];
			bool result = true;
			for (int i = 0; i < array.Length; i++)
			{
				string text = info.BackgroundPaths[i];
				if (text == null)
				{
					continue;
				}
				if (IsVideo(text))
				{
					array[i].Video = text;
					continue;
				}
				string reason2;
				IntPtr intPtr = JacketFactory.Build(text, "background", out reason2);
				if (intPtr == IntPtr.Zero)
				{
					result = false;
				}
				else
				{
					array[i].Material = intPtr;
				}
			}
			ByBase[info.BaseName] = array;
			return result;
		}

		internal static bool Find(IntPtr songInfo, int difficulty, out Entry entry, out string why)
		{
			entry = default(Entry);
			why = null;
			string text = JacketCatalog.NameOf(songInfo);
			if (text == null)
			{
				return false;
			}
			if (!ByBase.TryGetValue(text, out var value))
			{
				return false;
			}
			if (difficulty >= 0 && difficulty < value.Length)
			{
				entry = value[difficulty];
			}
			else
			{
				for (int i = 0; i < value.Length; i++)
				{
					if (i == 0)
					{
						entry = value[0];
					}
					else if (value[i].Material != entry.Material || value[i].Video != entry.Video)
					{
						return false;
					}
				}
				if (!entry.Said)
				{
					return false;
				}
			}
			if (!entry.Said)
			{
				return false;
			}
			if (entry.Video == null)
			{
				return true;
			}
			IntPtr intPtr = VideoBackground.Material(entry.Video, out why);
			if (intPtr == IntPtr.Zero)
			{
				return false;
			}
			entry.Material = intPtr;
			return true;
		}

		private static bool IsVideo(string path)
		{
			return string.Equals(Path.GetExtension(path), ".mp4", StringComparison.OrdinalIgnoreCase);
		}
	}
	internal static class BackgroundClone
	{
		internal static IntPtr Build(IntPtr gameMaterial, IntPtr ours, out string reason)
		{
			reason = null;
			if (VideoBackground.Owns(ours))
			{
				return VideoBackground.Rebase(gameMaterial, out reason);
			}
			IntPtr intPtr = JacketFactory.MainTexture(ours, out reason);
			if (intPtr == IntPtr.Zero)
			{
				return IntPtr.Zero;
			}
			IntPtr intPtr2 = JacketFactory.CloneFrom(gameMaterial, out reason);
			if (intPtr2 == IntPtr.Zero)
			{
				return IntPtr.Zero;
			}
			if (!JacketFactory.SetMainTexture(intPtr2, intPtr, out reason))
			{
				return IntPtr.Zero;
			}
			return intPtr2;
		}
	}
	internal static class VideoBackground
	{
		private const int TextureWidth = 1920;

		private const int TextureHeight = 1080;

		private const string CoreAssembly = "UnityEngine.CoreModule.dll";

		private const string VideoAssembly = "UnityEngine.VideoModule.dll";

		private const string CoreNamespace = "UnityEngine";

		private const string VideoNamespace = "UnityEngine.Video";

		private static IntPtr _object;

		private static IntPtr _player;

		private static IntPtr _texture;

		private static IntPtr _material;

		private static string _playing;

		private static bool _armed;

		private static bool _running;

		private static bool _held;

		private static bool _ended;

		private static double _heldClock = double.NaN;

		private static bool _rewound;

		private static IntPtr _gameSceneClass;

		private static int _engineField = -1;

		private static int _clockField = -1;

		private static bool _clockMissing;

		private static IntPtr _objectHandle;

		private static IntPtr _playerHandle;

		private static IntPtr _textureHandle;

		private static IntPtr _materialHandle;

		private static bool _unavailable;

		private const double ClockSlack = 0.3;

		private const double Chase = 6.0;

		private const long PlaceCooldownMs = 250L;

		private const string PlaceFailed = "the background video could not be placed on the chart clock; it will run out of step with the chart";

		private static bool _placeWarned;

		private static double _lastClock = double.NaN;

		private static long _lastPlace = long.MinValue;

		private static double _length = -1.0;

		private const string StartFailed = "the background video could not be started with the song; the background will not move with it";

		internal static long Starts;

		internal static long Holds;

		internal static long Resumes;

		private static IntPtr LastResult;

		internal static string State
		{
			get
			{
				if (_unavailable)
				{
					return "fail";
				}
				if (_player == IntPtr.Zero)
				{
					return "none";
				}
				if (_ended)
				{
					return "end";
				}
				if (Flag("get_isPlaying"))
				{
					return "play";
				}
				if (Flag("get_isPrepared"))
				{
					return "ready";
				}
				return "load";
			}
		}

		internal static IntPtr Material(string path, out string reason)
		{
			reason = null;
			if (_unavailable)
			{
				return IntPtr.Zero;
			}
			if (_playing == path && _material != IntPtr.Zero)
			{
				string reason2 = null;
				Park(out reason2);
				if (!Show(_texture, out reason))
				{
					return IntPtr.Zero;
				}
				_armed = false;
				_running = false;
				_held = false;
				_ended = false;
				return _material;
			}
			try
			{
				if (!Ensure(out reason))
				{
					_unavailable = true;
					return IntPtr.Zero;
				}
				if (_playing != null)
				{
					Stop();
				}
				if (!Set("set_source", 1, 1, out reason))
				{
					return IntPtr.Zero;
				}
				if (!Set("set_url", 1, FileUrl(path), out reason))
				{
					return IntPtr.Zero;
				}
				if (!Set("set_renderMode", 1, 2, out reason))
				{
					return IntPtr.Zero;
				}
				if (!Set("set_targetTexture", 1, _texture, out reason))
				{
					return IntPtr.Zero;
				}
				if (!Set("set_isLooping", 1, value: false, out reason))
				{
					return IntPtr.Zero;
				}
				if (!Set("set_playOnAwake", 1, value: false, out reason))
				{
					return IntPtr.Zero;
				}
				if (!Set("set_audioOutputMode", 1, 0, out reason))
				{
					return IntPtr.Zero;
				}
				if (!Call("Prepare", 0, _player, out reason))
				{
					return IntPtr.Zero;
				}
				if (!Show(_texture, out reason))
				{
					return IntPtr.Zero;
				}
				_playing = path;
				_length = -1.0;
				_armed = false;
				_running = false;
				_held = false;
				_ended = false;
				return _material;
			}
			catch (Exception)
			{
				return IntPtr.Zero;
			}
		}

		internal static bool Owns(IntPtr material)
		{
			if (material != IntPtr.Zero)
			{
				return material == _material;
			}
			return false;
		}

		internal static IntPtr Rebase(IntPtr gameMaterial, out string reason)
		{
			reason = null;
			if (_material == IntPtr.Zero)
			{
				reason = "no clip material is loaded";
				return IntPtr.Zero;
			}
			IntPtr intPtr = JacketFactory.CloneFrom(gameMaterial, out reason);
			if (intPtr == IntPtr.Zero)
			{
				return IntPtr.Zero;
			}
			if (!Show(intPtr, _texture, out reason))
			{
				return IntPtr.Zero;
			}
			_material = intPtr;
			return intPtr;
		}

		internal static void Restart()
		{
			if (!(_player == IntPtr.Zero) && _playing != null)
			{
				string reason = null;
				Park(out reason);
				_armed = true;
				_running = false;
				_held = false;
				_ended = false;
			}
		}

		private static void Park(out string reason)
		{
			if (Flag("get_isPrepared"))
			{
				Set("set_time", 1, 0.0, out reason);
			}
			Call("Pause", 0, _player, out reason);
		}

		internal static void Follow()
		{
			if (_player == IntPtr.Zero || _playing == null)
			{
				return;
			}
			double num = ChartClock();
			if (double.IsNaN(num))
			{
				if (_held)
				{
					_heldClock = double.NaN;
				}
				return;
			}
			bool moved = num != _lastClock;
			_lastClock = num;
			if (!_running && !_held && num < 0.0 && Flag("get_isPlaying"))
			{
				string reason = null;
				Park(out reason);
			}
			else
			{
				if (!_armed)
				{
					return;
				}
				if (_held)
				{
					if (double.IsNaN(_heldClock))
					{
						return;
					}
					if (!_rewound)
					{
						if (num < _heldClock - 0.3)
						{
							_rewound = true;
						}
						return;
					}
					double num2 = Time();
					if (!(num2 < 0.0) && !(num < num2))
					{
						Resume();
					}
				}
				else
				{
					if (_ended)
					{
						return;
					}
					if (!_running)
					{
						if (num >= 0.0)
						{
							Start(num);
						}
					}
					else if (!FilmOver())
					{
						Place(num, moved);
					}
				}
			}
		}

		private static bool FilmOver()
		{
			if (_ended)
			{
				return true;
			}
			if (_running && !Flag("get_isPlaying"))
			{
				double num = Length();
				double num2 = ChartClock();
				if (num > 0.0 && !double.IsNaN(num2) && num2 >= num)
				{
					_ended = true;
					_running = false;
					return true;
				}
			}
			return false;
		}

		private unsafe static double Length()
		{
			if (_length >= 0.0)
			{
				return _length;
			}
			IntPtr intPtr = Entry(_player, "get_length", 0);
			if (intPtr == IntPtr.Zero)
			{
				return 0.0;
			}
			IntPtr zero = IntPtr.Zero;
			IntPtr intPtr2 = IL2CPP.il2cpp_runtime_invoke(intPtr, _player, (void**)null, ref zero);
			if (zero != IntPtr.Zero || intPtr2 == IntPtr.Zero)
			{
				return 0.0;
			}
			return _length = *(double*)(void*)(intPtr2 + 16);
		}

		private static void Place(double clock, bool moved = true)
		{
			double num = Time();
			if (num < 0.0)
			{
				return;
			}
			double num2 = num - clock;
			if ((num2 <= 0.3 && num2 >= -0.3) || num2 > 6.0 || num2 < -6.0 || !moved)
			{
				return;
			}
			long tickCount = Environment.TickCount64;
			if (tickCount - _lastPlace < 250)
			{
				return;
			}
			string reason = null;
			if (!Set("set_time", 1, clock, out reason))
			{
				_lastPlace = tickCount;
				if (!_placeWarned)
				{
					_placeWarned = true;
					ModLog.WarnRelease("the background video could not be placed on the chart clock; it will run out of step with the chart");
				}
			}
			else
			{
				_lastPlace = tickCount;
			}
		}

		private static bool Start(double clock)
		{
			string reason = null;
			Park(out reason);
			if (!Show(_texture, out reason) || !Call("Play", 0, _player, out reason))
			{
				ModLog.WarnRelease("the background video could not be started with the song; the background will not move with it");
				_armed = false;
				return false;
			}
			_running = true;
			Starts++;
			return true;
		}

		private static double ChartClock()
		{
			if (_clockMissing)
			{
				return double.NaN;
			}
			if (_gameSceneClass == IntPtr.Zero)
			{
				_gameSceneClass = FieldResolver.ClassPointer("GameScene");
				if (_gameSceneClass == IntPtr.Zero)
				{
					return double.NaN;
				}
			}
			if (_engineField == -1)
			{
				_engineField = FieldResolver.Field("GameScene", "_DN");
				_clockField = FieldResolver.Field("_VD", "_WEA");
				if (_engineField < 0 || _clockField < 0)
				{
					_clockMissing = true;
					ModLog.Error("the chart's clock could not be read by name in this build; the in-play background video will not play");
					return double.NaN;
				}
			}
			IntPtr intPtr = FieldResolver.Statics(_gameSceneClass);
			if (intPtr == IntPtr.Zero)
			{
				return double.NaN;
			}
			IntPtr intPtr2 = Memory.Ptr(intPtr + _engineField);
			if (!Memory.LooksLikeObject(intPtr2))
			{
				return double.NaN;
			}
			return Memory.F64(intPtr2 + _clockField);
		}

		internal static void Hold(double at)
		{
			if (!(_player == IntPtr.Zero) && _running && !_held)
			{
				string reason = null;
				if (Call("Pause", 0, _player, out reason))
				{
					_held = true;
					_heldClock = ChartClock();
					_rewound = false;
					Holds++;
				}
			}
		}

		internal static void Resume(bool atMusicReturn = false)
		{
			if (!(_player == IntPtr.Zero) && _running && _held && !_ended && (!atMusicReturn || double.IsNaN(_heldClock)))
			{
				_held = false;
				double num = ChartClock();
				if (!double.IsNaN(num))
				{
					Place(num);
				}
				string reason = null;
				if (Call("Play", 0, _player, out reason))
				{
					Resumes++;
				}
			}
		}

		private unsafe static bool Flag(string name)
		{
			IntPtr intPtr = Entry(_player, name, 0);
			if (intPtr == IntPtr.Zero)
			{
				return false;
			}
			IntPtr zero = IntPtr.Zero;
			IntPtr intPtr2 = IL2CPP.il2cpp_runtime_invoke(intPtr, _player, (void**)null, ref zero);
			if (zero == IntPtr.Zero && intPtr2 != IntPtr.Zero)
			{
				return *(byte*)(void*)(intPtr2 + 16) != 0;
			}
			return false;
		}

		private unsafe static double Time()
		{
			IntPtr intPtr = Entry(_player, "get_time", 0);
			if (intPtr == IntPtr.Zero)
			{
				return -1.0;
			}
			IntPtr zero = IntPtr.Zero;
			IntPtr intPtr2 = IL2CPP.il2cpp_runtime_invoke(intPtr, _player, (void**)null, ref zero);
			if (zero != IntPtr.Zero || intPtr2 == IntPtr.Zero)
			{
				return -1.0;
			}
			return *(double*)(void*)(intPtr2 + 16);
		}

		internal static void Stop()
		{
			if (!(_player == IntPtr.Zero))
			{
				string reason = null;
				Call("Stop", 0, _player, out reason);
				_playing = null;
				_armed = false;
				_running = false;
				_held = false;
				_ended = false;
			}
		}

		private unsafe static bool Ensure(out string reason)
		{
			reason = null;
			if (_player != IntPtr.Zero)
			{
				return true;
			}
			IntPtr intPtr = Require("UnityEngine.CoreModule.dll", "UnityEngine", "GameObject", out reason);
			if (intPtr == IntPtr.Zero)
			{
				return false;
			}
			IntPtr intPtr2 = Require("UnityEngine.VideoModule.dll", "UnityEngine.Video", "VideoPlayer", out reason);
			if (intPtr2 == IntPtr.Zero)
			{
				return false;
			}
			IntPtr intPtr3 = Require("UnityEngine.CoreModule.dll", "UnityEngine", "RenderTexture", out reason);
			if (intPtr3 == IntPtr.Zero)
			{
				return false;
			}
			IntPtr intPtr4 = Require("UnityEngine.CoreModule.dll", "UnityEngine", "Object", out reason);
			if (intPtr4 == IntPtr.Zero)
			{
				return false;
			}
			IntPtr intPtr5 = IL2CPP.il2cpp_object_new(intPtr);
			if (intPtr5 == IntPtr.Zero)
			{
				return false;
			}
			IntPtr* ptr = stackalloc IntPtr[1];
			IntPtr intPtr6 = IL2CPP.ManagedStringToIl2Cpp("CustomSong background video");
			*ptr = intPtr6;
			if (!Invoke(Entry(intPtr5, ".ctor", 1), intPtr5, ptr, 1, out reason))
			{
				return false;
			}
			*ptr = intPtr5;
			if (!Invoke(EntryOn(intPtr4, "DontDestroyOnLoad", 1), IntPtr.Zero, ptr, 1, out reason))
			{
				return false;
			}
			IntPtr intPtr7 = IL2CPP.il2cpp_class_get_type(intPtr2);
			if (intPtr7 == IntPtr.Zero)
			{
				return false;
			}
			*ptr = IL2CPP.il2cpp_type_get_object(intPtr7);
			if (!Invoke(Entry(intPtr5, "AddComponent", 1), intPtr5, ptr, 1, out reason))
			{
				return false;
			}
			IntPtr lastResult = LastResult;
			if (!Memory.LooksLikeObject(lastResult))
			{
				return false;
			}
			if (!IsVideoPlayer(IL2CPP.il2cpp_object_get_class(lastResult), intPtr2))
			{
				return false;
			}
			IntPtr intPtr8 = IL2CPP.il2cpp_object_new(intPtr3);
			if (intPtr8 == IntPtr.Zero)
			{
				return false;
			}
			int num = 1920;
			int num2 = 1080;
			int num3 = 0;
			IntPtr* ptr2 = stackalloc IntPtr[3];
			*ptr2 = (IntPtr)(&num);
			ptr2[1] = (IntPtr)(&num2);
			ptr2[2] = (IntPtr)(&num3);
			if (!Invoke(Entry(intPtr8, ".ctor", 3), intPtr8, ptr2, 3, out reason))
			{
				return false;
			}
			if (!Call("Create", 0, intPtr8, out reason))
			{
				return false;
			}
			if (WidthOf(intPtr8) != 1920)
			{
				return false;
			}
			IntPtr intPtr9 = JacketFactory.BuildFromTexture(intPtr8, "background", out reason);
			if (intPtr9 == IntPtr.Zero)
			{
				return false;
			}
			_object = intPtr5;
			_player = lastResult;
			_texture = intPtr8;
			_material = intPtr9;
			Root(ref _objectHandle, intPtr5);
			Root(ref _playerHandle, lastResult);
			Root(ref _textureHandle, intPtr8);
			Root(ref _materialHandle, intPtr9);
			return true;
		}

		private unsafe static int WidthOf(IntPtr texture)
		{
			IntPtr intPtr = Entry(texture, "get_width", 0);
			if (intPtr == IntPtr.Zero)
			{
				return -1;
			}
			IntPtr zero = IntPtr.Zero;
			IntPtr intPtr2 = IL2CPP.il2cpp_runtime_invoke(intPtr, texture, (void**)null, ref zero);
			if (zero != IntPtr.Zero || intPtr2 == IntPtr.Zero)
			{
				return -1;
			}
			return *(int*)(void*)(intPtr2 + 16);
		}

		private static bool IsVideoPlayer(IntPtr klass, IntPtr wanted)
		{
			IntPtr intPtr = klass;
			while (intPtr != IntPtr.Zero)
			{
				if (intPtr == wanted)
				{
					return true;
				}
				intPtr = IL2CPP.il2cpp_class_get_parent(intPtr);
			}
			return false;
		}

		private static void Root(ref IntPtr handle, IntPtr obj)
		{
			if (!(handle != IntPtr.Zero) && !(obj == IntPtr.Zero))
			{
				handle = IL2CPP.il2cpp_gchandle_new(obj, false);
			}
		}

		private unsafe static bool Call(string name, int parameters, IntPtr instance, out string reason)
		{
			reason = null;
			IntPtr intPtr = Entry(instance, name, parameters);
			if (intPtr == IntPtr.Zero)
			{
				return false;
			}
			IntPtr zero = IntPtr.Zero;
			LastResult = IL2CPP.il2cpp_runtime_invoke(intPtr, instance, (void**)null, ref zero);
			if (zero == IntPtr.Zero)
			{
				return true;
			}
			return false;
		}

		private unsafe static bool Set(string name, int parameters, int value, out string reason)
		{
			int num = value;
			return SetArgument(name, (IntPtr)(&num), out reason);
		}

		private unsafe static bool Set(string name, int parameters, double value, out string reason)
		{
			double num = value;
			return SetArgument(name, (IntPtr)(&num), out reason);
		}

		private unsafe static bool Set(string name, int parameters, bool value, out string reason)
		{
			byte b = (value ? ((byte)1) : ((byte)0));
			return SetArgument(name, (IntPtr)(&b), out reason);
		}

		private static bool Set(string name, int parameters, IntPtr reference, out string reason)
		{
			return SetArgument(name, reference, out reason);
		}

		private static bool Set(string name, int parameters, string value, out string reason)
		{
			return SetArgument(name, IL2CPP.ManagedStringToIl2Cpp(value), out reason);
		}

		private unsafe static bool SetArgument(string name, IntPtr argument, out string reason)
		{
			reason = null;
			if (_player == IntPtr.Zero)
			{
				return false;
			}
			IntPtr intPtr = Entry(_player, name, 1);
			if (intPtr == IntPtr.Zero)
			{
				return false;
			}
			IntPtr* ptr = stackalloc IntPtr[1];
			*ptr = argument;
			IntPtr zero = IntPtr.Zero;
			IL2CPP.il2cpp_runtime_invoke(intPtr, _player, (void**)ptr, ref zero);
			if (zero == IntPtr.Zero)
			{
				return true;
			}
			return false;
		}

		private unsafe static bool Show(IntPtr material, IntPtr texture, out string reason)
		{
			reason = null;
			if (material == IntPtr.Zero)
			{
				return false;
			}
			IntPtr intPtr = Entry(material, "set_mainTexture", 1);
			if (intPtr == IntPtr.Zero)
			{
				reason = "Material.set_mainTexture could not be found";
				return false;
			}
			IntPtr* ptr = stackalloc IntPtr[1];
			*ptr = texture;
			IntPtr zero = IntPtr.Zero;
			IL2CPP.il2cpp_runtime_invoke(intPtr, material, (void**)ptr, ref zero);
			if (zero == IntPtr.Zero)
			{
				return true;
			}
			reason = "Material.set_mainTexture raised: " + Raised.Text(zero);
			return false;
		}

		private static bool Show(IntPtr texture, out string reason)
		{
			return Show(_material, texture, out reason);
		}

		private static string FileUrl(string path)
		{
			return "file:///" + path.Replace('\\', '/');
		}

		private static IntPtr Entry(IntPtr instance, string name, int parameters)
		{
			if (!Memory.LooksLikeObject(instance))
			{
				return IntPtr.Zero;
			}
			return IL2CPP.il2cpp_class_get_method_from_name(IL2CPP.il2cpp_object_get_class(instance), name, parameters);
		}

		private static IntPtr EntryOn(IntPtr klass, string name, int parameters)
		{
			if (klass == IntPtr.Zero)
			{
				return IntPtr.Zero;
			}
			return IL2CPP.il2cpp_class_get_method_from_name(klass, name, parameters);
		}

		private static IntPtr Require(string assembly, string namespaze, string typeName, out string reason)
		{
			reason = null;
			IntPtr il2CppClass = IL2CPP.GetIl2CppClass(assembly, namespaze, typeName);
			if (il2CppClass == IntPtr.Zero)
			{
				return IntPtr.Zero;
			}
			IL2CPP.il2cpp_runtime_class_init(il2CppClass);
			return il2CppClass;
		}

		private unsafe static bool Invoke(IntPtr method, IntPtr instance, IntPtr* args, int count, out string reason)
		{
			reason = null;
			if (method == IntPtr.Zero)
			{
				return false;
			}
			for (int i = 0; i < count; i++)
			{
				if (!(args[i] != IntPtr.Zero))
				{
					return false;
				}
			}
			IntPtr zero = IntPtr.Zero;
			LastResult = IL2CPP.il2cpp_runtime_invoke(method, instance, (void**)args, ref zero);
			if (zero == IntPtr.Zero)
			{
				return true;
			}
			return false;
		}
	}
	internal static class AudioCatalog
	{
		private sealed class Entry
		{
			internal int Index;

			internal string Path;

			internal int Length;

			private AudioCodec.Prepared _audio;

			internal byte[] Bytes
			{
				get
				{
					if (_audio != null)
					{
						return _audio.Bytes;
					}
					_audio = AudioCodec.Load(Path, out var reason);
					if (_audio != null)
					{
						return _audio.Bytes;
					}
					ModLog.Error("audio '" + Path + "' could not be prepared: " + reason);
					return Array.Empty<byte>();
				}
			}
		}

		private const string Extension = ".wav";

		private static readonly Dictionary<string, Entry> ByBase = new Dictionary<string, Entry>(StringComparer.Ordinal);

		private static readonly Dictionary<string, Entry> ByDifficulty = new Dictionary<string, Entry>(StringComparer.Ordinal);

		private static readonly Dictionary<int, Entry> ByIndex = new Dictionary<int, Entry>();

		internal static long Served;

		internal static long ServedBytes;

		internal static long Resolved;

		internal static void Reset()
		{
			ByBase.Clear();
			ByDifficulty.Clear();
			ByIndex.Clear();
		}

		internal static int IndexOf(string baseName)
		{
			if (!ByBase.TryGetValue(baseName, out var value))
			{
				return -1;
			}
			return value.Index;
		}

		internal static int BytesOf(string baseName)
		{
			if (!ByBase.TryGetValue(baseName, out var value))
			{
				return -1;
			}
			return value.Length;
		}

		internal static bool Add(string baseName, string path, int difficulty, out string reason)
		{
			reason = null;
			int num = AudioCodec.Measure(path, out reason);
			if (num <= 0)
			{
				return false;
			}
			int num2 = StreamingAssets.Add(num);
			if (num2 < 0)
			{
				reason = "the game's asset manager would not take another file";
				return false;
			}
			Entry value = new Entry
			{
				Index = num2,
				Path = path,
				Length = num
			};
			if (difficulty < 0)
			{
				ByBase[baseName] = value;
			}
			else
			{
				ByDifficulty[baseName + "\n" + difficulty] = value;
			}
			ByIndex[num2] = value;
			return true;
		}

		internal static bool TryResolve(IntPtr namePtr, IntPtr indexPtr)
		{
			if (!CouldBeAudio(namePtr))
			{
				return false;
			}
			string text = Memory.Text(namePtr, 128);
			if (text == null)
			{
				return false;
			}
			if (!text.EndsWith(".wav", StringComparison.Ordinal))
			{
				return false;
			}
			string text2 = text.Substring(0, text.Length - ".wav".Length);
			if (text2.Length == 0)
			{
				return false;
			}
			int appliedDifficulty = CurrentSong.AppliedDifficulty;
			if ((appliedDifficulty < 0 || !ByDifficulty.TryGetValue(text2 + "\n" + appliedDifficulty, out var value)) && !ByBase.TryGetValue(text2, out value))
			{
				return false;
			}
			Memory.WriteI32(indexPtr, value.Index);
			Resolved++;
			return true;
		}

		private unsafe static bool CouldBeAudio(IntPtr s)
		{
			if (!Memory.LooksLikeObject(s))
			{
				return false;
			}
			try
			{
				int num = IL2CPP.il2cpp_string_length(s);
				if (num < ".wav".Length + 1 || num > 128)
				{
					return false;
				}
				char* ptr = IL2CPP.il2cpp_string_chars(s);
				if (ptr == null)
				{
					return false;
				}
				return ptr[num - 1] == 'v';
			}
			catch (Exception)
			{
				return false;
			}
		}

		internal unsafe static bool TryServe(IntPtr record)
		{
			if (Hooks.CompleteRead == (delegate* unmanaged[Cdecl]<IntPtr, uint, void>)null)
			{
				return false;
			}
			int key = (int)(Memory.I64(record + 8) & 0xFFFFFF);
			if (!ByIndex.TryGetValue(key, out var value))
			{
				return false;
			}
			IntPtr intPtr = Memory.Ptr(record + 0);
			if (intPtr == IntPtr.Zero)
			{
				return false;
			}
			int num = Memory.I32(intPtr + 8);
			int num2 = Memory.I32(intPtr + 12);
			IntPtr intPtr2 = Memory.Ptr(intPtr + 32);
			if (intPtr2 == IntPtr.Zero || num2 <= 0)
			{
				return false;
			}
			byte[] bytes = value.Bytes;
			int val = ((num >= 0 && num < bytes.Length) ? (bytes.Length - num) : 0);
			int num3 = Math.Min(num2, val);
			if (num3 > 0)
			{
				Marshal.Copy(bytes, num, intPtr2, num3);
			}
			Served++;
			ServedBytes += num3;
			Memory.WriteI32(intPtr + 40, num3);
			ReadBookkeeping.Finished(record);
			byte* ptr = stackalloc byte[40];
			Buffer.MemoryCopy((void*)record, ptr, 40L, 40L);
			Hooks.CompleteRead((IntPtr)ptr, 0u);
			return true;
		}
	}
	internal static class ChartCatalog
	{
		private const string Extension = ".spc";

		private static readonly Dictionary<string, ChartInfo> ByBase = new Dictionary<string, ChartInfo>(StringComparer.Ordinal);

		private static int _sourceTextField = int.MinValue;

		internal static void Adopt(ChartInfo info)
		{
			ByBase[info.BaseName] = info;
		}

		internal static void Reset()
		{
			ByBase.Clear();
		}

		internal unsafe static bool TryServe(IntPtr namePtr, IntPtr result)
		{
			string text = Memory.Text(namePtr, 128);
			if (text == null)
			{
				return false;
			}
			if (!Split(text, out var baseName, out var difficulty))
			{
				return false;
			}
			if (!ByBase.TryGetValue(baseName, out var value))
			{
				return false;
			}
			string path = value.ChartPaths[difficulty];
			string seed = value.ChartNames[difficulty];
			bool flag = false;
			try
			{
				flag = ChartCodec.TryDecode(File.ReadAllBytes(path), seed, result, out var reason);
				if (!flag)
				{
					ModLog.Error($"'{value.Name}' difficulty {difficulty}: {reason}");
				}
			}
			catch (Exception e)
			{
				ModLog.Error($"'{value.Name}' difficulty {difficulty} could not be read: " + ModLog.Describe(e));
			}
			if (!flag && !ChartCodec.Empty(result))
			{
				return false;
			}
			int num = SourceTextField();
			if (num >= 0)
			{
				*(IntPtr*)(void*)(result + num) = IntPtr.Zero;
			}
			return true;
		}

		private static int SourceTextField()
		{
			if (_sourceTextField == int.MinValue)
			{
				_sourceTextField = FieldResolver.Field("_R", "_RC");
			}
			return _sourceTextField;
		}

		private static bool Split(string name, out string baseName, out int difficulty)
		{
			baseName = null;
			difficulty = 0;
			if (!name.EndsWith(".spc", StringComparison.Ordinal))
			{
				return false;
			}
			int num = name.Length - ".spc".Length;
			if (num < 2)
			{
				return false;
			}
			char c = name[num - 1];
			if (c < '0' || c > '3')
			{
				return false;
			}
			difficulty = c - 48;
			baseName = name.Substring(0, num - 1);
			return true;
		}
	}
	internal static class ChartFolderLibrary
	{
		private const string FolderName = "Charts";

		private const string DefaultPack = "Custom Song";

		private static string _located;

		internal static string ModFolder()
		{
			try
			{
				string location = Assembly.GetExecutingAssembly().Location;
				if (!string.IsNullOrEmpty(location))
				{
					return Path.GetDirectoryName(location);
				}
			}
			catch (Exception)
			{
			}
			return null;
		}

		private static string GameFolder()
		{
			try
			{
				string gameRootPath = Paths.GameRootPath;
				if (!string.IsNullOrEmpty(gameRootPath))
				{
					return gameRootPath;
				}
			}
			catch (Exception)
			{
			}
			try
			{
				string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
				if (!string.IsNullOrEmpty(baseDirectory))
				{
					return baseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
				}
			}
			catch (Exception)
			{
			}
			return null;
		}

		internal static string Locate()
		{
			if (_located != null)
			{
				return _located;
			}
			string text = ModFolder();
			if (text == null)
			{
				return null;
			}
			DirectoryInfo parent = Directory.GetParent(text);
			string text2 = GameFolder();
			string[] array = new string[4]
			{
				(text2 == null) ? null : Path.GetFullPath(Path.Combine(text2, "Charts")),
				(parent?.Parent == null) ? null : Path.GetFullPath(Path.Combine(parent.Parent.FullName, "Charts")),
				(parent == null) ? null : Path.GetFullPath(Path.Combine(parent.FullName, "Charts")),
				Path.GetFullPath(Path.Combine(text, "Charts"))
			};
			string[] array2 = array;
			foreach (string text3 in array2)
			{
				if (text3 != null && Directory.Exists(text3))
				{
					_located = text3;
					ModLog.Load("Charts folder: " + text3);
					return text3;
				}
			}
			string text4 = array[0] ?? array[1];
			if (text4 == null)
			{
				return null;
			}
			try
			{
				Directory.CreateDirectory(text4);
				Directory.CreateDirectory(Path.Combine(text4, "Custom Song"));
				_located = text4;
				ModLog.Load("Charts folder: " + text4 + " (created)");
				return text4;
			}
			catch (Exception)
			{
				return text4;
			}
		}

		internal static List<ChartPack> ImportPacks(string root, Func<ChartInfo, string> validateCharts)
		{
			List<ChartPack> list = new List<ChartPack>();
			if (string.IsNullOrEmpty(root) || !Directory.Exists(root))
			{
				ModLog.WarnRelease($"no {"Charts"} folder at {root}; nothing to load");
				return list;
			}
			string[] directories;
			try
			{
				directories = Directory.GetDirectories(root);
				Array.Sort(directories, (IComparer<string>?)StringComparer.OrdinalIgnoreCase);
			}
			catch (Exception e)
			{
				ModLog.WarnRelease(root + " could not be listed: " + ModLog.Describe(e));
				return list;
			}
			if (directories.Length == 0)
			{
				ModLog.WarnRelease("'" + root + "' has no pack folder inside it");
			}
			string[] files;
			try
			{
				files = Directory.GetFiles(root, "*.falsum");
				foreach (string path in files)
				{
					ModLog.WarnRelease("'" + Path.GetFileName(path) + "' is directly under Charts; put it inside a pack folder instead");
				}
			}
			catch (Exception)
			{
			}
			files = directories;
			foreach (string text in files)
			{
				string fileName = Path.GetFileName(text);
				if (File.Exists(Path.Combine(text, "if")))
				{
					continue;
				}
				ChartPack chartPack = ReadPack(text);
				string[] directories2;
				string[] files2;
				try
				{
					files2 = Directory.GetFiles(text, "*.falsum");
					for (int j = 0; j < files2.Length; j++)
					{
						FalsumSong.Import(files2[j]);
					}
					directories2 = Directory.GetDirectories(text);
					Array.Sort(directories2, (IComparer<string>?)StringComparer.OrdinalIgnoreCase);
				}
				catch (Exception)
				{
					continue;
				}
				files2 = directories2;
				foreach (string text2 in files2)
				{
					if (!File.Exists(Path.Combine(text2, "if")))
					{
						StudioProject.EnsureIf(text2);
					}
					if (!File.Exists(Path.Combine(text2, "if")))
					{
						string text3 = Nested(text2);
						ModLog.WarnRelease((text3 == null) ? ("chart folder '" + Path.GetFileName(text2) + "' has no if; skipped") : ($"chart folder '{Path.GetFileName(text2)}' has no if, but its '{text3}' " + "does; the song is one folder too deep"));
					}
					else
					{
						AddSong(chartPack, text2, validateCharts);
					}
				}
				if (chartPack.Charts.Count > 0)
				{
					list.Add(chartPack);
				}
				else
				{
					ModLog.WarnRelease("pack folder '" + fileName + "' has no chart folder; skipped");
				}
			}
			int num = 0;
			foreach (ChartPack item in list)
			{
				num += item.Charts.Count;
			}
			ModLog.Load($"packs: {list.Count}, charts: {num} imported");
			return list;
		}

		private static ChartPack ReadPack(string folder)
		{
			string fileName = Path.GetFileName(folder);
			ChartPack chartPack = new ChartPack
			{
				Folder = folder,
				Name = fileName,
				Slug = fileName
			};
			string path = Path.Combine(folder, "pack.json");
			if (File.Exists(path))
			{
				try
				{
					using JsonDocument jsonDocument = JsonDocument.Parse(File.ReadAllText(path), new JsonDocumentOptions
					{
						CommentHandling = JsonCommentHandling.Skip,
						AllowTrailingCommas = true
					});
					JsonElement rootElement = jsonDocument.RootElement;
					if (rootElement.ValueKind == JsonValueKind.Object)
					{
						if (rootElement.TryGetProperty("name", out var value))
						{
							if (value.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(value.GetString()))
							{
								chartPack.Name = value.GetString().Trim();
							}
							else if (value.ValueKind == JsonValueKind.Array || value.ValueKind == JsonValueKind.Object)
							{
								string[] array = Titles(value);
								if (array != null)
								{
									chartPack.Names = array;
									chartPack.Name = array[0];
								}
							}
						}
						if (rootElement.TryGetProperty("visuals", out var value2) && value2.ValueKind == JsonValueKind.Object)
						{
							foreach (JsonProperty item in value2.EnumerateObject())
							{
								if (Array.IndexOf(ChartPack.PictureKeys, item.Name) >= 0 && (item.Value.ValueKind == JsonValueKind.String || item.Value.ValueKind == JsonValueKind.Array || item.Value.ValueKind == JsonValueKind.Object))
								{
									string[] array2 = Pictures(folder, fileName, item.Name, item.Value);
									if (array2 != null)
									{
										chartPack.Visuals[item.Name] = array2;
									}
								}
							}
						}
					}
				}
				catch (Exception)
				{
				}
			}
			return chartPack;
		}

		private static string[] Pictures(string folder, string packName, string key, JsonElement value)
		{
			string[] array = new string[5];
			if (value.ValueKind == JsonValueKind.String)
			{
				string text = LocalPicture(folder, value.GetString());
				if (text == null)
				{
					return null;
				}
				for (int i = 0; i < array.Length; i++)
				{
					array[i] = text;
				}
				return array;
			}
			if (value.ValueKind == JsonValueKind.Object)
			{
				return NamedPictures(folder, packName, key, value);
			}
			if (value.GetArrayLength() != 5)
			{
				return null;
			}
			bool flag = false;
			for (int j = 0; j < array.Length; j++)
			{
				JsonElement jsonElement = value[j];
				if (jsonElement.ValueKind != JsonValueKind.Null)
				{
					if (jsonElement.ValueKind != JsonValueKind.String)
					{
						return null;
					}
					array[j] = LocalPicture(folder, jsonElement.GetString());
					if (array[j] != null)
					{
						flag = true;
					}
				}
			}
			if (!flag)
			{
				return null;
			}
			return array;
		}

		private static string[] NamedPictures(string folder, string packName, string key, JsonElement value)
		{
			string[] array = new string[5];
			bool flag = false;
			foreach (JsonProperty item in value.EnumerateObject())
			{
				int num = ChartPack.LanguageIndex(item.Name);
				if (num >= 0 && item.Value.ValueKind == JsonValueKind.String)
				{
					array[num] = LocalPicture(folder, item.Value.GetString());
					if (array[num] != null)
					{
						flag = true;
					}
				}
			}
			if (!flag)
			{
				return null;
			}
			return array;
		}

		private static string[] Titles(JsonElement value)
		{
			string[] array = new string[5];
			if (value.ValueKind == JsonValueKind.Array)
			{
				if (value.GetArrayLength() != 5)
				{
					return null;
				}
				for (int i = 0; i < array.Length; i++)
				{
					JsonElement jsonElement = value[i];
					if (jsonElement.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(jsonElement.GetString()))
					{
						return null;
					}
					array[i] = jsonElement.GetString();
				}
				return array;
			}
			foreach (JsonProperty item in value.EnumerateObject())
			{
				int num = ChartPack.LanguageIndex(item.Name);
				if (num >= 0)
				{
					if (item.Value.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(item.Value.GetString()))
					{
						return null;
					}
					array[num] = item.Value.GetString();
				}
			}
			string text = null;
			string[] array2 = array;
			foreach (string text2 in array2)
			{
				if (text2 != null)
				{
					text = text2;
					break;
				}
			}
			if (text == null)
			{
				return null;
			}
			for (int k = 0; k < array.Length; k++)
			{
				if (array[k] == null)
				{
					array[k] = text;
				}
			}
			return array;
		}

		private static string LocalPicture(string folder, string name)
		{
			if (string.IsNullOrWhiteSpace(name))
			{
				return null;
			}
			if (Path.GetFileName(name) != name)
			{
				return null;
			}
			string text = Path.GetExtension(name).ToLowerInvariant();
			if (text != ".png" && text != ".jpg" && text != ".jpeg")
			{
				return null;
			}
			string text2 = Path.Combine(folder, name);
			if (!File.Exists(text2))
			{
				return null;
			}
			return text2;
		}

		private static string Nested(string folder)
		{
			try
			{
				string[] directories = Directory.GetDirectories(folder);
				foreach (string text in directories)
				{
					if (File.Exists(Path.Combine(text, "if")))
					{
						return Path.GetFileName(text);
					}
				}
			}
			catch (Exception)
			{
			}
			return null;
		}

		private static void AddSong(ChartPack pack, string folder, Func<ChartInfo, string> validateCharts)
		{
			string fileName = Path.GetFileName(folder);
			if (!IfReader.TryRead(folder, out var info, out var reason))
			{
				ModLog.WarnRelease("chart folder '" + fileName + "' skipped: " + (reason ?? IfReader.WhyNot(folder)));
				return;
			}
			reason = validateCharts(info);
			if (reason != null)
			{
				ModLog.WarnRelease("chart folder '" + fileName + "' skipped: " + reason);
			}
			else
			{
				pack.Charts.Add(info);
			}
		}

		private static string Levels(ChartInfo info)
		{
			string[] array = new string[4];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = info.Level[i]?.ToString() ?? "-";
			}
			return string.Join("/", array);
		}
	}
	internal sealed class ChartInfo
	{
		internal const int Difficulties = 4;

		internal string Folder;

		internal string Name;

		internal string[] Titles;

		internal string[][] TitleSets;

		internal string Composer;

		internal string[] ComposerLanguages;

		internal string[][] ComposerSets;

		internal string[] IllustratorLanguages;

		internal string[][] IllustratorSets;

		internal string SongPath;

		internal string[] Songs;

		internal string[] JacketPaths;

		internal string[] BackgroundPaths;

		internal float PreviewStart;

		internal float PreviewEnd;

		internal float[][] Previews;

		internal string[] ChartPaths;

		internal string[] ChartNames;

		internal bool[] NoChart;

		internal string[] Charters;

		internal int?[] Level;

		internal string FolderName;

		internal string BaseName;
	}
	internal sealed class ChartPack
	{
		internal string Folder;

		internal string Name;

		internal string Slug;

		internal string[] Names;

		internal readonly Dictionary<string, string[]> Visuals = new Dictionary<string, string[]>(StringComparer.Ordinal);

		internal const int Languages = 5;

		internal const int FallbackLanguage = 2;

		internal readonly List<ChartInfo> Charts = new List<ChartInfo>();

		internal int PackId = -1;

		internal static readonly string[] PictureKeys = new string[7] { "songSelect", "songSelectHover", "selected", "selectedHover", "deselected", "deselectedHover", "outline" };

		internal string Visual(string key, int language)
		{
			if (!Visuals.TryGetValue(key, out var value))
			{
				return null;
			}
			if (language >= 0 && language < value.Length && value[language] != null)
			{
				return value[language];
			}
			if (value[2] != null)
			{
				return value[2];
			}
			string[] array = value;
			foreach (string text in array)
			{
				if (text != null)
				{
					return text;
				}
			}
			return null;
		}

		internal static int LanguageIndex(string key)
		{
			if (key == null)
			{
				return -1;
			}
			return key.Trim().ToLowerInvariant() switch
			{
				"sc" => 0, 
				"tc" => 1, 
				"en" => 2, 
				"ja" => 3, 
				"ko" => 4, 
				_ => -1, 
			};
		}
	}
	internal static class FalsumSong
	{
		internal const string Extension = ".falsum";

		internal const string ProjectFileName = "song.json";

		internal static void Import(string archive)
		{
			string text = Path.Combine(Path.GetDirectoryName(archive) ?? string.Empty, Path.GetFileNameWithoutExtension(archive));
			string path = Path.Combine(text, "if");
			if (File.Exists(path))
			{
				return;
			}
			try
			{
				if (!File.Exists(Path.Combine(text, "song.json")))
				{
					ZipFile.ExtractToDirectory(archive, text, overwriteFiles: true);
					ModLog.Load($"'{Path.GetFileName(archive)}': unpacked into '{Path.GetFileName(text)}'");
				}
				string reason;
				JsonNode jsonNode = Build(text, out reason);
				if (jsonNode == null)
				{
					ModLog.WarnRelease($"'{Path.GetFileName(text)}': its {"song.json"} could not be used: {reason}");
				}
				else
				{
					File.WriteAllText(path, StudioProject.Serialize(jsonNode), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
					ModLog.Load("'" + Path.GetFileName(text) + "': wrote an if from its song.json");
				}
			}
			catch (Exception e)
			{
				ModLog.WarnRelease("'" + Path.GetFileName(archive) + "' could not be unpacked: " + ModLog.Describe(e));
			}
		}

		private static JsonNode Build(string folder, out string reason)
		{
			reason = null;
			using JsonDocument jsonDocument = JsonDocument.Parse((ReadOnlyMemory<byte>)File.ReadAllBytes(Path.Combine(folder, "song.json")), default(JsonDocumentOptions));
			JsonElement rootElement = jsonDocument.RootElement;
			if (rootElement.ValueKind != JsonValueKind.Object || Text(rootElement, "format") != "falsum")
			{
				reason = "it is not a falsum song";
				return null;
			}
			string text = Named(rootElement, "audio");
			if (string.IsNullOrEmpty(text) || Path.GetFileName(text) != text || !File.Exists(Path.Combine(folder, text)))
			{
				reason = "the audio it names is not in the archive";
				return null;
			}
			JsonArray jsonArray = new JsonArray();
			JsonArray jsonArray2 = new JsonArray();
			JsonArray jsonArray3 = new JsonArray();
			int num = 0;
			if (rootElement.TryGetProperty("difficulties", out var value) && value.ValueKind == JsonValueKind.Array)
			{
				int num2 = 0;
				foreach (JsonElement item in value.EnumerateArray())
				{
					if (num2 >= 4)
					{
						break;
					}
					JsonElement value2;
					string text2 = ((item.ValueKind == JsonValueKind.Object && item.TryGetProperty("available", out value2) && value2.ValueKind == JsonValueKind.True) ? ChartAt(folder, Text(item, "chart")) : null);
					if (text2 == null)
					{
						jsonArray.Add(null);
						jsonArray2.Add(null);
						jsonArray3.Add(null);
					}
					else
					{
						string text3 = Text(item, "chartDesigner");
						jsonArray.Add(text2);
						jsonArray2.Add(string.IsNullOrWhiteSpace(text3) ? "Unknown" : text3);
						JsonElement value4;
						int value5;
						int value3 = ((!item.TryGetProperty("rating", out value4) || !value4.TryGetInt32(out value5)) ? 1 : value5);
						jsonArray3.Add(value3);
						num++;
					}
					num2++;
				}
			}
			while (jsonArray.Count < 4)
			{
				jsonArray.Add(null);
				jsonArray2.Add(null);
				jsonArray3.Add(null);
			}
			if (num == 0)
			{
				reason = "no difficulty is marked available with a chart file in the archive";
				return null;
			}
			StudioProject.DefaultPreview(AudioCodec.Measure(Path.Combine(folder, text), out var _), out var start, out var end);
			JsonObject jsonObject = new JsonObject();
			jsonObject["id"] = (string.IsNullOrWhiteSpace(Text(rootElement, "songId")) ? Path.GetFileName(folder) : Text(rootElement, "songId"));
			jsonObject["name"] = (string.IsNullOrWhiteSpace(Text(rootElement, "title")) ? Path.GetFileName(folder) : Text(rootElement, "title"));
			jsonObject["composer"] = (string.IsNullOrWhiteSpace(Text(rootElement, "artist")) ? "Unknown" : Text(rootElement, "artist"));
			jsonObject["song"] = text;
			jsonObject["preview_seconds"] = new JsonArray(Rounded(start), Rounded(end));
			jsonObject["charter"] = jsonArray2;
			jsonObject["chart"] = jsonArray;
			jsonObject["lv"] = jsonArray3;
			JsonObject jsonObject2 = jsonObject;
			string text4 = Picture(folder, Named(rootElement, "cover"), IfReader.ImageExtensions);
			if (text4 != null)
			{
				jsonObject2["jacket"] = text4;
			}
			string text5 = Picture(folder, Text(rootElement, "background"), IfReader.BackgroundExtensions);
			if (text5 != null)
			{
				jsonObject2["background"] = text5;
			}
			string text6 = Text(rootElement, "jacketDesigner");
			if (!string.IsNullOrWhiteSpace(text6))
			{
				jsonObject2["illust"] = text6;
			}
			return jsonObject2;
		}

		private static string ChartAt(string folder, string relative)
		{
			if (string.IsNullOrEmpty(relative))
			{
				return null;
			}
			string fullPath = Path.GetFullPath(folder);
			string fullPath2 = Path.GetFullPath(Path.Combine(fullPath, relative.Replace('\\', '/').Replace('/', Path.DirectorySeparatorChar)));
			if (!fullPath2.StartsWith(fullPath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) || !File.Exists(fullPath2))
			{
				return null;
			}
			string fileName = Path.GetFileName(fullPath2);
			if (fileName.Length == 0)
			{
				return null;
			}
			string text = Path.Combine(fullPath, fileName);
			if (!string.Equals(fullPath2, text, StringComparison.OrdinalIgnoreCase) && !File.Exists(text))
			{
				File.Move(fullPath2, text);
				ModLog.Load($"'{Path.GetFileName(folder)}': moved '{fileName}' out of {Path.GetDirectoryName(relative)}");
			}
			return fileName;
		}

		private static string Picture(string folder, string name, string[] allowed)
		{
			if (string.IsNullOrEmpty(name) || Path.GetFileName(name) != name)
			{
				return null;
			}
			string extension = Path.GetExtension(name);
			bool flag = false;
			foreach (string b in allowed)
			{
				if (string.Equals(extension, b, StringComparison.OrdinalIgnoreCase))
				{
					flag = true;
				}
			}
			if (!flag)
			{
				return null;
			}
			if (!File.Exists(Path.Combine(folder, name)))
			{
				return null;
			}
			return name;
		}

		private static string Named(JsonElement item, string key)
		{
			if (item.ValueKind != JsonValueKind.Object || !item.TryGetProperty(key, out var value))
			{
				return null;
			}
			if (value.ValueKind == JsonValueKind.String)
			{
				return value.GetString();
			}
			if (value.ValueKind != JsonValueKind.Object)
			{
				return null;
			}
			return Text(value, "file");
		}

		private static string Text(JsonElement item, string key)
		{
			if (item.ValueKind != JsonValueKind.Object || !item.TryGetProperty(key, out var value))
			{
				return null;
			}
			if (value.ValueKind != JsonValueKind.String)
			{
				return null;
			}
			return value.GetString();
		}

		private static JsonNode Rounded(float seconds)
		{
			return JsonValue.Create(Math.Round(seconds, 3));
		}
	}
	internal static class Icp1Writer
	{
		private struct State
		{
			private ulong _a;

			private ulong _b;

			private ulong _c;

			private ulong _d;

			private uint _count;

			internal static State Seed(string key, int noteCount)
			{
				State result = new State
				{
					_a = (ulong)(-4265267296055464877L + noteCount),
					_b = (((ulong)(uint)noteCount << 32) ^ 0x9E3779B185EBCA87uL),
					_c = 15111065706836454660uL,
					_d = 10723151780598845930uL,
					_count = 0u
				};
				for (int i = 0; i < key.Length; i++)
				{
					ulong num = ((ulong)(uint)i << 32) | key[i];
					result.Finalize(num, num + (ulong)noteCount, 1 ^ num);
				}
				result.Finalize((uint)noteCount, 1uL, (ulong)key.Length);
				return result;
			}

			internal ulong Field(int index, int field)
			{
				ulong num = ((ulong)(uint)index << 32) | (uint)field;
				return (field & 3) switch
				{
					0 => _d ^ (_a + Rol(_c ^ num, 11)), 
					1 => _a ^ (_b - Rol(num + _d, 17)), 
					2 => _b ^ (_c + Rol(_a ^ num, 29)), 
					_ => _d ^ Rol(num + _b + _c, 37), 
				};
			}

			internal void Advance(int[] fields, int[] plains, int index)
			{
				Finalize((ulong)fields[0], (ulong)fields[1], ((ulong)(uint)fields[3] << 32) | (uint)fields[2]);
				Finalize((ulong)fields[4], (ulong)fields[5], Pack(plains[1], plains[0]));
				Finalize(Pack(plains[3], plains[2]), Pack(plains[5], plains[4]), Pack(plains[7], plains[6]));
				Record(index, fields[0], Gcd(plains[0], plains[1]), Gcd(plains[2], plains[3]), Gcd(plains[4], plains[5]), Gcd(plains[6], plains[7]));
			}

			private static ulong Pack(int high, int low)
			{
				return (uint)low | ((ulong)(uint)high << 32);
			}

			private void Finalize(ulong first, ulong second, ulong third)
			{
				switch (_count & 3)
				{
				case 0u:
				{
					_a = _a + Rol(_d, 7) + first;
					ulong num4 = Rol(_a + second, 13);
					_b ^= num4;
					third ^= _b;
					_c -= third;
					_d = Rol(_c + _d, 27) ^ _a;
					break;
				}
				case 1u:
				{
					_b = _b + Rol(_a, 9) + third;
					ulong num3 = Rol(_b + first, 19);
					_c ^= num3;
					second ^= _c;
					_d -= second;
					_a = Rol(_d + _a, 31) ^ _b;
					break;
				}
				case 2u:
				{
					_c = _c + Rol(_b, 15) + second;
					ulong num2 = Rol(_c + third, 23);
					_d ^= num2;
					first ^= _d;
					_a -= first;
					_b = Rol(_b + _a, 39) ^ _c;
					break;
				}
				default:
				{
					ulong c = _c;
					_d = _d + Rol(_c, 21) + first;
					ulong num = Rol(_d + second, 29);
					_a ^= num;
					num = _a ^ third;
					_b -= num;
					_c = Rol(c + _b, 43) ^ _d;
					break;
				}
				}
				_count++;
			}

			private void Record(int index, int field0, int g0, int g1, int g2, int g3)
			{
				ulong num = ((uint)index | ((ulong)(uint)index << 32)) + ((_a + Rol(_b, 9)) ^ (_d + Rol(_c, 23)));
				ulong num2 = (num ^ (num >> 32)) & 0xFFFFFFFFu;
				ulong value = (ulong)(g3 - 1) ^ (num2 >> 24);
				ulong num3 = Rol((ulong)(g2 - 1) ^ ((num2 >> 16) & 0xFF), 29) ^ Rol(value, 47);
				ulong num4 = (ulong)(g0 - 1) ^ (num2 & 0xFF) ^ Rol((ulong)(g1 - 1) ^ ((num2 >> 8) & 0xFF), 13) ^ num3;
				long num5 = (uint)((field0 ^ index ^ (int)num4 ^ (int)(num4 >> 32) ^ (int)((ulong)field0 >> 32)) & -1);
				ulong num6 = (ulong)(num5 | (num5 << 32));
				_a ^= num6;
				_b += Rol(num6, 17);
				_c -= Rol(num6, 31);
				_d ^= Rol(num6, 47);
			}

			private static ulong Rol(ulong value, int bits)
			{
				return (value << bits) | (value >> 64 - bits);
			}
		}

		private const int HeaderSize = 28;

		private const int NoteSize = 80;

		private const int EventSize = 32;

		private const ulong M64 = ulong.MaxValue;

		private const uint M32 = uint.MaxValue;

		internal static byte[] Build(TextChart chart, string key)
		{
			int count = chart.Notes.Count;
			int count2 = chart.Events.Count;
			byte[] array = new byte[28 + count * 80 + count2 * 32];
			array[0] = 73;
			array[1] = 67;
			array[2] = 80;
			array[3] = 49;
			Put16(array, 4, 1);
			Put16(array, 6, 28);
			Put16(array, 8, 80);
			Put16(array, 10, 32);
			Put32(array, 12, (uint)count);
			Put32(array, 16, (uint)count2);
			PutF32(array, 20, chart.Bpm);
			PutF32(array, 24, chart.Meter);
			State state = State.Seed(key, count);
			for (int i = 0; i < count; i++)
			{
				int num = 28 + i * 80;
				TextChart.Note note = chart.Notes[i];
				int[] plains = note.Plains;
				int[] array2 = new int[10]
				{
					i,
					note.Line,
					note.Start,
					note.End,
					(note.Type << 8) | note.Track,
					Field5(in note),
					Gcd(plains[0], plains[1]),
					Gcd(plains[2], plains[3]),
					Gcd(plains[4], plains[5]),
					Gcd(plains[6], plains[7])
				};
				Put64(array, num, Masked(in state, i, 0, (ulong)array2[0], 8));
				Put64(array, num + 8, Masked(in state, i, 1, (ulong)array2[1], 8));
				Put32(array, num + 16, (uint)Masked(in state, i, 2, (ulong)array2[2], 4));
				Put32(array, num + 20, (uint)Masked(in state, i, 3, (ulong)array2[3], 4));
				Put32(array, num + 24, (uint)Masked(in state, i, 4, (ulong)array2[4], 4));
				Put32(array, num + 28, (uint)Masked(in state, i, 5, (ulong)array2[5], 4));
				Put32(array, num + 32, (uint)Masked(in state, i, 6, (ulong)array2[6], 4));
				Put32(array, num + 36, (uint)Masked(in state, i, 7, (ulong)array2[7], 4));
				Put32(array, num + 40, (uint)Masked(in state, i, 8, (ulong)array2[8], 4));
				Put32(array, num + 44, (uint)Masked(in state, i, 9, (ulong)array2[9], 4));
				for (int j = 0; j < 8; j++)
				{
					Put32(array, num + 48 + j * 4, (uint)plains[j]);
				}
				state.Advance(array2, plains, i);
			}
			int num2 = 28 + count * 80;
			for (int k = 0; k < count2; k++)
			{
				int num3 = num2 + k * 32;
				TextChart.Event obj = chart.Events[k];
				ulong value = (ulong)((obj.Type == 3) ? ((uint)obj.Flags) : BitConverter.DoubleToInt64Bits(obj.Number));
				ulong value2 = (ulong)(obj.HasSpeed ? BitConverter.SingleToInt32Bits(obj.Speed) : 0);
				Put64(array, num3, (ulong)k);
				Put32(array, num3 + 8, (uint)obj.TimeMs);
				Put32(array, num3 + 12, (uint)obj.Type);
				Put64(array, num3 + 16, value);
				Put64(array, num3 + 24, value2);
			}
			return array;
		}

		private static int Field5(in TextChart.Note note)
		{
			if (note.Flags != 0)
			{
				return note.Flags;
			}
			if (note.Type == 4)
			{
				return note.Flick << 8;
			}
			return note.LeftEase | note.RightEase;
		}

		private static ulong Masked(in State state, int index, int field, ulong value, int size)
		{
			ulong num = state.Field(index, field);
			if (size == 4)
			{
				num &= 0xFFFFFFFFu;
			}
			return value + num;
		}

		private static int Gcd(int a, int b)
		{
			long num = Math.Abs((long)a);
			long num2 = Math.Abs((long)b);
			while (num2 != 0L)
			{
				long num3 = num % num2;
				num = num2;
				num2 = num3;
			}
			return (int)((num == 0L) ? 1 : num);
		}

		private static void Put16(byte[] file, int at, int value)
		{
			file[at] = (byte)value;
			file[at + 1] = (byte)(value >> 8);
		}

		private static void Put32(byte[] file, int at, uint value)
		{
			for (int i = 0; i < 4; i++)
			{
				file[at + i] = (byte)(value >> 8 * i);
			}
		}

		private static void PutF32(byte[] file, int at, float value)
		{
			Put32(file, at, (uint)BitConverter.SingleToInt32Bits(value));
		}

		private static void Put64(byte[] file, int at, ulong value)
		{
			for (int i = 0; i < 8; i++)
			{
				file[at + i] = (byte)(value >> 8 * i);
			}
		}
	}
	internal static class IfReader
	{
		private static readonly string[] AudioExtensions = new string[3] { ".wav", ".mp3", ".ogg" };

		internal static readonly string[] ImageExtensions = new string[3] { ".png", ".jpg", ".jpeg" };

		internal static readonly string[] BackgroundExtensions = new string[2] { ".png", ".mp4" };

		internal const string InfoFileName = "if";

		private static readonly JsonDocumentOptions JsonOptions = new JsonDocumentOptions
		{
			CommentHandling = JsonCommentHandling.Skip,
			AllowTrailingCommas = true
		};

		private static readonly JsonElement Null = NullElement();

		private static JsonElement NullElement()
		{
			using JsonDocument jsonDocument = JsonDocument.Parse("null");
			return jsonDocument.RootElement.Clone();
		}

		internal static bool TryRead(string folder, out ChartInfo info, out string reason)
		{
			info = null;
			reason = null;
			string path = Path.Combine(folder, "if");
			if (!File.Exists(path))
			{
				return false;
			}
			string json;
			try
			{
				json = File.ReadAllText(path);
			}
			catch (Exception)
			{
				return false;
			}
			JsonDocument jsonDocument;
			try
			{
				jsonDocument = JsonDocument.Parse(json, JsonOptions);
			}
			catch (Exception)
			{
				return false;
			}
			using JsonDocument jsonDocument2 = jsonDocument;
			JsonElement rootElement = jsonDocument2.RootElement;
			if (rootElement.ValueKind != JsonValueKind.Object)
			{
				return false;
			}
			string fileName = Path.GetFileName(folder);
			ChartInfo chartInfo = new ChartInfo
			{
				Folder = folder,
				FolderName = fileName
			};
			if (!Text(rootElement, "id", out var value, out reason))
			{
				return false;
			}
			chartInfo.BaseName = value;
			if (!Faces(rootElement, "name", required: true, out chartInfo.Titles, out chartInfo.TitleSets, out reason))
			{
				return false;
			}
			chartInfo.Name = chartInfo.Titles[0];
			if (!Faces(rootElement, "composer", required: true, out chartInfo.ComposerLanguages, out chartInfo.ComposerSets, out reason))
			{
				return false;
			}
			chartInfo.Composer = chartInfo.ComposerLanguages[0];
			if (!Faces(rootElement, "illust", required: false, out chartInfo.IllustratorLanguages, out chartInfo.IllustratorSets, out reason))
			{
				return false;
			}
			if (!Songs(rootElement, folder, out chartInfo.Songs, out reason))
			{
				return false;
			}
			chartInfo.SongPath = chartInfo.Songs[0];
			if (!Jackets(rootElement, folder, out chartInfo.JacketPaths, out reason))
			{
				return false;
			}
			if (!Backgrounds(rootElement, folder, out chartInfo.BackgroundPaths, out reason))
			{
				return false;
			}
			if (!Previews(rootElement, out chartInfo.Previews, out reason))
			{
				return false;
			}
			chartInfo.PreviewStart = chartInfo.Previews[0][0];
			chartInfo.PreviewEnd = chartInfo.Previews[0][1];
			if (!DifficultyValue(rootElement, "chart", out var token, out reason))
			{
				return false;
			}
			if (!DifficultyValue(rootElement, "charter", out var token2, out reason))
			{
				return false;
			}
			if (!DifficultyValue(rootElement, "lv", out var token3, out reason))
			{
				return false;
			}
			JsonElement[] array = Slots(token);
			JsonElement[] array2 = Slots(token2);
			JsonElement[] array3 = Slots(token3);
			if (!Aligned(array, array2, array3, out reason))
			{
				return false;
			}
			if (!Texts(array2, "charter", out chartInfo.Charters, out reason))
			{
				return false;
			}
			if (!Levels(array3, "lv", out chartInfo.Level, out reason))
			{
				return false;
			}
			if (!ChartFiles(array, folder, out chartInfo.ChartPaths, out chartInfo.ChartNames, out chartInfo.NoChart, out reason))
			{
				return false;
			}
			info = chartInfo;
			return true;
		}

		private static bool Faces(JsonElement root, string key, bool required, out string[] languages, out string[][] sets, out string reason)
		{
			languages = null;
			sets = null;
			reason = null;
			if (!root.TryGetProperty(key, out var value) || value.ValueKind == JsonValueKind.Null)
			{
				if (!required)
				{
					return true;
				}
				return false;
			}
			if (value.ValueKind == JsonValueKind.String)
			{
				if (!One(value, key, out var text, out reason))
				{
					return false;
				}
				languages = SameFive(text);
				return true;
			}
			if (value.ValueKind == JsonValueKind.Object)
			{
				return Dictionary(value, key, required, out languages, out sets, out reason);
			}
			if (value.ValueKind != JsonValueKind.Array)
			{
				return false;
			}
			switch (value.GetArrayLength())
			{
			case 5:
				if (!Nested(value, key, out sets, out reason))
				{
					return false;
				}
				if (sets == null)
				{
					return Five(value, key, required, out languages, out reason);
				}
				languages = Base(null, sets, required, out reason);
				if (languages == null)
				{
					return !required;
				}
				Fill(sets, languages);
				return true;
			default:
				return false;
			case 4:
			{
				string[][] array = new string[4][];
				bool flag = false;
				for (int i = 0; i < 4; i++)
				{
					JsonElement item = value[i];
					if (item.ValueKind != JsonValueKind.Null)
					{
						if (item.ValueKind == JsonValueKind.Array)
						{
							return false;
						}
						if (!One(item, key, out var text2, out reason))
						{
							return false;
						}
						array[i] = SameFive(text2);
						flag = true;
					}
				}
				if (!flag)
				{
					return !required;
				}
				languages = Base(null, array, required, out reason);
				if (languages == null)
				{
					return false;
				}
				sets = array;
				return true;
			}
			}
		}

		private static bool Dictionary(JsonElement token, string key, bool required, out string[] languages, out string[][] sets, out string reason)
		{
			languages = null;
			sets = null;
			reason = null;
			string[] array = new string[5];
			string[][] array2 = new string[4][];
			bool flag = false;
			bool flag2 = false;
			foreach (JsonProperty item2 in token.EnumerateObject())
			{
				int num = ChartPack.LanguageIndex(item2.Name);
				if (num < 0)
				{
					ModLog.WarnRelease($"\"{key}\": '{item2.Name}' is not a language " + "(sc/tc/en/ja/ko); it is ignored");
					continue;
				}
				JsonElement value = item2.Value;
				if (value.ValueKind == JsonValueKind.String)
				{
					if (!One(value, key, out var text, out reason))
					{
						return false;
					}
					array[num] = text;
					flag = true;
					continue;
				}
				if (value.ValueKind != JsonValueKind.Array || value.GetArrayLength() != 4)
				{
					return false;
				}
				for (int i = 0; i < 4; i++)
				{
					JsonElement item = value[i];
					if (item.ValueKind != JsonValueKind.Null)
					{
						if (!One(item, key, out var text2, out reason))
						{
							return false;
						}
						string[][] array3 = array2;
						int num2 = i;
						if (array3[num2] == null)
						{
							array3[num2] = new string[5];
						}
						array2[i][num] = text2;
						flag2 = true;
					}
				}
			}
			if (!flag && !flag2)
			{
				return !required;
			}
			string[] slots = (flag ? array : null);
			languages = Base(slots, flag2 ? array2 : null, required, out reason);
			if (languages == null)
			{
				return !required;
			}
			if (flag2)
			{
				Fill(array2, languages);
				sets = array2;
			}
			return true;
		}

		private static bool Nested(JsonElement token, string key, out string[][] sets, out string reason)
		{
			sets = null;
			reason = null;
			for (int i = 0; i < 5; i++)
			{
				JsonElement jsonElement = token[i];
				if (jsonElement.ValueKind != JsonValueKind.Array)
				{
					return true;
				}
				if (jsonElement.GetArrayLength() != 4)
				{
					return false;
				}
			}
			string[][] array = new string[4][];
			for (int j = 0; j < 4; j++)
			{
				array[j] = new string[5];
			}
			for (int k = 0; k < 5; k++)
			{
				JsonElement jsonElement2 = token[k];
				for (int l = 0; l < 4; l++)
				{
					JsonElement item = jsonElement2[l];
					if (item.ValueKind != JsonValueKind.Null)
					{
						if (!One(item, key, out var text, out reason))
						{
							return false;
						}
						array[l][k] = text;
					}
				}
			}
			sets = array;
			return true;
		}

		private static string[] Base(string[] slots, string[][] sets, bool required, out string reason)
		{
			reason = null;
			string[] array = new string[5];
			bool flag = false;
			for (int i = 0; i < array.Length; i++)
			{
				if (slots != null && slots[i] != null)
				{
					array[i] = slots[i];
					flag = true;
				}
				else
				{
					if (sets == null)
					{
						continue;
					}
					foreach (string[] array2 in sets)
					{
						if (array2 != null && array2[i] != null)
						{
							array[i] = array2[i];
							flag = true;
							break;
						}
					}
				}
			}
			if (!flag)
			{
				return null;
			}
			string text = null;
			string[] array3 = array;
			foreach (string text2 in array3)
			{
				if (text2 != null)
				{
					text = text2;
					break;
				}
			}
			for (int k = 0; k < array.Length; k++)
			{
				if (array[k] == null)
				{
					array[k] = text;
				}
			}
			return array;
		}

		private static void Fill(string[][] sets, string[] languages)
		{
			foreach (string[] array in sets)
			{
				if (array == null)
				{
					continue;
				}
				for (int j = 0; j < array.Length; j++)
				{
					if (array[j] == null)
					{
						array[j] = languages[j];
					}
				}
			}
		}

		private static bool Five(JsonElement token, string key, bool required, out string[] values, out string reason)
		{
			values = null;
			reason = null;
			string[] array = new string[5];
			for (int i = 0; i < array.Length; i++)
			{
				JsonElement item = token[i];
				if ((item.ValueKind != JsonValueKind.Null || required) && !One(item, key, out array[i], out reason))
				{
					return false;
				}
			}
			values = array;
			return true;
		}

		private static bool One(JsonElement item, string key, out string text, out string reason)
		{
			text = null;
			reason = null;
			if (item.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(item.GetString()))
			{
				return false;
			}
			text = item.GetString();
			return true;
		}

		internal static string[] SameFive(string text)
		{
			return new string[5] { text, text, text, text, text };
		}

		internal static string WhyNot(string folder)
		{
			string path = Path.Combine(folder, "if");
			if (!File.Exists(path))
			{
				return "no if file";
			}
			try
			{
				using (JsonDocument.Parse(File.ReadAllText(path), JsonOptions))
				{
				}
			}
			catch (Exception ex)
			{
				return "if is not valid JSON: " + ex.Message;
			}
			return "its if was read but turned down; install the Debug build for the reason";
		}

		private static bool Text(JsonElement root, string key, out string value, out string reason)
		{
			value = null;
			reason = null;
			if (!root.TryGetProperty(key, out var value2) || value2.ValueKind == JsonValueKind.Null)
			{
				return false;
			}
			if (value2.ValueKind != JsonValueKind.String)
			{
				return false;
			}
			value = value2.GetString();
			if (string.IsNullOrWhiteSpace(value))
			{
				return false;
			}
			return true;
		}

		private static bool LocalFile(JsonElement root, string folder, string key, string[] allowed, out string path, out string reason)
		{
			path = null;
			if (!Text(root, key, out var value, out reason))
			{
				return false;
			}
			return LocalFileAt(folder, key, value, allowed, out path, out reason);
		}

		private static bool LocalFileAt(string folder, string key, string name, string[] allowed, out string path, out string reason)
		{
			path = null;
			reason = null;
			if (Path.GetFileName(name) != name)
			{
				return false;
			}
			string value = Path.GetExtension(name).ToLowerInvariant();
			if (Array.IndexOf(allowed, value) < 0)
			{
				return false;
			}
			path = Path.Combine(folder, name);
			if (!File.Exists(path))
			{
				path = null;
				return false;
			}
			return true;
		}

		private static bool Jackets(JsonElement root, string folder, out string[] paths, out string reason)
		{
			return OneOrFour(root, folder, "jacket", ImageExtensions, entriesMayBeNull: false, out paths, out reason);
		}

		private static bool Backgrounds(JsonElement root, string folder, out string[] paths, out string reason)
		{
			paths = null;
			reason = null;
			if (!root.TryGetProperty("background", out var value) || value.ValueKind == JsonValueKind.Null)
			{
				return true;
			}
			return OneOrFour(root, folder, "background", BackgroundExtensions, entriesMayBeNull: true, out paths, out reason);
		}

		private static bool OneOrFour(JsonElement root, string folder, string key, string[] allowed, bool entriesMayBeNull, out string[] paths, out string reason)
		{
			paths = null;
			reason = null;
			if (!root.TryGetProperty(key, out var value) || value.ValueKind == JsonValueKind.Null)
			{
				return false;
			}
			if (value.ValueKind == JsonValueKind.String)
			{
				if (!LocalFile(root, folder, key, allowed, out var path, out reason))
				{
					return false;
				}
				string[] array = new string[4];
				for (int i = 0; i < array.Length; i++)
				{
					array[i] = path;
				}
				paths = array;
				return true;
			}
			if (value.ValueKind != JsonValueKind.Array)
			{
				return false;
			}
			if (value.GetArrayLength() != 4)
			{
				return false;
			}
			string[] array2 = new string[4];
			for (int j = 0; j < array2.Length; j++)
			{
				JsonElement jsonElement = value[j];
				if (!(jsonElement.ValueKind == JsonValueKind.Null && entriesMayBeNull))
				{
					if (jsonElement.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(jsonElement.GetString()))
					{
						return false;
					}
					if (!LocalFileAt(folder, key, jsonElement.GetString(), allowed, out array2[j], out reason))
					{
						return false;
					}
				}
			}
			paths = array2;
			return true;
		}

		private static bool Preview(JsonElement root, out float start, out float end, out string reason)
		{
			start = 0f;
			end = 0f;
			reason = null;
			if (!root.TryGetProperty("preview_seconds", out var value) || value.ValueKind == JsonValueKind.Null)
			{
				return false;
			}
			if (value.ValueKind != JsonValueKind.Array)
			{
				return false;
			}
			if (value.GetArrayLength() != 2)
			{
				return false;
			}
			if (!Seconds(value[0], 0, out start, out reason))
			{
				return false;
			}
			if (!Seconds(value[1], 1, out end, out reason))
			{
				return false;
			}
			return true;
		}

		private static bool Seconds(JsonElement item, int index, out float value, out string reason)
		{
			value = 0f;
			reason = null;
			if (item.ValueKind == JsonValueKind.Number && item.TryGetDouble(out var value2))
			{
				value = (float)value2;
				return true;
			}
			if (item.ValueKind == JsonValueKind.String && float.TryParse(item.GetString(), NumberStyles.Float, CultureInfo.InvariantCulture, out value))
			{
				return true;
			}
			return false;
		}

		private static bool DifficultyValue(JsonElement root, string key, out JsonElement token, out string reason)
		{
			token = default(JsonElement);
			reason = null;
			if (!root.TryGetProperty(key, out var value))
			{
				return false;
			}
			if (value.ValueKind == JsonValueKind.Array && value.GetArrayLength() != 4)
			{
				return false;
			}
			token = value;
			return true;
		}

		private static JsonElement[] Slots(JsonElement token)
		{
			JsonElement[] array = new JsonElement[4];
			if (token.ValueKind != JsonValueKind.Array)
			{
				for (int i = 0; i < array.Length - 1; i++)
				{
					array[i] = Null;
				}
				array[^1] = token;
				return array;
			}
			for (int j = 0; j < array.Length; j++)
			{
				array[j] = token[j];
			}
			return array;
		}

		private static bool Aligned(JsonElement[] charts, JsonElement[] charters, JsonElement[] levels, out string reason)
		{
			reason = null;
			for (int i = 0; i < 4; i++)
			{
				if (charts[i].ValueKind != JsonValueKind.Null && (charters[i].ValueKind == JsonValueKind.Null || levels[i].ValueKind == JsonValueKind.Null))
				{
					return false;
				}
			}
			return true;
		}

		private static bool Texts(JsonElement[] slots, string key, out string[] values, out string reason)
		{
			values = null;
			reason = null;
			string[] array = new string[4];
			for (int i = 0; i < array.Length; i++)
			{
				JsonElement jsonElement = slots[i];
				if (jsonElement.ValueKind != JsonValueKind.Null)
				{
					if (jsonElement.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(jsonElement.GetString()))
					{
						return false;
					}
					array[i] = jsonElement.GetString();
				}
			}
			values = array;
			return true;
		}

		private static bool Levels(JsonElement[] slots, string key, out int?[] values, out string reason)
		{
			values = null;
			reason = null;
			int?[] array = new int?[4];
			for (int i = 0; i < array.Length; i++)
			{
				JsonElement item = slots[i];
				if (item.ValueKind != JsonValueKind.Null)
				{
					if (!Whole(item, out var value))
					{
						return false;
					}
					if (value <= 0)
					{
						return false;
					}
					array[i] = value;
				}
			}
			values = array;
			return true;
		}

		private static bool Whole(JsonElement item, out int value)
		{
			value = 0;
			if (item.ValueKind != JsonValueKind.Number)
			{
				return false;
			}
			string rawText = item.GetRawText();
			if (rawText.IndexOf('.') >= 0 || rawText.IndexOf('e') >= 0 || rawText.IndexOf('E') >= 0)
			{
				return false;
			}
			return item.TryGetInt32(out value);
		}

		private static bool ChartFiles(JsonElement[] slots, string folder, out string[] paths, out string[] chartNames, out bool[] noChart, out string reason)
		{
			paths = null;
			chartNames = null;
			noChart = null;
			reason = null;
			string[] array = new string[4];
			string[] array2 = new string[4];
			bool[] array3 = new bool[4];
			for (int i = 0; i < 4; i++)
			{
				JsonElement jsonElement = slots[i];
				if (jsonElement.ValueKind == JsonValueKind.Null)
				{
					array3[i] = true;
					continue;
				}
				if (jsonElement.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(jsonElement.GetString()))
				{
					return false;
				}
				string text = jsonElement.GetString();
				if (Path.GetFileName(text) != text)
				{
					return false;
				}
				string text2 = Path.Combine(folder, text);
				if (!File.Exists(text2))
				{
					return false;
				}
				array[i] = text2;
				array2[i] = text;
			}
			paths = array;
			chartNames = array2;
			noChart = array3;
			return true;
		}

		private static bool Names(JsonElement root, out string[] names, out string reason)
		{
			names = null;
			reason = null;
			if (!root.TryGetProperty("name", out var value) || value.ValueKind == JsonValueKind.Null)
			{
				return false;
			}
			if (value.ValueKind == JsonValueKind.String)
			{
				string value2 = value.GetString();
				if (string.IsNullOrWhiteSpace(value2))
				{
					return false;
				}
				names = Same(value2);
				return true;
			}
			return Texts(root, "name", out names, out reason);
		}

		private static bool Songs(JsonElement root, string folder, out string[] songs, out string reason)
		{
			songs = null;
			reason = null;
			if (!root.TryGetProperty("song", out var value) || value.ValueKind == JsonValueKind.Null)
			{
				return false;
			}
			if (value.ValueKind == JsonValueKind.String)
			{
				if (!LocalFileAt(folder, "song", value.GetString(), AudioExtensions, out var path, out reason))
				{
					return false;
				}
				songs = Same(path);
				return true;
			}
			if (value.ValueKind != JsonValueKind.Array)
			{
				return false;
			}
			string[] slots = new string[4];
			if (!FilesAcross(value, folder, "song", AudioExtensions, slots, out reason))
			{
				return false;
			}
			songs = Carry(slots, "song", out reason);
			return songs != null;
		}

		private static bool Previews(JsonElement root, out float[][] previews, out string reason)
		{
			previews = null;
			if (!List(root, "preview_seconds", out var token, out reason))
			{
				return false;
			}
			if (token.GetArrayLength() == 0 || token[0].ValueKind != JsonValueKind.Array)
			{
				if (!Preview(root, out var start, out var end, out reason))
				{
					return false;
				}
				float[][] array = new float[4][];
				for (int i = 0; i < array.Length; i++)
				{
					array[i] = new float[2] { start, end };
				}
				previews = array;
				return true;
			}
			if (token.GetArrayLength() > 4)
			{
				return false;
			}
			float[][] array2 = new float[4][];
			for (int j = 0; j < token.GetArrayLength(); j++)
			{
				JsonElement jsonElement = token[j];
				if (jsonElement.ValueKind != JsonValueKind.Null)
				{
					if (jsonElement.GetArrayLength() != 2 || !Seconds(jsonElement[0], 0, out var value, out reason) || !Seconds(jsonElement[1], 1, out var value2, out reason))
					{
						return false;
					}
					array2[j] = new float[2] { value, value2 };
				}
			}
			if (array2[0] == null)
			{
				return false;
			}
			for (int k = 1; k < array2.Length; k++)
			{
				float[][] array3 = array2;
				int num = k;
				if (array3[num] == null)
				{
					array3[num] = array2[0];
				}
			}
			previews = array2;
			return true;
		}

		private static bool List(JsonElement root, string key, out JsonElement token, out string reason)
		{
			token = default(JsonElement);
			reason = null;
			if (!root.TryGetProperty(key, out token) || token.ValueKind == JsonValueKind.Null)
			{
				return false;
			}
			if (token.ValueKind != JsonValueKind.Array)
			{
				return false;
			}
			return true;
		}

		private static bool Texts(JsonElement root, string key, out string[] texts, out string reason)
		{
			texts = null;
			if (!List(root, key, out var token, out reason))
			{
				return false;
			}
			int arrayLength = token.GetArrayLength();
			if (arrayLength == 0 || arrayLength > 4)
			{
				return false;
			}
			string[] array = new string[4];
			for (int i = 0; i < arrayLength; i++)
			{
				JsonElement jsonElement = token[i];
				if (jsonElement.ValueKind != JsonValueKind.Null)
				{
					if (jsonElement.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(jsonElement.GetString()))
					{
						return false;
					}
					array[i] = jsonElement.GetString();
				}
			}
			texts = Carry(array, key, out reason);
			return texts != null;
		}

		private static bool FilesAcross(JsonElement token, string folder, string key, string[] allowed, string[] slots, out string reason)
		{
			reason = null;
			int arrayLength = token.GetArrayLength();
			if (arrayLength == 0 || arrayLength > 4)
			{
				return false;
			}
			for (int i = 0; i < arrayLength; i++)
			{
				JsonElement jsonElement = token[i];
				if (jsonElement.ValueKind != JsonValueKind.Null)
				{
					if (jsonElement.ValueKind != JsonValueKind.String)
					{
						return false;
					}
					if (!LocalFileAt(folder, key, jsonElement.GetString(), allowed, out var path, out reason))
					{
						return false;
					}
					slots[i] = path;
				}
			}
			return true;
		}

		private static string[] Same(string value)
		{
			string[] array = new string[4];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = value;
			}
			return array;
		}

		private static string[] Carry(string[] slots, string key, out string reason)
		{
			reason = null;
			if (slots[0] == null)
			{
				return null;
			}
			for (int i = 1; i < slots.Length; i++)
			{
				ref string reference = ref slots[i];
				if (reference == null)
				{
					reference = slots[0];
				}
			}
			return slots;
		}
	}
	internal static class Json
	{
		internal static object Parse(string text, out string reason)
		{
			reason = null;
			int at = 0;
			try
			{
				object result = Value(text, ref at);
				Space(text, ref at);
				if (at != text.Length)
				{
					throw new FormatException("trailing text");
				}
				return result;
			}
			catch (Exception ex)
			{
				reason = $"at {at}: {ex.Message}";
				return null;
			}
		}

		internal static Dictionary<string, object> Object(object value)
		{
			return value as Dictionary<string, object>;
		}

		internal static List<object> Array(object value)
		{
			return value as List<object>;
		}

		internal static double Number(Dictionary<string, object> at, string key, double fallback = 0.0)
		{
			if (at != null && at.TryGetValue(key, out var value) && value is double)
			{
				return (double)value;
			}
			return fallback;
		}

		internal static bool Has(Dictionary<string, object> at, string key)
		{
			return at?.ContainsKey(key) ?? false;
		}

		internal static object Field(Dictionary<string, object> at, string key)
		{
			if (at == null || !at.TryGetValue(key, out var value))
			{
				return null;
			}
			return value;
		}

		internal static double Scalar(object value)
		{
			if (!(value is double result))
			{
				if (value is string s)
				{
					double result2;
					return double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out result2) ? result2 : 0.0;
				}
				return 0.0;
			}
			return result;
		}

		private static object Value(string text, ref int at)
		{
			Space(text, ref at);
			if (at >= text.Length)
			{
				throw new FormatException("unexpected end");
			}
			switch (text[at])
			{
			case '{':
				return Map(text, ref at);
			case '[':
				return List(text, ref at);
			case '"':
				return String(text, ref at);
			case 't':
				Word(text, ref at, "true");
				return true;
			case 'f':
				Word(text, ref at, "false");
				return false;
			case 'n':
				Word(text, ref at, "null");
				return null;
			default:
				return Number(text, ref at);
			}
		}

		private static Dictionary<string, object> Map(string text, ref int at)
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
			at++;
			Space(text, ref at);
			if (at < text.Length && text[at] == '}')
			{
				at++;
				return dictionary;
			}
			while (true)
			{
				Space(text, ref at);
				string key = String(text, ref at);
				Space(text, ref at);
				if (at >= text.Length || text[at] != ':')
				{
					throw new FormatException("expected ':'");
				}
				at++;
				dictionary[key] = Value(text, ref at);
				Space(text, ref at);
				if (at >= text.Length)
				{
					throw new FormatException("unterminated object");
				}
				if (text[at] != ',')
				{
					break;
				}
				at++;
			}
			if (text[at] == '}')
			{
				at++;
				return dictionary;
			}
			throw new FormatException("expected ',' or '}'");
		}

		private static List<object> List(string text, ref int at)
		{
			List<object> list = new List<object>();
			at++;
			Space(text, ref at);
			if (at < text.Length && text[at] == ']')
			{
				at++;
				return list;
			}
			while (true)
			{
				list.Add(Value(text, ref at));
				Space(text, ref at);
				if (at >= text.Length)
				{
					throw new FormatException("unterminated array");
				}
				if (text[at] != ',')
				{
					break;
				}
				at++;
			}
			if (text[at] == ']')
			{
				at++;
				return list;
			}
			throw new FormatException("expected ',' or ']'");
		}

		private static string String(string text, ref int at)
		{
			if (at >= text.Length || text[at] != '"')
			{
				throw new FormatException("expected a string");
			}
			at++;
			StringBuilder stringBuilder = new StringBuilder();
			while (at < text.Length)
			{
				char c = text[at++];
				switch (c)
				{
				case '"':
					return stringBuilder.ToString();
				default:
					stringBuilder.Append(c);
					break;
				case '\\':
				{
					if (at >= text.Length)
					{
						throw new FormatException("unterminated escape");
					}
					char c2 = text[at++];
					switch (c2)
					{
					case 'n':
						stringBuilder.Append('\n');
						break;
					case 't':
						stringBuilder.Append('\t');
						break;
					case 'r':
						stringBuilder.Append('\r');
						break;
					case 'b':
						stringBuilder.Append('\b');
						break;
					case 'f':
						stringBuilder.Append('\f');
						break;
					case 'u':
						if (at + 4 > text.Length)
						{
							throw new FormatException("bad \\u escape");
						}
						stringBuilder.Append((char)int.Parse(text.Substring(at, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture));
						at += 4;
						break;
					default:
						stringBuilder.Append(c2);
						break;
					}
					break;
				}
				}
			}
			throw new FormatException("unterminated string");
		}

		private static double Number(string text, ref int at)
		{
			int num = at;
			while (at < text.Length && (char.IsDigit(text[at]) || "+-.eE".IndexOf(text[at]) >= 0))
			{
				at++;
			}
			if (at == num)
			{
				throw new FormatException("expected a number");
			}
			return double.Parse(text.Substring(num, at - num), NumberStyles.Float, CultureInfo.InvariantCulture);
		}

		private static void Word(string text, ref int at, string word)
		{
			if (string.CompareOrdinal(text, at, word, 0, word.Length) != 0)
			{
				throw new FormatException("expected " + word);
			}
			at += word.Length;
		}

		private static void Space(string text, ref int at)
		{
			while (at < text.Length && char.IsWhiteSpace(text[at]))
			{
				at++;
			}
		}
	}
	internal static class PackText
	{
		private enum TextWhat
		{
			PackName,
			SongText
		}

		internal enum SongText
		{
			Title,
			Artist,
			Illustrator
		}

		private static int PackAssetsTable = -1;

		private static int PackAssetsField = -1;

		private static int StringMappingField = -1;

		private const int TemplateRow = 2;

		private static readonly IntPtr[] DefaultArt = new IntPtr[14];

		private static bool _defaultArt;

		private static readonly (string Field, string Key, string File)[] VisualSet = new(string, string, string)[14]
		{
			("SongSelectLargeNonCompleted", "songSelect", "packvisual-songselect-default.png"),
			("SongSelectLargeNonCompletedHover", "songSelectHover", "packvisual-songselect-hover.png"),
			("SongSelectLargeCompleted", "songSelect", "packvisual-songselect-default.png"),
			("SongSelectLargeCompletedHover", "songSelectHover", "packvisual-songselect-hover.png"),
			("PackSelectLargeNonCompleted", "selected", "packvisual-selected-default.png"),
			("PackSelectLargeNonCompletedHover", "selectedHover", "packvisual-selected-hover.png"),
			("PackSelectLargeCompleted", "selected", "packvisual-selected-default.png"),
			("PackSelectLargeCompletedHover", "selectedHover", "packvisual-selected-hover.png"),
			("PackSelectNonCompleted", "deselected", "packvisual-deselected-default.png"),
			("PackSelectNonCompletedHover", "deselectedHover", "packvisual-deselected-hover.png"),
			("PackSelectCompleted", "deselected", "packvisual-deselected-default.png"),
			("PackSelectCompletedHover", "deselectedHover", "packvisual-deselected-hover.png"),
			("OutlineNonCompleted", "outline", "outline-packvisua.png"),
			("OutlineCompleted", "outline", "outline-packvisua.png")
		};

		private static readonly Dictionary<string, IntPtr> Built = new Dictionary<string, IntPtr>(StringComparer.Ordinal);

		private static int _language = int.MinValue;

		private const int FallbackLanguage = 2;

		private static int PackIdTypeMapping = -1;

		private static int SongTitleTypeMapping = -1;

		private static int SongArtistTypeMapping = -1;

		private static int SongIllustratorTypeMapping = -1;

		private static bool _fieldsResolved;

		private static bool _fieldsMissing;

		private static readonly int TextMappingValuesSize = 5 * IntPtr.Size;

		private static readonly int ValueEnglish;

		private static readonly int ValueJapanese;

		private static readonly int ValueKorean;

		private static readonly int ValueTraditionalChinese;

		private static readonly int ValueSimplifiedChinese;

		private static int _mappingOffset;

		internal static bool TryGetVisuals(out IntPtr packAssets)
		{
			packAssets = IntPtr.Zero;
			if (!ResolveFields())
			{
				return false;
			}
			IntPtr intPtr = FieldResolver.ClassPointer("DataAccess");
			if (intPtr == IntPtr.Zero)
			{
				return false;
			}
			IntPtr intPtr2 = FieldResolver.Statics(intPtr);
			if (intPtr2 == IntPtr.Zero)
			{
				return false;
			}
			packAssets = Memory.Ptr(intPtr2 + PackAssetsField);
			return Memory.LooksLikeObject(packAssets);
		}

		internal unsafe static bool PrepareVisualRows(IntPtr packAssets, int highestId)
		{
			IntPtr intPtr = Memory.Ptr(packAssets + PackAssetsTable);
			if (!Memory.LooksLikeObject(intPtr))
			{
				return false;
			}
			int num = Memory.I32(intPtr + 24);
			if (num <= 0)
			{
				return false;
			}
			int num2 = highestId + 1;
			if (num == num2)
			{
				return true;
			}
			int num3 = (int)(IL2CPP.il2cpp_array_get_byte_length(intPtr) / num);
			if (num3 <= 0)
			{
				return false;
			}
			IntPtr intPtr2 = SongCatalog.NewArray(intPtr, num2);
			if (intPtr2 == IntPtr.Zero)
			{
				return false;
			}
			IntPtr intPtr3 = intPtr + 32;
			IntPtr intPtr4 = intPtr2 + 32;
			long num4 = (long)Math.Min(num, num2) * (long)num3;
			Buffer.MemoryCopy((void*)intPtr3, (void*)intPtr4, num4, num4);
			StoreDefaultArt(intPtr3, num3);
			IntPtr intPtr5 = intPtr3 + Math.Min(2, num - 1) * num3;
			for (int i = num; i < num2; i++)
			{
				Buffer.MemoryCopy((void*)intPtr5, (void*)(intPtr4 + i * num3), num3, num3);
			}
			Memory.WritePtr(packAssets + PackAssetsTable, intPtr2);
			ModLog.Load($"pack visual rows: {num} -> {num2} (shape=row {Math.Min(2, num - 1)})");
			return true;
		}

		private static void StoreDefaultArt(IntPtr rows, int stride)
		{
			if (_defaultArt)
			{
				return;
			}
			for (int i = 0; i < VisualSet.Length; i++)
			{
				int num = FieldResolver.Lookup("SongSelectPackAssets", VisualSet[i].Field);
				if (num < 0)
				{
					return;
				}
				DefaultArt[i] = Memory.Ptr(rows + num);
			}
			_defaultArt = true;
		}

		internal static IntPtr RowMaterial(IntPtr packAssets, int packId, string field)
		{
			IntPtr intPtr = Memory.Ptr(packAssets + PackAssetsTable);
			if (!Memory.LooksLikeObject(intPtr))
			{
				return IntPtr.Zero;
			}
			int num = Memory.I32(intPtr + 24);
			if (packId < 0 || packId >= num)
			{
				return IntPtr.Zero;
			}
			long num2 = IL2CPP.il2cpp_array_get_byte_length(intPtr);
			int num3 = (int)((num > 0) ? (num2 / num) : 0);
			if (num3 <= 0)
			{
				return IntPtr.Zero;
			}
			int num4 = FieldResolver.Lookup("SongSelectPackAssets", field);
			if (num4 < 0)
			{
				return IntPtr.Zero;
			}
			return Memory.Ptr(intPtr + 32 + packId * num3 + num4);
		}

		internal static int SetPackVisuals(IntPtr packAssets, int packId, ChartPack pack)
		{
			IntPtr intPtr = Memory.Ptr(packAssets + PackAssetsTable);
			if (!Memory.LooksLikeObject(intPtr))
			{
				return 0;
			}
			int num = Memory.I32(intPtr + 24);
			if (packId < 0 || packId >= num)
			{
				return 0;
			}
			long num2 = IL2CPP.il2cpp_array_get_byte_length(intPtr);
			int num3 = (int)((num > 0) ? (num2 / num) : 0);
			if (num3 <= 0)
			{
				return 0;
			}
			IntPtr intPtr2 = intPtr + 32 + packId * num3;
			int language = Language();
			int num4 = 0;
			int i = 0;
			for (int j = 0; j < VisualSet.Length; j++, i++)
			{
				(string Field, string Key, string File) tuple = VisualSet[j];
				string item = tuple.Field;
				string item2 = tuple.Key;
				string item3 = tuple.File;
				string text = Picture(pack, item2, item3, language);
				int num5 = FieldResolver.Lookup("SongSelectPackAssets", item);
				if (num5 < 0)
				{
					continue;
				}
				if (string.IsNullOrEmpty(text) || !File.Exists(text))
				{
					IntPtr intPtr3 = DefaultArt[i];
					if (Memory.LooksLikeObject(intPtr3))
					{
						Memory.WritePtr(intPtr2 + num5, intPtr3);
					}
					continue;
				}
				IntPtr intPtr4 = Memory.Ptr(intPtr2 + num5);
				if (!Memory.LooksLikeObject(intPtr4))
				{
					continue;
				}
				string key = packId + "|" + text;
				if (!Built.TryGetValue(key, out var value))
				{
					value = JacketFactory.Cover(text, intPtr4, out var _);
					if (value == IntPtr.Zero)
					{
						continue;
					}
					Built[key] = value;
					ModLog.Load($"pack {packId} visual {item} off=0x{num5:X} src=0x{intPtr4.ToInt64():X} mat=0x{value.ToInt64():X}");
				}
				Memory.WritePtr(intPtr2 + num5, value);
				num4++;
			}
			return num4;
		}

		internal static void ResetVisualCache()
		{
			Built.Clear();
		}

		internal static int Language()
		{
			if (_language != int.MinValue)
			{
				return _language;
			}
			_language = ReadLanguage();
			return _language;
		}

		private static int ReadLanguage()
		{
			try
			{
				MethodInfo methodInfo = MethodResolver.ManagedMethod("Strings", "GetCurrentLocalization", "Str");
				if (methodInfo == null)
				{
					ModLog.WarnRelease("the game's current language could not be found; pack pictures use their English slot");
					return 2;
				}
				int num = Convert.ToInt32(methodInfo.Invoke(null, null), CultureInfo.InvariantCulture);
				string text = (methodInfo.ReturnType.IsEnum ? Enum.GetName(methodInfo.ReturnType, num) : null);
				int num2 = SlotOf(text);
				ModLog.Load($"pack pictures: the game is showing {text ?? num.ToString()} (language slot {num2})");
				return num2;
			}
			catch (Exception e)
			{
				ModLog.WarnRelease("the game's current language could not be read: " + ModLog.Describe(e));
				return 2;
			}
		}

		private static int SlotOf(string name)
		{
			if (name == null)
			{
				return 2;
			}
			if (name.IndexOf("Simplified", StringComparison.OrdinalIgnoreCase) >= 0 || name.EndsWith("SC", StringComparison.Ordinal))
			{
				return 0;
			}
			if (name.IndexOf("Traditional", StringComparison.OrdinalIgnoreCase) >= 0 || name.EndsWith("TC", StringComparison.Ordinal))
			{
				return 1;
			}
			if (name.IndexOf("Japanese", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return 3;
			}
			if (name.IndexOf("Korean", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return 4;
			}
			return 2;
		}

		internal static bool Install()
		{
			MethodInfo methodInfo = HarmonyHooks.Target("Strings", "SetDefaultLocalization", "Str");
			if (methodInfo == null)
			{
				ModLog.WarnRelease("the game's language setting could not be watched; changing it needs a restart for the pack pictures");
				return false;
			}
			bool num = HarmonyHooks.Patch(methodInfo, null, HarmonyHooks.Handler(typeof(PackText), "AfterLanguage"));
			if (num)
			{
				ModLog.Load("pack pictures follow the game's language");
			}
			return num;
		}

		private static void AfterLanguage()
		{
			_language = int.MinValue;
			CustomSongCore.InvalidateVisualTables();
		}

		private static string Picture(ChartPack pack, string key, string file, int language)
		{
			string text = pack.Visual(key, language);
			if (text != null && File.Exists(text))
			{
				return text;
			}
			string text2 = Path.Combine(pack.Folder, file);
			if (File.Exists(text2))
			{
				return text2;
			}
			string path = (file.Contains("-hover") ? file.Replace("-hover", "-default") : file.Replace("-default", "-hover"));
			text2 = Path.Combine(pack.Folder, path);
			if (!File.Exists(text2))
			{
				return null;
			}
			return text2;
		}

		internal unsafe static void RefreshLookup(IntPtr packData)
		{
			IntPtr intPtr = FieldResolver.ClassPointer("PackData");
			if (!(intPtr == IntPtr.Zero))
			{
				IntPtr intPtr2 = IL2CPP.il2cpp_class_get_method_from_name(intPtr, "_SkA", 0);
				if (!(intPtr2 == IntPtr.Zero))
				{
					IntPtr zero = IntPtr.Zero;
					IL2CPP.il2cpp_runtime_invoke(intPtr2, packData, (void**)null, ref zero);
					_ = zero != IntPtr.Zero;
				}
			}
		}

		private static bool ResolveFields()
		{
			if (_fieldsResolved)
			{
				return true;
			}
			if (_fieldsMissing)
			{
				return false;
			}
			if (FieldResolver.ClassPointer("DataAccess") == IntPtr.Zero)
			{
				return false;
			}
			PackAssetsTable = FieldResolver.Field("PackSelectSceneAssets", "packToAssets");
			PackIdTypeMapping = FieldResolver.Field("DynamicStringMapping", "packIdTypeMapping");
			SongTitleTypeMapping = FieldResolver.Field("DynamicStringMapping", "songIdTitleTypeMapping");
			SongArtistTypeMapping = FieldResolver.Field("DynamicStringMapping", "songIdArtistTypeMapping");
			SongIllustratorTypeMapping = FieldResolver.Field("DynamicStringMapping", "jacketIllustratorNameTypeMapping");
			PackAssetsField = FieldResolver.Field("DataAccess", "_NAb");
			StringMappingField = FieldResolver.Field("DataAccess", "_mAb");
			if (PackAssetsTable < 0 || PackIdTypeMapping < 0 || SongTitleTypeMapping < 0 || SongArtistTypeMapping < 0 || SongIllustratorTypeMapping < 0 || PackAssetsField < 0 || StringMappingField < 0)
			{
				_fieldsMissing = true;
				ModLog.Error("the pack's fields could not be read by name in this build; the custom pack keeps the game's own visuals and text");
				return false;
			}
			_fieldsResolved = true;
			return true;
		}

		private static string[] SameFive(string text)
		{
			return new string[5] { text, text, text, text, text };
		}

		internal static bool SetPackName(int packId, string name)
		{
			return SetPackName(packId, SameFive(name));
		}

		internal static bool SetPackName(int packId, string[] names)
		{
			if (!ResolveFields())
			{
				return false;
			}
			return SetText(PackIdTypeMapping, packId, names, TextWhat.PackName);
		}

		internal static bool SetSongText(SongText which, int songId, string text)
		{
			return SetSongText(which, songId, SameFive(text));
		}

		internal static bool SetSongText(SongText which, int songId, string[] names)
		{
			if (!ResolveFields())
			{
				return false;
			}
			return SetText(which switch
			{
				SongText.Artist => SongArtistTypeMapping, 
				SongText.Title => SongTitleTypeMapping, 
				_ => SongIllustratorTypeMapping, 
			}, songId, names, TextWhat.SongText);
		}

		private static bool SetText(int mappingField, int id, string[] names, TextWhat what)
		{
			if (!ResolveFields())
			{
				return false;
			}
			if (names == null || names.Length != 5)
			{
				return false;
			}
			IntPtr intPtr = FieldResolver.ClassPointer("DataAccess");
			if (intPtr == IntPtr.Zero)
			{
				return false;
			}
			IntPtr intPtr2 = FieldResolver.Statics(intPtr);
			if (intPtr2 == IntPtr.Zero)
			{
				return false;
			}
			IntPtr intPtr3 = Memory.Ptr(intPtr2 + StringMappingField);
			if (!Memory.LooksLikeObject(intPtr3))
			{
				return false;
			}
			IntPtr intPtr4 = Memory.Ptr(intPtr3 + mappingField);
			if (!Memory.LooksLikeObject(intPtr4))
			{
				return false;
			}
			int num = MappingOffset(intPtr4);
			if (num < 0)
			{
				return false;
			}
			IntPtr intPtr5 = Memory.Ptr(intPtr4 + num);
			if (!Memory.LooksLikeObject(intPtr5))
			{
				return false;
			}
			if (!Insert(intPtr5, id, names))
			{
				return false;
			}
			return true;
		}

		[Conditional("DEBUG")]
		private static void NotWritten(TextWhat what, string where)
		{
		}

		private static string Name(TextWhat what)
		{
			return null;
		}

		private static int MappingOffset(IntPtr typeMapping)
		{
			if (_mappingOffset != int.MinValue)
			{
				return _mappingOffset;
			}
			IntPtr intPtr = IL2CPP.il2cpp_object_get_class(typeMapping);
			if (intPtr != IntPtr.Zero)
			{
				IntPtr intPtr2 = IL2CPP.il2cpp_class_get_field_from_name(intPtr, "Mapping");
				if (intPtr2 != IntPtr.Zero)
				{
					int num = (int)IL2CPP.il2cpp_field_get_offset(intPtr2);
					if (num > 0)
					{
						return _mappingOffset = num;
					}
				}
			}
			foreach (KeyValuePair<string, List<Type>> item in InteropTypeIndex.ByName())
			{
				if (item.Key.Contains("StringTypeMapping") && item.Key.IndexOf('`') < 0)
				{
					int num2 = FieldResolver.Lookup(item.Key, "Mapping");
					if (num2 > 0)
					{
						return _mappingOffset = num2;
					}
				}
			}
			ModLog.Error("StringTypeMapping.Mapping could not be found on its own class in this build; the custom pack's texts will not be written");
			return _mappingOffset = -1;
		}

		private unsafe static bool Insert(IntPtr table, int packId, string[] names)
		{
			ushort num = (ushort)packId;
			fixed (byte* ptr = new byte[TextMappingValuesSize])
			{
				*(IntPtr*)(ptr + ValueSimplifiedChinese) = IL2CPP.ManagedStringToIl2Cpp(names[0]);
				*(IntPtr*)(ptr + ValueTraditionalChinese) = IL2CPP.ManagedStringToIl2Cpp(names[1]);
				*(IntPtr*)(ptr + ValueEnglish) = IL2CPP.ManagedStringToIl2Cpp(names[2]);
				*(IntPtr*)(ptr + ValueJapanese) = IL2CPP.ManagedStringToIl2Cpp(names[3]);
				*(IntPtr*)(ptr + ValueKorean) = IL2CPP.ManagedStringToIl2Cpp(names[4]);
				IntPtr* ptr2 = stackalloc IntPtr[2];
				*ptr2 = (IntPtr)(&num);
				ptr2[1] = (IntPtr)ptr;
				IntPtr intPtr = IL2CPP.il2cpp_class_get_method_from_name(IL2CPP.il2cpp_object_get_class(table), "set_Item", 2);
				if (intPtr == IntPtr.Zero)
				{
					return false;
				}
				IntPtr zero = IntPtr.Zero;
				IL2CPP.il2cpp_runtime_invoke(intPtr, table, (void**)ptr2, ref zero);
				if (zero != IntPtr.Zero)
				{
					return false;
				}
			}
			return true;
		}

		static PackText()
		{
			_ = IntPtr.Size;
			ValueEnglish = 0;
			ValueJapanese = IntPtr.Size;
			ValueKorean = 2 * IntPtr.Size;
			ValueTraditionalChinese = 3 * IntPtr.Size;
			ValueSimplifiedChinese = 4 * IntPtr.Size;
			_mappingOffset = int.MinValue;
		}
	}
	internal static class ScoreKeys
	{
		internal static long Refiled;

		private const string Records = "GameResultsV4.highscores";

		private const string KeyCollection = "Dictionary.KeyCollection";

		private const string ValueCollection = "Dictionary.ValueCollection";

		internal unsafe static void Migrate(IntPtr table)
		{
			if (table == IntPtr.Zero)
			{
				return;
			}
			try
			{
				IntPtr intPtr = FieldResolver.ClassPointer("GameResultsV4");
				int num = FieldResolver.Field("GameResultsV4", "highscores");
				if (intPtr == IntPtr.Zero || num < 0)
				{
					MigrateFailed("no table");
					return;
				}
				IntPtr intPtr2 = Memory.Ptr(table + num);
				if (!Memory.LooksLikeObject(intPtr2))
				{
					return;
				}
				Dictionary<string, ushort> dictionary = new Dictionary<string, ushort>();
				foreach (KeyValuePair<ushort, ChartInfo> item in SongCatalog.ById)
				{
					dictionary[item.Value.BaseName] = item.Key;
				}
				if (dictionary.Count == 0)
				{
					return;