Decompiled source of False Son Skin Pack v0.2.1

BepInEx/plugins/FalseSonNativeSkins/Rodanius.FalseSon.NativeSkins.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using HG.Reflection;
using R2API;
using R2API.Utils;
using RoR2;
using RoR2.UI.MainMenu;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Rendering;
using UnityEngine.ResourceManagement.AsyncOperations;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: OptIn]
[assembly: AssemblyVersion("0.0.0.0")]
namespace Rodanius.FalseSonNativeSkins;

[BepInPlugin("rodanius.falseson.native-skins", "False Son - Native Skin Additions", "0.1.1")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class FalseSonNativeSkins : BaseUnityPlugin
{
	[Serializable]
	public sealed class FileEntry
	{
		public string file;

		public string sha256;
	}

	[Serializable]
	public sealed class Surface
	{
		public FileEntry mesh;

		public FileEntry albedo;

		public FileEntry normal;
	}

	[Serializable]
	public sealed class Entry
	{
		public string skinId;

		public string name;

		public string revision;

		public Surface body;

		public Surface maul;

		public FileEntry icon;
	}

	[Serializable]
	public sealed class Manifest
	{
		public int schema;

		public Entry[] skins;
	}

	public const string Guid = "rodanius.falseson.native-skins";

	public const string Version = "0.1.1";

	private static FalseSonNativeSkins instance;

	private string folder;

	private float deadline;

	private bool registered;

	private ModelSkinController bodyController;

	private ModelSkinController displayController;

	private SkinDef[] originalBody;

	private SkinDef[] committedBody;

	private SkinDef[] originalDisplay;

	private SkinDef[] committedDisplay;

	private readonly List<AsyncOperationHandle> retained = new List<AsyncOperationHandle>();

	private readonly List<SkinDef> ownedSkins = new List<SkinDef>();

	private void Awake()
	{
		instance = this;
		folder = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
		SystemInitializerInjector.InjectDependency(typeof(SkinCatalog), typeof(FalseSonNativeSkins));
		RoR2Application.onLoad = (Action)Delegate.Combine(RoR2Application.onLoad, new Action(SyncDisplay));
		MainMenuController.OnMainMenuInitialised += SyncDisplay;
	}

	[SystemInitializer(new Type[]
	{
		typeof(BodyCatalog),
		typeof(SurvivorCatalog)
	})]
	private static IEnumerator InitializeSkins()
	{
		if (!Object.op_Implicit((Object)(object)instance))
		{
			return Empty();
		}
		instance.deadline = Time.realtimeSinceStartup + 60f;
		return StartupGuard.Run(instance.Register, () => Time.realtimeSinceStartup > instance.deadline, delegate(Exception ex)
		{
			instance.Rollback();
			((BaseUnityPlugin)instance).Logger.LogError((object)("FALSESON_NATIVE_SKINS_DISABLED " + ex));
		});
	}

	private static IEnumerator Empty()
	{
		yield break;
	}

	private void Rollback()
	{
		registered = false;
		if (Object.op_Implicit((Object)(object)bodyController) && bodyController.skins == committedBody)
		{
			bodyController.skins = originalBody;
		}
		if (Object.op_Implicit((Object)(object)displayController) && displayController.skins == committedDisplay)
		{
			displayController.skins = originalDisplay;
		}
	}

	private byte[] Read(FileEntry entry)
	{
		if (entry == null || string.IsNullOrWhiteSpace(entry.file) || Path.GetFileName(entry.file) != entry.file || entry.file.Contains("..") || entry.sha256 == null || entry.sha256.Length != 64)
		{
			throw new InvalidDataException("Invalid asset entry");
		}
		string text = Path.Combine(folder, entry.file);
		long length = new FileInfo(text).Length;
		if (length < 1 || length > 67108864)
		{
			throw new InvalidDataException("Asset size out of bounds: " + entry.file);
		}
		byte[] array = File.ReadAllBytes(text);
		using SHA256 sHA = SHA256.Create();
		if (BitConverter.ToString(sHA.ComputeHash(array)).Replace("-", "").ToLowerInvariant() != entry.sha256)
		{
			throw new InvalidDataException("Asset hash mismatch: " + entry.file);
		}
		return array;
	}

	private IEnumerator Load<T>(object key, Action<T> assign) where T : Object
	{
		AsyncOperationHandle<T> handle = Addressables.LoadAssetAsync<T>(key);
		retained.Add(AsyncOperationHandle<T>.op_Implicit(handle));
		while (!handle.IsDone)
		{
			yield return null;
		}
		if ((int)handle.Status != 1 || !Object.op_Implicit((Object)(object)handle.Result))
		{
			throw new InvalidOperationException("Native asset unavailable: " + typeof(T).Name);
		}
		assign(handle.Result);
	}

	private IEnumerator Register()
	{
		Manifest manifest = JsonUtility.FromJson<Manifest>(File.ReadAllText(Path.Combine(folder, "false-son-native-assets.json")));
		if (manifest == null || manifest.schema != 1 || manifest.skins == null || manifest.skins.Length < 1 || manifest.skins.Length > 7)
		{
			throw new InvalidDataException("Invalid False Son native manifest");
		}
		if (manifest.skins.Any((Entry e) => e == null || string.IsNullOrWhiteSpace(e.skinId) || !e.skinId.StartsWith("RodaniusFalseSonE", StringComparison.Ordinal) || string.IsNullOrWhiteSpace(e.name) || string.IsNullOrWhiteSpace(e.revision)) || manifest.skins.Select((Entry e) => e.skinId).Distinct().Count() != manifest.skins.Length)
		{
			throw new InvalidDataException("Invalid or duplicate skin identity");
		}
		GameObject body = BodyCatalog.FindBodyPrefab("FalseSonBody");
		if (!Object.op_Implicit((Object)(object)body) || !Object.op_Implicit((Object)(object)body.GetComponent<ModelLocator>()))
		{
			throw new InvalidOperationException("False Son body unavailable");
		}
		GameObject model = ((Component)body.GetComponent<ModelLocator>().modelTransform).gameObject;
		bodyController = model.GetComponent<ModelSkinController>();
		if (!Object.op_Implicit((Object)(object)bodyController) || bodyController.skins == null || bodyController.skins.Length == 0)
		{
			throw new InvalidOperationException("Native skin list unavailable");
		}
		originalBody = bodyController.skins;
		if (originalBody.Any((SkinDef s) => Object.op_Implicit((Object)(object)s) && manifest.skins.Any((Entry e) => e.skinId == ((Object)s).name)))
		{
			throw new InvalidOperationException("Duplicate native pack registration");
		}
		SkinDef baseSkin = originalBody[0];
		SkinDefParams native = baseSkin.skinDefParams;
		if (!Object.op_Implicit((Object)(object)native) && baseSkin.skinDefParamsAddress != null && ((AssetReference)baseSkin.skinDefParamsAddress).RuntimeKeyIsValid())
		{
			yield return Load<SkinDefParams>(((AssetReference)baseSkin.skinDefParamsAddress).RuntimeKey, (Action<SkinDefParams>)delegate(SkinDefParams v)
			{
				native = v;
			});
		}
		if (!Object.op_Implicit((Object)(object)native))
		{
			throw new InvalidOperationException("Native parameters unavailable");
		}
		SkinnedMeshRenderer[] skinRenderers = model.GetSkinRenderers<SkinnedMeshRenderer>(inactive: true, native);
		SkinnedMeshRenderer[] targets = (SkinnedMeshRenderer[])(object)new SkinnedMeshRenderer[2]
		{
			skinRenderers.Single((SkinnedMeshRenderer r) => ((Object)r).name == "FalseSon_Body"),
			skinRenderers.Single((SkinnedMeshRenderer r) => ((Object)r).name == "L_FalseSon_Weapon")
		};
		SkinnedMeshRenderer cloth = skinRenderers.Single((SkinnedMeshRenderer r) => ((Object)r).name == "FalseSon_Cloth");
		Mesh[] references = (Mesh[])(object)new Mesh[2];
		Material[] materials = (Material[])(object)new Material[2];
		RendererInfo[] infos = (RendererInfo[])(object)new RendererInfo[2];
		for (int i = 0; i < 2; i++)
		{
			int slot = i;
			SkinnedMeshRenderer target = targets[i];
			MeshReplacement val = ((IEnumerable<MeshReplacement>)native.meshReplacements).FirstOrDefault((Func<MeshReplacement, bool>)((MeshReplacement r) => (Object)(object)r.renderer == (Object)(object)target));
			references[i] = val.mesh;
			if (!Object.op_Implicit((Object)(object)references[i]) && val.meshAddress != null && ((AssetReference)val.meshAddress).RuntimeKeyIsValid())
			{
				yield return Load<Mesh>(((AssetReference)val.meshAddress).RuntimeKey, (Action<Mesh>)delegate(Mesh v)
				{
					references[slot] = v;
				});
			}
			if (!Object.op_Implicit((Object)(object)references[i]))
			{
				references[i] = target.sharedMesh;
			}
			infos[i] = native.rendererInfos.First((RendererInfo r) => (Object)(object)r.renderer == (Object)(object)target);
			materials[i] = infos[i].defaultMaterial;
			if (!Object.op_Implicit((Object)(object)materials[i]) && infos[i].defaultMaterialAddress != null && ((AssetReference)infos[i].defaultMaterialAddress).RuntimeKeyIsValid())
			{
				yield return Load<Material>(((AssetReference)infos[i].defaultMaterialAddress).RuntimeKey, (Action<Material>)delegate(Material v)
				{
					materials[slot] = v;
				});
			}
			if (!Object.op_Implicit((Object)(object)materials[i]))
			{
				materials[i] = ((Renderer)target).sharedMaterial;
			}
			if (!Object.op_Implicit((Object)(object)references[i]) || !Object.op_Implicit((Object)(object)materials[i]) || !Object.op_Implicit((Object)(object)materials[i].shader) || target.bones.Length != 70 || references[i].bindposes.Length != 70)
			{
				throw new InvalidOperationException("Native renderer contract differs: " + ((Object)target).name);
			}
		}
		SurvivorDef val2 = SurvivorCatalog.FindSurvivorDefFromBody(body);
		if (!Object.op_Implicit((Object)(object)val2) || !Object.op_Implicit((Object)(object)val2.displayPrefab))
		{
			throw new InvalidOperationException("False Son display unavailable");
		}
		CharacterModel componentInChildren = val2.displayPrefab.GetComponentInChildren<CharacterModel>(true);
		if (!Object.op_Implicit((Object)(object)componentInChildren))
		{
			throw new InvalidOperationException("False Son display model unavailable");
		}
		SkinnedMeshRenderer[] array = targets;
		foreach (SkinnedMeshRenderer target2 in array)
		{
			SkinnedMeshRenderer val3 = ((Component)(object)componentInChildren).GetSkinRenderers<SkinnedMeshRenderer>(inactive: true, (SkinDefParams)null).Single((SkinnedMeshRenderer r) => ((Object)r).name == ((Object)target2).name);
			if (val3.bones.Length != 70 || !val3.bones.Select((Transform b) => (!Object.op_Implicit((Object)(object)b)) ? "" : ((Object)b).name).SequenceEqual(target2.bones.Select((Transform b) => (!Object.op_Implicit((Object)(object)b)) ? "" : ((Object)b).name)))
			{
				throw new InvalidOperationException("Display bone order differs: " + ((Object)target2).name);
			}
		}
		displayController = ((Component)componentInChildren).GetComponent<ModelSkinController>() ?? ((Component)componentInChildren).gameObject.AddComponent<ModelSkinController>();
		originalDisplay = displayController.skins;
		Entry[] skins = manifest.skins;
		foreach (Entry entry in skins)
		{
			Surface[] array2 = new Surface[2] { entry.body, entry.maul };
			RendererInfo[] array3 = (RendererInfo[])(object)new RendererInfo[2];
			List<MeshReplacement> list = new List<MeshReplacement>();
			for (int num2 = 0; num2 < 2; num2++)
			{
				if (array2[num2] == null)
				{
					throw new InvalidDataException("Missing body or separate maul");
				}
				Mesh val4 = ReadMesh(Read(array2[num2].mesh), targets[num2], references[num2]);
				((Object)val4).name = entry.skinId + ((num2 == 0) ? "_Body" : "_Maul");
				Material val5 = new Material(materials[num2])
				{
					name = ((Object)val4).name + "_Material"
				};
				val5.SetTexture("_MainTex", (Texture)(object)Texture(Read(array2[num2].albedo), linear: false));
				val5.SetTexture("_NormalTex", (Texture)(object)Texture(Read(array2[num2].normal), linear: true));
				val5.SetColor("_Color", Color.white);
				val5.SetFloat("_NormalStrength", 0.45f);
				val5.SetFloat("_Smoothness", 0.35f);
				val5.SetFloat("_SpecularStrength", 0.3f);
				val5.SetFloat("_SpecularExponent", 8f);
				val5.SetTexture("_EmTex", (Texture)(object)Texture2D.blackTexture);
				val5.SetFloat("_EmPower", 0f);
				string[] array4 = new string[3] { "FLOWMAP", "FORCE_SPEC", "LIMBREMOVAL" };
				foreach (string text in array4)
				{
					val5.DisableKeyword(text);
				}
				val5.SetFloat("_FlowmapOn", 0f);
				val5.SetFloat("_FlowEmissionStrength", 0f);
				val5.SetFloat("_ForceSpecOn", 0f);
				val5.SetFloat("_LimbRemovalOn", 0f);
				RendererInfo val6 = infos[num2];
				val6.defaultMaterial = val5;
				val6.defaultMaterialAddress = new AssetReferenceT<Material>("");
				array3[num2] = val6;
				list.Add(new MeshReplacement
				{
					renderer = (Renderer)(object)targets[num2],
					mesh = val4,
					meshAddress = new AssetReferenceT<Mesh>("")
				});
			}
			Mesh val7 = new Mesh
			{
				name = entry.skinId + "_ReplacedCloth"
			};
			val7.vertices = Array.Empty<Vector3>();
			val7.triangles = Array.Empty<int>();
			list.Add(new MeshReplacement
			{
				renderer = (Renderer)(object)cloth,
				mesh = val7,
				meshAddress = new AssetReferenceT<Mesh>("")
			});
			SkinDefParams val8 = ScriptableObject.CreateInstance<SkinDefParams>();
			val8.rendererInfos = array3;
			val8.meshReplacements = list.ToArray();
			SkinDef skin = ScriptableObject.CreateInstance<SkinDef>();
			((Object)skin).name = entry.skinId;
			skin.nameToken = entry.skinId.ToUpperInvariant();
			LanguageAPI.Add(skin.nameToken, entry.name + " (Playtest)");
			skin.rootObject = model;
			skin.baseSkins = (SkinDef[])(object)new SkinDef[1] { baseSkin };
			skin.skinDefParams = val8;
			skin.skinDefParamsAddress = new AssetReferenceT<SkinDefParams>("");
			skin.unlockableDef = null;
			Texture2D val9 = Texture(Read(entry.icon), linear: false);
			skin.icon = Sprite.Create(val9, new Rect(0f, 0f, (float)((Texture)val9).width, (float)((Texture)val9).height), new Vector2(0.5f, 0.5f));
			if (val8.gameObjectActivations == null || val8.lightReplacements == null || val8.projectileGhostReplacements == null || val8.minionSkinReplacements == null)
			{
				throw new InvalidOperationException("Native SkinDefParams collection defaults changed");
			}
			yield return skin.BakeAsync();
			if (skin.runtimeSkin == null)
			{
				throw new InvalidOperationException("Private bake failed: " + entry.skinId);
			}
			ownedSkins.Add(skin);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("FALSESON_NATIVE_SKIN_PREPARED id=" + entry.skinId + " revision=" + entry.revision + " separateMaul=true"));
		}
		if (bodyController.skins != originalBody)
		{
			throw new InvalidOperationException("Skin list changed during native-pack startup");
		}
		committedBody = originalBody.Concat(ownedSkins).ToArray();
		committedDisplay = (SkinDef[])committedBody.Clone();
		bodyController.skins = committedBody;
		displayController.skins = committedDisplay;
		registered = true;
		((BaseUnityPlugin)this).Logger.LogInfo((object)("FALSESON_NATIVE_SKINS_REGISTERED count=" + ownedSkins.Count + " nativeAnimatorRetained=true"));
	}

	private static Texture2D Texture(byte[] data, bool linear)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Expected O, but got Unknown
		//IL_002e: Expected O, but got Unknown
		Texture2D val = new Texture2D(2, 2, (TextureFormat)4, true, linear)
		{
			filterMode = (FilterMode)2,
			anisoLevel = 4
		};
		if (!ImageConversion.LoadImage(val, data, false))
		{
			throw new InvalidDataException("Texture decode failed");
		}
		return val;
	}

	private static bool Finite(float v)
	{
		if (!float.IsNaN(v))
		{
			return !float.IsInfinity(v);
		}
		return false;
	}

	private static float Float(BinaryReader r)
	{
		float num = r.ReadSingle();
		if (!Finite(num))
		{
			throw new InvalidDataException("Non-finite mesh value");
		}
		return num;
	}

	private static Mesh ReadMesh(byte[] bytes, SkinnedMeshRenderer renderer, Mesh reference)
	{
		//IL_013e: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0207: Unknown result type (might be due to invalid IL or missing references)
		//IL_020c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0227: Unknown result type (might be due to invalid IL or missing references)
		//IL_022c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0241: Unknown result type (might be due to invalid IL or missing references)
		//IL_0246: Unknown result type (might be due to invalid IL or missing references)
		//IL_03c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_03c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_034c: Unknown result type (might be due to invalid IL or missing references)
		//IL_034e: Unknown result type (might be due to invalid IL or missing references)
		//IL_03d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_03e1: Unknown result type (might be due to invalid IL or missing references)
		//IL_03e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_03f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_03f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0401: Unknown result type (might be due to invalid IL or missing references)
		//IL_0409: Unknown result type (might be due to invalid IL or missing references)
		//IL_040f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0417: Expected O, but got Unknown
		using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes, writable: false));
		if (Encoding.ASCII.GetString(binaryReader.ReadBytes(4)) != "PWE2")
		{
			throw new InvalidDataException("Mesh signature");
		}
		int num = binaryReader.ReadInt32();
		int num2 = binaryReader.ReadInt32();
		int num3 = binaryReader.ReadInt32();
		if (num < 3 || num > 100000 || num2 < 3 || num2 > 600000 || num2 % 3 != 0 || num3 != 70 || renderer.bones.Length != num3)
		{
			throw new InvalidDataException("Mesh dimensions");
		}
		int[] array = new int[num3];
		HashSet<int> hashSet = new HashSet<int>();
		for (int i = 0; i < num3; i++)
		{
			int num4 = binaryReader.ReadInt32();
			if (num4 < 1 || num4 > 128)
			{
				throw new InvalidDataException("Bone-name size");
			}
			string name = Encoding.UTF8.GetString(binaryReader.ReadBytes(num4));
			int num5 = Array.FindIndex(renderer.bones, (Transform b) => Object.op_Implicit((Object)(object)b) && ((Object)b).name == name);
			if (num5 < 0 || !hashSet.Add(num5))
			{
				throw new InvalidDataException("Bone-name mapping");
			}
			array[i] = num5;
		}
		Matrix4x4[] array2 = (Matrix4x4[])(object)new Matrix4x4[num3];
		Matrix4x4[] bindposes = reference.bindposes;
		for (int num6 = 0; num6 < num3; num6++)
		{
			Matrix4x4 val = default(Matrix4x4);
			for (int num7 = 0; num7 < 4; num7++)
			{
				for (int num8 = 0; num8 < 4; num8++)
				{
					((Matrix4x4)(ref val))[num7, num8] = Float(binaryReader);
					if (Math.Abs(((Matrix4x4)(ref val))[num7, num8] - ((Matrix4x4)(ref bindposes[array[num6]]))[num7, num8]) > 0.001f)
					{
						throw new InvalidDataException("Native inverse-bind mismatch");
					}
				}
			}
			array2[array[num6]] = val;
		}
		Vector3[] array3 = (Vector3[])(object)new Vector3[num];
		Vector3[] array4 = (Vector3[])(object)new Vector3[num];
		Vector2[] array5 = (Vector2[])(object)new Vector2[num];
		BoneWeight[] array6 = (BoneWeight[])(object)new BoneWeight[num];
		for (int num9 = 0; num9 < num; num9++)
		{
			array3[num9] = new Vector3(Float(binaryReader), Float(binaryReader), Float(binaryReader));
			array4[num9] = new Vector3(Float(binaryReader), Float(binaryReader), Float(binaryReader));
			array5[num9] = new Vector2(Float(binaryReader), Float(binaryReader));
			int[] array7 = new int[4];
			for (int num10 = 0; num10 < 4; num10++)
			{
				int num11 = binaryReader.ReadInt32();
				if (num11 < 0 || num11 >= num3)
				{
					throw new InvalidDataException("Joint index");
				}
				array7[num10] = array[num11];
			}
			float[] array8 = new float[4];
			for (int num12 = 0; num12 < 4; num12++)
			{
				array8[num12] = Float(binaryReader);
				if (array8[num12] < 0f)
				{
					throw new InvalidDataException("Negative weight");
				}
			}
			if (Math.Abs(array8.Sum() - 1f) > 0.0001f)
			{
				throw new InvalidDataException("Weight sum");
			}
			int num13 = num9;
			BoneWeight val2 = default(BoneWeight);
			((BoneWeight)(ref val2)).boneIndex0 = array7[0];
			((BoneWeight)(ref val2)).boneIndex1 = array7[1];
			((BoneWeight)(ref val2)).boneIndex2 = array7[2];
			((BoneWeight)(ref val2)).boneIndex3 = array7[3];
			((BoneWeight)(ref val2)).weight0 = array8[0];
			((BoneWeight)(ref val2)).weight1 = array8[1];
			((BoneWeight)(ref val2)).weight2 = array8[2];
			((BoneWeight)(ref val2)).weight3 = array8[3];
			array6[num13] = val2;
		}
		int[] array9 = new int[num2];
		for (int num14 = 0; num14 < num2; num14++)
		{
			array9[num14] = binaryReader.ReadInt32();
			if (array9[num14] < 0 || array9[num14] >= num)
			{
				throw new InvalidDataException("Triangle index");
			}
		}
		if (binaryReader.BaseStream.Position != binaryReader.BaseStream.Length)
		{
			throw new InvalidDataException("Trailing mesh bytes");
		}
		Mesh val3 = new Mesh
		{
			indexFormat = (IndexFormat)(num > 65535),
			vertices = array3,
			normals = array4,
			uv = array5,
			boneWeights = array6,
			bindposes = array2,
			triangles = array9
		};
		val3.RecalculateBounds();
		val3.RecalculateTangents();
		return val3;
	}

	private void SyncDisplay()
	{
		if (registered && Object.op_Implicit((Object)(object)bodyController) && Object.op_Implicit((Object)(object)displayController))
		{
			displayController.skins = (SkinDef[])bodyController.skins.Clone();
		}
	}

	private void OnDestroy()
	{
		RoR2Application.onLoad = (Action)Delegate.Remove(RoR2Application.onLoad, new Action(SyncDisplay));
		MainMenuController.OnMainMenuInitialised -= SyncDisplay;
		if ((Object)(object)instance == (Object)(object)this)
		{
			instance = null;
		}
	}
}
internal static class NativeRenderers
{
	private static readonly HashSet<string> NativePaths = new HashSet<string>(StringComparer.Ordinal)
	{
		"mdlLoader/PilotFemaleMesh", "mdlLoader/LoaderMechMesh", "mdlLoader/PilotFemaleFoldedOverJumpsuit", "mdlFalseSon/FalseSon_Body", "mdlFalseSon/FalseSon_Cloth", "mdlFalseSon/L_FalseSon_Weapon", "mdlSeeker/meshSeekerBody", "mdlSeeker/meshSeekerCloth", "mdlSeeker/meshSeekerGlass", "mdlChef/meshChef",
		"mdlChef/meshChefPizzaCutter", "mdlChef/meshlChefCleaver", "mdlChef/meshChefOven", "mdlChef/meshChefIceBox", "mdlCroco/CrocoMesh", "mdlCroco/CrocoSpineMesh", "mdlHuntress/HuntressMesh", "mdlHuntress/BowMesh", "mdlHuntress/HuntressScarfMesh", "mdlHuntress (1)/HuntressMesh",
		"mdlHuntress (1)/BowMesh", "mdlHuntress (1)/HuntressScarfMesh", "mdlMerc/MercMesh", "mdlMerc/MercSwordMesh", "mdlTreebot/TreebotBotMesh", "mdlTreebot/TreebotRootMesh", "mdlTreebot/TreebotLeafletMesh", "mdlTreebot/TreebotFlowerMesh", "mdlVoidSurvivor/mdlVoidSurvivorBody", "mdlVoidSurvivor/mdlVoidSurvivorHead",
		"mdlVoidSurvivor/mdlVoidSurvivorMetal", "mdlVoidSurvivor/metalcollar.001", "mdlBandit2/BanditArmature/ROOT/base/MainWeapon/BanditShotgunMesh", "mdlBandit2/BanditArmature/SideWeapon/SideWeaponSpinner/BanditPistolMesh", "mdlBandit2/Bandit2AccessoriesMesh"
	};

