Decompiled source of PunyaFeiopar v1.2.2

BepInEx/plugins/PunyaFeiopar/FeioparReskin.dll

Decompiled a week ago
using System;
using System.Buffers.Binary;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.0.0.0")]
[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;
		}
	}
}
[BepInPlugin("local.feioparreskin", "Feiopar Reskin", "1.2.2")]
public sealed class FeioparReskin : BaseUnityPlugin
{
	private const string PluginId = "local.feioparreskin";

	private const string SkinFileName = "Feiopar.png";

	private const int MaxPngBytes = 33554432;

	private const int MaxDimension = 4096;

	private static FeioparReskin instance;

	private static readonly int BaseColorMap = Shader.PropertyToID("_BaseColorMap");

	private static readonly int EmissiveColor = Shader.PropertyToID("_EmissiveColor");

	private Texture2D skin;

	private PunyaGeometry geometry;

	private bool reportedApplication;

	private void Awake()
	{
		//IL_0233: Unknown result type (might be due to invalid IL or missing references)
		//IL_024b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0258: Expected O, but got Unknown
		ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>("Appearance", "UsePunyaMesh", true, "Use the optional Punya.patch beside the skin and soften the eye glow.");
		string directoryName = Path.GetDirectoryName(typeof(FeioparReskin).Assembly.Location);
		string path = Path.Combine(directoryName, "Feiopar.png");
		try
		{
			skin = LoadSkin(path);
		}
		catch (FileNotFoundException)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Put Feiopar.png beside the DLL to enable the reskin.");
			return;
		}
		catch (Exception ex2) when (ex2 is IOException || ex2 is UnauthorizedAccessException)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Cannot read Feiopar.png: " + ex2.Message));
			return;
		}
		if ((Object)(object)skin == (Object)null)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)string.Format("Invalid {0}. Use a PNG up to {1}×{2} and 32 MiB.", "Feiopar.png", 4096, 4096));
			return;
		}
		string path2 = Path.Combine(directoryName, "Punya.mesh");
		string text = Path.Combine(directoryName, "Punya.patch");
		if (val.Value && (File.Exists(text) || File.Exists(path2)))
		{
			try
			{
				if (File.Exists(text))
				{
					using (MemoryStream data = new MemoryStream(PunyaPatch.Rebuild(text, Path.Combine(Application.dataPath, "sharedassets1.assets")), writable: false))
					{
						geometry = PunyaGeometry.Load(data);
					}
					((BaseUnityPlugin)this).Logger.LogInfo((object)"Rebuilt Punya geometry from local Steam v81 assets.");
				}
				else
				{
					using FileStream data2 = File.OpenRead(path2);
					geometry = PunyaGeometry.Load(data2);
				}
			}
			catch (Exception ex3) when (ex3 is IOException || ex3 is UnauthorizedAccessException || ex3 is ArgumentException || ex3 is InvalidOperationException || ex3 is OverflowException || ex3 is CryptographicException)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not load the Punya geometry. Using the stock shape. " + ex3.Message));
			}
		}
		instance = this;
		MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(PumaAI), "Start", Type.EmptyTypes, (Type[])null);
		new Harmony("local.feioparreskin").Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(FeioparReskin), "ApplySkin", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("Loaded {0} ({1}×{2}).", "Feiopar.png", ((Texture)skin).width, ((Texture)skin).height));
	}

	private static Texture2D LoadSkin(string path)
	{
		//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ff: Expected O, but got Unknown
		byte[] array;
		using (FileStream fileStream = File.OpenRead(path))
		{
			using BinaryReader binaryReader = new BinaryReader(fileStream);
			if (fileStream.Length < 33 || fileStream.Length > 33554432)
			{
				return null;
			}
			array = binaryReader.ReadBytes((int)fileStream.Length);
		}
		if (array.Length < 33 || BinaryPrimitives.ReadUInt64BigEndian(array.AsSpan(0, 8)) != 9894494448401390090uL || BinaryPrimitives.ReadUInt32BigEndian(array.AsSpan(8, 4)) != 13 || BinaryPrimitives.ReadUInt32BigEndian(array.AsSpan(12, 4)) != 1229472850)
		{
			return null;
		}
		uint num = BinaryPrimitives.ReadUInt32BigEndian(array.AsSpan(16, 4));
		uint num2 = BinaryPrimitives.ReadUInt32BigEndian(array.AsSpan(20, 4));
		if (num == 0 || num2 == 0 || num > 4096 || num2 > 4096)
		{
			return null;
		}
		Texture2D val = new Texture2D(2, 2, (TextureFormat)4, true, false);
		if (!ImageConversion.LoadImage(val, array, true))
		{
			Object.Destroy((Object)(object)val);
			return null;
		}
		((Object)val).name = "FeioparReskin_BaseColor";
		((Texture)val).filterMode = (FilterMode)2;
		return val;
	}

	private static void ApplySkin(PumaAI __instance)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Expected O, but got Unknown
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a1: 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)
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
		FeioparReskin feioparReskin = instance;
		MaterialPropertyBlock val = new MaterialPropertyBlock();
		SkinnedMeshRenderer[] array = Array.FindAll(((Component)__instance).GetComponentsInChildren<SkinnedMeshRenderer>(true), HasStockColor);
		bool flag = feioparReskin.geometry != null && feioparReskin.geometry.TryApply(array);
		SkinnedMeshRenderer[] array2 = array;
		foreach (SkinnedMeshRenderer obj in array2)
		{
			Material sharedMaterial = ((Renderer)obj).sharedMaterial;
			((Renderer)obj).GetPropertyBlock(val);
			val.SetTexture(BaseColorMap, (Texture)(object)feioparReskin.skin);
			if (flag && sharedMaterial.HasProperty(EmissiveColor))
			{
				Color color = sharedMaterial.GetColor(EmissiveColor);
				val.SetColor(EmissiveColor, new Color(color.r * 0.24f, color.g * 0.24f, color.b * 0.24f, color.a));
			}
			((Renderer)obj).SetPropertyBlock(val);
		}
		if (!feioparReskin.reportedApplication)
		{
			feioparReskin.reportedApplication = true;
			if (array.Length == 0)
			{
				((BaseUnityPlugin)feioparReskin).Logger.LogWarning((object)"Could not find the stock Feiopar color texture on the spawned enemy.");
			}
			else
			{
				((BaseUnityPlugin)feioparReskin).Logger.LogInfo((object)string.Format("Applied {0} to {1} Feiopar renderer(s).", "Feiopar.png", array.Length));
			}
			if (feioparReskin.geometry != null)
			{
				((BaseUnityPlugin)feioparReskin).Logger.LogInfo((object)(flag ? "Applied Punya geometry to all three Feiopar LODs." : "Stock Feiopar rig did not match the Punya geometry. Used texture only."));
			}
		}
	}

	private static bool HasStockColor(SkinnedMeshRenderer renderer)
	{
		Material sharedMaterial = ((Renderer)renderer).sharedMaterial;
		if ((Object)(object)sharedMaterial == (Object)null || !sharedMaterial.HasProperty(BaseColorMap))
		{
			return false;
		}
		Texture texture = sharedMaterial.GetTexture(BaseColorMap);
		if ((Object)(object)texture != (Object)null)
		{
			return ((Object)texture).name == "PumaTextureCombined";
		}
		return false;
	}
}
internal sealed class PunyaGeometry
{
	private sealed class Lod
	{
		public Mesh Mesh;

