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 BirthdayPack v1.5.0
plugins/BirthdayPhotos.dll
Decompiled a day agousing System; using System.Collections; 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.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Unity.Netcode; 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: AssemblyCompany("BirthdayPhotos")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.2.0.0")] [assembly: AssemblyInformationalVersion("1.2.0")] [assembly: AssemblyProduct("BirthdayPhotos")] [assembly: AssemblyTitle("BirthdayPhotos")] [assembly: AssemblyVersion("1.2.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; } } } namespace BirthdayPhotos { internal static class MaterialSwapper { private static readonly string[] TextureProps = new string[2] { "_BaseColorMap", "_MainTex" }; private static readonly string[] ColorProps = new string[2] { "_BaseColor", "_Color" }; private static readonly Dictionary<string, Material> Copies = new Dictionary<string, Material>(); private static readonly HashSet<int> OurMaterials = new HashSet<int>(); private static readonly HashSet<int> LoggedObjects = new HashSet<int>(); private static int radarLayer = -2; public static void ApplyNextFrame(MonoBehaviour owner, Action apply) { try { owner.StartCoroutine(NextFrame(apply)); } catch (Exception arg) { Plugin.Log.LogError((object)$"Не удалось запланировать замену для {((owner != null) ? ((Object)owner).name : null)}: {arg}"); } } private static IEnumerator NextFrame(Action apply) { yield return null; Safe(apply); } public static void Safe(Action action) { try { action(); } catch (Exception arg) { Plugin.Log.LogError((object)$"Ошибка замены текстуры: {arg}"); } } public static void ApplyEnemy(EnemyAI enemy) { if (!((Object)(object)enemy == (Object)null)) { string name = (((Object)(object)enemy.enemyType != (Object)null) ? enemy.enemyType.enemyName : ((Object)((Component)enemy).gameObject).name); IEnumerable<Renderer> renderers = from r in Renderers(((Component)enemy).gameObject) where (Object)(object)((Component)r).GetComponentInParent<GrabbableObject>(true) == (Object)null && (Object)(object)((Component)r).GetComponentInParent<PlayerControllerB>(true) == (Object)null select r; Apply((Component)(object)enemy, "Монстр", name, renderers, Plugin.MainPhotos, 0uL); } } public static void ApplyItem(GrabbableObject item) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item.itemProperties == (Object)null)) { IEnumerable<Renderer> renderers = from r in Renderers(((Component)item).gameObject) where (Object)(object)((Component)r).GetComponentInParent<GrabbableObject>(true) == (Object)(object)item select r; Apply((Component)(object)item, "Лут", item.itemProperties.itemName, renderers, Plugin.MainPhotos, 0uL); } } public static void ApplyStoreItem(GrabbableObject item) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item.itemProperties == (Object)null)) { IEnumerable<Renderer> renderers = from r in Renderers(((Component)item).gameObject) where (Object)(object)((Component)r).GetComponentInParent<GrabbableObject>(true) == (Object)(object)item select r; ulong salt = (((NetworkBehaviour)item).IsSpawned ? (((NetworkBehaviour)item).NetworkObjectId + 1) : 0); Apply((Component)(object)item, "Предмет", item.itemProperties.itemName, renderers, Plugin.StorePhotos, salt); } } private static IEnumerable<Renderer> Renderers(GameObject root) { if (radarLayer == -2) { radarLayer = LayerMask.NameToLayer("MapRadar"); } return from r in root.GetComponentsInChildren<Renderer>(true) where (r is MeshRenderer || r is SkinnedMeshRenderer) && ((Component)r).gameObject.layer != radarLayer select r; } private static void Apply(Component target, string kind, string name, IEnumerable<Renderer> renderers, PhotoLibrary library, ulong salt) { string reason; Photo photo = library.Choose(name, out reason, salt); bool flag = LoggedObjects.Add(((Object)target).GetInstanceID()); if (photo == null) { if (flag) { Plugin.Log.LogWarning((object)("[" + kind + "] " + name + ": нет фото в папке " + library.Name + " — пропускаю")); } return; } int num = 0; int num2 = 0; int num3 = 0; SortedSet<string> sortedSet = new SortedSet<string>(); SortedSet<string> sortedSet2 = new SortedSet<string>(); foreach (Renderer renderer in renderers) { Material[] sharedMaterials = renderer.sharedMaterials; bool flag2 = false; for (int i = 0; i < sharedMaterials.Length; i++) { Material val = sharedMaterials[i]; if ((Object)(object)val == (Object)null) { continue; } num++; string item = (((Object)(object)val.shader != (Object)null) ? ((Object)val.shader).name : "?"); sortedSet.Add(item); if (OurMaterials.Contains(((Object)val).GetInstanceID())) { num3++; continue; } Material copy = GetCopy(val, photo); if ((Object)(object)copy == (Object)null) { sortedSet2.Add(item); continue; } sharedMaterials[i] = copy; flag2 = true; num2++; } if (flag2) { renderer.sharedMaterials = sharedMaterials; } } if (flag || num2 != 0) { string text = $"[{kind}] {name} → {photo.FileName} ({reason}) | заменено материалов: {num2 + num3} из {num}" + " | шейдеры: " + ((sortedSet.Count > 0) ? string.Join(", ", sortedSet) : "нет"); if (sortedSet2.Count > 0) { text = text + " | без _BaseColorMap/_MainTex: " + string.Join(", ", sortedSet2); } Plugin.Log.LogInfo((object)text); } } private static Material GetCopy(Material src, Photo photo) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) string key = ((Object)src).GetInstanceID() + "|" + ((Object)photo.Texture).GetInstanceID(); if (Copies.TryGetValue(key, out var value) && (Object)(object)value != (Object)null) { return value; } if (!((IEnumerable<string>)TextureProps).Any((Func<string, bool>)src.HasProperty)) { return null; } Material val = new Material(src) { name = ((Object)src).name + " (BirthdayPhoto)", hideFlags = (HideFlags)32 }; float num = Plugin.Tiling.Value; string[] textureProps = TextureProps; foreach (string text in textureProps) { if (val.HasProperty(text)) { val.SetTexture(text, (Texture)(object)photo.Texture); val.SetTextureScale(text, new Vector2(num, num)); val.SetTextureOffset(text, Vector2.zero); } } textureProps = ColorProps; foreach (string text2 in textureProps) { if (val.HasProperty(text2)) { val.SetColor(text2, Color.white); } } Copies[key] = val; OurMaterials.Add(((Object)val).GetInstanceID()); return val; } } internal static class PaintingSwapper { private static readonly Dictionary<string, string> Targets = new Dictionary<string, string> { { "LethalGothicPainting", "classic_painting" }, { "RuinsByTheSea", "manor_painting_1" }, { "WallFallingDownPainting", "manor_painting_2" }, { "WallFallingDownPainting 1", "manor_painting_3" }, { "Posters", "ship_posters" } }; private static readonly string[] TextureProps = new string[2] { "_BaseColorMap", "_MainTex" }; private static readonly HashSet<int> Done = new HashSet<int>(); public static void ApplyAll(string reason) { if (Plugin.Paintings == null || !Plugin.Paintings.Value || Plugin.PaintingPhotos.Count == 0) { return; } int num = 0; Material[] array = Resources.FindObjectsOfTypeAll<Material>(); foreach (Material val in array) { if ((Object)(object)val == (Object)null || Done.Contains(((Object)val).GetInstanceID())) { continue; } string text = ((Object)val).name.Replace(" (Instance)", ""); if (!Targets.TryGetValue(text, out var value)) { continue; } Photo photo = Plugin.PaintingPhotos.Get(value); if (photo == null) { Plugin.Log.LogWarning((object)("[Картина] " + text + ": нет файла " + value + ".jpg в папке Paintings")); Done.Add(((Object)val).GetInstanceID()); continue; } bool flag = false; string[] textureProps = TextureProps; foreach (string text2 in textureProps) { if (val.HasProperty(text2)) { val.SetTexture(text2, (Texture)(object)photo.Texture); flag = true; } } Done.Add(((Object)val).GetInstanceID()); if (flag) { num++; Plugin.Log.LogInfo((object)("[Картина] " + ((Object)val).name + " → " + photo.FileName + " | шейдер: " + (((Object)(object)val.shader != (Object)null) ? ((Object)val.shader).name : "?"))); } else { Plugin.Log.LogWarning((object)("[Картина] " + ((Object)val).name + ": у шейдера " + (((Object)(object)val.shader != (Object)null) ? ((Object)val.shader).name : "?") + " нет _BaseColorMap/_MainTex")); } } if (num > 0) { Plugin.Log.LogInfo((object)$"Картины заменены: {num} ({reason})"); } } } internal static class Patches { private static readonly HashSet<string> StoreItemExclusions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "clipboard", "Sticky note", "Body" }; public static void Apply(Harmony harmony) { Patch(harmony, typeof(EnemyAI), "Start", "EnemyStarted"); Patch(harmony, typeof(MaskedPlayerEnemy), "Start", "EnemyStarted"); Patch(harmony, typeof(MaskedPlayerEnemy), "SetSuit", "MaskedSuitChanged"); Patch(harmony, typeof(GrabbableObject), "Start", "ItemStarted"); Patch(harmony, typeof(CaveDwellerPhysicsProp), "Start", "ItemStarted"); Patch(harmony, typeof(RoundManager), "SyncScrapValuesClientRpc", "ScrapValuesSynced"); Patch(harmony, typeof(StartOfRound), "Start", "ShipLoaded"); Patch(harmony, typeof(RoundManager), "FinishGeneratingLevel", "DungeonGenerated"); } private static void Patch(Harmony harmony, Type type, string method, string postfix) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.DeclaredMethod(type, method, (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Log.LogWarning((object)("Метод " + type.Name + "." + method + " не найден — этот патч пропущен")); } else { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), postfix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)("Патч: " + type.Name + "." + method)); } } catch (Exception arg) { Plugin.Log.LogError((object)$"Не удалось пропатчить {type.Name}.{method}: {arg}"); } } private static void EnemyStarted(EnemyAI __instance) { MaterialSwapper.Safe(delegate { if (Plugin.Enemies.Value) { MaterialSwapper.ApplyNextFrame((MonoBehaviour)(object)__instance, delegate { MaterialSwapper.ApplyEnemy(__instance); }); } }); } private static void MaskedSuitChanged(MaskedPlayerEnemy __instance) { MaterialSwapper.Safe(delegate { if (Plugin.Enemies.Value) { MaterialSwapper.ApplyEnemy((EnemyAI)(object)__instance); } }); } private static void ItemStarted(GrabbableObject __instance) { MaterialSwapper.Safe(delegate { if (IsScrap(__instance)) { MaterialSwapper.ApplyNextFrame((MonoBehaviour)(object)__instance, delegate { MaterialSwapper.ApplyItem(__instance); }); } else if (IsStoreItem(__instance)) { MaterialSwapper.ApplyNextFrame((MonoBehaviour)(object)__instance, delegate { MaterialSwapper.ApplyStoreItem(__instance); }); } }); } private static void DungeonGenerated() { MaterialSwapper.Safe(delegate { PaintingSwapper.ApplyAll("здание сгенерировано"); }); } private static void ScrapValuesSynced(NetworkObjectReference[] spawnedScrap) { MaterialSwapper.Safe(delegate { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (spawnedScrap != null) { NetworkObjectReference[] array = spawnedScrap; NetworkObject val2 = default(NetworkObject); for (int i = 0; i < array.Length; i++) { NetworkObjectReference val = array[i]; if (((NetworkObjectReference)(ref val)).TryGet(ref val2, (NetworkManager)null)) { GrabbableObject item = ((Component)val2).GetComponent<GrabbableObject>(); if (IsScrap(item)) { MaterialSwapper.Safe(delegate { MaterialSwapper.ApplyItem(item); }); } } } } }); } private static bool IsStoreItem(GrabbableObject item) { if (Plugin.StoreItems.Value && (Object)(object)item != (Object)null && (Object)(object)item.itemProperties != (Object)null && !item.itemProperties.isScrap && !(item is RagdollGrabbableObject) && !(item is CaveDwellerPhysicsProp)) { return !StoreItemExclusions.Contains(item.itemProperties.itemName ?? ""); } return false; } private static bool IsScrap(GrabbableObject item) { if (Plugin.Scrap.Value && (Object)(object)item != (Object)null && (Object)(object)item.itemProperties != (Object)null && item.itemProperties.isScrap) { return !(item is RagdollGrabbableObject); } return false; } private static void ShipLoaded(StartOfRound __instance) { MaterialSwapper.Safe(delegate { WriteNames(__instance); }); MaterialSwapper.Safe(delegate { PaintingSwapper.ApplyAll("корабль загружен"); }); } private static void WriteNames(StartOfRound round) { SortedSet<string> sortedSet = new SortedSet<string>(StringComparer.OrdinalIgnoreCase); EnemyType[] array = Resources.FindObjectsOfTypeAll<EnemyType>(); foreach (EnemyType val in array) { Add(sortedSet, ((Object)(object)val != (Object)null) ? val.enemyName : null); } if (round.levels != null) { foreach (SelectableLevel item in round.levels.Where((SelectableLevel l) => (Object)(object)l != (Object)null)) { List<SpawnableEnemyWithRarity>[] array2 = new List<SpawnableEnemyWithRarity>[3] { item.Enemies, item.OutsideEnemies, item.DaytimeEnemies }; foreach (List<SpawnableEnemyWithRarity> list in array2) { if (list == null) { continue; } foreach (SpawnableEnemyWithRarity item2 in list) { Add(sortedSet, ((Object)(object)item2?.enemyType != (Object)null) ? item2.enemyType.enemyName : null); } } } } SortedSet<string> sortedSet2 = new SortedSet<string>(StringComparer.OrdinalIgnoreCase); SortedSet<string> sortedSet3 = new SortedSet<string>(StringComparer.OrdinalIgnoreCase); if ((Object)(object)round.allItemsList != (Object)null && round.allItemsList.itemsList != null) { foreach (Item items in round.allItemsList.itemsList) { if ((Object)(object)items != (Object)null) { Add(items.isScrap ? sortedSet2 : sortedSet3, items.itemName); } } } Directory.CreateDirectory(Plugin.PhotosDir); File.WriteAllLines(Path.Combine(Plugin.PhotosDir, "_enemy_names.txt"), sortedSet); File.WriteAllLines(Path.Combine(Plugin.PhotosDir, "_scrap_names.txt"), sortedSet2); if (Directory.Exists(Plugin.Photos2Dir)) { File.WriteAllLines(Path.Combine(Plugin.Photos2Dir, "_item_names.txt"), sortedSet3); } Plugin.Log.LogInfo((object)($"Записаны имена: монстров {sortedSet.Count} (_enemy_names.txt), лута {sortedSet2.Count} (_scrap_names.txt), " + $"предметов магазина {sortedSet3.Count} (Photos2/_item_names.txt)")); Plugin.Log.LogInfo((object)("Монстры: " + string.Join(", ", sortedSet))); } private static void Add(SortedSet<string> set, string name) { if (!string.IsNullOrWhiteSpace(name)) { set.Add(name.Trim()); } } } internal class Photo { public string FileName; public string Key; public Texture2D Texture; } internal class PhotoLibrary { private static readonly string[] Extensions = new string[3] { ".jpg", ".jpeg", ".png" }; private readonly List<Photo> _photos = new List<Photo>(); private readonly Dictionary<string, KeyValuePair<Photo, string>> _choices = new Dictionary<string, KeyValuePair<Photo, string>>(); public string Name { get; } public int Count => _photos.Count; public PhotoLibrary(string name) { Name = name; } public void Load(string dir, TextureWrapMode wrap = (TextureWrapMode)0) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_00e3: Unknown result type (might be due to invalid IL or missing references) if (!Directory.Exists(dir)) { Plugin.Log.LogWarning((object)("[" + Name + "] Папка не найдена: " + dir)); return; } foreach (string item in (from f in Directory.GetFiles(dir) where Extensions.Contains(Path.GetExtension(f).ToLowerInvariant()) select f).OrderBy<string, string>((string f) => Path.GetFileName(f), StringComparer.Ordinal)) { try { Texture2D val = new Texture2D(2, 2, (TextureFormat)4, true); if (!ImageConversion.LoadImage(val, File.ReadAllBytes(item), true)) { Plugin.Log.LogError((object)("[" + Name + "] Не удалось прочитать картинку: " + item)); continue; } ((Object)val).name = "BirthdayPhoto_" + Path.GetFileName(item); ((Texture)val).wrapMode = wrap; ((Texture)val).filterMode = (FilterMode)2; ((Texture)val).anisoLevel = 4; ((Object)val).hideFlags = (HideFlags)32; _photos.Add(new Photo { FileName = Path.GetFileName(item), Key = Normalize(Path.GetFileNameWithoutExtension(item)), Texture = val }); } catch (Exception arg) { Plugin.Log.LogError((object)$"[{Name}] Ошибка загрузки {item}: {arg}"); } } Plugin.Log.LogInfo((object)$"[{Name}] Загружено фото: {_photos.Count} из {dir}"); } public Photo Get(string fileNameWithoutExtension) { string key = Normalize(fileNameWithoutExtension); return _photos.FirstOrDefault((Photo p) => p.Key == key); } public Photo Choose(string name, out string reason, ulong salt = 0uL) { reason = null; if (_photos.Count == 0) { return null; } string text = Normalize(name ?? ""); string key = text + "#" + salt; if (_choices.TryGetValue(key, out var value)) { reason = value.Value; return value.Key; } Photo photo = null; if (text.Length > 0) { foreach (Photo photo2 in _photos) { if (photo2.Key.Contains(text) && (photo == null || photo2.Key.Length < photo.Key.Length)) { photo = photo2; } } } if (photo != null) { reason = "по имени файла"; } else { photo = _photos[(int)(Fnv1a((salt == 0L) ? text : (text + "#" + salt)) % (uint)_photos.Count)]; reason = ((salt == 0L) ? "по хешу" : "по хешу экземпляра"); } _choices[key] = new KeyValuePair<Photo, string>(photo, reason); return photo; } public static string Normalize(string s) { StringBuilder stringBuilder = new StringBuilder(s.Length); string text = s.ToLowerInvariant(); foreach (char c in text) { if (c != ' ' && c != '-' && c != '_') { stringBuilder.Append(c); } } return stringBuilder.ToString(); } private static uint Fnv1a(string s) { uint num = 2166136261u; byte[] bytes = Encoding.UTF8.GetBytes(s); foreach (byte b in bytes) { num ^= b; num *= 16777619; } return num; } } [BepInPlugin("birthday.birthdayphotos", "BirthdayPhotos", "1.2.0")] public class Plugin : BaseUnityPlugin { public const string Guid = "birthday.birthdayphotos"; public const string PluginName = "BirthdayPhotos"; public const string Version = "1.2.0"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enemies; internal static ConfigEntry<bool> Scrap; internal static ConfigEntry<bool> StoreItems; internal static ConfigEntry<bool> Paintings; internal static ConfigEntry<int> Tiling; internal static readonly PhotoLibrary MainPhotos = new PhotoLibrary("Photos"); internal static readonly PhotoLibrary StorePhotos = new PhotoLibrary("Photos2"); internal static readonly PhotoLibrary PaintingPhotos = new PhotoLibrary("Paintings"); internal static string PhotosDir; internal static string Photos2Dir; private void Awake() { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; try { Enemies = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enemies", true, "Ставить фото на монстров (папка Photos)"); Scrap = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Scrap", true, "Ставить фото на лут (scrap, папка Photos)"); StoreItems = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "StoreItems", true, "Ставить фото на предметы из магазина: фонарики, лопаты, рации и т.д. (папка Photos2)"); Paintings = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Paintings", true, "Заменить картины в особняке, картину-украшение корабля и постеры на корабле (папка Paintings)"); Tiling = ((BaseUnityPlugin)this).Config.Bind<int>("General", "Tiling", 1, new ConfigDescription("1 = одно фото на материал, 3 = сетка 3×3 и т.д. (монстры и предметы)", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>())); string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location); PhotosDir = Path.Combine(directoryName, "Photos"); Photos2Dir = Path.Combine(directoryName, "Photos2"); MainPhotos.Load(PhotosDir, (TextureWrapMode)0); StorePhotos.Load(Photos2Dir, (TextureWrapMode)0); PaintingPhotos.Load(Path.Combine(directoryName, "Paintings"), (TextureWrapMode)1); Patches.Apply(new Harmony("birthday.birthdayphotos")); SceneManager.sceneLoaded += delegate(Scene scene, LoadSceneMode _) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) MaterialSwapper.Safe(delegate { PaintingSwapper.ApplyAll("сцена " + ((Scene)(ref scene)).name); }); }; Log.LogInfo((object)(string.Format("{0} {1} загружен. Enemies={2}, Scrap={3}, ", "BirthdayPhotos", "1.2.0", Enemies.Value, Scrap.Value) + $"StoreItems={StoreItems.Value}, Paintings={Paintings.Value}, Tiling={Tiling.Value}")); } catch (Exception arg) { Log.LogError((object)$"Ошибка запуска плагина: {arg}"); } } } }