	public static T[] GetSkinRenderers<T>(this Component owner, bool inactive = false, SkinDefParams parameters = null) where T : Renderer
	{
		return owner.gameObject.GetSkinRenderers<T>(inactive, parameters);
	}

	public static T[] GetSkinRenderers<T>(this GameObject owner, bool inactive = false, SkinDefParams parameters = null) where T : Renderer
	{
		//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_017e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0183: Unknown result type (might be due to invalid IL or missing references)
		//IL_0185: Unknown result type (might be due to invalid IL or missing references)
		//IL_011c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0121: Unknown result type (might be due to invalid IL or missing references)
		//IL_0123: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0199: Unknown result type (might be due to invalid IL or missing references)
		//IL_0137: Unknown result type (might be due to invalid IL or missing references)
		T[] componentsInChildren = owner.GetComponentsInChildren<T>(inactive);
		CharacterModel val = owner.GetComponent<CharacterModel>() ?? owner.GetComponentInParent<CharacterModel>();
		ModelSkinController val2 = owner.GetComponent<ModelSkinController>() ?? (Object.op_Implicit((Object)(object)val) ? ((Component)val).GetComponent<ModelSkinController>() : null);
		HashSet<Renderer> skinRefs = new HashSet<Renderer>();
		SkinDefParams val3 = parameters;
		if (!Object.op_Implicit((Object)(object)val3) && Object.op_Implicit((Object)(object)val2) && val2.skins != null && val2.skins.Length != 0 && Object.op_Implicit((Object)(object)val2.skins[0]))
		{
			val3 = val2.skins[0].skinDefParams;
		}
		if (Object.op_Implicit((Object)(object)val3))
		{
			if (val3.rendererInfos != null)
			{
				RendererInfo[] rendererInfos = val3.rendererInfos;
				foreach (RendererInfo val4 in rendererInfos)
				{
					if (Object.op_Implicit((Object)(object)val4.renderer))
					{
						skinRefs.Add(val4.renderer);
					}
				}
			}
			if (val3.meshReplacements != null)
			{
				MeshReplacement[] meshReplacements = val3.meshReplacements;
				foreach (MeshReplacement val5 in meshReplacements)
				{
					if (Object.op_Implicit((Object)(object)val5.renderer))
					{
						skinRefs.Add(val5.renderer);
					}
				}
			}
		}
		HashSet<Renderer> modelRefs = new HashSet<Renderer>();
		if (Object.op_Implicit((Object)(object)val) && val.baseRendererInfos != null)
		{
			RendererInfo[] rendererInfos = val.baseRendererInfos;
			foreach (RendererInfo val6 in rendererInfos)
			{
				if (Object.op_Implicit((Object)(object)val6.renderer))
				{
					modelRefs.Add(val6.renderer);
				}
			}
		}
		return Select(componentsInChildren, (T x) => ((Object)(object)x).name, (T x) => skinRefs.Contains((Renderer)(object)x), (T x) => modelRefs.Contains((Renderer)(object)x), (T x) => Path(((Component)(object)x).transform, owner.transform), (T x) => IsKnownNativePath(Path(((Component)(object)x).transform, owner.transform)));
	}