		public int OriginalVertexCount;

		public string[] BoneNames;
	}

	private static readonly string[] LodNames = new string[3] { "BigCatLOD0b", "BigCatLOD1", "BigCatLOD2" };

	private const int BoneCount = 38;

	private readonly Lod[] lods = new Lod[LodNames.Length];

	public static PunyaGeometry Load(Stream data)
	{
		PunyaGeometry punyaGeometry = new PunyaGeometry();
		try
		{
			using (BinaryReader binaryReader = new BinaryReader(data, Encoding.UTF8, leaveOpen: true))
			{
				if (data.Length > 2097152 || binaryReader.ReadUInt32() != 1297698384 || binaryReader.ReadInt32() != 1 || binaryReader.ReadInt32() != LodNames.Length)
				{
					throw new IOException("Invalid mesh header.");
				}
				for (int i = 0; i < LodNames.Length; i++)
				{
					punyaGeometry.lods[i] = ReadLod(binaryReader, LodNames[i]);
				}
				if (data.Position != data.Length)
				{
					throw new IOException("Unexpected bytes after the LOD data.");
				}
			}
			return punyaGeometry;
		}
		catch
		{
			Lod[] array = punyaGeometry.lods;
			foreach (Lod lod in array)
			{
				if (lod != null)
				{
					Object.Destroy((Object)(object)lod.Mesh);
				}
			}
			throw;
		}
	}

