Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PaintingReplacer v0.0.2
plugins/PaintingReplacer/PaintingReplacer.dll
Decompiled 2 weeks agousing 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)); } } }