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 Posters v0.1.0
plugins/tech.rethunk.mortar.Posters.dll
Decompiled 11 hours 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.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Logging; using ConfigSync; using Microsoft.CodeAnalysis; using MonoMod.RuntimeDetour; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.Networking; [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("Rethunk Tech")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 Rethunk Tech")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+939405354fd52367dd2c5b7d612b2ae5b146d83f")] [assembly: AssemblyProduct("tech.rethunk.mortar.Posters")] [assembly: AssemblyTitle("Posters")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/Rethunk-Tech/mortar-lc-posters")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.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 Posters { [BepInPlugin("tech.rethunk.mortar.Posters", "Posters", "0.1.0")] [BepInProcess("Lethal Company.exe")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public enum Change { EachLanding, Never } private sealed class Loader : MonoBehaviour { } [CompilerGenerated] private static class <>O { public static Action<IReadOnlyCollection<string>> <0>__Reload; public static Action<Action<StartOfRound>, StartOfRound> <1>__ShipLoaded; public static Action<Action<StartOfRound, ulong, int, ulong[], int, int, int, int, int, int, int, bool>, StartOfRound, ulong, int, ulong[], int, int, int, int, int, int, int, bool> <2>__PlayerConnected; public static Action<Action<RoundManager, int, int, int, int, int[]>, RoundManager, int, int, int, int, int[]> <3>__LevelGenerated; public static Action<Action<GrabbableObject, int>, GrabbableObject, int> <4>__ValueSet; public static Action<Action<RoundManager, NetworkObjectReference[], int[]>, RoundManager, NetworkObjectReference[], int[]> <5>__ScrapSynced; public static Action<Action<GameNetworkManager>, GameNetworkManager> <6>__ItemsSaved; public static Action<Action<StartOfRound>, StartOfRound> <7>__ItemsLoaded; public static Func<string, IEnumerable<string>> <8>__GetFiles; public static Func<string, bool> <9>__IsImage; public static Func<string, bool> <10>__Exists; public static HandleNamedMessageDelegate <11>__Received; } private static readonly List<Hook> hooks = new List<Hook>(); private static readonly Dictionary<string, Texture2D> textures = new Dictionary<string, Texture2D>(); private static readonly Dictionary<Material, string> wanted = new Dictionary<Material, string>(); private static Loader runner = null; private const int MaxSide = 1024; private const string Message = "tech.rethunk.mortar.Posters"; private const string SaveKey = "tech.rethunk.mortar.Posters.paintings"; private static readonly Dictionary<ulong, string> saved = new Dictionary<ulong, string>(); private static readonly ConditionalWeakTable<GrabbableObject, string> shown = new ConditionalWeakTable<GrabbableObject, string>(); private static string[] posters = Array.Empty<string>(); private static string[] tips = Array.Empty<string>(); private static string[] paintings = Array.Empty<string>(); private static SyncedEntry<Change> change = null; private static SyncedEntry<bool> mixGame = null; private static int lastSeed; private static GameObject? artPlane; private static Texture? gamePoster; private static Texture? gameTips; internal static ManualLogSource Log { get; private set; } = null; private void Awake() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Expected O, but got Unknown //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Expected O, but got Unknown //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Expected O, but got Unknown //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Expected O, but got Unknown //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Expected O, but got Unknown //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Expected O, but got Unknown //IL_0311: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; GameObject val = new GameObject("Posters loader"); Object.DontDestroyOnLoad((Object)(object)val); ((Object)val).hideFlags = (HideFlags)61; runner = val.AddComponent<Loader>(); Watch.Start(val, Reload, Paths.PluginPath); Find(); Log.LogInfo((object)string.Format("{0} {1} loaded {2} posters, {3} tips, {4} paintings", "Posters", "0.1.0", posters.Length, tips.Length, paintings.Length)); SyncedConfig val2 = SyncedConfig.For((BaseUnityPlugin)(object)this); change = val2.Share<Change>(((BaseUnityPlugin)this).Config.Bind<Change>("Ship", "Change", Change.EachLanding, "When the ship's posters and tips change: each landing, or never (the same pictures every game)."), (Sync)1); mixGame = val2.Share<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Ship", "Include game posters", false, "Keep the game's own poster and tips pictures in the mix."), (Sync)1); change.Changed += delegate { Ship(lastSeed); }; mixGame.Changed += delegate { Ship(lastSeed); }; hooks.Add(new Hook((MethodBase)typeof(StartOfRound).GetMethod("Start", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), (Delegate)new Action<Action<StartOfRound>, StartOfRound>(ShipLoaded))); hooks.Add(new Hook((MethodBase)typeof(StartOfRound).GetMethod("OnPlayerConnectedClientRpc", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), (Delegate)new Action<Action<StartOfRound, ulong, int, ulong[], int, int, int, int, int, int, int, bool>, StartOfRound, ulong, int, ulong[], int, int, int, int, int, int, int, bool>(PlayerConnected))); hooks.Add(new Hook((MethodBase)typeof(RoundManager).GetMethod("GenerateNewLevelClientRpc"), (Delegate)new Action<Action<RoundManager, int, int, int, int, int[]>, RoundManager, int, int, int, int, int[]>(LevelGenerated))); hooks.Add(new Hook((MethodBase)typeof(GrabbableObject).GetMethod("SetScrapValue"), (Delegate)new Action<Action<GrabbableObject, int>, GrabbableObject, int>(ValueSet))); hooks.Add(new Hook((MethodBase)typeof(RoundManager).GetMethod("SyncScrapValuesClientRpc"), (Delegate)new Action<Action<RoundManager, NetworkObjectReference[], int[]>, RoundManager, NetworkObjectReference[], int[]>(ScrapSynced))); hooks.Add(new Hook((MethodBase)typeof(GameNetworkManager).GetMethod("SaveItemsInShip", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), (Delegate)new Action<Action<GameNetworkManager>, GameNetworkManager>(ItemsSaved))); hooks.Add(new Hook((MethodBase)typeof(StartOfRound).GetMethod("LoadShipGrabbableItems", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), (Delegate)new Action<Action<StartOfRound>, StartOfRound>(ItemsLoaded))); } private static void Reload(IReadOnlyCollection<string> changed) { if (!changed.Any((string f) => IsImage(f) || Path.GetExtension(f) == "")) { return; } Find(); foreach (string item2 in changed) { if (textures.Remove(item2, out Texture2D value)) { Object.Destroy((Object)(object)value); } } Log.LogInfo((object)$"Found {posters.Length} posters, {tips.Length} tips, {paintings.Length} paintings"); Ship(lastSeed); GrabbableObject[] array = Object.FindObjectsByType<GrabbableObject>((FindObjectsSortMode)0); foreach (GrabbableObject item in array) { Paint(item); } } private static bool IsImage(string file) { if (!file.EndsWith(".png", StringComparison.OrdinalIgnoreCase) && !file.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase)) { return file.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase); } return true; } private static void Find() { posters = Images("posters", "LethalPosters", "posters"); tips = Images("tips", "LethalPosters", "tips"); paintings = Images("paintings", "LethalPaintings", "paintings"); } private static string[] Images(string kind, string oldPack, string oldKind) { return Folders("mortar", kind).Concat(Folders(oldPack, oldKind)).SelectMany(Directory.GetFiles).Where(IsImage) .Distinct() .OrderBy<string, string>((string f) => Path.GetFileName(f), StringComparer.Ordinal) .ToArray(); } private static IEnumerable<string> Folders(string parent, string kind) { return (from dir in Directory.GetDirectories(Paths.PluginPath, parent, SearchOption.AllDirectories) select Path.Combine(dir, kind)).Where(Directory.Exists); } private static void ShipLoaded(Action<StartOfRound> orig, StartOfRound self) { saved.Clear(); wanted.Clear(); orig(self); Listen(); Ship(self.randomMapSeed); } private unsafe static void Listen() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || singleton.CustomMessagingManager == null) { return; } CustomMessagingManager customMessagingManager = singleton.CustomMessagingManager; object obj = <>O.<11>__Received; if (obj == null) { HandleNamedMessageDelegate val = Received; <>O.<11>__Received = val; obj = (object)val; } customMessagingManager.RegisterNamedMessageHandler("tech.rethunk.mortar.Posters", (HandleNamedMessageDelegate)obj); if (singleton.IsServer) { return; } FastBufferWriter val2 = default(FastBufferWriter); ((FastBufferWriter)(ref val2))..ctor(1, (Allocator)2, -1); try { singleton.CustomMessagingManager.SendNamedMessage("tech.rethunk.mortar.Posters", 0uL, val2, (NetworkDelivery)3); } finally { ((IDisposable)(*(FastBufferWriter*)(&val2))/*cast due to .constrained prefix*/).Dispose(); } } private unsafe static void Received(ulong sender, FastBufferReader reader) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0067: 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_00a8: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if (singleton.IsServer) { FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(256, (Allocator)2, 1048576); try { int count = saved.Count; ((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref count, default(ForPrimitives)); foreach (KeyValuePair<ulong, string> item2 in saved) { ulong key = item2.Key; ((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref key, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(item2.Value, false); } singleton.CustomMessagingManager.SendNamedMessage("tech.rethunk.mortar.Posters", sender, val, (NetworkDelivery)4); return; } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives)); ulong key2 = default(ulong); string value = default(string); for (int i = 0; i < num; i++) { ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref key2, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref value, false); saved[key2] = value; } GrabbableObject[] array = Object.FindObjectsByType<GrabbableObject>((FindObjectsSortMode)0); foreach (GrabbableObject item in array) { Paint(item); } } private static string Where(Vector3 position) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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) return $"{position.x:F2},{position.y:F2},{position.z:F2}"; } private static void ItemsSaved(Action<GameNetworkManager> orig, GameNetworkManager self) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) orig(self); if ((Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.isChallengeFile) { return; } Dictionary<string, string> dictionary = new Dictionary<string, string>(); GrabbableObject[] array = Object.FindObjectsByType<GrabbableObject>((FindObjectsSortMode)0); foreach (GrabbableObject val in array) { if (shown.TryGetValue(val, out string value)) { dictionary[Where(((Component)val).transform.position)] = value; } } ES3.Save<Dictionary<string, string>>("tech.rethunk.mortar.Posters.paintings", dictionary, self.currentSaveFileName); } private static void ItemsLoaded(Action<StartOfRound> orig, StartOfRound self) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) orig(self); string currentSaveFileName = GameNetworkManager.Instance.currentSaveFileName; if (!ES3.KeyExists("tech.rethunk.mortar.Posters.paintings", currentSaveFileName)) { return; } Dictionary<string, string> dictionary = ES3.Load<Dictionary<string, string>>("tech.rethunk.mortar.Posters.paintings", currentSaveFileName); GrabbableObject[] array = Object.FindObjectsByType<GrabbableObject>((FindObjectsSortMode)0); foreach (GrabbableObject val in array) { if (IsPainting(val) && dictionary.TryGetValue(Where(((Component)val).transform.position), out var value)) { saved[((NetworkBehaviour)val).NetworkObjectId] = value; Paint(val); } } } private static void PlayerConnected(Action<StartOfRound, ulong, int, ulong[], int, int, int, int, int, int, int, bool> orig, StartOfRound self, ulong clientId, int connectedPlayers, ulong[] connectedPlayerIdsOrdered, int assignedPlayerObjectId, int serverMoneyAmount, int levelID, int profitQuota, int timeUntilDeadline, int quotaFulfilled, int randomSeed, bool isChallenge) { orig(self, clientId, connectedPlayers, connectedPlayerIdsOrdered, assignedPlayerObjectId, serverMoneyAmount, levelID, profitQuota, timeUntilDeadline, quotaFulfilled, randomSeed, isChallenge); Ship(self.randomMapSeed); } private static void LevelGenerated(Action<RoundManager, int, int, int, int, int[]> orig, RoundManager self, int randomSeed, int levelID, int moldIterations, int moldStartPosition, int[] syncDestroyedMold) { orig(self, randomSeed, levelID, moldIterations, moldStartPosition, syncDestroyedMold); Ship(randomSeed); } private static void Ship(int seed) { lastSeed = seed; GameObject val = GameObject.Find("HangarShip/Plane.001"); if (!((Object)(object)val == (Object)null)) { Material[] materials = ((Renderer)val.GetComponent<MeshRenderer>()).materials; if ((Object)(object)val != (Object)(object)artPlane) { artPlane = val; gamePoster = materials[0].mainTexture; gameTips = materials[1].mainTexture; } if (change.Value == Change.Never) { seed = 0; } Show(materials[0], posters, seed, mixGame.Value ? gamePoster : null); Show(materials[1], tips, seed, mixGame.Value ? gameTips : null); } } private static void ValueSet(Action<GrabbableObject, int> orig, GrabbableObject self, int value) { orig(self, value); Paint(self); } private static void ScrapSynced(Action<RoundManager, NetworkObjectReference[], int[]> orig, RoundManager self, NetworkObjectReference[] spawnedScrap, int[] allScrapValue) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) orig(self, spawnedScrap, allScrapValue); NetworkObject val2 = default(NetworkObject); GrabbableObject item = default(GrabbableObject); for (int i = 0; i < spawnedScrap.Length; i++) { NetworkObjectReference val = spawnedScrap[i]; if (((NetworkObjectReference)(ref val)).TryGet(ref val2, (NetworkManager)null) && ((Component)val2).TryGetComponent<GrabbableObject>(ref item)) { Paint(item); } } } private static bool IsPainting(GrabbableObject item) { if ((Object)(object)item.itemProperties != (Object)null) { return item.itemProperties.itemName == "Painting"; } return false; } private static void Paint(GrabbableObject item) { MeshRenderer val = default(MeshRenderer); if (!IsPainting(item) || !((Component)item).TryGetComponent<MeshRenderer>(ref val)) { return; } string text = null; if (((NetworkBehaviour)item).IsSpawned && saved.TryGetValue(((NetworkBehaviour)item).NetworkObjectId, out string name)) { text = Array.Find(paintings, (string f) => Path.GetFileName(f) == name); } text = ((text != null) ? ShowFile(((Renderer)val).material, text) : Show(((Renderer)val).material, paintings, item.scrapValue)); shown.Remove(item); if (text != null) { shown.Add(item, Path.GetFileName(text)); } } private static string? Show(Material material, string[] files, int seed, Texture? game = null) { int num = files.Length + (((Object)(object)game != (Object)null) ? 1 : 0); if (num == 0) { return null; } int num2 = (seed % num + num) % num; if (num2 == files.Length) { material.mainTexture = game; return null; } return ShowFile(material, files[num2]); } private static string ShowFile(Material material, string file) { wanted[material] = file; if (textures.TryGetValue(file, out Texture2D value)) { material.mainTexture = (Texture)(object)value; } else { ((MonoBehaviour)runner).StartCoroutine(Load(file, material)); } return file; } private static IEnumerator Load(string file, Material material) { UnityWebRequest request = UnityWebRequestTexture.GetTexture(FileUrl(file), true); try { yield return request.SendWebRequest(); if ((int)request.result != 1) { Log.LogError((object)(file + ": " + request.error)); yield break; } if (!textures.TryGetValue(file, out Texture2D value)) { value = Fit(DownloadHandlerTexture.GetContent(request)); textures[file] = value; } if ((Object)(object)material != (Object)null && wanted.TryGetValue(material, out string value2) && value2 == file) { material.mainTexture = (Texture)(object)value; } } finally { ((IDisposable)request)?.Dispose(); } } private static Texture2D Fit(Texture2D full) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(((Texture)full).width, ((Texture)full).height); if (num <= 1024) { return full; } int num2 = ((Texture)full).width * 1024 / num; int num3 = ((Texture)full).height * 1024 / num; RenderTexture temporary = RenderTexture.GetTemporary(num2, num3); Graphics.Blit((Texture)(object)full, temporary); RenderTexture active = RenderTexture.active; RenderTexture.active = temporary; Texture2D val = new Texture2D(num2, num3, (TextureFormat)4, true); val.ReadPixels(new Rect(0f, 0f, (float)num2, (float)num3), 0, 0); val.Apply(true, true); RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); Object.Destroy((Object)(object)full); return val; } private static string FileUrl(string file) { return "file:///" + file.Replace('\\', '/').Replace("%", "%25").Replace("#", "%23"); } } internal sealed class Watch : MonoBehaviour { private static readonly TimeSpan quiet = TimeSpan.FromSeconds(1.0); private readonly List<FileSystemWatcher> watchers = new List<FileSystemWatcher>(); private readonly object gate = new object(); private readonly HashSet<string> changed = new HashSet<string>(); private DateTime last; private Action<IReadOnlyCollection<string>> onChange = delegate { }; public static void Start(GameObject host, Action<IReadOnlyCollection<string>> onChange, params string[] dirs) { Watch watch = host.AddComponent<Watch>(); watch.onChange = onChange; foreach (string item in dirs.Where(Directory.Exists)) { try { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(item) { IncludeSubdirectories = true }; fileSystemWatcher.Created += delegate(object _, FileSystemEventArgs e) { watch.Note(e.FullPath); }; fileSystemWatcher.Changed += delegate(object _, FileSystemEventArgs e) { watch.Note(e.FullPath); }; fileSystemWatcher.Deleted += delegate(object _, FileSystemEventArgs e) { watch.Note(e.FullPath); }; fileSystemWatcher.Renamed += delegate(object _, RenamedEventArgs e) { watch.Note(e.OldFullPath); watch.Note(e.FullPath); }; fileSystemWatcher.EnableRaisingEvents = true; watch.watchers.Add(fileSystemWatcher); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Can't watch " + item + " for new files, so they need a restart: " + ex.Message)); } } } private void Note(string path) { lock (gate) { changed.Add(path); last = DateTime.UtcNow; } } private void Update() { string[] obj; lock (gate) { if (changed.Count == 0 || DateTime.UtcNow - last < quiet) { return; } obj = changed.ToArray(); changed.Clear(); } try { onChange((IReadOnlyCollection<string>)(object)obj); } catch (Exception arg) { Plugin.Log.LogError((object)$"Couldn't pick up changed files: {arg}"); } } private void OnDestroy() { foreach (FileSystemWatcher watcher in watchers) { watcher.Dispose(); } } } internal static class MyPluginInfo { public const string Id = "tech.rethunk.mortar.Posters"; public const string Name = "Posters"; public const string Version = "0.1.0"; } }