	internal static T[] Select<T>(T[] found, Func<T, string> name, Func<T, bool> skin, Func<T, bool> model, Func<T, string> path, Func<T, bool> contract = null)
	{
		HashSet<T> hashSet = new HashSet<T>();
		if (contract == null)
		{
			contract = (T x) => false;
		}
		bool flag = found.Any(skin) || found.Any(model) || found.Any(contract);
		foreach (IGrouping<string, T> item in found.GroupBy(name))
		{
			T[] array = item.ToArray();
			if (array.Length == 1)
			{
				if (!flag || skin(array[0]) || model(array[0]) || contract(array[0]))
				{
					hashSet.Add(array[0]);
				}
				continue;
			}
			T[] array2 = array.Where(skin).ToArray();
			T[] array3 = array.Where(model).ToArray();
			T[] array4 = ((array2.Length == 1) ? ((array3.Length == 0 || array3.Contains(array2[0])) ? array2 : new T[0]) : ((array2.Length <= 1) ? array3 : array2.Where(((IEnumerable<T>)array3).Contains<T>).ToArray()));
			if (array2.Length == 0 && array3.Length == 0)
			{
				T[] array5 = array.Where(contract).ToArray();
				if (array5.Length != 0)
				{
					if (array5.Length != 1)
					{
						throw new InvalidOperationException("ECLIPSE_RENDERER_PATH_AMBIGUOUS " + string.Join(";", array.Select(path)));
					}
					hashSet.Add(array5[0]);
					Debug.Log((object)("ECLIPSE_RENDERER_NATIVE_PATH name=" + item.Key + " native=" + path(array5[0])));
					continue;
				}
			}
			if (array2.Length == 0 && array3.Length == 0 && flag)
			{
				Debug.Log((object)("ECLIPSE_RENDERER_FOREIGN_DUPLICATES name=" + item.Key + " ignored=" + array.Length));
				continue;
			}
			if (array4.Length != 1)
			{
				throw new InvalidOperationException("ECLIPSE_RENDERER_AMBIGUOUS name=" + item.Key + " candidates=" + string.Join(";", array.Select(path)) + " skinRefs=" + array2.Length + " modelRefs=" + array3.Length);
			}
			hashSet.Add(array4[0]);
			Debug.Log((object)("ECLIPSE_RENDERER_RESOLVED name=" + item.Key + " native=" + path(array4[0]) + " ignoredDuplicates=" + (array.Length - 1)));
		}
		return found.Where(hashSet.Contains).ToArray();
	}

