Decompiled source of CustomSosigToonSkin v1.0.0

Niko666.CustomSosigToonSkin.dll

Decompiled 16 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.VR;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyCompany("Niko666")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Swaps the CustomSosigModelFramework Standard material for a bundled RealToon cel shader with an #FF06B5 outline and rim light.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Niko666.CustomSosigToonSkin")]
[assembly: AssemblyTitle("Custom Sosig Toon Skin")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Niko666
{
	[HarmonyPatch]
	internal static class HandIkReachPatch
	{
		internal static float Value = 0.22f;

		internal static readonly List<string> Targets = new List<string>();

		private const float Default = 0.22f;

		private static readonly byte[] Pattern = BitConverter.GetBytes(0.22f);

		private static readonly MethodInfo Getter = AccessTools.Method(typeof(HandIkReachPatch), "GetReach", (Type[])null, (Type[])null);

		private static IEnumerable<MethodBase> TargetMethods()
		{
			List<MethodBase> list = new List<MethodBase>();
			Type type = AccessTools.TypeByName("CustomSosigReplacer.Plugin");
			if ((object)type == null)
			{
				return list;
			}
			Type[] types = type.Assembly.GetTypes();
			foreach (Type type2 in types)
			{
				if (!(type2.FullName ?? string.Empty).StartsWith("CustomSosigReplacer.SosigVisualProxy", StringComparison.Ordinal))
				{
					continue;
				}
				MethodInfo[] methods = type2.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodBase methodBase in methods)
				{
					MethodBody methodBody = methodBase.GetMethodBody();
					if (methodBody != null)
					{
						byte[] iLAsByteArray = methodBody.GetILAsByteArray();
						if (iLAsByteArray != null && LoadsDefaultReach(iLAsByteArray))
						{
							list.Add(methodBase);
						}
					}
				}
			}
			foreach (MethodBase item in list)
			{
				Targets.Add(item.Name);
			}
			return list;
		}

		private static bool LoadsDefaultReach(byte[] il)
		{
			for (int i = 0; i + 5 <= il.Length; i++)
			{
				if (il[i] == 34 && il[i + 1] == Pattern[0] && il[i + 2] == Pattern[1] && il[i + 3] == Pattern[2] && il[i + 4] == Pattern[3])
				{
					return true;
				}
			}
			return false;
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			int num = 0;
			for (int i = 0; i < list.Count; i++)
			{
				if (!(list[i].opcode != OpCodes.Ldc_R4) && list[i].operand is float && !(Mathf.Abs((float)list[i].operand - 0.22f) > 1E-06f))
				{
					list[i] = new CodeInstruction(OpCodes.Call, (object)Getter);
					num++;
				}
			}
			ManualLogSource log = ToonSkinPlugin.Log;
			if (log != null)
			{
				if (num > 0)
				{
					log.LogInfo((object)("Hand IK reach constant replaced at " + num + " site(s); targets: " + string.Join(", ", Targets.ToArray())));
				}
				else
				{
					log.LogWarning((object)"No 0.22f reach constant found while transpiling.");
				}
			}
			return list;
		}

		internal static float GetReach()
		{
			return Value;
		}
	}
	[BepInPlugin("niko666.customsosigtoonskin", "Custom Sosig Toon Skin", "1.0.0")]
	public sealed class ToonSkinPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "niko666.customsosigtoonskin";

		public const string PluginName = "Custom Sosig Toon Skin";

		public const string PluginVersion = "1.0.0";

		internal const string BundleFileName = "nikotoonskin.bundle";

		internal const string FrameworkType = "CustomSosigReplacer.Plugin";

		internal const string FrameworkMethod = "CreateMaterial";

		internal static ManualLogSource Log;

		internal static ToonSkinPlugin Instance;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<string> OutlineColorHex;

		internal static ConfigEntry<float> OutlineWidth;

		internal static ConfigEntry<string> RimColorHex;

		internal static ConfigEntry<float> RimPower;

		internal static ConfigEntry<bool> UseNormalMap;

		internal static ConfigEntry<bool> UseAlphaCutout;

		internal static ConfigEntry<bool> EnableInstancing;

		internal static ConfigEntry<bool> OutlinePass;

		internal static ConfigEntry<float> HandIkReach;

		internal static ConfigEntry<string> ShaderMode;

		internal static ConfigEntry<string> NameFilter;

		private readonly Dictionary<string, Material> _templates = new Dictionary<string, Material>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, string> ModeAliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			{ "RealToon", "NikoToon" },
			{ "RealToonLite", "NikoToonLite" },
			{ "Simple", "NikoSimple" },
			{ "Probe", "NikoProbe" }
		};

		private static string _reportedMissingMode;

		private void Awake()
		{
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Toon", "Enabled", true, "Replace the framework's opaque Standard material with the bundled RealToon cel shader.");
			OutlineColorHex = ((BaseUnityPlugin)this).Config.Bind<string>("Toon", "OutlineColor", "FF06B5", "Outline colour, RRGGBB hex.");
			OutlineWidth = ((BaseUnityPlugin)this).Config.Bind<float>("Toon", "OutlineWidth", 0.1f, "RealToon outline width. 0 disables the outline pass.");
			RimColorHex = ((BaseUnityPlugin)this).Config.Bind<string>("Toon", "RimLightColor", "FF06B5", "Rim light colour, RRGGBB hex.");
			RimPower = ((BaseUnityPlugin)this).Config.Bind<float>("Toon", "RimLightPower", 10f, "RealToon rim light power/strength.");
			UseNormalMap = ((BaseUnityPlugin)this).Config.Bind<bool>("Toon", "UseNormalMap", true, "Feed the addon's normal map into the toon shader.");
			UseAlphaCutout = ((BaseUnityPlugin)this).Config.Bind<bool>("Toon", "UseAlphaCutout", false, "Enable RealToon's alpha cutout. The addon textures keep a usable alpha mask, but the mesh is already alpha-trimmed offline.");
			EnableInstancing = ((BaseUnityPlugin)this).Config.Bind<bool>("Toon", "EnableInstancing", true, "Enable GPU instancing on the generated materials. Required for correct rendering in stereo (single pass / single pass instanced) - turn off only to reproduce the old one-eye offset.");
			OutlinePass = ((BaseUnityPlugin)this).Config.Bind<bool>("Toon", "OutlinePass", true, "Render the RealToon outline pass. Turning this off isolates the outline from the body when debugging stereo artifacts.");
			HandIkReach = ((BaseUnityPlugin)this).Config.Bind<float>("Motion", "HandIkReachMeters", 0.22f, "Distance within which the framework's hand IK is allowed to pull a visual hand onto the native gun grip. The framework hardcodes 0.22; raise it (0.3-0.5) when the model's authored hands sit too far from the Sosig's gun.");
			HandIkReach.SettingChanged += delegate
			{
				HandIkReachPatch.Value = HandIkReach.Value;
			};
			HandIkReachPatch.Value = HandIkReach.Value;
			ShaderMode = ((BaseUnityPlugin)this).Config.Bind<string>("Toon", "ShaderMode", "RealToon", "Which bundled material to use: RealToon (cel + outline + rim), RealToonLite (lighter RealToon variants), Simple (Niko666/SimpleToon, single pass, no outline), or Probe (Niko666/StereoProbe, paints per-eye state: red = right eye, green = single pass stereo, blue = instanced variant).");
			NameFilter = ((BaseUnityPlugin)this).Config.Bind<string>("Toon", "NameFilter", "Niko", "Only rebuild materials whose name or albedo path contains this text (the framework calls CreateMaterial for every installed model addon, so without a filter this plugin would restyle all of them). Leave empty to apply the toon material to every addon.");
			if (!Enabled.Value)
			{
				Log.LogInfo((object)"Toon skin disabled in config; the framework's Standard materials are left alone.");
			}
			else if (LoadTemplate())
			{
				LogStereoState();
				PatchFramework();
				((MonoBehaviour)this).StartCoroutine(LogDelayedState());
			}
		}

		private IEnumerator LogDelayedState()
		{
			yield return (object)new WaitForSeconds(8f);
			try
			{
				Log.LogInfo((object)("Delayed stereo probe: VRSettings.enabled=" + VRSettings.enabled + ", isDeviceActive=" + VRSettings.isDeviceActive + ", loadedDevice=" + VRSettings.loadedDeviceName + ", renderScale=" + VRSettings.renderScale + ", eyeTexture=" + VRSettings.eyeTextureWidth + "x" + VRSettings.eyeTextureHeight));
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Delayed stereo probe failed: " + ex.Message));
			}
			try
			{
				Camera main = Camera.main;
				if ((Object)(object)main == (Object)null)
				{
					Log.LogInfo((object)"Delayed camera probe: no camera tagged MainCamera.");
					yield break;
				}
				Log.LogInfo((object)("Delayed camera probe: '" + ((Object)main).name + "', stereoTargetEye=" + ((object)main.stereoTargetEye/*cast due to .constrained prefix*/).ToString() + ", stereoEnabled=" + main.stereoEnabled + ", pixel=" + main.pixelWidth + "x" + main.pixelHeight + ", targetTexture=" + ((Object)(object)main.targetTexture != (Object)null)));
			}
			catch (Exception ex2)
			{
				Log.LogWarning((object)("Delayed camera probe failed: " + ex2.Message));
			}
		}

		private static void LogStereoState()
		{
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Log.LogInfo((object)("Stereo probe: VRSettings.enabled=" + VRSettings.enabled + ", isDeviceActive=" + VRSettings.isDeviceActive + ", loadedDevice=" + VRSettings.loadedDeviceName + ", renderScale=" + VRSettings.renderScale + ", eyeTexture=" + VRSettings.eyeTextureWidth + "x" + VRSettings.eyeTextureHeight + " (double-wide single pass if width is about twice the height)"));
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Stereo probe failed: " + ex.Message));
			}
			try
			{
				Camera[] allCameras = Camera.allCameras;
				for (int i = 0; i < allCameras.Length; i++)
				{
					Camera val = allCameras[i];
					if (!((Object)(object)val == (Object)null))
					{
						Log.LogInfo((object)("Camera " + i + ": " + ((Object)val).name + ", stereoTargetEye=" + ((object)val.stereoTargetEye/*cast due to .constrained prefix*/).ToString() + ", targetTexture=" + ((Object)(object)val.targetTexture != (Object)null) + ", enabled=" + ((Behaviour)val).enabled));
					}
				}
			}
			catch (Exception ex2)
			{
				Log.LogWarning((object)("Camera probe failed: " + ex2.Message));
			}
		}

		internal static Material SelectedTemplate()
		{
			if ((Object)(object)Instance == (Object)null || Instance._templates.Count == 0)
			{
				return null;
			}
			string text = ((ShaderMode == null || ShaderMode.Value == null) ? "RealToon" : ShaderMode.Value.Trim());
			if (Instance._templates.TryGetValue(text, out var value))
			{
				return value;
			}
			if (ModeAliases.TryGetValue(text, out var value2) && Instance._templates.TryGetValue(value2, out value))
			{
				return value;
			}
			foreach (KeyValuePair<string, Material> template in Instance._templates)
			{
				if (template.Key.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return template.Value;
				}
			}
			LogMissingMode(text);
			using (Dictionary<string, Material>.ValueCollection.Enumerator enumerator2 = Instance._templates.Values.GetEnumerator())
			{
				if (enumerator2.MoveNext())
				{
					return enumerator2.Current;
				}
			}
			return null;
		}

		private static void LogMissingMode(string mode)
		{
			if (Log != null && !(_reportedMissingMode == mode))
			{
				_reportedMissingMode = mode;
				Log.LogWarning((object)("Unknown Toon.ShaderMode '" + mode + "', falling back to the first bundled material."));
			}
		}

		private bool LoadTemplate()
		{
			string text = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? string.Empty, "nikotoonskin.bundle");
			if (!File.Exists(text))
			{
				Log.LogError((object)("AssetBundle not found next to the plugin: " + text));
				return false;
			}
			AssetBundle val = AssetBundle.LoadFromFile(text);
			if ((Object)(object)val == (Object)null)
			{
				Log.LogError((object)("Unity refused to load the AssetBundle: " + text));
				return false;
			}
			Object[] array = val.LoadAllAssets();
			foreach (Object obj in array)
			{
				Material val2 = (Material)(object)((obj is Material) ? obj : null);
				if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.shader == (Object)null))
				{
					_templates[((Object)val2).name] = val2;
					Log.LogInfo((object)("Bundle material '" + ((Object)val2).name + "': shader '" + ((Object)val2.shader).name + "', " + val2.passCount + " passes, supported=" + val2.shader.isSupported));
				}
			}
			if (_templates.Count == 0)
			{
				Log.LogError((object)"The AssetBundle contains no material with a shader.");
				val.Unload(true);
				return false;
			}
			return true;
		}

		private void PatchFramework()
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Expected O, but got Unknown
			Type type = AccessTools.TypeByName("CustomSosigReplacer.Plugin");
			if ((object)type == null)
			{
				Log.LogWarning((object)"CustomSosigModelFramework (CustomSosigReplacer.Plugin) is not loaded, nothing to patch.");
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(type, "CreateMaterial", new Type[7]
			{
				typeof(string),
				typeof(string),
				typeof(string),
				typeof(Color),
				typeof(bool),
				typeof(float),
				typeof(float)
			}, (Type[])null);
			if ((object)methodInfo == null)
			{
				Log.LogWarning((object)"Could not find CustomSosigReplacer.Plugin.CreateMaterial with the expected signature; the framework build may have changed.");
				return;
			}
			new Harmony("niko666.customsosigtoonskin").Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(ToonSkinPlugin), "CreateMaterialPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Log.LogInfo((object)"Patched CustomSosigReplacer.Plugin.CreateMaterial.");
			PatchHandIkReach();
		}

		private static void PatchHandIkReach()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				new Harmony("niko666.customsosigtoonskin").PatchAll(typeof(HandIkReachPatch));
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Could not install the hand IK reach patch: " + ex.Message));
			}
		}

		private static void CreateMaterialPostfix(string name, string albedoPath, string normalPath, ref Material __result)
		{
			Material val = __result;
			if (!((Object)(object)val == (Object)null) && !((Object)(object)Instance == (Object)null) && MatchesNameFilter(((Object)val).name, albedoPath))
			{
				Material val2 = BuildToonMaterial(val, normalPath);
				if (!((Object)(object)val2 == (Object)null))
				{
					__result = val2;
					Object.Destroy((Object)(object)val);
					Log.LogDebug((object)("Toon material created for '" + name + "' from " + Path.GetFileName(albedoPath)));
				}
			}
		}

		private static bool MatchesNameFilter(string materialName, string albedoPath)
		{
			string text = ((NameFilter == null) ? null : NameFilter.Value);
			if (text == null)
			{
				return true;
			}
			text = text.Trim();
			if (text.Length == 0)
			{
				return true;
			}
			if (!string.IsNullOrEmpty(materialName) && materialName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			if (!string.IsNullOrEmpty(albedoPath) && albedoPath.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			if (Log != null)
			{
				Log.LogDebug((object)("Leaving '" + materialName + "' alone (Toon.NameFilter = " + text + ")"));
			}
			return false;
		}

		private static Material BuildToonMaterial(Material original, string normalPath)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			Material val = SelectedTemplate();
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Material val2 = new Material(val)
			{
				name = ((Object)original).name + " (toon)",
				mainTexture = original.mainTexture
			};
			if (((Object)val).name.IndexOf("Probe", StringComparison.OrdinalIgnoreCase) < 0)
			{
				val2.SetColor("_MainColor", original.color);
				val2.SetColor("_HighlightColor", Color.white);
				val2.SetColor("_OutlineColor", ParseColor(OutlineColorHex.Value, new Color(1f, 0.023529f, 0.709804f, 1f)));
				val2.SetFloat("_OutlineWidth", Mathf.Max(0f, OutlineWidth.Value));
				val2.SetColor("_RimLightColor", ParseColor(RimColorHex.Value, new Color(1f, 0.023529f, 0.709804f, 1f)));
				val2.SetFloat("_RimLightColorPower", Mathf.Max(0f, RimPower.Value));
				val2.SetFloat("_RimLightStrength", 1f);
				if (UseNormalMap.Value && val2.HasProperty("_NormalMap"))
				{
					Texture texture = original.GetTexture("_BumpMap");
					if ((Object)(object)texture != (Object)null)
					{
						val2.SetTexture("_NormalMap", texture);
						val2.SetFloat("_NormalMapIntensity", 1f);
					}
				}
				if (UseAlphaCutout.Value)
				{
					val2.SetFloat("_Cutout", 0.5f);
					val2.SetFloat("_AlphaBaseCutout", 1f);
					val2.SetFloat("_EnableTextureTransparent", 0f);
				}
			}
			val2.enableInstancing = EnableInstancing.Value;
			if (!OutlinePass.Value && val2.HasProperty("_OutlineWidth"))
			{
				val2.SetShaderPassEnabled("Outline", false);
			}
			if (Log != null)
			{
				Log.LogInfo((object)("Toon material '" + ((Object)val2).name + "': mode=" + ShaderMode.Value + ", instancing=" + val2.enableInstancing + ", keywords=[" + string.Join(",", val2.shaderKeywords) + "], passes=" + val2.passCount));
			}
			return val2;
		}

		private static Color ParseColor(string text, Color fallback)
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			string text2 = (text ?? string.Empty).Trim().TrimStart(new char[1] { '#' });
			if (text2.Length == 3)
			{
				text2 = string.Concat(text2[0], text2[0], text2[1], text2[1], text2[2], text2[2]);
			}
			if (text2.Length != 6)
			{
				return fallback;
			}
			if (!uint.TryParse(text2, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
			{
				return fallback;
			}
			return new Color((float)((result >> 16) & 0xFF) / 255f, (float)((result >> 8) & 0xFF) / 255f, (float)(result & 0xFF) / 255f, 1f);
		}
	}
}