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)
{
}
}
}