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