Decompiled source of AppearancePlus v3.2.0

AppearancePlus.dll

Decompiled 5 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("AppearancePlus")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Template plugin for Atlyss using BepInEx 5")]
[assembly: AssemblyTitle("AppearancePlus")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace AppearancePlus
{
	[HarmonyPatch(typeof(MainMenuManager), "Set_MenuCondition")]
	public static class MenuPatch
	{
		[HarmonyPostfix]
		private static void SetMenuConditionPatch(MainMenuManager __instance)
		{
			__instance._versionDisplayText.text = Application.version + " Appearance +";
		}
	}
	[BepInPlugin("Nuilescent.AppearancePlus", "AppearancePlus", "3.2.0")]
	internal class AppearancePlus : BaseUnityPlugin
	{
		public static string PluginPath = Application.dataPath.Replace("ATLYSS_Data", "BepInEx/plugins/");

		public static string ConfigPath;

		public static Dictionary<string, List<(string, float)>> DynamicBoneValues = new Dictionary<string, List<(string, float)>>();

		public static Dictionary<string, List<(string, (float, float))>> RaceDisplayValues = new Dictionary<string, List<(string, (float, float))>>();

		public static List<string> races = new List<string> { "imp", "poon", "chang", "kubold", "byrdle" };

		public static List<string> raceProperties = new List<string> { "height", "width", "torso", "chest", "arms", "belly", "bottom", "head", "muzzle", "voice" };

		public static List<string> dbProperties = new List<string> { "damping", "elasticity", "stiffness", "inert" };

		public static ScriptablePlayerRace[] ScriptablePlayerRaces;

		public static List<PlayerRaceModel> PlayerRaceModels = new List<PlayerRaceModel>();

		private static string CustomTextureFolder = PluginPath;

		internal static Dictionary<string, Texture2D> PreloadedTextures = new Dictionary<string, Texture2D>();

		internal static Dictionary<string, Texture2D> NonReplacedTextures = new Dictionary<string, Texture2D>();

		private void Awake()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			new Harmony("AppearancePlus").PatchAll();
			SetupMonomodHooks();
			SetupPluginValues();
		}

		public static void SetupPluginValues()
		{
			string[] files = Directory.GetFiles(PluginPath, "AppearancePlus_Config.txt", SearchOption.AllDirectories);
			if (files.Length == 0)
			{
				Debug.LogError((object)"[AppearancePlus] Could not find config file! \nMake sure AppearancePlus_Config.txt exists in ATLYSS/BepInEx/plugins/AppearancePlus");
				return;
			}
			if (files.Length > 1)
			{
				Debug.LogWarning((object)"[AppearancePlus] Found multiple AppearancePlus_Config.txt. Only first one will be used!");
			}
			ConfigPath = files.First();
			DynamicBoneValues.Clear();
			RaceDisplayValues.Clear();
			StreamReader streamReader = new StreamReader(ConfigPath);
			string text = streamReader.ReadLine();
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("[AppearancePlus] Reading Config:");
			string race;
			while (text != null && !text.StartsWith("end"))
			{
				List<string> list = text.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
				if (!text.StartsWith("//") && !(text == string.Empty) && list.Count != 0)
				{
					if (list.First() == "character")
					{
						if (list.Count < 3)
						{
							stringBuilder.Append("\nInvalid syntax for a character edit. Should be: character <race> <property>:<min>/<max>");
						}
						else
						{
							list.RemoveAt(0);
							race = list.First().ToLower();
							if (races.All((string r) => r != race))
							{
								stringBuilder.Append("\n" + race + " isn't a valid race. Skipping.");
							}
							else
							{
								list.RemoveAt(0);
								RaceDisplayValues.Add(race, new List<(string, (float, float))>());
								stringBuilder.Append("\n-> (" + race + ") Saving Properties:");
								foreach (string item in list)
								{
									string[] propertySplit2 = item.Split(':');
									if (propertySplit2.Length != 2)
									{
										stringBuilder.Append("\nIncorrect syntax for " + item + ". Should be <property>:<min>/<max>");
										break;
									}
									if (raceProperties.All((string p) => p != propertySplit2[0]))
									{
										stringBuilder.Append("\nNo matching race slider properties found for " + propertySplit2[0] + ". Skipping.");
										break;
									}
									string[] array = propertySplit2.Last().Split('/');
									if (propertySplit2.Length != 2)
									{
										stringBuilder.Append("\nIncorrect syntax for value " + propertySplit2[1] + " of " + propertySplit2[0] + ". Should be <min>/<max>");
										if (text.Contains(" / "))
										{
											stringBuilder.Append("\nDo not use spaces on either sides of '/' symbol.");
										}
										break;
									}
									if (!float.TryParse(array[0], NumberStyles.Any, CultureInfo.InvariantCulture, out var result) || !float.TryParse(array[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var result2))
									{
										stringBuilder.Append("\nCannot parse min/max value " + propertySplit2[1] + " of " + propertySplit2[0] + ". Value should be written like: 0.12, no comma.");
										break;
									}
									RaceDisplayValues[race].Add((propertySplit2[0], (result, result2)));
									stringBuilder.Append($"{propertySplit2[0]}:{result}/{result2} ✔ | ");
								}
							}
						}
					}
					else if (list.First() == "bone")
					{
						if (list.Count < 3)
						{
							stringBuilder.Append("\nInvalid syntax for a bone edit. Should be: bone <bodypart> <property>:<value>");
							break;
						}
						list.RemoveAt(0);
						string text2 = list.First();
						list.RemoveAt(0);
						DynamicBoneValues.Add(text2, new List<(string, float)>());
						stringBuilder.Append("\n-> (" + text2 + ") Saving Properties:");
						foreach (string item2 in list)
						{
							string[] propertySplit = item2.Split(':');
							if (propertySplit.Length != 2)
							{
								stringBuilder.Append("\nInvalid syntax for " + propertySplit[0] + ". Should be <property>:<value>.");
								if (text.Contains(" : "))
								{
									stringBuilder.Append("\nDo not use spaces on either sides of ':' symbol.");
								}
								break;
							}
							propertySplit[0] = propertySplit[0].ToLower();
							propertySplit[1] = propertySplit[1].ToLower();
							if (dbProperties.All((string p) => p != propertySplit[0]))
							{
								stringBuilder.Append("\nNo matching dynamic bone properties found for " + propertySplit[0] + ". Skipping.");
								break;
							}
							if (!float.TryParse(propertySplit[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var result3))
							{
								stringBuilder.Append("\nCannot parse value " + propertySplit[1] + " of " + propertySplit[0] + ". Value should be written like: 0.12, no comma.");
								break;
							}
							DynamicBoneValues[text2].Add((propertySplit[0], result3));
							stringBuilder.Append(propertySplit[0] + ":" + propertySplit[1] + " ✔ | ");
						}
					}
				}
				text = streamReader.ReadLine();
			}
			Debug.Log((object)stringBuilder.ToString());
		}

		public static void SetRaceSliderValues()
		{
			//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_030e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0313: Unknown result type (might be due to invalid IL or missing references)
			//IL_0360: Unknown result type (might be due to invalid IL or missing references)
			//IL_0365: Unknown result type (might be due to invalid IL or missing references)
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_0298: 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_0337: Unknown result type (might be due to invalid IL or missing references)
			//IL_033c: Unknown result type (might be due to invalid IL or missing references)
			//IL_03af: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_026a: Unknown result type (might be due to invalid IL or missing references)
			//IL_026f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0389: Unknown result type (might be due to invalid IL or missing references)
			//IL_038e: Unknown result type (might be due to invalid IL or missing references)
			Debug.Log((object)"[AppearancePlus] Loading Race Display Properties...");
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			ScriptablePlayerRace[] scriptablePlayerRaces = ScriptablePlayerRaces;
			foreach (ScriptablePlayerRace val in scriptablePlayerRaces)
			{
				string text = val._raceName.ToLower();
				if (!RaceDisplayValues.ContainsKey(text))
				{
					continue;
				}
				Debug.Log((object)$"[AppearancePlus] Loading {RaceDisplayValues[text].Count} {text} properties...");
				num++;
				CharacterParamsGroup raceDisplayParams = val._raceDisplayParams;
				foreach (var item in RaceDisplayValues[text])
				{
					switch (item.Item1)
					{
					case "height":
						raceDisplayParams._heightRange = new Vector2(item.Item2.Item1, item.Item2.Item2);
						break;
					case "width":
						raceDisplayParams._widthRange = new Vector2(item.Item2.Item1, item.Item2.Item2);
						break;
					case "chest":
						raceDisplayParams._boobRange = new Vector2(item.Item2.Item1, item.Item2.Item2);
						break;
					case "arms":
						raceDisplayParams._armRange = new Vector2(item.Item2.Item1, item.Item2.Item2);
						break;
					case "belly":
						raceDisplayParams._bellyRange = new Vector2(item.Item2.Item1, item.Item2.Item2);
						break;
					case "bottom":
						raceDisplayParams._bottomRange = new Vector2(item.Item2.Item1, item.Item2.Item2);
						break;
					case "torso":
						raceDisplayParams._torsoRange = new Vector2(item.Item2.Item1, item.Item2.Item2);
						break;
					case "head":
						raceDisplayParams._headWidthRange = new Vector2(item.Item2.Item1, item.Item2.Item2);
						break;
					case "muzzle":
						raceDisplayParams._headModRange = new Vector2(item.Item2.Item1, item.Item2.Item2);
						break;
					case "voice":
						raceDisplayParams._pitchRange = new Vector2(item.Item2.Item1, item.Item2.Item2);
						break;
					default:
						Debug.Log((object)("[AppearancePlus] Could not apply property " + item.Item1 + " to " + text + "."));
						num3++;
						continue;
					}
					num2++;
				}
			}
			if (num3 > 0)
			{
				Debug.Log((object)$"[AppearancePlus] Loaded {num2} properties in {num} races. Failed to load {num3} properties.");
			}
			else
			{
				Debug.Log((object)$"[AppearancePlus] Loaded all {num2} properties in {num} races.");
			}
		}

		public static void SetDynamicBoneValues(PlayerRaceModel __instance)
		{
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			List<DynamicBone> list = ((Component)__instance).GetComponentsInChildren<DynamicBone>().ToList();
			foreach (DynamicBone item in list)
			{
				num++;
				StringBuilder stringBuilder = new StringBuilder(((Object)item).name);
				stringBuilder.Replace("Base", "");
				stringBuilder.Replace("base", "");
				stringBuilder.Replace(" ", "");
				stringBuilder.Replace(".l", "");
				stringBuilder.Replace(".r", "");
				string text = stringBuilder.ToString().ToLower();
				if (!DynamicBoneValues.ContainsKey(text))
				{
					continue;
				}
				foreach (var item2 in DynamicBoneValues[text])
				{
					switch (item2.Item1)
					{
					case "damping":
						item.m_Damping = item2.Item2;
						break;
					case "elasticity":
						item.m_Elasticity = item2.Item2;
						break;
					case "stiffness":
						item.m_Stiffness = item2.Item2;
						break;
					case "inert":
						item.m_Inert = item2.Item2;
						break;
					default:
						Debug.Log((object)("[AppearancePlus] Could not apply property " + item2.Item1 + " to " + text + "."));
						num3++;
						continue;
					}
					num2++;
				}
			}
			list.ForEach(delegate(DynamicBone d)
			{
				d.UpdateParameters();
			});
			if (num3 > 0)
			{
				Debug.Log((object)$"[AppearancePlus] Loaded {num2} properties in {num} parts. Failed to load {num3} properties.");
			}
			else
			{
				Debug.Log((object)$"[AppearancePlus] Loaded all {num2} properties in {num} parts.");
			}
		}

		public static void RefreshAllDynamicBones()
		{
			foreach (PlayerRaceModel playerRaceModel in PlayerRaceModels)
			{
				if ((Object)(object)playerRaceModel != (Object)null)
				{
					SetDynamicBoneValues(playerRaceModel);
				}
			}
		}

		private void SetupMonomodHooks()
		{
		}

		public static bool PreloadTextures()
		{
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Expected O, but got Unknown
			PreloadedTextures.Clear();
			int num = 0;
			try
			{
				string[] directories = Directory.GetDirectories(PluginPath, "CustomTextures", SearchOption.AllDirectories);
				if (directories.Length == 0)
				{
					Debug.LogWarning((object)"[AppearancePlus] Custom texture folder not found.");
					return false;
				}
				if (directories.Length != 0)
				{
					string[] directories2 = Directory.GetDirectories(Path.Combine(PluginPath, "AppearancePlus"), "CustomTextures", SearchOption.AllDirectories);
					if (directories2.Length == 0)
					{
						Debug.LogWarning((object)"[AppearancePlus] Multiple custom texture folders found. First one will be used.");
						CustomTextureFolder = directories.First();
					}
					else
					{
						if (directories2.Length > 1)
						{
							Debug.LogWarning((object)"[AppearancePlus] Multiple custom texture folders found. First one will be used.");
						}
						CustomTextureFolder = directories2.First();
					}
				}
				else
				{
					CustomTextureFolder = directories.First();
				}
				string[] files = Directory.GetFiles(CustomTextureFolder, "*.png", SearchOption.AllDirectories);
				foreach (string text in files)
				{
					try
					{
						byte[] array = File.ReadAllBytes(text);
						Texture2D val = new Texture2D(2, 2);
						if (ImageConversion.LoadImage(val, array))
						{
							string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text);
							if (!PreloadedTextures.ContainsKey(fileNameWithoutExtension))
							{
								SetSingleTextureFilteringMode(val);
								((Object)val).name = fileNameWithoutExtension;
								PreloadedTextures[fileNameWithoutExtension] = val;
								num++;
							}
							else
							{
								Debug.LogWarning((object)("[AppearancePlus] Duplicate texture name detected: " + fileNameWithoutExtension + ". Skipping."));
							}
						}
						else
						{
							Debug.LogError((object)("[AppearancePlus] Failed to load texture: " + text));
						}
					}
					catch (Exception ex)
					{
						Debug.LogError((object)("[AppearancePlus] Error loading texture file '" + text + "': " + ex.Message));
					}
				}
			}
			catch (Exception ex2)
			{
				Debug.LogError((object)("[AppearancePlus] Error preloading textures: " + ex2.Message));
			}
			Debug.Log((object)$"[AppearancePlus] Preloaded {PreloadedTextures.Count} textures successfully!");
			return PreloadedTextures.Count > 0;
		}

		public static void SetAllTextureFilteringMode()
		{
			if (PreloadedTextures == null)
			{
				Debug.LogWarning((object)"[AppearancePlus] PreloadedTextures is null or empty.");
				return;
			}
			foreach (Texture2D value in PreloadedTextures.Values)
			{
				if ((Object)(object)value != (Object)null)
				{
					SetSingleTextureFilteringMode(value);
				}
				else
				{
					Debug.LogWarning((object)"[AppearancePlus] Null texture found in PreloadedTextures.");
				}
			}
		}

		public static void SetSingleTextureFilteringMode(Texture2D texture)
		{
			if (!((Texture)texture).isReadable)
			{
				Debug.LogWarning((object)("[AppearancePlus] Texture '" + ((Object)texture).name + "' is not readable. Skipping."));
				return;
			}
			if ((Object)(object)SettingsManager._current == (Object)null || SettingsManager._current._settingsProfile == null)
			{
				Debug.LogError((object)"[AppearancePlus] SettingsManager or its profile is null. Cannot set texture filtering mode.");
				return;
			}
			switch (SettingsManager._current._settingsProfile._textureFilterSetting)
			{
			case 0:
				((Texture)texture).anisoLevel = 16;
				((Texture)texture).filterMode = (FilterMode)1;
				break;
			case 1:
				((Texture)texture).filterMode = (FilterMode)0;
				break;
			default:
				Debug.LogWarning((object)"[AppearancePlus] Unsupported texture filter setting. Defaulting to Point.");
				((Texture)texture).filterMode = (FilterMode)0;
				break;
			}
		}

		public static void SwapCustomTextures()
		{
			NonReplacedTextures = new Dictionary<string, Texture2D>(PreloadedTextures);
			int num = 0;
			ScriptablePlayerRace[] scriptablePlayerRaces = ScriptablePlayerRaces;
			for (int i = 0; i < scriptablePlayerRaces.Length; i++)
			{
				SkinTextureGroup[] skinTextureGroups = scriptablePlayerRaces[i]._skinTextureGroups;
				foreach (SkinTextureGroup obj in skinTextureGroups)
				{
					if (ReplaceTexture(ref obj._headTexture, "Head Texture"))
					{
						num++;
					}
					if (ReplaceTexture(ref obj._bodyTexture, "Body Texture"))
					{
						num++;
					}
					if (ReplaceTexture(ref obj._legTexture, "Leg Texture"))
					{
						num++;
					}
					if (ReplaceTexture(ref obj._tailTexture, "Tail Texture"))
					{
						num++;
					}
					if (ReplaceTexture(ref obj._earTexture, "Ear Texture"))
					{
						num++;
					}
					if (ReplaceTexture(ref obj._hairTexture, "Hair Texture"))
					{
						num++;
					}
				}
			}
			Debug.Log((object)$"[AppearancePlus] Replaced {num} textures successfully!");
			if (NonReplacedTextures.Count <= 0)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine($"[AppearancePlus] {NonReplacedTextures.Count} Non-Replaced Textures:");
			foreach (KeyValuePair<string, Texture2D> nonReplacedTexture in NonReplacedTextures)
			{
				stringBuilder.AppendLine("- " + nonReplacedTexture.Key);
			}
			Debug.Log((object)stringBuilder.ToString());
		}

		private static bool ReplaceTexture(ref Texture texture, string textureName)
		{
			if ((Object)(object)texture == (Object)null)
			{
				Debug.LogWarning((object)(textureName + " is null"));
				return false;
			}
			if (PreloadedTextures.TryGetValue(((Object)texture).name, out var value))
			{
				texture = (Texture)(object)value;
				NonReplacedTextures.Remove(((Object)texture).name);
				return true;
			}
			return false;
		}

		public static void RefreshAllTextures()
		{
			PreloadTextures();
			SetAllTextureFilteringMode();
			SwapCustomTextures();
		}
	}
	[HarmonyPatch(typeof(PlayerRaceModel), "Awake")]
	public static class PlayerRaceModelPatch
	{
		[HarmonyPostfix]
		private static void AwakePostfix(PlayerRaceModel __instance)
		{
			AppearancePlus.PlayerRaceModels.Add(__instance);
			AppearancePlus.SetDynamicBoneValues(__instance);
		}
	}
	[HarmonyPatch(typeof(CharacterCreationManager), "Awake")]
	public static class CharacterCreationManagerPatch
	{
		[HarmonyPostfix]
		private static void AwakePostfix(CharacterCreationManager __instance)
		{
			AppearancePlus.ScriptablePlayerRaces = __instance._scriptablePlayerRaces;
			AppearancePlus.SetRaceSliderValues();
			AppearancePlus.RefreshAllTextures();
		}
	}
	[HarmonyPatch(typeof(ChatBehaviour), "Send_ChatMessage")]
	public static class AddCommandsPatch
	{
		[HarmonyPrefix]
		public static void Send_ChatMessage_Prefix(ChatBehaviour __instance, string _message)
		{
			if (!_message.StartsWith("/"))
			{
				return;
			}
			switch (_message.TrimStart('/'))
			{
			case "reload sliders":
				AppearancePlus.SetupPluginValues();
				AppearancePlus.SetRaceSliderValues();
				_message = "";
				__instance._chatInput.text = "";
				__instance.New_ChatMessage("<color=#ea8e09>[AppearancePlus] Refreshing race slider values</color>");
				break;
			case "reload dbones":
				AppearancePlus.SetupPluginValues();
				AppearancePlus.RefreshAllDynamicBones();
				_message = "";
				__instance._chatInput.text = "";
				__instance.New_ChatMessage("<color=#ea8e09>[AppearancePlus] Refreshing dynamic bone values</color>");
				break;
			case "reload textures":
				AppearancePlus.RefreshAllTextures();
				__instance._chatInput.text = "";
				__instance.New_ChatMessage("<color=#ea8e09>[AppearancePlus] Refreshing custom textures</color>");
				break;
			case "print dbones":
			{
				StringBuilder stringBuilder = new StringBuilder();
				foreach (KeyValuePair<string, List<(string, float)>> dynamicBoneValue in AppearancePlus.DynamicBoneValues)
				{
					stringBuilder.AppendLine(dynamicBoneValue.Key + ":");
					foreach (var item in dynamicBoneValue.Value)
					{
						stringBuilder.AppendLine($" {item.Item1}:{item.Item2}");
					}
				}
				_message = "";
				__instance._chatInput.text = "";
				__instance.New_ChatMessage(stringBuilder.ToString());
				break;
			}
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}