	internal static bool IsKnownNativePath(string path)
	{
		if (path == null)
		{
			return false;
		}
		int num = path.IndexOf('/');
		if (num < 0)
		{
			return false;
		}
		string text = path.Substring(0, num);
		if (text.EndsWith("(Clone)", StringComparison.Ordinal))
		{
			text = text.Substring(0, text.Length - 7);
		}
		return NativePaths.Contains(text + path.Substring(num));
	}

	private static string Path(Transform t, Transform root)
	{
		List<string> list = new List<string>();
		Transform val = t;
		while (Object.op_Implicit((Object)(object)val))
		{
			list.Add(((Object)val).name);
			if ((Object)(object)val == (Object)(object)root)
			{
				break;
			}
			val = val.parent;
		}
		list.Reverse();
		return string.Join("/", list);
	}
}
internal static class StartupGuard
{
	internal static IEnumerator Run(Func<IEnumerator> create, Func<bool> expired, Action<Exception> failed)
	{
		Stack<IEnumerator> stack = new Stack<IEnumerator>();
		Exception failure = null;
		try
		{
			stack.Push(create());
		}
		catch (Exception ex)
		{
			failure = ex;
		}
		try
		{
			while (failure == null && stack.Count > 0)
			{
				bool flag = false;
				object obj = null;
				try
				{
					if (expired())
					{
						throw new TimeoutException("False Son native pack startup deadline exceeded.");
					}
					IEnumerator enumerator = stack.Peek();
					flag = enumerator.MoveNext();
					if (flag)
					{
						obj = enumerator.Current;
					}
					else
					{
						stack.Pop();
						(enumerator as IDisposable)?.Dispose();
					}
				}
				catch (Exception ex2)
				{
					failure = ex2;
				}
				if (failure != null)
				{
					break;
				}
				if (flag)
				{
					if (obj is IEnumerator item)
					{
						stack.Push(item);
					}
					else
					{
						yield return obj;
					}
				}
			}
		}
		finally
		{
			if (stack.Count > 0 && failure == null)
			{
				failure = new OperationCanceledException("False Son native pack startup iterator was cancelled.");
			}
			while (stack.Count > 0)
			{
				try
				{
					(stack.Pop() as IDisposable)?.Dispose();
				}
				catch (Exception ex3)
				{
					if (failure == null)
					{
						failure = ex3;
					}
				}
			}
			if (failure != null)
			{
				try
				{
					failed(failure);
				}
				catch
				{
				}
			}
		}
	}
}

BepInEx/plugins/FalseSonPenguin/Eclipse.FalseSon.E3.Penguin.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using BepInEx;
using Eclipse.AdvancedSkins;
using EclipseSkinRuntimeFix;
using EntityStates;
using HG.Reflection;
using R2API;
using R2API.Utils;
using RoR2;
using RoR2.UI.MainMenu;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: OptIn]
[assembly: AssemblyVersion("0.0.0.0")]
namespace EclipseSkinRuntimeFix
{
	internal static class NativeRenderers
	{
		private static readonly HashSet<string> NativePaths = new HashSet<string>(StringComparer.Ordinal)
		{
			"mdlLoader/PilotFemaleMesh", "mdlLoader/LoaderMechMesh", "mdlLoader/PilotFemaleFoldedOverJumpsuit", "mdlFalseSon/FalseSon_Body", "mdlFalseSon/FalseSon_Cloth", "mdlFalseSon/L_FalseSon_Weapon", "mdlSeeker/meshSeekerBody", "mdlSeeker/meshSeekerCloth", "mdlSeeker/meshSeekerGlass", "mdlChef/meshChef",
			"mdlChef/meshChefPizzaCutter", "mdlChef/meshlChefCleaver", "mdlChef/meshChefOven", "mdlChef/meshChefIceBox", "mdlCroco/CrocoMesh", "mdlCroco/CrocoSpineMesh", "mdlHuntress/HuntressMesh", "mdlHuntress/BowMesh", "mdlHuntress/HuntressScarfMesh", "mdlHuntress (1)/HuntressMesh",
			"mdlHuntress (1)/BowMesh", "mdlHuntress (1)/HuntressScarfMesh", "mdlMerc/MercMesh", "mdlMerc/MercSwordMesh", "mdlTreebot/TreebotBotMesh", "mdlTreebot/TreebotRootMesh", "mdlTreebot/TreebotLeafletMesh", "mdlTreebot/TreebotFlowerMesh", "mdlVoidSurvivor/mdlVoidSurvivorBody", "mdlVoidSurvivor/mdlVoidSurvivorHead",
			"mdlVoidSurvivor/mdlVoidSurvivorMetal", "mdlVoidSurvivor/metalcollar.001", "mdlBandit2/BanditArmature/ROOT/base/MainWeapon/BanditShotgunMesh", "mdlBandit2/BanditArmature/SideWeapon/SideWeaponSpinner/BanditPistolMesh", "mdlBandit2/Bandit2AccessoriesMesh"
		};