	private static Lod ReadLod(BinaryReader reader, string name)
	{
		//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
		//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)
		//IL_015f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0164: Unknown result type (might be due to invalid IL or missing references)
		//IL_0189: Unknown result type (might be due to invalid IL or missing references)
		//IL_018e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0294: Unknown result type (might be due to invalid IL or missing references)
		//IL_0299: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ac: Expected O, but got Unknown
		int num = reader.ReadInt32();
		int num2 = reader.ReadInt32();
		int num3 = reader.ReadInt32();
		if (num < 1 || num2 < num || num2 > 12000 || num3 < 3 || num3 > 90000 || num3 % 3 != 0)
		{
			throw new IOException("Invalid mesh size.");
		}
		string[] array = new string[38];
		Matrix4x4[] array2 = (Matrix4x4[])(object)new Matrix4x4[38];
		for (int i = 0; i < 38; i++)
		{
			array[i] = reader.ReadString();
		}
		for (int j = 0; j < 38; j++)
		{
			for (int k = 0; k < 4; k++)
			{
				for (int l = 0; l < 4; l++)
				{
					((Matrix4x4)(ref array2[j]))[k, l] = ReadFinite(reader);
				}
			}
		}
		Vector3[] array3 = (Vector3[])(object)new Vector3[num2];
		Vector3[] array4 = (Vector3[])(object)new Vector3[num2];
		Vector4[] array5 = (Vector4[])(object)new Vector4[num2];
		Vector2[] array6 = (Vector2[])(object)new Vector2[num2];
		BoneWeight[] array7 = (BoneWeight[])(object)new BoneWeight[num2];
		for (int m = 0; m < num2; m++)
		{
			array3[m] = new Vector3(ReadFinite(reader), ReadFinite(reader), ReadFinite(reader));
		}
		for (int n = 0; n < num2; n++)
		{
			array4[n] = new Vector3(ReadFinite(reader), ReadFinite(reader), ReadFinite(reader));
		}
		for (int num4 = 0; num4 < num2; num4++)
		{
			array5[num4] = new Vector4(ReadFinite(reader), ReadFinite(reader), ReadFinite(reader), ReadFinite(reader));
		}
		for (int num5 = 0; num5 < num2; num5++)
		{
			array6[num5] = new Vector2(ReadFinite(reader), ReadFinite(reader));
		}
		for (int num6 = 0; num6 < num2; num6++)
		{
			((BoneWeight)(ref array7[num6])).weight0 = ReadFinite(reader);
			((BoneWeight)(ref array7[num6])).weight1 = ReadFinite(reader);
			((BoneWeight)(ref array7[num6])).weight2 = ReadFinite(reader);
			((BoneWeight)(ref array7[num6])).weight3 = ReadFinite(reader);
		}
		for (int num7 = 0; num7 < num2; num7++)
		{
			((BoneWeight)(ref array7[num7])).boneIndex0 = ReadBoneIndex(reader);
			((BoneWeight)(ref array7[num7])).boneIndex1 = ReadBoneIndex(reader);
			((BoneWeight)(ref array7[num7])).boneIndex2 = ReadBoneIndex(reader);
			((BoneWeight)(ref array7[num7])).boneIndex3 = ReadBoneIndex(reader);
		}
		int[] array8 = new int[num3];
		for (int num8 = 0; num8 < num3; num8++)
		{
			array8[num8] = reader.ReadUInt16();
			if (array8[num8] >= num2)
			{
				throw new IOException("Triangle references a missing vertex.");
			}
		}
		Mesh val = new Mesh
		{
			name = "Punya_" + name
		};
		try
		{
			val.vertices = array3;
			val.normals = array4;
			val.tangents = array5;
			val.uv = array6;
			val.bindposes = array2;
			val.boneWeights = array7;
			val.subMeshCount = 1;
			val.SetTriangles(array8, 0);
			return new Lod
			{
				Mesh = val,
				OriginalVertexCount = num,
				BoneNames = array
			};
		}
		catch
		{
			Object.Destroy((Object)(object)val);
			throw;
		}
	}

