Decompiled source of PaintingReplacer v0.0.2

plugins/PaintingReplacer/PaintingReplacer.dll

Decompiled 2 weeks ago
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("")]
[assembly: AssemblyCompany("tsharp42")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+69530554098175ea97fd1d77707bf3e913105835")]
[assembly: AssemblyProduct("PaintingReplacer")]
[assembly: AssemblyTitle("PaintingReplacer")]
[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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 PaintingReplacer
{
	internal static class ConfigLoader
	{
		private static string DefaultSearchDirectory = "PaintingsConfig";

		public static string[] GetAllConfigFiles()
		{
			string[] configPaths = GetConfigPaths();
			List<string> list = new List<string>();
			string[] array = configPaths;
			foreach (string path in array)
			{
				string[] collection = (from file in Directory.EnumerateFiles(path)
					where file.Contains(".paintings.json")
					select file).ToArray();
				list.AddRange(collection);
			}
			return list.ToArray();
		}

		private static string[] GetConfigPaths()
		{
			string bepInExRootPath = Paths.BepInExRootPath;
			string path = Path.Combine(Paths.BepInExRootPath);
			List<string> list = new List<string>();
			list.AddRange(Directory.GetDirectories(path, DefaultSearchDirectory, SearchOption.AllDirectories));
			return list.ToArray();
		}
	}
	public static class ImageDimensions
	{
		public static (int Width, int Height) GetDimensions(string path)
		{
			using FileStream fileStream = File.OpenRead(path);
			Span<byte> buffer = stackalloc byte[8];
			fileStream.Read(buffer);
			if (buffer[0] == 137 && buffer[1] == 80 && buffer[2] == 78 && buffer[3] == 71)
			{
				return GetPngDimensions(fileStream);
			}
			if (buffer[0] == byte.MaxValue && buffer[1] == 216)
			{
				fileStream.Position = 0L;
				return GetJpegDimensions(fileStream);
			}
			if (buffer[0] == 66 && buffer[1] == 77)
			{
				fileStream.Position = 0L;
				return GetBmpDimensions(fileStream);
			}
			if (buffer[0] == 82 && buffer[1] == 73 && buffer[2] == 70 && buffer[3] == 70)
			{
				fileStream.Position = 8L;
				Span<byte> buffer2 = stackalloc byte[4];
				fileStream.Read(buffer2);
				if (buffer2[0] == 87 && buffer2[1] == 69 && buffer2[2] == 66 && buffer2[3] == 80)
				{
					fileStream.Position = 0L;
					return GetWebpDimensions(fileStream);
				}
			}
			return (Width: -1, Height: -1);
		}

		private static (int Width, int Height) GetPngDimensions(Stream s)
		{
			s.Position = 16L;
			Span<byte> buffer = stackalloc byte[8];
			s.Read(buffer);
			int item = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(0, 4));
			int item2 = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(4, 4));
			return (Width: item, Height: item2);
		}

		private static (int Width, int Height) GetBmpDimensions(Stream s)
		{
			s.Position = 18L;
			Span<byte> buffer = stackalloc byte[8];
			s.Read(buffer);
			int item = BitConverter.ToInt32(buffer.Slice(0, 4));
			int item2 = BitConverter.ToInt32(buffer.Slice(4, 4));
			return (Width: item, Height: item2);
		}

		private static (int Width, int Height) GetJpegDimensions(Stream s)
		{
			using BinaryReader binaryReader = new BinaryReader(s);
			binaryReader.ReadUInt16();
			while (s.Position < s.Length)
			{
				if (binaryReader.ReadByte() != byte.MaxValue)
				{
					continue;
				}
				byte b = binaryReader.ReadByte();
				while (true)
				{
					switch (b)
					{
					case byte.MaxValue:
						goto IL_002c;
					default:
					{
						ushort num = ReadBigEndianUInt16(binaryReader);
						if ((b >= 192 && b <= 195) || (b >= 197 && b <= 199) || (b >= 201 && b <= 203) || (b >= 205 && b <= 207))
						{
							binaryReader.ReadByte();
							int item = ReadBigEndianUInt16(binaryReader);
							int item2 = ReadBigEndianUInt16(binaryReader);
							return (Width: item2, Height: item);
						}
						s.Position += num - 2;
						break;
					}
					case 216:
					case 217:
						break;
					}
					break;
					IL_002c:
					b = binaryReader.ReadByte();
				}
			}
			throw new InvalidDataException("Could not find JPEG dimensions.");
		}

		private static ushort ReadBigEndianUInt16(BinaryReader br)
		{
			int num = br.ReadByte();
			int num2 = br.ReadByte();
			return (ushort)((num << 8) | num2);
		}

		private static (int Width, int Height) GetWebpDimensions(Stream s)
		{
			using BinaryReader binaryReader = new BinaryReader(s);
			s.Position = 12L;
			string text = new string(binaryReader.ReadChars(4));
			uint num = binaryReader.ReadUInt32();
			switch (text)
			{
			case "VP8 ":
			{
				s.Position += 10L;
				ushort num5 = binaryReader.ReadUInt16();
				ushort num6 = binaryReader.ReadUInt16();
				return (Width: num5 & 0x3FFF, Height: num6 & 0x3FFF);
			}
			case "VP8L":
			{
				binaryReader.ReadByte();
				uint num4 = (uint)(binaryReader.ReadByte() | (binaryReader.ReadByte() << 8) | (binaryReader.ReadByte() << 16) | (binaryReader.ReadByte() << 24));
				int item = (int)((num4 & 0x3FFF) + 1);
				int item2 = (int)(((num4 >> 14) & 0x3FFF) + 1);
				return (Width: item, Height: item2);
			}
			case "VP8X":
			{
				s.Position += 4L;
				int num2 = binaryReader.ReadByte() | (binaryReader.ReadByte() << 8) | (binaryReader.ReadByte() << 16);
				int num3 = binaryReader.ReadByte() | (binaryReader.ReadByte() << 8) | (binaryReader.ReadByte() << 16);
				return (Width: num2 + 1, Height: num3 + 1);
			}
			default:
				return (Width: -1, Height: -1);
			}
		}
	}
	internal class ImageFile
	{
		public enum ImageAspectRatioType : byte
		{
			Landscape,
			Portrait,
			Square
		}

		private string? _fileName;

		private string? _fileNameHash;

		private float _ratio;

		private int _width;

		private int _height;

		public string? FileNameHash => _fileNameHash;

		public string? FullPath
		{
			get
			{
				return _fileName;
			}
			set
			{
				_fileName = value;
				if (!File.Exists(value))
				{
					_fileName = null;
				}
				if (value != null)
				{
					_fileNameHash = ImageFileHelper.GetImageFileHash(RelativePath ?? "");
					var (num, num2) = ImageDimensions.GetDimensions(value);
					if (num > 0 && num2 > 0)
					{
						_width = num;
						_height = num2;
						_ratio = (float)num / (float)num2;
					}
				}
			}
		}

		public string? RelativePath
		{
			get
			{
				if (_fileName != null)
				{
					return _fileName.Replace(Paths.BepInExRootPath, "");
				}
				return null;
			}
		}

		public float Ratio => _ratio;

		public int Width => _width;

		public int Height => _height;

		public bool IsValid
		{
			get
			{
				if (_fileName == null)
				{
					return false;
				}
				if (_fileNameHash == null)
				{
					return false;
				}
				if (_width == -1 || _height == -1)
				{
					return false;
				}
				return true;
			}
		}

		public ImageAspectRatioType AspectRatioType
		{
			get
			{
				if (_ratio > 1.3f)
				{
					return ImageAspectRatioType.Landscape;
				}
				if (_ratio < 0.85714287f)
				{
					return ImageAspectRatioType.Portrait;
				}
				return ImageAspectRatioType.Square;
			}
		}
	}
	internal static class ImageFileHelper
	{
		private static string[] Extensions = new string[4] { ".png", ".jpg", ".jpeg", ".bmp" };

		private static string[] DefaultSearchDirectories = new string[1] { "CustomPaintings" };

		private static MD5 MD5Hasher = MD5.Create();

		public static string[] GetImagePaths(string[]? imageDirectories = null)
		{
			if (imageDirectories == null)
			{
				imageDirectories = DefaultSearchDirectories;
			}
			string bepInExRootPath = Paths.BepInExRootPath;
			string path = Path.Combine(Paths.BepInExRootPath);
			List<string> list = new List<string>();
			string[] array = imageDirectories;
			foreach (string searchPattern in array)
			{
				list.AddRange(Directory.GetDirectories(path, searchPattern, SearchOption.AllDirectories));
			}
			return list.ToArray();
		}

		public static ImageFile[] GetImageFiles(string[] ImagePaths)
		{
			List<ImageFile> list = new List<ImageFile>();
			List<string> list2 = new List<string>();
			foreach (string path in ImagePaths)
			{
				string[] array = (from file in Directory.EnumerateFiles(path)
					where Extensions.Contains(Path.GetExtension(file).ToLower())
					select file).ToArray();
				string[] array2 = array;
				foreach (string fullPath in array2)
				{
					ImageFile imageFile = new ImageFile
					{
						FullPath = fullPath
					};
					if (imageFile.IsValid)
					{
						list.Add(new ImageFile
						{
							FullPath = fullPath
						});
					}
				}
			}
			list.OrderBy((ImageFile f) => f.FileNameHash);
			return list.ToArray();
		}

		public static string GetRelativePath(string path)
		{
			return path.Replace(Paths.BepInExRootPath, "");
		}

		public static string GetImageFileHash(string path)
		{
			string s = Path.GetFileName(path).ToLowerInvariant();
			byte[] array = MD5Hasher.ComputeHash(Encoding.UTF8.GetBytes(s));
			StringBuilder stringBuilder = new StringBuilder();
			byte[] array2 = array;
			foreach (byte b in array2)
			{
				stringBuilder.Append(b.ToString("X2"));
			}
			return stringBuilder.ToString();
		}

		public static ImageFile? GetReplacementImage(ImageFile[] images, Vector3 position, ImageFile.ImageAspectRatioType aspectRatioType, string targetName, ManualLogSource logger)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: 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)
			List<ImageFile> list = (from x in images
				where x.AspectRatioType == aspectRatioType
				orderby x.FileNameHash
				select x).ToList();
			if (list.Count == 0)
			{
				logger.LogWarning((object)$"No replacement images found for {aspectRatioType}");
				return null;
			}
			string text = $"{Mathf.RoundToInt(position.x * 10f)},{Mathf.RoundToInt(position.y * 10f)},{Mathf.RoundToInt(position.z * 10f)}:{targetName}:{aspectRatioType}";
			byte[] value = MD5Hasher.ComputeHash(Encoding.UTF8.GetBytes(text));
			int num = BitConverter.ToInt32(value, 0) & 0x7FFFFFFF;
			ImageFile imageFile = list[num % list.Count];
			logger.LogInfo((object)$"-- Image Selection Input: {num} = {text}");
			logger.LogInfo((object)("-- Swap: " + targetName + " -> [" + imageFile.FileNameHash + "]"));
			return imageFile;
		}
	}
	[BepInPlugin("tsharp42.PaintingReplacer", "PaintingReplacer", "0.0.2")]
	public class PaintingReplacer : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(PlayerAvatar), "LoadingLevelAnimationCompletedRPC")]
		public class PaintingSwapPatch
		{
			private static void Postfix()
			{
				doSwap = true;
			}
		}

		private static ImageFile[] replacementImages = new ImageFile[0];

		private static bool doSwap = false;

		private static Dictionary<string, ImageFile.ImageAspectRatioType> paintingReferences = new Dictionary<string, ImageFile.ImageAspectRatioType>();

		private static List<Texture2D> loadedTextures = new List<Texture2D>();

		private static List<Material> loadedMaterials = new List<Material>();

		internal static PaintingReplacer Instance { get; private set; } = null;

		internal static ManualLogSource Logger => Instance._logger;

		private ManualLogSource _logger => ((BaseUnityPlugin)this).Logger;

		internal Harmony? Harmony { get; set; }

		private void Awake()
		{
			Instance = this;
			((Component)this).gameObject.transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			Patch();
			Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} Begin initialisation");
			string[] imagePaths = ImageFileHelper.GetImagePaths();
			Logger.LogInfo((object)$"{imagePaths.Length} image paths found, Loading Images...");
			Stopwatch stopwatch = Stopwatch.StartNew();
			replacementImages = ImageFileHelper.GetImageFiles(imagePaths);
			Logger.LogInfo((object)$"{replacementImages.Length} file paths found");
			ImageFile[] array = replacementImages;
			foreach (ImageFile imageFile in array)
			{
				Logger.LogDebug((object)("Image File: \nPath : " + imageFile.RelativePath + "\nHash : " + imageFile.FileNameHash + "\nRatio: " + imageFile.Ratio + "\nClassification: " + imageFile.AspectRatioType.ToString() + "\nSize : " + imageFile.Width + " x " + imageFile.Height));
			}
			stopwatch.Stop();
			Logger.LogInfo((object)$"Loaded {replacementImages.Length} images in {stopwatch.Elapsed.TotalSeconds}s");
			string[] allConfigFiles = ConfigLoader.GetAllConfigFiles();
			Logger.LogInfo((object)$"Config files found: {allConfigFiles.Length}");
			string[] array2 = allConfigFiles;
			foreach (string text in array2)
			{
				Logger.LogInfo((object)("Loading Painting Config: " + text.Replace(Paths.BepInExRootPath, "")));
				if (!File.Exists(text))
				{
					continue;
				}
				string text2 = File.ReadAllText(text);
				Dictionary<string, ImageFile.ImageAspectRatioType> dictionary = JsonConvert.DeserializeObject<Dictionary<string, ImageFile.ImageAspectRatioType>>(text2);
				if (dictionary == null)
				{
					continue;
				}
				Logger.LogInfo((object)$"Replacements in this config: {dictionary.Count}");
				foreach (KeyValuePair<string, ImageFile.ImageAspectRatioType> item in dictionary)
				{
					Logger.LogDebug((object)$"{item.Key}:{item.Value}");
					if (!paintingReferences.TryAdd(item.Key, item.Value))
					{
						Logger.LogWarning((object)("Duplicate painting config: " + item.Key + " in " + text));
						paintingReferences[item.Key] = item.Value;
					}
				}
			}
		}

		internal void Patch()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0025: Expected O, but got Unknown
			if (Harmony == null)
			{
				Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
				Harmony val2 = val;
				Harmony = val;
			}
			Harmony.PatchAll();
		}

		internal void Unpatch()
		{
			Harmony? harmony = Harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			//IL_022e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			//IL_026a: Expected O, but got Unknown
			//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b1: Expected O, but got Unknown
			if (!doSwap)
			{
				return;
			}
			doSwap = false;
			Logger.LogDebug((object)$"Destroying previously allocated textures: {loadedTextures.Count}");
			foreach (Texture2D loadedTexture in loadedTextures)
			{
				Object.Destroy((Object)(object)loadedTexture);
			}
			loadedTextures.Clear();
			Logger.LogDebug((object)$"Destroying previously allocated materials: {loadedMaterials.Count}");
			foreach (Material loadedMaterial in loadedMaterials)
			{
				Object.Destroy((Object)(object)loadedMaterial);
			}
			loadedMaterials.Clear();
			int num = 0;
			Scene activeScene = SceneManager.GetActiveScene();
			GameObject val = ((IEnumerable<GameObject>)((Scene)(ref activeScene)).GetRootGameObjects()).FirstOrDefault((Func<GameObject, bool>)((GameObject g) => ((Object)g).name == "Level Generator"));
			if ((Object)(object)val == (Object)null)
			{
				Logger.LogError((object)"Could not find 'Level Generator'");
				return;
			}
			MeshRenderer[] componentsInChildren = val.GetComponentsInChildren<MeshRenderer>();
			foreach (MeshRenderer val2 in componentsInChildren)
			{
				Material[] sharedMaterials = ((Renderer)val2).sharedMaterials;
				bool flag = false;
				for (int num3 = 0; num3 < sharedMaterials.Length; num3++)
				{
					if (!Object.op_Implicit((Object)(object)sharedMaterials[num3]))
					{
						Logger.LogDebug((object)"No material");
						continue;
					}
					string text = ((Object)sharedMaterials[num3]).name.Trim();
					if (paintingReferences.TryGetValue(text, out var aspectRatioType))
					{
						int num4 = replacementImages.Count((ImageFile im) => im.AspectRatioType == aspectRatioType);
						Logger.LogDebug((object)$"- {((Object)val2).name} > {text} ({aspectRatioType}) {num4}");
						if (num4 == 0)
						{
							Logger.LogWarning((object)$"-- Tried to swap on type {aspectRatioType} but there are no images of this type");
							continue;
						}
						Vector3 position = ((Component)val2).transform.position;
						ImageFile replacementImage = ImageFileHelper.GetReplacementImage(replacementImages, position, aspectRatioType, text, Logger);
						if (replacementImage != null)
						{
							byte[] array = File.ReadAllBytes(replacementImage.FullPath);
							Texture2D val3 = new Texture2D(2, 2);
							if (!ImageConversion.LoadImage(val3, array, true))
							{
								Logger.LogWarning((object)("Failed to load image: " + replacementImage.RelativePath));
								Object.Destroy((Object)(object)val3);
								continue;
							}
							((Texture)val3).filterMode = (FilterMode)1;
							Material val4 = new Material(sharedMaterials[num3]);
							val4.mainTexture = (Texture)(object)val3;
							sharedMaterials[num3] = val4;
							loadedTextures.Add(val3);
							loadedMaterials.Add(val4);
							num++;
							flag = true;
						}
					}
					else if (text.ToLower().Contains("painting") && !text.ToLower().Contains("frame"))
					{
						Logger.LogWarning((object)("No Replacement for " + ((Object)val2).name + " > " + text));
					}
				}
				if (flag)
				{
					((Renderer)val2).sharedMaterials = sharedMaterials;
				}
			}
			Logger.LogInfo((object)("Total Paintings Swapped: " + num));
		}
	}
}