		public static T[] GetSkinRenderers<T>(this Component owner, bool inactive = false, SkinDefParams parameters = null) where T : Renderer
		{
			return owner.gameObject.GetSkinRenderers<T>(inactive, parameters);
		}

		public static T[] GetSkinRenderers<T>(this GameObject owner, bool inactive = false, SkinDefParams parameters = null) where T : Renderer
		{
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			T[] componentsInChildren = owner.GetComponentsInChildren<T>(inactive);
			CharacterModel val = owner.GetComponent<CharacterModel>() ?? owner.GetComponentInParent<CharacterModel>();
			ModelSkinController val2 = owner.GetComponent<ModelSkinController>() ?? (Object.op_Implicit((Object)(object)val) ? ((Component)val).GetComponent<ModelSkinController>() : null);
			HashSet<Renderer> skinRefs = new HashSet<Renderer>();
			SkinDefParams val3 = parameters;
			if (!Object.op_Implicit((Object)(object)val3) && Object.op_Implicit((Object)(object)val2) && val2.skins != null && val2.skins.Length != 0 && Object.op_Implicit((Object)(object)val2.skins[0]))
			{
				val3 = val2.skins[0].skinDefParams;
			}
			if (Object.op_Implicit((Object)(object)val3))
			{
				if (val3.rendererInfos != null)
				{
					RendererInfo[] rendererInfos = val3.rendererInfos;
					foreach (RendererInfo val4 in rendererInfos)
					{
						if (Object.op_Implicit((Object)(object)val4.renderer))
						{
							skinRefs.Add(val4.renderer);
						}
					}
				}
				if (val3.meshReplacements != null)
				{
					MeshReplacement[] meshReplacements = val3.meshReplacements;
					foreach (MeshReplacement val5 in meshReplacements)
					{
						if (Object.op_Implicit((Object)(object)val5.renderer))
						{
							skinRefs.Add(val5.renderer);
						}
					}
				}
			}
			HashSet<Renderer> modelRefs = new HashSet<Renderer>();
			if (Object.op_Implicit((Object)(object)val) && val.baseRendererInfos != null)
			{
				RendererInfo[] rendererInfos = val.baseRendererInfos;
				foreach (RendererInfo val6 in rendererInfos)
				{
					if (Object.op_Implicit((Object)(object)val6.renderer))
					{
						modelRefs.Add(val6.renderer);
					}
				}
			}
			return Select(componentsInChildren, (T x) => ((Object)(object)x).name, (T x) => skinRefs.Contains((Renderer)(object)x), (T x) => modelRefs.Contains((Renderer)(object)x), (T x) => Path(((Component)(object)x).transform, owner.transform), (T x) => IsKnownNativePath(Path(((Component)(object)x).transform, owner.transform)));
		}

		internal static T[] Select<T>(T[] found, Func<T, string> name, Func<T, bool> skin, Func<T, bool> model, Func<T, string> path, Func<T, bool> contract = null)
		{
			HashSet<T> hashSet = new HashSet<T>();
			if (contract == null)
			{
				contract = (T x) => false;
			}
			bool flag = found.Any(skin) || found.Any(model) || found.Any(contract);
			foreach (IGrouping<string, T> item in found.GroupBy(name))
			{
				T[] array = item.ToArray();
				if (array.Length == 1)
				{
					if (!flag || skin(array[0]) || model(array[0]) || contract(array[0]))
					{
						hashSet.Add(array[0]);
					}
					continue;
				}
				T[] array2 = array.Where(skin).ToArray();
				T[] array3 = array.Where(model).ToArray();
				T[] array4 = ((array2.Length == 1) ? ((array3.Length == 0 || array3.Contains(array2[0])) ? array2 : new T[0]) : ((array2.Length <= 1) ? array3 : array2.Where(((IEnumerable<T>)array3).Contains<T>).ToArray()));
				if (array2.Length == 0 && array3.Length == 0)
				{
					T[] array5 = array.Where(contract).ToArray();
					if (array5.Length != 0)
					{
						if (array5.Length != 1)
						{
							throw new InvalidOperationException("ECLIPSE_RENDERER_PATH_AMBIGUOUS " + string.Join(";", array.Select(path)));
						}
						hashSet.Add(array5[0]);
						Debug.Log((object)("ECLIPSE_RENDERER_NATIVE_PATH name=" + item.Key + " native=" + path(array5[0])));
						continue;
					}
				}
				if (array2.Length == 0 && array3.Length == 0 && flag)
				{
					Debug.Log((object)("ECLIPSE_RENDERER_FOREIGN_DUPLICATES name=" + item.Key + " ignored=" + array.Length));
					continue;
				}
				if (array4.Length != 1)
				{
					throw new InvalidOperationException("ECLIPSE_RENDERER_AMBIGUOUS name=" + item.Key + " candidates=" + string.Join(";", array.Select(path)) + " skinRefs=" + array2.Length + " modelRefs=" + array3.Length);
				}
				hashSet.Add(array4[0]);
				Debug.Log((object)("ECLIPSE_RENDERER_RESOLVED name=" + item.Key + " native=" + path(array4[0]) + " ignoredDuplicates=" + (array.Length - 1)));
			}
			return found.Where(hashSet.Contains).ToArray();
		}

		internal static bool IsKnownNativePath(string path)
		{
			if (path == null)
			{
				return false;
			}
			int num = path.IndexOf('/');
			if (num < 0)
			{
				return false;
			}
			string text = path.Substring(0, num);
			if (text.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				text = text.Substring(0, text.Length - 7);
			}
			return NativePaths.Contains(text + path.Substring(num));
		}

