Decompiled source of JojoScoutHats v0.5.2

BepInEx/plugins/JojoHats/JojoHats.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using Microsoft.CodeAnalysis;
using MoreCustomizations;
using MoreCustomizations.Data;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("JojoHats")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.5.2.0")]
[assembly: AssemblyInformationalVersion("0.5.2+25b3a737a1724d093b59586ae47f5c8abceca380")]
[assembly: AssemblyProduct("JojoHats")]
[assembly: AssemblyTitle("JojoHats")]
[assembly: AssemblyVersion("0.5.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace JojoHats
{
	internal static class CatalogMerge
	{
		internal static IReadOnlyDictionary<TKey, IReadOnlyList<TValue>> Append<TKey, TValue>(IReadOnlyDictionary<TKey, IReadOnlyList<TValue>> existing, TKey hatType, IReadOnlyList<TValue> additions, Func<TValue, string> identity) where TKey : notnull
		{
			if (existing == null)
			{
				throw new ArgumentNullException("existing");
			}
			if (hatType == null)
			{
				throw new ArgumentNullException("hatType");
			}
			if (additions == null)
			{
				throw new ArgumentNullException("additions");
			}
			if (identity == null)
			{
				throw new ArgumentNullException("identity");
			}
			List<KeyValuePair<string, TValue>> list = new List<KeyValuePair<string, TValue>>(additions.Count);
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			for (int i = 0; i < additions.Count; i++)
			{
				TValue val = additions[i];
				if (val == null)
				{
					throw new ArgumentException("An added cosmetic cannot be null.", "additions");
				}
				string text = identity(val);
				if (string.IsNullOrWhiteSpace(text))
				{
					throw new ArgumentException("Every added cosmetic needs a non-empty identity.", "additions");
				}
				if (!hashSet.Add(text))
				{
					throw new ArgumentException("Duplicate added cosmetic identity: " + text, "additions");
				}
				list.Add(new KeyValuePair<string, TValue>(text, val));
			}
			IReadOnlyList<TValue> value;
			bool num = existing.TryGetValue(hatType, out value);
			if (num && value == null)
			{
				throw new ArgumentException("The existing hat category cannot be null.", "existing");
			}
			HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
			List<TValue> list2 = new List<TValue>(num ? (value.Count + list.Count) : list.Count);
			if (num)
			{
				foreach (TValue item in value)
				{
					if (item == null)
					{
						throw new ArgumentException("An existing hat cannot be null.", "existing");
					}
					string text2 = identity(item);
					if (!string.IsNullOrWhiteSpace(text2))
					{
						hashSet2.Add(text2);
					}
					list2.Add(item);
				}
			}
			int count = list2.Count;
			foreach (KeyValuePair<string, TValue> item2 in list)
			{
				if (hashSet2.Add(item2.Key))
				{
					list2.Add(item2.Value);
				}
			}
			if (list2.Count == count)
			{
				return existing;
			}
			Dictionary<TKey, IReadOnlyList<TValue>> dictionary = new Dictionary<TKey, IReadOnlyList<TValue>>();
			foreach (KeyValuePair<TKey, IReadOnlyList<TValue>> item3 in existing)
			{
				dictionary.Add(item3.Key, item3.Value);
			}
			dictionary[hatType] = list2.AsReadOnly();
			return new ReadOnlyDictionary<TKey, IReadOnlyList<TValue>>(dictionary);
		}
	}
	internal static class NetworkIndexSmoke
	{
		private static readonly string[] HatNames = new string[6] { "jojo_mvp_01_jotaro_cap", "jojo_mvp_02_josuke_pompadour", "jojo_mvp_03_giorno_rolls", "jojo_mvp_04_jonathan_hair", "jojo_mvp_05_jolyne_buns", "jojo_mvp_06_joseph_hair" };

		private static readonly string[] FieldNames = new string[8] { "currentSkin", "currentAccessory", "currentEyes", "currentMouth", "currentOutfit", "currentHat", "currentSash", "currentMedal" };

		internal static void Verify(List<string> results, int firstJojoRuntimeIndex)
		{
			if (results == null)
			{
				throw new ArgumentNullException("results");
			}
			FieldInfo? obj = typeof(Customization).BaseType?.GetField("_instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingFieldException("Customization singleton _instance");
			FieldInfo fieldInfo = typeof(Customization).BaseType?.GetField("m_shuttingDown", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingFieldException("Customization singleton m_shuttingDown");
			object? value = obj.GetValue(null);
			Customization val = (Customization)((value is Customization) ? value : null);
			if (val != null)
			{
				Object val2 = (Object)(object)((val is Object) ? val : null);
				if (val2 != null && Object.op_Implicit(val2) && !(bool)fieldInfo.GetValue(null))
				{
					int baseHatCount = MoreCustomizationsPlugin.BaseHatCount;
					int overrideHatCount = MoreCustomizationsPlugin.OverrideHatCount;
					if (baseHatCount <= 0 || overrideHatCount < 0 || firstJojoRuntimeIndex < baseHatCount + overrideHatCount)
					{
						throw new InvalidOperationException("Local serialization test has invalid vanilla/override hat bounds.");
					}
					int num = firstJojoRuntimeIndex - overrideHatCount;
					if (val.hats == null || num < 0 || num > val.hats.Length - HatNames.Length)
					{
						throw new InvalidOperationException("JOJO runtime indices do not fit the real passport catalog.");
					}
					for (int i = 0; i < HatNames.Length; i++)
					{
						if (!Object.op_Implicit((Object)(object)val.hats[num + i]) || ((Object)val.hats[num + i]).name != HatNames[i])
						{
							throw new InvalidOperationException("JOJO menu/runtime index mapping differs at ordinal " + i);
						}
					}
					string[] array = new string[7] { "skins", "accessories", "eyes", "mouths", "fits", "sashes", "medals" };
					foreach (string text in array)
					{
						if (!(typeof(Customization).GetField(text)?.GetValue(val) is CustomizationOption[] array2) || array2.Length == 0)
						{
							throw new InvalidOperationException("Real customization category unavailable: " + text);
						}
					}
					Type typeFromHandle = typeof(CharacterCustomizationData);
					MethodInfo methodInfo = typeFromHandle.GetMethod("Serialize", BindingFlags.Instance | BindingFlags.Public) ?? throw new MissingMethodException(typeFromHandle.FullName, "Serialize");
					MethodInfo methodInfo2 = typeFromHandle.GetMethod("Deserialize", BindingFlags.Instance | BindingFlags.Public) ?? throw new MissingMethodException(typeFromHandle.FullName, "Deserialize");
					Type parameterType = methodInfo.GetParameters()[0].ParameterType;
					Type parameterType2 = methodInfo2.GetParameters()[0].ParameterType;
					ConstructorInfo constructorInfo = parameterType2.GetConstructor(new Type[1] { parameterType }) ?? throw new MissingMethodException(parameterType2.FullName, ".ctor(BinarySerializer)");
					PropertyInfo propertyInfo = parameterType.GetProperty("Position") ?? throw new MissingMemberException(parameterType.FullName, "Position");
					FieldInfo[] array3 = new FieldInfo[FieldNames.Length];
					for (int k = 0; k < array3.Length; k++)
					{
						array3[k] = typeFromHandle.GetField(FieldNames[k], BindingFlags.Instance | BindingFlags.Public) ?? throw new MissingFieldException(typeFromHandle.FullName, FieldNames[k]);
						if (array3[k].FieldType != typeof(int))
						{
							throw new InvalidOperationException("Game cosmetic format changed: " + FieldNames[k]);
						}
					}
					for (int l = 0; l < HatNames.Length; l++)
					{
						int num2 = checked(firstJojoRuntimeIndex + l);
						object obj2 = Activator.CreateInstance(typeFromHandle);
						object obj3 = Activator.CreateInstance(typeFromHandle);
						array3[5].SetValue(obj2, num2);
						object obj4 = Activator.CreateInstance(parameterType);
						try
						{
							methodInfo.Invoke(obj2, new object[1] { obj4 });
							if ((int)propertyInfo.GetValue(obj4) != FieldNames.Length * 4)
							{
								throw new InvalidOperationException("Game cosmetic serialization byte count changed.");
							}
							object obj5 = constructorInfo.Invoke(new object[1] { obj4 });
							methodInfo2.Invoke(obj3, new object[1] { obj5 });
							for (int m = 0; m < array3.Length; m++)
							{
								int num3 = ((m == 5) ? num2 : 0);
								if ((int)array3[m].GetValue(obj2) != num3 || (int)array3[m].GetValue(obj3) != num3)
								{
									throw new InvalidOperationException("Local cosmetic round-trip changed " + FieldNames[m] + " for " + HatNames[l]);
								}
							}
						}
						finally
						{
							((IDisposable)obj4).Dispose();
						}
					}
					results.Add("localGameSerializationRoundTrip=6/6; actual Serialize/Deserialize+CorrectValues; runtimeIndices=" + firstJojoRuntimeIndex + ".." + (firstJojoRuntimeIndex + HatNames.Length - 1) + "; saves=untouched; packetsSent=0; twoClientMultiplayer=NOT_TESTED");
					return;
				}
			}
			throw new InvalidOperationException("Local serialization test needs the real Customization singleton.");
		}
	}
	[BepInPlugin("midor.peak.jojohats", "JOJO Scout Hats", "0.5.2")]
	[BepInDependency("MoreCustomizations", "1.1.10")]
	public sealed class Plugin : BaseUnityPlugin
	{
		internal const string Version = "0.5.2";

		private const string Prefix = "jojo_mvp_";

		private static readonly string[] Ids = new string[6] { "01_jotaro_cap", "02_josuke_pompadour", "03_giorno_rolls", "04_jonathan_hair", "05_jolyne_buns", "06_joseph_hair" };

		private readonly List<Object> ownedAssets = new List<Object>();

		private readonly List<CustomHat_V1> hats = new List<CustomHat_V1>();

		private GameObject? templates;

		private IReadOnlyDictionary<Type, IReadOnlyList<CustomizationData>>? originalCatalog;

		private string contentFingerprint = "";

		private bool catalogPublished;

		private void Awake()
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Expected O, but got Unknown
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				string text = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "assets");
				IReadOnlyDictionary<Type, IReadOnlyList<CustomizationData>> readOnlyDictionary = (originalCatalog = MoreCustomizationsPlugin.AllCustomizationsData ?? throw new InvalidOperationException("More Customizations did not initialize. Reinstall the official More Customizations dependency in your mod-manager profile and keep its built-in.pcab. Do not run the old JOJO-only patch script. No other cosmetics or files were changed."));
				Texture2D val = LoadTexture(Path.Combine(text, "palette.png"));
				((Texture)val).filterMode = (FilterMode)0;
				templates = new GameObject("JOJO Hat Templates");
				templates.SetActive(false);
				Object.DontDestroyOnLoad((Object)(object)templates);
				string[] ids = Ids;
				foreach (string text2 in ids)
				{
					string path = Path.Combine(text, text2);
					MeshData meshData = JsonUtility.FromJson<MeshData>(File.ReadAllText(Path.Combine(path, "mesh.json")));
					Validate(meshData, text2);
					Mesh val2 = MakeMesh(meshData);
					Texture2D val3 = val;
					if (!string.IsNullOrEmpty(meshData.texture))
					{
						val3 = LoadTexture(Path.Combine(path, meshData.texture));
						((Texture)val3).filterMode = (FilterMode)1;
					}
					GameObject val4 = new GameObject("jojo_mvp_" + text2);
					val4.transform.SetParent(templates.transform, false);
					val4.AddComponent<MeshFilter>().sharedMesh = val2;
					val4.AddComponent<MeshRenderer>();
					CustomHat_V1 val5 = ScriptableObject.CreateInstance<CustomHat_V1>();
					ownedAssets.Add((Object)(object)val5);
					((Object)val5).name = "jojo_mvp_" + text2;
					Set(val5, "Icon", LoadTexture(Path.Combine(path, "icon.png")));
					Set(val5, "Prefab", val4);
					Set(val5, "MainTexture", val3);
					Set(val5, "SubTexture", val3);
					Set(val5, "PositionOffset", Vector3.zero);
					Set(val5, "EulerAngleOffset", Vector3.zero);
					if (!((CustomizationData)val5).IsValid)
					{
						throw new InvalidDataException("Hat validation failed: " + text2);
					}
					hats.Add(val5);
					((BaseUnityPlugin)this).Logger.LogInfo((object)$"Prepared {text2}: {val2.vertexCount} vertices, {meshData.triangles.Length / 3} triangles");
				}
				IReadOnlyDictionary<Type, IReadOnlyList<CustomizationData>> readOnlyDictionary2 = CatalogMerge.Append(readOnlyDictionary, (Type)50, hats.Cast<CustomizationData>().ToArray(), (CustomizationData item) => ((Object)item).name);
				contentFingerprint = Fingerprint(text);
				SetStatic(typeof(MoreCustomizationsPlugin), "AllCustomizationsData", readOnlyDictionary2);
				catalogPublished = true;
				IReadOnlyList<CustomizationData> value;
				int num = (readOnlyDictionary.TryGetValue((Type)50, out value) ? value.Count : 0);
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"JOJO_REGISTERED=6; mode=append; existingHats={num}; totalHats={readOnlyDictionary2[(Type)50].Count}; contentSHA256={contentFingerprint}");
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Other cosmetics preserved. For multiplayer, every player needs the same complete cosmetic profile and versions; mesh files are not sent over the network.");
				if (Environment.GetCommandLineArgs().Contains("--jojo-smoke-test"))
				{
					File.WriteAllLines(Path.Combine(Paths.BepInExRootPath, "jojo-smoke.txt"), new string[2] { "version=0.5.2", "result=PENDING" });
					((MonoBehaviour)this).StartCoroutine(SmokeTest());
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)((catalogPublished ? "JOJO post-registration diagnostic failed: " : "JOJO initialization aborted; other cosmetics left unchanged: ") + ex));
				if (!catalogPublished)
				{
					if (Object.op_Implicit((Object)(object)templates))
					{
						Object.Destroy((Object)(object)templates);
					}
					foreach (Object ownedAsset in ownedAssets)
					{
						if (Object.op_Implicit(ownedAsset))
						{
							Object.Destroy(ownedAsset);
						}
					}
					((Behaviour)this).enabled = false;
				}
				if (Environment.GetCommandLineArgs().Contains("--jojo-smoke-test"))
				{
					File.WriteAllLines(Path.Combine(Paths.BepInExRootPath, "jojo-smoke.txt"), new string[2]
					{
						"version=0.5.2",
						"result=FAIL: " + ex
					});
					if (Environment.GetCommandLineArgs().Contains("--jojo-test-quit"))
					{
						Application.Quit();
					}
				}
			}
		}

		private static string Fingerprint(string root)
		{
			using SHA256 sHA = SHA256.Create();
			List<string> list = new List<string>();
			foreach (string item in new string[1] { "palette.png" }.Concat(Ids.SelectMany((string id) => new string[2]
			{
				id + "/mesh.json",
				id + "/icon.png"
			})))
			{
				list.Add(item + ":" + BitConverter.ToString(sHA.ComputeHash(File.ReadAllBytes(Path.Combine(root, item)))).Replace("-", ""));
			}
			return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(string.Join("\n", list)))).Replace("-", "").ToLowerInvariant();
		}

		private Texture2D LoadTexture(string path)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Expected O, but got Unknown
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
			ownedAssets.Add((Object)(object)val);
			if (!ImageConversion.LoadImage(val, File.ReadAllBytes(path)))
			{
				throw new InvalidDataException("Cannot decode " + path);
			}
			((Object)val).name = Path.GetFileName(path);
			((Texture)val).wrapMode = (TextureWrapMode)1;
			return val;
		}

		private Mesh MakeMesh(MeshData data)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Expected O, but got Unknown
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			int num = data.positions.Length / 3;
			Vector3[] array = (Vector3[])(object)new Vector3[num];
			Vector2[] array2 = (Vector2[])(object)new Vector2[num];
			Color[] array3 = (Color[])(object)new Color[num];
			for (int i = 0; i < num; i++)
			{
				array[i] = new Vector3(data.positions[i * 3], data.positions[i * 3 + 1], data.positions[i * 3 + 2]);
				array2[i] = new Vector2(data.uv[i * 2], data.uv[i * 2 + 1]);
				array3[i] = Color.white;
			}
			Mesh val = new Mesh
			{
				name = "jojo_mvp_" + data.name
			};
			ownedAssets.Add((Object)(object)val);
			val.vertices = array;
			val.uv = array2;
			val.colors = array3;
			val.subMeshCount = 2;
			val.SetTriangles(data.triangles, 0);
			val.SetTriangles(Array.Empty<int>(), 1);
			if (data.normals != null && data.normals.Length == num * 3)
			{
				Vector3[] array4 = (Vector3[])(object)new Vector3[num];
				for (int j = 0; j < num; j++)
				{
					array4[j] = new Vector3(data.normals[j * 3], data.normals[j * 3 + 1], data.normals[j * 3 + 2]);
				}
				val.normals = array4;
			}
			else
			{
				val.RecalculateNormals();
			}
			val.RecalculateBounds();
			return val;
		}

		internal static void Validate(MeshData data, string expected)
		{
			if (data == null || data.name != expected || data.positions == null || data.triangles == null || data.uv == null)
			{
				throw new InvalidDataException("Missing or mismatched mesh: " + expected);
			}
			int n = data.positions.Length / 3;
			if (!string.IsNullOrEmpty(data.texture) && data.texture != "albedo.png")
			{
				throw new InvalidDataException("Unsupported texture path: " + expected);
			}
			if (data.normals != null && data.normals.Length != 0)
			{
				if (data.normals.Length != data.positions.Length || data.normals.Any((float x) => float.IsNaN(x) || float.IsInfinity(x)))
				{
					throw new InvalidDataException("Invalid normal data: " + expected);
				}
				for (int num = 0; num < data.normals.Length; num += 3)
				{
					float num2 = data.normals[num] * data.normals[num] + data.normals[num + 1] * data.normals[num + 1] + data.normals[num + 2] * data.normals[num + 2];
					if (num2 < 0.98f || num2 > 1.02f)
					{
						throw new InvalidDataException("Non-unit normal: " + expected);
					}
				}
			}
			if (n == 0 || n > 65000 || data.positions.Length % 3 != 0 || data.uv.Length != n * 2 || data.triangles.Length == 0 || data.triangles.Length % 3 != 0)
			{
				throw new InvalidDataException("Invalid mesh dimensions: " + expected);
			}
			if (data.positions.Any((float x) => float.IsNaN(x) || float.IsInfinity(x) || Math.Abs(x) > 8f) || data.uv.Any((float x) => float.IsNaN(x) || float.IsInfinity(x) || x < 0f || x > 1f) || data.triangles.Any((int i) => i < 0 || i >= n))
			{
				throw new InvalidDataException("Invalid mesh coordinates or indices: " + expected);
			}
		}

		private static void Set(object target, string name, object value)
		{
			((target.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public) ?? throw new MissingMemberException(target.GetType().FullName, name)).GetSetMethod(nonPublic: true) ?? throw new MissingMethodException(name)).Invoke(target, new object[1] { value });
		}

		private static void SetStatic(Type type, string name, object value)
		{
			((type.GetProperty(name, BindingFlags.Static | BindingFlags.Public) ?? throw new MissingMemberException(type.FullName, name)).GetSetMethod(nonPublic: true) ?? throw new MissingMethodException(name)).Invoke(null, new object[1] { value });
		}

		private IEnumerator SmokeTest()
		{
			yield return (object)new WaitForSecondsRealtime(18f);
			List<string> list = new List<string>
			{
				"version=0.5.2",
				"registered=" + hats.Count,
				"contentSHA256=" + contentFingerprint
			};
			IReadOnlyDictionary<Type, IReadOnlyList<CustomizationData>> allCustomizationsData = MoreCustomizationsPlugin.AllCustomizationsData;
			list.Add("customNonHatEntries=" + allCustomizationsData.Where((KeyValuePair<Type, IReadOnlyList<CustomizationData>> p) => (int)p.Key != 50).Sum((KeyValuePair<Type, IReadOnlyList<CustomizationData>> p) => p.Value.Count));
			list.Add("customHatEntries=" + allCustomizationsData[(Type)50].Count);
			list.Add("pcabFiles=" + Directory.GetFiles(Paths.PluginPath, "*.pcab", SearchOption.AllDirectories).Length);
			try
			{
				foreach (CustomHat_V1 hat in hats)
				{
					GameObject val = Object.Instantiate<GameObject>(hat.Prefab);
					val.SetActive(true);
					MeshRenderer component = val.GetComponent<MeshRenderer>();
					Mesh sharedMesh = val.GetComponent<MeshFilter>().sharedMesh;
					bool flag = (Object)(object)hat.MainTexture == (Object)(object)hats[0].MainTexture && hat.MainTexture.height == 16 && (int)hat.MainTexture.filterMode == 0;
					bool flag2 = sharedMesh.normals.Length == sharedMesh.vertexCount;
					list.Add(((Object)hat).name + ": active=" + val.activeSelf + ", vertices=" + sharedMesh.vertexCount + ", renderer=" + ((Object)(object)component != (Object)null));
					Object.Destroy((Object)(object)val);
					if (!flag || !flag2)
					{
						throw new InvalidOperationException("Shared palette/normal assertion failed: " + ((Object)hat).name);
					}
				}
				Shader val2 = Shader.Find("W/Character");
				list.Add("characterShader=" + ((Object)(object)val2 != (Object)null));
				list.Add("runtime=" + Application.unityVersion);
				if (!Object.op_Implicit((Object)(object)val2))
				{
					throw new InvalidOperationException("Game character shader missing.");
				}
				VerifyPreservedCatalog(allCustomizationsData);
				if (CatalogMerge.Append(allCustomizationsData, (Type)50, hats.Cast<CustomizationData>().ToArray(), (CustomizationData item) => ((Object)item).name)[(Type)50].Count != allCustomizationsData[(Type)50].Count)
				{
					throw new InvalidOperationException("Duplicate registration changed the catalog.");
				}
				list.Add("existingCatalogPreserved=PASS; duplicateRegistration=PASS");
				IntegrationSmoke(list);
				list.Add("result=PASS");
			}
			catch (Exception ex)
			{
				list.Add("result=FAIL: " + ex);
				((BaseUnityPlugin)this).Logger.LogError((object)("JOJO integration check: " + ex));
			}
			File.WriteAllLines(Path.Combine(Paths.BepInExRootPath, "jojo-smoke.txt"), list);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("JOJO_SMOKE_FINISHED: " + list.Last()));
			if (Environment.GetCommandLineArgs().Contains("--jojo-test-quit"))
			{
				Application.Quit();
			}
		}

		private void VerifyPreservedCatalog(IReadOnlyDictionary<Type, IReadOnlyList<CustomizationData>> actual)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Invalid comparison between Unknown and I4
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			if (originalCatalog == null)
			{
				throw new InvalidOperationException("Missing pre-registration snapshot.");
			}
			foreach (KeyValuePair<Type, IReadOnlyList<CustomizationData>> item in originalCatalog)
			{
				if (!actual.TryGetValue(item.Key, out IReadOnlyList<CustomizationData> value) || value.Count < item.Value.Count)
				{
					throw new InvalidOperationException("Existing category removed or shortened: " + ((object)item.Key/*cast due to .constrained prefix*/).ToString());
				}
				for (int i = 0; i < item.Value.Count; i++)
				{
					if (value[i] != item.Value[i])
					{
						throw new InvalidOperationException("Existing cosmetic changed index: " + ((object)item.Key/*cast due to .constrained prefix*/).ToString() + "/" + i);
					}
				}
				if ((int)item.Key != 50 && value.Count != item.Value.Count)
				{
					throw new InvalidOperationException("Unrelated cosmetic category changed.");
				}
			}
			string[] ids = Ids;
			foreach (string id in ids)
			{
				if (actual[(Type)50].Count((CustomizationData item) => ((Object)item).name == "jojo_mvp_" + id) != 1)
				{
					throw new InvalidOperationException("Missing or duplicate JOJO hat: " + id);
				}
			}
		}

		private void IntegrationSmoke(List<string> results)
		{
			//IL_03b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_03be: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0300: Unknown result type (might be due to invalid IL or missing references)
			//IL_0321: Unknown result type (might be due to invalid IL or missing references)
			//IL_0326: Unknown result type (might be due to invalid IL or missing references)
			//IL_045b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0460: Unknown result type (might be due to invalid IL or missing references)
			//IL_0481: Unknown result type (might be due to invalid IL or missing references)
			//IL_0486: Unknown result type (might be due to invalid IL or missing references)
			//IL_0513: Unknown result type (might be due to invalid IL or missing references)
			//IL_051a: Expected O, but got Unknown
			GameObject obj = Resources.Load<GameObject>("Character");
			if (!Object.op_Implicit((Object)(object)obj))
			{
				throw new InvalidOperationException("Game Character prefab not found.");
			}
			Type type = typeof(MoreCustomizationsPlugin).Assembly.GetType("MoreCustomizations.Patches.CharacterCustomizationPatch", throwOnError: true);
			Type type2 = typeof(MoreCustomizationsPlugin).Assembly.GetType("MoreCustomizations.Patches.PassportManagerPatch", throwOnError: true);
			FieldInfo fieldInfo = type.GetField("_characterShader", BindingFlags.Static | BindingFlags.NonPublic) ?? throw new MissingFieldException(type.FullName, "_characterShader");
			FieldInfo fieldInfo2 = type2.GetField("materialTemplate", BindingFlags.Static | BindingFlags.NonPublic) ?? throw new MissingFieldException(type2.FullName, "materialTemplate");
			object value = fieldInfo.GetValue(null);
			object value2 = fieldInfo2.GetValue(null);
			HashSet<int> hashSet = new HashSet<int>(from material in Resources.FindObjectsOfTypeAll<Material>()
				select ((Object)material).GetInstanceID());
			HashSet<int> optionsBeforePassport = null;
			GameObject val = Object.Instantiate<GameObject>(obj, templates.transform, false);
			GameObject val2 = null;
			int baseHatCount = MoreCustomizationsPlugin.BaseHatCount;
			int overrideHatCount = MoreCustomizationsPlugin.OverrideHatCount;
			try
			{
				((Object)val).name = "JOJO isolated prefab integration";
				CharacterCustomization component = val.GetComponent<CharacterCustomization>();
				if (!Object.op_Implicit((Object)(object)component) || !Object.op_Implicit((Object)(object)component.refs))
				{
					throw new InvalidOperationException("CharacterCustomization refs missing.");
				}
				int num = component.refs.playerHats.Length;
				type.GetMethod("Awake", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[1] { component });
				Renderer[] array = component.refs.playerHats.Where((Renderer r) => Object.op_Implicit((Object)(object)r) && ((Object)r).name.StartsWith("jojo_mvp_", StringComparison.Ordinal)).ToArray();
				if (array.Length != 6)
				{
					throw new InvalidOperationException("Not all six hats attached to actual Character prefab.");
				}
				int count = MoreCustomizationsPlugin.AllCustomizationsData[(Type)50].Count;
				if (component.refs.playerHats.Length < num + count)
				{
					throw new InvalidOperationException("Existing custom hat renderers missing.");
				}
				results.Add($"actualCharacterPrefab: baseRenderers={num}; jojoRenderers={array.Length}");
				Renderer[] array2 = array;
				foreach (Renderer val3 in array2)
				{
					Mesh sharedMesh = ((Component)val3).GetComponent<MeshFilter>().sharedMesh;
					if (sharedMesh.normals.Length != sharedMesh.vertexCount || sharedMesh.normals.Any((Vector3 n) => ((Vector3)(ref n)).sqrMagnitude < 0.98f || ((Vector3)(ref n)).sqrMagnitude > 1.02f))
					{
						throw new InvalidOperationException("Missing or invalid authored normals: " + ((Object)val3).name);
					}
					if (val3.sharedMaterials.Any((Material m) => !Object.op_Implicit((Object)(object)m) || (Object)(object)m.shader != (Object)(object)Shader.Find("W/Character")))
					{
						throw new InvalidOperationException("Wrong renderer shader: " + ((Object)val3).name);
					}
					results.Add(((Object)val3).name + ": headLocalPosition=" + ((object)((Component)val3).transform.localPosition/*cast due to .constrained prefix*/).ToString() + "; rotation=" + ((object)((Component)val3).transform.localEulerAngles/*cast due to .constrained prefix*/).ToString());
					results.Add(((Object)val3).name + ": authoredNormals=" + sharedMesh.normals.Length);
				}
				results.Add("headTransform=" + (object)component.refs.hatTransform);
				results.Add("bodyTransform=" + ((object)((Component)component.refs.mainRenderer).transform.position/*cast due to .constrained prefix*/).ToString() + "; scale=" + ((object)((Component)component.refs.mainRenderer).transform.lossyScale/*cast due to .constrained prefix*/).ToString());
				foreach (Renderer item2 in component.refs.playerHats.Take(8))
				{
					if (Object.op_Implicit((Object)(object)item2))
					{
						results.Add("baseHat=" + ((Object)item2).name + "; local=" + ((object)((Component)item2).transform.localPosition/*cast due to .constrained prefix*/).ToString() + "; world=" + ((object)((Component)item2).transform.position/*cast due to .constrained prefix*/).ToString());
					}
				}
				if (Environment.GetCommandLineArgs().Contains("--jojo-network-index-test"))
				{
					NetworkIndexSmoke.Verify(results, Array.FindIndex(component.refs.playerHats, (Renderer renderer) => Object.op_Implicit((Object)(object)renderer) && ((Object)renderer).name == "jojo_mvp_" + Ids[0]));
				}
				else
				{
					results.Add("localGameSerializationRoundTrip=NOT_RUN; requires real initialized passport; twoClientMultiplayer=NOT_TESTED");
				}
				val2 = new GameObject("JOJO isolated passport registration");
				val2.transform.SetParent(templates.transform, false);
				object customization = val2.AddComponent(typeof(Customization));
				FieldInfo[] fields = typeof(Customization).GetFields(BindingFlags.Instance | BindingFlags.Public);
				foreach (FieldInfo fieldInfo3 in fields)
				{
					if (fieldInfo3.FieldType == typeof(CustomizationOption[]))
					{
						fieldInfo3.SetValue(customization, Array.Empty<CustomizationOption>());
					}
				}
				object obj2 = val2.AddComponent(typeof(PassportManager));
				optionsBeforePassport = new HashSet<int>(from option in Resources.FindObjectsOfTypeAll<CustomizationOption>()
					select ((Object)option).GetInstanceID());
				type2.GetMethod("Awake", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[1] { obj2 });
				CustomizationOption[] array3 = (CustomizationOption[])typeof(Customization).GetField("hats").GetValue(customization);
				CustomizationOption[] array4 = array3.Where((CustomizationOption o) => ((Object)o).name.StartsWith("jojo_mvp_", StringComparison.Ordinal)).ToArray();
				if (array3.Length < count)
				{
					throw new InvalidOperationException("Existing custom passport hats missing.");
				}
				if (array4.Length != 6 || array4.Where((CustomizationOption o, int i) => ((Object)o).name != ((Object)hats[i]).name || (Object)(object)o.texture != (Object)(object)((CustomizationData)hats[i]).IconTexture).Any())
				{
					throw new InvalidOperationException("Passport names/icons/order mismatch.");
				}
				IEnumerable<CustomizationOption> source = (from field in typeof(Customization).GetFields(BindingFlags.Instance | BindingFlags.Public)
					where field.FieldType == typeof(CustomizationOption[])
					select field).SelectMany((FieldInfo field) => (CustomizationOption[])field.GetValue(customization));
				foreach (KeyValuePair<Type, IReadOnlyList<CustomizationData>> item3 in originalCatalog)
				{
					foreach (CustomizationData item in item3.Value)
					{
						if (!source.Any((CustomizationOption option) => ((Object)option).name == ((Object)item).name))
						{
							throw new InvalidOperationException("Existing passport cosmetic was lost: " + ((Object)item).name);
						}
					}
				}
				results.Add($"isolatedPassportRegistration={array3.Length}; jojoOptions=6; existing custom entries preserved; all JOJO icons and names match fixed order");
			}
			finally
			{
				CustomizationOption[] array5 = ((optionsBeforePassport == null) ? Array.Empty<CustomizationOption>() : (from option in Resources.FindObjectsOfTypeAll<CustomizationOption>()
					where !optionsBeforePassport.Contains(((Object)option).GetInstanceID())
					select option).ToArray());
				HashSet<Material> hashSet2 = new HashSet<Material>();
				Renderer[] array2 = val.GetComponentsInChildren<Renderer>(true);
				for (int num2 = 0; num2 < array2.Length; num2++)
				{
					Material[] sharedMaterials = array2[num2].sharedMaterials;
					foreach (Material val4 in sharedMaterials)
					{
						if (Object.op_Implicit((Object)(object)val4) && !hashSet.Contains(((Object)val4).GetInstanceID()))
						{
							hashSet2.Add(val4);
						}
					}
				}
				CustomizationOption[] array6 = array5;
				foreach (CustomizationOption obj3 in array6)
				{
					FieldInfo[] fields = typeof(CustomizationOption).GetFields(BindingFlags.Instance | BindingFlags.Public);
					foreach (FieldInfo fieldInfo4 in fields)
					{
						if (fieldInfo4.FieldType == typeof(Material))
						{
							object? value3 = fieldInfo4.GetValue(obj3);
							Material val5 = (Material)((value3 is Material) ? value3 : null);
							if (val5 != null && Object.op_Implicit((Object)(object)val5) && !hashSet.Contains(((Object)val5).GetInstanceID()))
							{
								hashSet2.Add(val5);
							}
						}
					}
				}
				object? value4 = fieldInfo2.GetValue(null);
				Material val6 = (Material)((value4 is Material) ? value4 : null);
				if (val6 != null && Object.op_Implicit((Object)(object)val6) && !hashSet.Contains(((Object)val6).GetInstanceID()))
				{
					hashSet2.Add(val6);
				}
				try
				{
					SetStatic(typeof(MoreCustomizationsPlugin), "BaseHatCount", baseHatCount);
					SetStatic(typeof(MoreCustomizationsPlugin), "OverrideHatCount", overrideHatCount);
					fieldInfo.SetValue(null, value);
					fieldInfo2.SetValue(null, value2);
					results.Add("isolatedFrameworkGlobalsRestored=PASS");
				}
				finally
				{
					array6 = array5;
					foreach (CustomizationOption val7 in array6)
					{
						if (Object.op_Implicit((Object)(object)val7))
						{
							Object.Destroy((Object)(object)val7);
						}
					}
					foreach (Material item4 in hashSet2)
					{
						if (Object.op_Implicit((Object)(object)item4))
						{
							Object.Destroy((Object)(object)item4);
						}
					}
					if (Object.op_Implicit((Object)(object)val2))
					{
						Object.Destroy((Object)(object)val2);
					}
					Object.Destroy((Object)(object)val);
				}
			}
		}
	}
	[Serializable]
	internal sealed class MeshData
	{
		public string name = "";

		public float[] positions = Array.Empty<float>();

		public int[] triangles = Array.Empty<int>();

		public float[] uv = Array.Empty<float>();

		public float[] normals = Array.Empty<float>();

		public string texture = "";
	}
}