	private static float ReadFinite(BinaryReader reader)
	{
		float num = reader.ReadSingle();
		if (float.IsNaN(num) || float.IsInfinity(num))
		{
			throw new IOException("Non-finite mesh coordinate.");
		}
		return num;
	}

	private static int ReadBoneIndex(BinaryReader reader)
	{
		byte num = reader.ReadByte();
		if (num >= 38)
		{
			throw new IOException("Bone index out of range.");
		}
		return num;
	}

	public bool TryApply(SkinnedMeshRenderer[] renderers)
	{
		//IL_0100: Unknown result type (might be due to invalid IL or missing references)
		//IL_0105: 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)
		SkinnedMeshRenderer[] array = (SkinnedMeshRenderer[])(object)new SkinnedMeshRenderer[LodNames.Length];
		for (int i = 0; i < LodNames.Length; i++)
		{
			string name = LodNames[i];
			SkinnedMeshRenderer val = Array.Find(renderers, (SkinnedMeshRenderer renderer) => (Object)(object)renderer.sharedMesh != (Object)null && ((Object)renderer.sharedMesh).name == name && renderer.sharedMesh.vertexCount == lods[i].OriginalVertexCount);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Transform[] bones = val.bones;
			if (bones.Length != 38)
			{
				return false;
			}
			for (int num = 0; num < 38; num++)
			{
				if ((Object)(object)bones[num] == (Object)null || ((Object)bones[num]).name != lods[i].BoneNames[num])
				{
					return false;
				}
			}
			array[i] = val;
		}
		for (int num2 = 0; num2 < array.Length; num2++)
		{
			Bounds localBounds = ((Renderer)array[num2]).localBounds;
			((Bounds)(ref localBounds)).Expand(0.16f);
			array[num2].sharedMesh = lods[num2].Mesh;
			((Renderer)array[num2]).localBounds = localBounds;
		}
		return true;
	}
}
internal static class PunyaPatch
{
	private sealed class StockLod
	{
		public readonly int Vertices;

		public readonly int VertexOffset;

		public readonly int VertexBytes;

		public readonly int IndexOffset;

		public readonly int IndexBytes;

		public readonly int PoseOffset;

		public readonly string Hash;

		public StockLod(int vertices, int vertexOffset, int vertexBytes, int indexOffset, int indexBytes, int poseOffset, string hash)
		{
			Vertices = vertices;
			VertexOffset = vertexOffset;
			VertexBytes = vertexBytes;
			IndexOffset = indexOffset;
			IndexBytes = indexBytes;
			PoseOffset = poseOffset;
			Hash = hash;
		}
	}

	private static readonly StockLod[] Stock = new StockLod[3]
	{
		new StockLod(4947, 9930836, 375984, 9899220, 31548, 9895696, "a4ee81eec697b8af617b6686ef66f49e05f6b1ca61e9a9a5989cbc78a48053b9"),
		new StockLod(2877, 15896648, 218672, 15882388, 14190, 15878864, "10c044d34cdc6db7c107a1aa6c8fa5b409be8ff5cb5b92a857f2ddef04e3ec02"),
		new StockLod(1506, 15686524, 114464, 15681236, 5220, 15677712, "dfb6d62d9b4b5831780a05070069dba783abf7599552b9c9f79d2b2b96b36943")
	};

	private const int PoseBytes = 2432;

	public static byte[] Rebuild(string patchPath, string gameAssetPath)
	{
		using BinaryReader binaryReader = new BinaryReader(File.OpenRead(patchPath));
		using BinaryReader source = new BinaryReader(File.OpenRead(gameAssetPath));
		using MemoryStream memoryStream = new MemoryStream();
		using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
		if (binaryReader.BaseStream.Length > 524288 || binaryReader.ReadUInt32() != 1146703440 || binaryReader.ReadInt32() != 1)
		{
			throw new IOException("Invalid Punya.patch header.");
		}
		binaryWriter.Write(1297698384u);
		binaryWriter.Write(1);
		binaryWriter.Write(Stock.Length);
		StockLod[] stock = Stock;
		foreach (StockLod stock2 in stock)
		{
			RebuildLod(binaryReader, source, binaryWriter, stock2);
		}
		if (binaryReader.BaseStream.Position != binaryReader.BaseStream.Length)
		{
			throw new IOException("Unexpected data after the patch.");
		}
		return memoryStream.ToArray();
	}