		private static string Path(Transform t, Transform root)
		{
			List<string> list = new List<string>();
			Transform val = t;
			while (Object.op_Implicit((Object)(object)val))
			{
				list.Add(((Object)val).name);
				if ((Object)(object)val == (Object)(object)root)
				{
					break;
				}
				val = val.parent;
			}
			list.Reverse();
			return string.Join("/", list);
		}
	}
}
namespace EclipsePlaytestFalseSonE3
{
	[BepInPlugin("eclipseplaytest.false-son.e3", "False Son - Penguin (Playtest)", "0.1.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Penguin : BaseUnityPlugin
	{
		[Serializable]
		public sealed class AssetFile
		{
			public string file;

			public string sha256;
		}

		[Serializable]
		public sealed class AssetManifest
		{
			public int schema;

			public string skinId;

			public string revision;

			public AssetFile mesh;

			public AssetFile motion;

			public AssetFile albedo;

			public AssetFile attachments;

			public AssetFile icon;

			public string[] nativeBoneNames;
		}

		[Serializable]
		public sealed class AttachmentsFile
		{
			public int schema;

			public string skinId;

			public SkinItemAttachmentRule[] rules;
		}

		public const string Guid = "eclipseplaytest.false-son.e3";

		public const string Version = "0.1.2";

		public const string SkinId = "EclipsePlaytestFalseSonE3Penguin";

		private static Penguin instance;

		private string folder;

		private float deadline;

		private bool registered;

		private GameObject modelPrefab;

		private GameObject displayModelPrefab;

		private Mesh nativeMesh;

		private Material nativeMaterial;

		private AssetManifest manifest;

		private IDisposable registration;

		private readonly List<AsyncOperationHandle> retainedAssets = new List<AsyncOperationHandle>();

		private void Awake()
		{
			instance = this;
			folder = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
			SystemInitializerInjector.InjectDependency(typeof(SkinCatalog), typeof(Penguin));
			RoR2Application.onLoad = (Action)Delegate.Combine(RoR2Application.onLoad, new Action(SyncDisplaySkins));
			MainMenuController.OnMainMenuInitialised += SyncDisplaySkins;
		}

		[SystemInitializer(new Type[]
		{
			typeof(BodyCatalog),
			typeof(SurvivorCatalog)
		})]
		private static IEnumerator InitializeSkin()
		{
			if (!Object.op_Implicit((Object)(object)instance))
			{
				yield break;
			}
			instance.deadline = Time.realtimeSinceStartup + 45f;
			IEnumerator routine = instance.Register();
			try
			{
				while (true)
				{
					bool flag;
					try
					{
						flag = routine.MoveNext();
					}
					catch (Exception ex)
					{
						((BaseUnityPlugin)instance).Logger.LogError((object)("FALSESON_E3_REGISTRATION_FAILED " + ex));
						break;
					}
					if (flag)
					{
						yield return routine.Current;
						continue;
					}
					break;
				}
			}
			finally
			{
				IDisposable disposable = routine as IDisposable;
				if (disposable != null)
				{
					disposable.Dispose();
				}
			}
		}

		private void CheckDeadline()
		{
			if (Time.realtimeSinceStartup > deadline)
			{
				throw new TimeoutException("Penguin asset loading timed out; other startup work continues.");
			}
		}

		private byte[] ReadAsset(AssetFile entry)
		{
			if (entry == null || string.IsNullOrWhiteSpace(entry.file) || Path.GetFileName(entry.file) != entry.file || entry.file.Contains("..") || entry.sha256 == null || entry.sha256.Length != 64)
			{
				throw new InvalidDataException("Invalid penguin asset manifest entry");
			}
			string text = Path.Combine(folder, entry.file);
			if (new FileInfo(text).Length > 67108864)
			{
				throw new InvalidDataException("Penguin asset too large");
			}
			byte[] array = File.ReadAllBytes(text);
			using SHA256 sHA = SHA256.Create();
			if (BitConverter.ToString(sHA.ComputeHash(array)).Replace("-", "").ToLowerInvariant() != entry.sha256)
			{
				throw new InvalidDataException("Penguin asset hash mismatch: " + entry.file);
			}
			return array;
		}

		private void PrepareTemplate(GameObject model)
		{
			AdvancedSkinModel component = model.GetComponent<AdvancedSkinModel>();
			if (Object.op_Implicit((Object)(object)component) && component.HasNativeSnapshot)
			{
				component.Prepare();
				return;
			}
			SkinnedMeshRenderer val = model.GetSkinRenderers<SkinnedMeshRenderer>(inactive: true, (SkinDefParams)null).Single((SkinnedMeshRenderer r) => ((Object)r).name == "FalseSon_Body");
			Animator component2 = model.GetComponent<Animator>();
			if (!Object.op_Implicit((Object)(object)component2) || val.bones.Length != manifest.nativeBoneNames.Length)
			{
				throw new InvalidOperationException("Penguin requires the original FalseSon skeleton");
			}
			for (int num = 0; num < val.bones.Length; num++)
			{
				if (!Object.op_Implicit((Object)(object)val.bones[num]) || ((Object)val.bones[num]).name != manifest.nativeBoneNames[num])
				{
					throw new InvalidOperationException("Native bone slot mismatch " + num);
				}
			}
			AdvancedSkinModel val2 = model.GetComponent<AdvancedSkinModel>();
			if (!Object.op_Implicit((Object)(object)val2))
			{
				val2 = model.AddComponent<AdvancedSkinModel>();
			}
			val2.CaptureNativeTemplate(val, component2, nativeMesh, (Material[])(object)new Material[1] { nativeMaterial });
			val2.Prepare();
		}

		private IEnumerator Register()
		{
			manifest = JsonUtility.FromJson<AssetManifest>(File.ReadAllText(Path.Combine(folder, "penguin-assets.json")));
			if (manifest == null || manifest.schema != 1 || manifest.skinId != "EclipsePlaytestFalseSonE3Penguin" || string.IsNullOrWhiteSpace(manifest.revision) || manifest.nativeBoneNames == null || manifest.nativeBoneNames.Length != 70)
			{
				throw new InvalidDataException("Invalid penguin asset identity");
			}
			MotionBank bank;
			using (MemoryStream memoryStream = new MemoryStream(ReadAsset(manifest.motion), writable: false))
			{
				bank = MotionBank.Read((Stream)memoryStream);
			}
			if (bank.SkinId != "EclipsePlaytestFalseSonE3Penguin")
			{
				throw new InvalidDataException("Motion identity mismatch");
			}
			bank.Find("idle");
			bank.Find("death");
			Mesh mesh;
			using (MemoryStream memoryStream2 = new MemoryStream(ReadAsset(manifest.mesh), writable: false))
			{
				mesh = SkinMesh.Read((Stream)memoryStream2, bank);
			}
			Texture2D albedo = new Texture2D(2, 2, (TextureFormat)4, true, false)
			{
				name = "PenguinAlbedo",
				filterMode = (FilterMode)2,
				anisoLevel = 4
			};
			if (!ImageConversion.LoadImage(albedo, ReadAsset(manifest.albedo), false))
			{
				throw new InvalidDataException("Invalid penguin albedo");
			}
			Texture2D icon = new Texture2D(2, 2, (TextureFormat)4, false, false);
			if (!ImageConversion.LoadImage(icon, ReadAsset(manifest.icon), false))
			{
				throw new InvalidDataException("Invalid penguin icon");
			}
			GameObject body = BodyCatalog.FindBodyPrefab("FalseSonBody");
			if (!Object.op_Implicit((Object)(object)body) || !Object.op_Implicit((Object)(object)body.GetComponent<ModelLocator>()))
			{
				throw new InvalidOperationException("FalseSon body unavailable");
			}
			modelPrefab = ((Component)body.GetComponent<ModelLocator>().modelTransform).gameObject;
			ModelSkinController controller = modelPrefab.GetComponent<ModelSkinController>();
			if (!Object.op_Implicit((Object)(object)controller) || controller.skins == null || controller.skins.Length == 0)
			{
				throw new InvalidOperationException("Native FalseSon skins unavailable");
			}
			if (controller.skins.Any((SkinDef s) => Object.op_Implicit((Object)(object)s) && ((Object)s).name == "EclipsePlaytestFalseSonE3Penguin"))
			{
				yield break;
			}
			SkinDef baseSkin = controller.skins[0];
			SkinDefParams parameters = baseSkin.skinDefParams;
			if (!Object.op_Implicit((Object)(object)parameters) && baseSkin.skinDefParamsAddress != null && ((AssetReference)baseSkin.skinDefParamsAddress).RuntimeKeyIsValid())
			{
				AsyncOperationHandle<SkinDefParams> op = Addressables.LoadAssetAsync<SkinDefParams>(((AssetReference)baseSkin.skinDefParamsAddress).RuntimeKey);
				retainedAssets.Add(AsyncOperationHandle<SkinDefParams>.op_Implicit(op));
				while (!op.IsDone)
				{
					CheckDeadline();
					yield return null;
				}
				parameters = op.Result;
			}
			if (!Object.op_Implicit((Object)(object)parameters))
			{
				throw new InvalidOperationException("Native skin parameters unavailable");
			}
			SkinnedMeshRenderer[] renderers = modelPrefab.GetSkinRenderers<SkinnedMeshRenderer>(inactive: true, parameters);
			SkinnedMeshRenderer target = renderers.Single((SkinnedMeshRenderer r) => ((Object)r).name == "FalseSon_Body");
			MeshReplacement val = ((IEnumerable<MeshReplacement>)parameters.meshReplacements).FirstOrDefault((Func<MeshReplacement, bool>)((MeshReplacement r) => (Object)(object)r.renderer == (Object)(object)target));
			nativeMesh = val.mesh;
			if (!Object.op_Implicit((Object)(object)nativeMesh) && val.meshAddress != null && ((AssetReference)val.meshAddress).RuntimeKeyIsValid())
			{
				AsyncOperationHandle<Mesh> op2 = Addressables.LoadAssetAsync<Mesh>(((AssetReference)val.meshAddress).RuntimeKey);
				retainedAssets.Add(AsyncOperationHandle<Mesh>.op_Implicit(op2));
				while (!op2.IsDone)
				{
					CheckDeadline();
					yield return null;
				}
				nativeMesh = op2.Result;
			}
			if (!Object.op_Implicit((Object)(object)nativeMesh))
			{
				nativeMesh = target.sharedMesh;
			}
			if (!Object.op_Implicit((Object)(object)nativeMesh) || nativeMesh.bindposes.Length != 70)
			{
				throw new InvalidOperationException("Native fallback mesh invalid");
			}
			RendererInfo info = parameters.rendererInfos.First((RendererInfo r) => (Object)(object)r.renderer == (Object)(object)target);
			nativeMaterial = info.defaultMaterial;
			if (!Object.op_Implicit((Object)(object)nativeMaterial) && info.defaultMaterialAddress != null && ((AssetReference)info.defaultMaterialAddress).RuntimeKeyIsValid())
			{
				AsyncOperationHandle<Material> op3 = Addressables.LoadAssetAsync<Material>(((AssetReference)info.defaultMaterialAddress).RuntimeKey);
				retainedAssets.Add(AsyncOperationHandle<Material>.op_Implicit(op3));
				while (!op3.IsDone)
				{
					CheckDeadline();
					yield return null;
				}
				nativeMaterial = op3.Result;
			}
			if (!Object.op_Implicit((Object)(object)nativeMaterial) || !Object.op_Implicit((Object)(object)nativeMaterial.shader))
			{
				throw new InvalidOperationException("Native fallback material invalid");
			}
			string[] companionNames = new string[2] { "FalseSon_Cloth", "L_FalseSon_Weapon" };
			Mesh[] companionMeshes = (Mesh[])(object)new Mesh[companionNames.Length];
			int i;
			for (i = 0; i < companionNames.Length; i++)
			{
				SkinnedMeshRenderer companion = renderers.Single((SkinnedMeshRenderer r) => ((Object)r).name == companionNames[i]);
				MeshReplacement val2 = ((IEnumerable<MeshReplacement>)parameters.meshReplacements).FirstOrDefault((Func<MeshReplacement, bool>)((MeshReplacement r) => (Object)(object)r.renderer == (Object)(object)companion));
				Mesh val3 = val2.mesh;
				if (!Object.op_Implicit((Object)(object)val3) && val2.meshAddress != null && ((AssetReference)val2.meshAddress).RuntimeKeyIsValid())
				{
					AsyncOperationHandle<Mesh> op2 = Addressables.LoadAssetAsync<Mesh>(((AssetReference)val2.meshAddress).RuntimeKey);
					retainedAssets.Add(AsyncOperationHandle<Mesh>.op_Implicit(op2));
					while (!op2.IsDone)
					{
						CheckDeadline();
						yield return null;
					}
					val3 = op2.Result;
				}
				if (!Object.op_Implicit((Object)(object)val3))
				{
					val3 = companion.sharedMesh;
				}
				if (!Object.op_Implicit((Object)(object)val3))
				{
					throw new InvalidOperationException("Native companion fallback unavailable: " + companionNames[i]);
				}
				companionMeshes[i] = val3;
			}
			SurvivorDef val4 = SurvivorCatalog.FindSurvivorDefFromBody(body);
			if (!Object.op_Implicit((Object)(object)val4) || !Object.op_Implicit((Object)(object)val4.displayPrefab))
			{
				throw new InvalidOperationException("FalseSon display unavailable");
			}
			CharacterModel componentInChildren = val4.displayPrefab.GetComponentInChildren<CharacterModel>(true);
			if (!Object.op_Implicit((Object)(object)componentInChildren))
			{
				throw new InvalidOperationException("FalseSon display model unavailable");
			}
			displayModelPrefab = ((Component)componentInChildren).gameObject;
			PrepareTemplate(modelPrefab);
			PrepareTemplate(displayModelPrefab);
			ModelSkinController val5 = ((Component)componentInChildren).GetComponent<ModelSkinController>() ?? ((Component)componentInChildren).gameObject.AddComponent<ModelSkinController>();
			Material val6 = new Material(nativeMaterial)
			{
				name = "matEclipseFalseSonE3Penguin"
			};
			val6.SetTexture("_MainTex", (Texture)(object)albedo);
			val6.SetColor("_Color", Color.white);
			val6.SetFloat("_NormalStrength", 0f);
			val6.SetFloat("_Smoothness", 0.16f);
			val6.SetFloat("_SpecularStrength", 0.15f);
			val6.SetFloat("_SpecularExponent", 8f);
			val6.SetFloat("_EmPower", 0f);
			val6.SetTexture("_EmTex", (Texture)(object)Texture2D.blackTexture);
			val6.DisableKeyword("FLOWMAP");
			val6.SetFloat("_FlowmapOn", 0f);
			val6.SetFloat("_FlowEmissionStrength", 0f);
			val6.DisableKeyword("FORCE_SPEC");
			val6.SetFloat("_ForceSpecOn", 0f);
			val6.DisableKeyword("LIMBREMOVAL");
			val6.SetFloat("_LimbRemovalOn", 0f);
			AdvancedSkinDefinition val7 = new AdvancedSkinDefinition
			{
				Mesh = mesh,
				Motion = bank,
				FallbackClip = "idle",
				AirTransitionSeconds = 0.12f,
				CreateMotionSource = (Animator animator, MotionBank motion) => (ISkinMotionSource)(object)new PenguinStateObserver(animator, motion),
				CreateMotionTiming = (SkinnedMeshRenderer renderer, MotionBank motion) => (ISkinMotionTiming)(object)new PenguinGaitTiming(renderer)
			};
			val7.NativeLayerPreference.Add("locomotion", new string[3] { "Body", "Base Layer", "Idle, Additive" });
			val7.NativeLayerPreference.Add("primary", new string[3] { "FullBody, Override", "Gesture, Override", "Gesture, Additive" });
			val7.NativeLayerPreference.Add("slam", new string[4] { "FullBody, Override", "Gesture, Override", "Gesture, Additive", "Body" });
			val7.NativeLayerPreference.Add("secondary", new string[3] { "Gesture, RightArm, Override", "Gesture, Override", "Gesture, Additive" });
			val7.NativeLayerPreference.Add("special", new string[6] { "FullBody, Override", "Gesture, Override", "Gesture, Head, Override", "Gesture, RightArm, Override", "Gesture, Head, Additive", "Gesture, Additive" });
			val7.NativeLayerPreference.Add("alternate_special", new string[3] { "FullBody, Override", "Gesture, Override", "Gesture, RightArm, Override" });
			val7.NativeLayerPreference.Add("utility", new string[2] { "Gesture, FullBody, No Arms, Override", "FullBody, Override" });
			val7.NativeLayerPreference.Add("landing", new string[2] { "Impact", "ImpactRestricted" });
			val7.NativeLayerPreference.Add("lobby", new string[1] { "Base Layer" });
			val7.NativeLayerPreference.Add("lobby_look", new string[2] { "Looks", "VFX Sync Layer" });
			AdvancedSkinRegistry.Register(val7);
			Mesh val8 = new Mesh
			{
				name = "EclipseFalseSonE3EmptyNativeClothAndClub"
			};
			val8.vertices = Array.Empty<Vector3>();
			val8.triangles = Array.Empty<int>();
			GameObject[] array = (GameObject[])(object)new GameObject[2] { modelPrefab, displayModelPrefab };
			foreach (GameObject val9 in array)
			{
				(val9.GetComponent<PenguinCompanions>() ?? val9.AddComponent<PenguinCompanions>()).Configure(nativeMesh, val8, companionNames, companionMeshes);
			}
			info.defaultMaterial = val6;
			info.defaultMaterialAddress = new AssetReferenceT<Material>("");
			SkinDefParams val10 = ScriptableObject.CreateInstance<SkinDefParams>();
			val10.rendererInfos = (RendererInfo[])(object)new RendererInfo[1] { info };
			val10.meshReplacements = (MeshReplacement[])(object)new MeshReplacement[3]
			{
				new MeshReplacement
				{
					renderer = (Renderer)(object)target,
					mesh = mesh,
					meshAddress = new AssetReferenceT<Mesh>("")
				},
				new MeshReplacement
				{
					renderer = (Renderer)(object)renderers.Single((SkinnedMeshRenderer r) => ((Object)r).name == "FalseSon_Cloth"),
					mesh = val8,
					meshAddress = new AssetReferenceT<Mesh>("")
				},
				new MeshReplacement
				{
					renderer = (Renderer)(object)renderers.Single((SkinnedMeshRenderer r) => ((Object)r).name == "L_FalseSon_Weapon"),
					mesh = val8,
					meshAddress = new AssetReferenceT<Mesh>("")
				}
			};
			SkinDef val11 = ScriptableObject.CreateInstance<SkinDef>();
			((Object)val11).name = "EclipsePlaytestFalseSonE3Penguin";
			val11.nameToken = "ECLIPSE_PLAYTEST_FALSESON_E3_PENGUIN";
			LanguageAPI.Add(val11.nameToken, "Penguin (Playtest)");
			val11.unlockableDef = null;
			val11.rootObject = modelPrefab;
			val11.baseSkins = (SkinDef[])(object)new SkinDef[1] { baseSkin };
			val11.skinDefParams = val10;
			val11.icon = Sprite.Create(icon, new Rect(0f, 0f, (float)((Texture)icon).width, (float)((Texture)icon).height), new Vector2(0.5f, 0.5f));
			registration = AdvancedSkinApplications.Register(val11, mesh);
			val5.skins = (SkinDef[])(controller.skins = controller.skins.Concat((IEnumerable<SkinDef>)(object)new SkinDef[1] { val11 }).ToArray()).Clone();
			registered = true;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("FALSESON_E3_REGISTERED revision=" + manifest.revision + " customBones=" + bank.Bones.Length + " unlocked=true audio=not_integrated items=native_fallback"));
		}

		private void SyncDisplaySkins()
		{
			if (!registered)
			{
				return;
			}
			try
			{
				if (!Object.op_Implicit((Object)(object)displayModelPrefab))
				{
					throw new InvalidOperationException("Prepared penguin display was destroyed");
				}
				PrepareTemplate(displayModelPrefab);
				ModelSkinController component = displayModelPrefab.GetComponent<ModelSkinController>();
				if (!Object.op_Implicit((Object)(object)component))
				{
					throw new InvalidOperationException("Prepared penguin display controller was removed");
				}
				component.skins = (SkinDef[])modelPrefab.GetComponent<ModelSkinController>().skins.Clone();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("FALSESON_E3_DISPLAY_FAILED " + ex));
			}
		}

		private void OnDestroy()
		{
			RoR2Application.onLoad = (Action)Delegate.Remove(RoR2Application.onLoad, new Action(SyncDisplaySkins));
			MainMenuController.OnMainMenuInitialised -= SyncDisplaySkins;
			registration?.Dispose();
			if ((Object)(object)instance == (Object)(object)this)
			{
				instance = null;
			}
		}
	}
	[DefaultExecutionOrder(32001)]
	public sealed class PenguinCompanions : MonoBehaviour
	{
		[SerializeField]
		private SkinnedMeshRenderer body;

		[SerializeField]
		private SkinnedMeshRenderer[] companions;

		[SerializeField]
		private Mesh nativeBody;

		[SerializeField]
		private Mesh empty;

		[SerializeField]
		private Mesh[] nativeMeshes;

		public void Configure(Mesh stockBody, Mesh ownedEmpty, string[] names, Mesh[] meshes)
		{
			SkinnedMeshRenderer[] renderers = ((Component)(object)this).GetSkinRenderers<SkinnedMeshRenderer>(inactive: true, (SkinDefParams)null);
			body = renderers.Single((SkinnedMeshRenderer r) => ((Object)r).name == "FalseSon_Body");
			companions = names.Select((string n) => renderers.Single((SkinnedMeshRenderer r) => ((Object)r).name == n)).ToArray();
			if (meshes.Length != names.Length || meshes.Any((Mesh m) => !Object.op_Implicit((Object)(object)m)))
			{
				throw new ArgumentException("Incomplete companion fallback");
			}
			nativeBody = stockBody;
			empty = ownedEmpty;
			nativeMeshes = meshes;
		}

		private void LateUpdate()
		{
			if (!Object.op_Implicit((Object)(object)body) || (Object)(object)body.sharedMesh != (Object)(object)nativeBody || !Object.op_Implicit((Object)(object)empty) || companions == null)
			{
				return;
			}
			for (int i = 0; i < companions.Length; i++)
			{
				if (Object.op_Implicit((Object)(object)companions[i]) && (Object)(object)companions[i].sharedMesh == (Object)(object)empty)
				{
					companions[i].sharedMesh = nativeMeshes[i];
				}
			}
		}
	}
	internal static class PenguinActor
	{
		public static CharacterBody Resolve(Component member)
		{
			if (!Object.op_Implicit((Object)(object)member))
			{
				return null;
			}
			CharacterModel componentInParent = member.GetComponentInParent<CharacterModel>();
			if (Object.op_Implicit((Object)(object)componentInParent) && Object.op_Implicit((Object)(object)componentInParent.body) && Object.op_Implicit((Object)(object)componentInParent.body.modelLocator) && (Object)(object)componentInParent.body.modelLocator.modelTransform == (Object)(object)((Component)componentInParent).transform)
			{
				return componentInParent.body;
			}
			AdvancedSkinModel componentInParent2 = member.GetComponentInParent<AdvancedSkinModel>();
			if (!Object.op_Implicit((Object)(object)componentInParent2))
			{
				return null;
			}
			return componentInParent2.AssociatedBody;
		}
	}
	public sealed class PenguinStateObserver : ISkinMotionSource, IDisposable
	{
		private readonly Animator animator;