	private static void RebuildLod(BinaryReader patch, BinaryReader source, BinaryWriter output, StockLod stock)
	{
		int num = patch.ReadInt32();
		int num2 = patch.ReadInt32();
		if (num < 0 || num > 1000 || num2 < 0 || num2 > 10000 || num2 % 3 != 0)
		{
			throw new IOException("Invalid Punya.patch LOD sizes.");
		}
		byte[] array = ReadBlock(source, stock.VertexOffset, stock.VertexBytes);
		byte[] array2 = ReadBlock(source, stock.IndexOffset, stock.IndexBytes);
		byte[] array3 = ReadBlock(source, stock.PoseOffset, 2432);
		using (SHA256 sHA = SHA256.Create())
		{
			sHA.TransformBlock(array, 0, array.Length, array, 0);
			sHA.TransformBlock(array2, 0, array2.Length, array2, 0);
			sHA.TransformFinalBlock(array3, 0, array3.Length);
			if (!string.Equals(BitConverter.ToString(sHA.Hash).Replace("-", ""), stock.Hash, StringComparison.OrdinalIgnoreCase))
			{
				throw new IOException("Installed Feiopar mesh data differs from the supported Steam v81 build.");
			}
		}
		int vertices = stock.Vertices;
		int num3 = (vertices * 40 + 15) & -16;
		int num4 = (num3 + vertices * 4 + 15) & -16;
		output.Write(vertices);
		output.Write(vertices + num);
		output.Write(array2.Length / 2 + num2);
		for (int i = 0; i < 38; i++)
		{
			output.Write(patch.ReadString());
		}
		output.Write(array3);
		for (int j = 0; j < vertices; j++)
		{
			for (int k = 0; k < 3; k++)
			{
				output.Write(BitConverter.ToSingle(array, j * 40 + k * 4) + patch.ReadSingle());
			}
		}
		output.Write(ReadBytes(patch, num * 12));
		WriteStockAndExtras(output, patch, array, 12, 40, 12, vertices, num);
		WriteStockAndExtras(output, patch, array, 24, 40, 16, vertices, num);
		for (int l = 0; l < vertices * 2; l++)
		{
			output.Write(HalfToFloat(BitConverter.ToUInt16(array, num3 + l * 2)));
		}
		output.Write(ReadBytes(patch, num * 8));
		WriteStockAndExtras(output, patch, array, num4, 32, 16, vertices, num);
		for (int m = 0; m < vertices; m++)
		{
			for (int n = 0; n < 4; n++)
			{
				output.Write(checked((byte)BitConverter.ToUInt32(array, num4 + m * 32 + 16 + n * 4)));
			}
		}
		output.Write(ReadBytes(patch, num * 4));
		output.Write(array2);
		output.Write(ReadBytes(patch, num2 * 2));
	}

	private static void WriteStockAndExtras(BinaryWriter output, BinaryReader patch, byte[] vertices, int start, int stride, int bytes, int count, int extras)
	{
		for (int i = 0; i < count; i++)
		{
			output.Write(vertices, start + stride * i, bytes);
		}
		output.Write(ReadBytes(patch, extras * bytes));
	}

	private static byte[] ReadBlock(BinaryReader reader, int offset, int count)
	{
		reader.BaseStream.Position = offset;
		return ReadBytes(reader, count);
	}

	private static byte[] ReadBytes(BinaryReader reader, int count)
	{
		byte[] array = reader.ReadBytes(count);
		if (array.Length != count)
		{
			throw new EndOfStreamException();
		}
		return array;
	}

	private static float HalfToFloat(ushort value)
	{
		int num = (value >> 10) & 0x1F;
		int num2 = value & 0x3FF;
		return (float)((double)(((value & 0x8000) == 0) ? 1 : (-1)) * num switch
		{
			31 => throw new IOException("Non-finite UV."), 
			0 => (double)num2 * Math.Pow(2.0, -24.0), 
			_ => (1.0 + (double)num2 / 1024.0) * Math.Pow(2.0, num - 15), 
		});
	}
}