		private readonly MotionBank bank;

		private CharacterBody actor;

		private EntityState death;

		private float began;

		private bool disposed;

		public PenguinStateObserver(Animator animator, MotionBank bank)
		{
			this.animator = animator;
			this.bank = bank;
			bank.Find("death");
		}

		public void Observe(List<NativeMotionCandidate> candidates, HashSet<string> replaced)
		{
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Expected O, but got Unknown
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Expected O, but got Unknown
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Expected O, but got Unknown
			if (disposed)
			{
				return;
			}
			CharacterBody val = PenguinActor.Resolve((Component)(object)animator);
			if ((Object)(object)val != (Object)(object)actor)
			{
				actor = val;
				death = null;
			}
			if (!Object.op_Implicit((Object)(object)actor))
			{
				return;
			}
			EntityState val2 = (from m in ((Component)actor).GetComponents<EntityStateMachine>()
				where Object.op_Implicit((Object)(object)m)
				select m.state).FirstOrDefault((Func<EntityState, bool>)((EntityState s) => s is GenericCharacterDeath));
			if (val2 == null)
			{
				death = null;
				return;
			}
			if (val2 != death)
			{
				death = val2;
				began = Time.time;
			}
			MotionClip val3 = bank.Find("death");
			NativeMotionCandidate val4 = new NativeMotionCandidate();
			val4.SourceLayer = -100;
			val4.SourceRank = 1000;
			MotionLayer val5 = new MotionLayer();
			val5.Group = "death";
			val5.Priority = 1000;
			val5.Blend = (MotionBlend)0;
			val5.Samples = (WeightedMotion[])(object)new WeightedMotion[1]
			{
				new WeightedMotion
				{
					Clip = val3,
					Time = Math.Max(0f, Time.time - began) / val3.Duration,
					Weight = 1f,
					Token = "penguin-death:" + began,
					KnownEntry = true
				}
			};
			val4.Motion = val5;
			candidates.Add(val4);
		}

		public void Dispose()
		{
			disposed = true;
			actor = null;
			death = null;
		}
	}
	public sealed class PenguinGaitTiming : ISkinMotionTiming, IDisposable
	{
		private readonly SkinnedMeshRenderer renderer;

		private readonly HashSet<string> gaits = new HashSet<string> { "run", "sprint", "backpedal", "strafe_left", "strafe_right" };

		private CharacterMotor motor;

		private double phase;

		private float last = -1f;

		private float rate;

		private bool disposed;

		public PenguinGaitTiming(SkinnedMeshRenderer renderer)
		{
			this.renderer = renderer;
		}

		public void Retime(MotionLayer[] composed, IEnumerable<NativeMotionCandidate> observed)
		{
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			if (disposed)
			{
				return;
			}
			CharacterBody val = PenguinActor.Resolve((Component)(object)renderer);
			CharacterMotor val2 = (Object.op_Implicit((Object)(object)val) ? val.characterMotor : null);
			if ((Object)(object)val2 != (Object)(object)motor)
			{
				motor = val2;
				last = -1f;
				phase = 0.0;
				rate = 0f;
			}
			if (!Object.op_Implicit((Object)(object)motor))
			{
				return;
			}
			float time = Time.time;
			float num = ((last < 0f) ? 0f : (time - last));
			last = time;
			if (num < 0f || num > 0.25f)
			{
				rate = 0f;
				return;
			}
			Vector3 velocity = motor.velocity;
			float num2 = Mathf.Sqrt(velocity.x * velocity.x + velocity.z * velocity.z);
			WeightedMotion val3 = (from w in composed.SelectMany((MotionLayer l) => l.Samples)
				where gaits.Contains(w.Clip.Id)
				orderby w.Weight descending
				select w).FirstOrDefault();
			float num3 = ((val3 != null && val3.Clip.Id == "sprint") ? 0.64f : 0.48f);
			float num4 = ((motor.isGrounded && num2 > 0.05f) ? Mathf.Min(8f, num2 * 0.62f / num3) : 0f);
			rate = Mathf.MoveTowards(rate, num4, num * 24f);
			phase += num * rate;
			foreach (WeightedMotion item in composed.SelectMany((MotionLayer l) => l.Samples).Concat(observed.SelectMany((NativeMotionCandidate c) => c.Motion.Samples)).Distinct())
			{
				if (gaits.Contains(item.Clip.Id))
				{
					item.Time = phase;
					item.KnownEntry = false;
				}
			}
		}

		public bool OwnsCues(MotionClip clip)
		{
			return false;
		}

		public void AdvanceCues(Action<string> emit)
		{
		}

		public void Dispose()
		{
			disposed = true;
			motor = null;
		}
	}
}