diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2176308
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+# Visual Studio
+.vs/
+*.user
+*.suo
+
+# Build output
+bin/
+obj/
+
+# NuGet packages
+packages/
+
+# OS-specific
+Thumbs.db
+.DS_Store
diff --git a/LICENSE b/LICENSE
index 45c5e74..3bffb94 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,3 +1,14 @@
+<<<<<<< HEAD
+MIT License
+
+Copyright (c) 2025 SawatoMosswartsEnjoyersClub
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=======
MIT License
Copyright (c) 2025 SawatoMosswartsEnjoyersClub
@@ -7,3 +18,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+>>>>>>> 5022342fd81a32611a921fcd07267023dacc6eca
diff --git a/MosswartMassacreNG.Loader/LoaderCore.cs b/MosswartMassacreNG.Loader/LoaderCore.cs
new file mode 100644
index 0000000..f83bfc1
--- /dev/null
+++ b/MosswartMassacreNG.Loader/LoaderCore.cs
@@ -0,0 +1,224 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using Decal.Adapter;
+using Microsoft.Win32;
+
+namespace MosswartMassacreNG
+{
+ [FriendlyName("MosswartMassacreNG.Loader")]
+ public class LoaderCore : FilterBase
+ {
+ private Assembly pluginAssembly;
+ private Type pluginType;
+ private object pluginInstance;
+ private FileSystemWatcher pluginWatcher;
+ private bool isSubscribedToRenderFrame = false;
+ private bool needsReload;
+ private int oldCurrentUserValue = -1;
+
+ public static string PluginAssemblyNamespace => typeof(LoaderCore).Namespace.Replace(".Loader", "");
+ public static string PluginAssemblyName => $"{PluginAssemblyNamespace}.dll";
+ public static string PluginAssemblyGuid => "4b1f02bb-9b95-46f0-ad5b-223fea7392fb";
+
+ public static bool IsPluginLoaded { get; private set; }
+
+ ///
+ /// Assembly directory (contains both loader and plugin dlls)
+ ///
+ public static string AssemblyDirectory => System.IO.Path.GetDirectoryName(Assembly.GetAssembly(typeof(LoaderCore)).Location);
+
+ public DateTime LastDllChange { get; private set; }
+
+ #region Event Handlers
+ protected override void Startup()
+ {
+ try
+ {
+ Core.PluginInitComplete += Core_PluginInitComplete;
+ Core.PluginTermComplete += Core_PluginTermComplete;
+ Core.FilterInitComplete += Core_FilterInitComplete;
+
+ // watch the AssemblyDirectory for any .dll file changes
+ pluginWatcher = new FileSystemWatcher();
+ pluginWatcher.Path = AssemblyDirectory;
+ pluginWatcher.NotifyFilter = NotifyFilters.LastWrite;
+ pluginWatcher.Filter = "*.dll";
+ pluginWatcher.Changed += PluginWatcher_Changed;
+ pluginWatcher.EnableRaisingEvents = true;
+ }
+ catch (Exception ex)
+ {
+ Log(ex);
+ }
+ }
+
+ private void Core_FilterInitComplete(object sender, EventArgs e)
+ {
+ Core.EchoFilter.ClientDispatch += EchoFilter_ClientDispatch;
+ }
+
+ private void EchoFilter_ClientDispatch(object sender, NetworkMessageEventArgs e)
+ {
+ try
+ {
+ // Login_SendEnterWorldRequest
+ if (e.Message.Type == 0xF7C8)
+ {
+ //EnsurePluginIsDisabledInRegistry();
+ }
+ }
+ catch (Exception ex)
+ {
+ Log(ex);
+ }
+ }
+
+ private void Core_PluginInitComplete(object sender, EventArgs e)
+ {
+ try
+ {
+ LoadPluginAssembly();
+ }
+ catch (Exception ex)
+ {
+ Log(ex);
+ }
+ }
+
+ private void Core_PluginTermComplete(object sender, EventArgs e)
+ {
+ try
+ {
+ UnloadPluginAssembly();
+ }
+ catch (Exception ex)
+ {
+ Log(ex);
+ }
+ }
+
+ protected override void Shutdown()
+ {
+ try
+ {
+ Core.PluginInitComplete -= Core_PluginInitComplete;
+ Core.PluginTermComplete -= Core_PluginTermComplete;
+ Core.FilterInitComplete -= Core_FilterInitComplete;
+ UnloadPluginAssembly();
+ }
+ catch (Exception ex)
+ {
+ Log(ex);
+ }
+ }
+
+ private void Core_RenderFrame(object sender, EventArgs e)
+ {
+ try
+ {
+ if (IsPluginLoaded && needsReload && DateTime.UtcNow - LastDllChange > TimeSpan.FromSeconds(1))
+ {
+ needsReload = false;
+ Core.RenderFrame -= Core_RenderFrame;
+ isSubscribedToRenderFrame = false;
+ LoadPluginAssembly();
+ }
+ }
+ catch (Exception ex)
+ {
+ Log(ex);
+ }
+ }
+
+ private void PluginWatcher_Changed(object sender, FileSystemEventArgs e)
+ {
+ try
+ {
+ LastDllChange = DateTime.UtcNow;
+ needsReload = true;
+
+ if (!isSubscribedToRenderFrame)
+ {
+ isSubscribedToRenderFrame = true;
+ Core.RenderFrame += Core_RenderFrame;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log(ex);
+ }
+ }
+ #endregion
+
+ #region Plugin Loading/Unloading
+ internal void LoadPluginAssembly()
+ {
+ try
+ {
+ if (IsPluginLoaded)
+ {
+ UnloadPluginAssembly();
+ try
+ {
+ CoreManager.Current.Actions.AddChatText($"Reloading {PluginAssemblyName}", 1);
+ }
+ catch { }
+ }
+
+ pluginAssembly = Assembly.Load(File.ReadAllBytes(System.IO.Path.Combine(AssemblyDirectory, PluginAssemblyName)));
+ pluginType = pluginAssembly.GetType($"{PluginAssemblyNamespace}.PluginCore");
+ pluginInstance = Activator.CreateInstance(pluginType);
+
+ var assemblyDirAttr = pluginType.GetProperty("AssemblyDirectory", BindingFlags.Public | BindingFlags.Static);
+ assemblyDirAttr?.SetValue(null, AssemblyDirectory);
+
+ var startupMethod = pluginType.GetMethod("Startup", BindingFlags.NonPublic | BindingFlags.Instance);
+ startupMethod.Invoke(pluginInstance, new object[] { });
+
+ IsPluginLoaded = true;
+ }
+ catch (Exception ex)
+ {
+ Log(ex);
+ }
+ }
+
+ private void UnloadPluginAssembly()
+ {
+ try
+ {
+ if (pluginInstance != null && pluginType != null)
+ {
+ MethodInfo shutdownMethod = pluginType.GetMethod("Shutdown", BindingFlags.NonPublic | BindingFlags.Instance);
+ shutdownMethod.Invoke(pluginInstance, null);
+ pluginInstance = null;
+ pluginType = null;
+ pluginAssembly = null;
+ }
+ IsPluginLoaded = false;
+ }
+ catch (Exception ex)
+ {
+ Log(ex);
+ }
+ }
+ #endregion
+
+ private void Log(Exception ex)
+ {
+ Log(ex.ToString());
+ }
+
+ private void Log(string message)
+ {
+ File.AppendAllText(System.IO.Path.Combine(AssemblyDirectory, "log.txt"), $"{message}\n");
+ try
+ {
+ CoreManager.Current.Actions.AddChatText(message, 1);
+ }
+ catch { }
+ }
+ }
+}
diff --git a/MosswartMassacreNG.Loader/MosswartMassacreNG.Loader.csproj b/MosswartMassacreNG.Loader/MosswartMassacreNG.Loader.csproj
new file mode 100644
index 0000000..1845709
--- /dev/null
+++ b/MosswartMassacreNG.Loader/MosswartMassacreNG.Loader.csproj
@@ -0,0 +1,32 @@
+
+
+
+ net48
+ Library
+ false
+ ..\bin\
+ x86
+ 10
+ True
+ MosswartMassacreNG
+ MosswartMassacreNG.Loader
+
+
+
+
+
+
+ .\..\deps\Decal.Adapter.dll
+ False
+
+
+ False
+ True
+ .\..\deps\Decal.Interop.Core.dll
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MosswartMassacreNG.sln b/MosswartMassacreNG.sln
new file mode 100644
index 0000000..f4935b9
--- /dev/null
+++ b/MosswartMassacreNG.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.13.35931.197 d17.13
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MosswartMassacreNG.Loader", "MosswartMassacreNG.Loader\MosswartMassacreNG.Loader.csproj", "{6AF1A09E-A17D-4C9B-971F-3A6BAD982B80}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MosswartMassacreNG", "MosswartMassacreNG\MosswartMassacreNG.csproj", "{4B1F02BB-9B95-46F0-AD5B-223FEA7392FB}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6AF1A09E-A17D-4C9B-971F-3A6BAD982B80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6AF1A09E-A17D-4C9B-971F-3A6BAD982B80}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6AF1A09E-A17D-4C9B-971F-3A6BAD982B80}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6AF1A09E-A17D-4C9B-971F-3A6BAD982B80}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4B1F02BB-9B95-46F0-AD5B-223FEA7392FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4B1F02BB-9B95-46F0-AD5B-223FEA7392FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4B1F02BB-9B95-46F0-AD5B-223FEA7392FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4B1F02BB-9B95-46F0-AD5B-223FEA7392FB}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {56117760-A4A3-48AD-83D8-012B69980C69}
+ EndGlobalSection
+EndGlobal
diff --git a/MosswartMassacreNG/ExampleUI.cs b/MosswartMassacreNG/ExampleUI.cs
new file mode 100644
index 0000000..5572cd3
--- /dev/null
+++ b/MosswartMassacreNG/ExampleUI.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Numerics;
+using Decal.Adapter;
+using ImGuiNET;
+using UtilityBelt.Service;
+using UtilityBelt.Service.Views;
+
+namespace MosswartMassacreNG
+{
+ internal class ExampleUI : IDisposable
+ {
+ ///
+ /// The UBService Hud
+ ///
+ private readonly Hud hud;
+
+ ///
+ /// The default value for TestText.
+ ///
+ public const string DefaultTestText = "Some Test Text";
+
+ ///
+ /// Some test text. This value is used to the text input in our UI.
+ ///
+ public string TestText = DefaultTestText.ToString();
+
+ public ExampleUI()
+ {
+ // Create a new UBService Hud
+ hud = UBService.Huds.CreateHud("MosswartMassacreNG");
+
+ // set to show our icon in the UBService HudBar
+ hud.ShowInBar = true;
+
+ // subscribe to the hud render event so we can draw some controls
+ hud.OnRender += Hud_OnRender;
+ }
+
+ ///
+ /// Called every time the ui is redrawing.
+ ///
+ private void Hud_OnRender(object sender, EventArgs e)
+ {
+ try
+ {
+ ImGui.InputTextMultiline("Test Text", ref TestText, 5000, new Vector2(400, 150));
+
+ if (ImGui.Button("Print Test Text"))
+ {
+ OnPrintTestTextButtonPressed();
+ }
+
+ ImGui.SameLine();
+
+ if (ImGui.Button("Reset Test Text"))
+ {
+ TestText = DefaultTestText;
+ }
+ }
+ catch (Exception ex)
+ {
+ PluginCore.Log(ex);
+ }
+ }
+
+ ///
+ /// Called when our print test text button is pressed
+ ///
+ private void OnPrintTestTextButtonPressed()
+ {
+ var textToShow = $"Test Text:\n{TestText}";
+
+ CoreManager.Current.Actions.AddChatText(textToShow, 1);
+ UBService.Huds.Toaster.Add(textToShow, ToastType.Info);
+ }
+
+ public void Dispose()
+ {
+ hud.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/MosswartMassacreNG/MosswartMassacreNG.csproj b/MosswartMassacreNG/MosswartMassacreNG.csproj
new file mode 100644
index 0000000..87d07f9
--- /dev/null
+++ b/MosswartMassacreNG/MosswartMassacreNG.csproj
@@ -0,0 +1,40 @@
+
+
+
+ net48
+ Library
+ true
+ ..\bin\
+ x86
+ 1.0.0
+ 12
+ True
+ 4b1f02bb-9b95-46f0-ad5b-223fea7392fb
+ MosswartMassacreNG
+ MosswartMassacreNG
+
+
+
+
+
+
+
+
+
+ .\..\deps\Decal.Adapter.dll
+ False
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ runtime; build;
+
+
+
+
+
+
diff --git a/MosswartMassacreNG/PluginCore.cs b/MosswartMassacreNG/PluginCore.cs
new file mode 100644
index 0000000..aede109
--- /dev/null
+++ b/MosswartMassacreNG/PluginCore.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Decal.Adapter;
+
+namespace MosswartMassacreNG
+{
+ ///
+ /// This is the main plugin class. When your plugin is loaded, Startup() is called, and when it's unloaded Shutdown() is called.
+ ///
+ [FriendlyName("MosswartMassacreNG")]
+ public class PluginCore : PluginBase
+ {
+ private static string _assemblyDirectory = null;
+ private ExampleUI ui;
+
+ ///
+ /// Assembly directory containing the plugin dll
+ ///
+ public static string AssemblyDirectory
+ {
+ get
+ {
+ if (_assemblyDirectory == null)
+ {
+ try
+ {
+ _assemblyDirectory = System.IO.Path.GetDirectoryName(typeof(PluginCore).Assembly.Location);
+ }
+ catch
+ {
+ _assemblyDirectory = Environment.CurrentDirectory;
+ }
+ }
+ return _assemblyDirectory;
+ }
+ set
+ {
+ _assemblyDirectory = value;
+ }
+ }
+
+ ///
+ /// Called when your plugin is first loaded.
+ ///
+ protected override void Startup()
+ {
+ try
+ {
+ // subscribe to CharacterFilter_LoginComplete event, make sure to unscribe later.
+ // note: if the plugin was reloaded while ingame, this event will never trigger on the newly reloaded instance.
+ CoreManager.Current.CharacterFilter.LoginComplete += CharacterFilter_LoginComplete;
+
+ // this adds text to the chatbox. it's output is local only, other players do not see this.
+ CoreManager.Current.Actions.AddChatText($"This is my new decal plugin. Startup was called. MosswartMassacreNG", 1);
+
+ ui = new ExampleUI();
+ }
+ catch (Exception ex)
+ {
+ Log(ex);
+ }
+ }
+
+ protected void FilterSetup(string assemblyDirectory)
+ {
+ AssemblyDirectory = assemblyDirectory;
+ }
+
+ ///
+ /// CharacterFilter_LoginComplete event handler.
+ ///
+ private void CharacterFilter_LoginComplete(object sender, EventArgs e)
+ {
+ // it's generally a good idea to use try/catch blocks inside of decal event handlers.
+ // throwing an uncaught exception inside one will generally hard crash the client.
+ try
+ {
+ CoreManager.Current.Actions.AddChatText($"This is my new decal plugin. CharacterFilter_LoginComplete", 1);
+ }
+ catch (Exception ex)
+ {
+ Log(ex);
+ }
+ }
+
+ ///
+ /// Called when your plugin is unloaded. Either when logging out, closing the client, or hot reloading.
+ ///
+ protected override void Shutdown()
+ {
+ try
+ {
+ // make sure to unsubscribe from any events we were subscribed to. Not doing so
+ // can cause the old plugin to stay loaded between hot reloads.
+ CoreManager.Current.CharacterFilter.LoginComplete -= CharacterFilter_LoginComplete;
+
+ // clean up our ui view
+ ui.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Log(ex);
+ }
+ }
+
+ #region logging
+ ///
+ /// Log an exception to log.txt in the same directory as the plugin.
+ ///
+ ///
+ internal static void Log(Exception ex)
+ {
+ Log(ex.ToString());
+ }
+
+ ///
+ /// Log a string to log.txt in the same directory as the plugin.
+ ///
+ ///
+ internal static void Log(string message)
+ {
+ try
+ {
+ File.AppendAllText(System.IO.Path.Combine(AssemblyDirectory, "log.txt"), $"{message}\n");
+
+ CoreManager.Current.Actions.AddChatText(message, 1);
+ }
+ catch { }
+ }
+ #endregion // logging
+ }
+}
diff --git a/MosswartMassacreNG/scripts/installer.nsi b/MosswartMassacreNG/scripts/installer.nsi
new file mode 100644
index 0000000..f9fddf3
--- /dev/null
+++ b/MosswartMassacreNG/scripts/installer.nsi
@@ -0,0 +1,212 @@
+; Define your application name
+
+!define APPNAME "MosswartMassacreNG"
+!define SOFTWARECOMPANY "MosswartMassacreNG"
+!define APPGUID "{4b1f02bb-9b95-46f0-ad5b-223fea7392fb}"
+!define CLASSNAME "MosswartMassacreNG.PluginCore"
+!define ASSEMBLY "MosswartMassacreNG.dll"
+InstallDir "C:\Games\DecalPlugins\${APPNAME}"
+;Icon "Installer\Res\Decal.ico"
+
+!define BUILDPATH ".\..\..\bin\net48"
+
+!getdllversion "${BUILDPATH}\${ASSEMBLY}" Expv_
+!define VERSION ${Expv_1}.${Expv_2}.${Expv_3}
+
+OutFile "${BUILDPATH}\${APPNAME}Installer-${VERSION}.exe"
+
+; Main Install settings
+; compressor goes first
+SetCompressor LZMA
+
+Name "${APPNAME} ${VERSION}"
+InstallDirRegKey HKLM "Software\${SOFTWARECOMPANY}\${APPNAME}" ""
+;SetFont "Verdana" 8
+
+; Use compression
+
+; Modern interface settings
+!include "MUI.nsh"
+
+!define MUI_ABORTWARNING
+
+!insertmacro MUI_PAGE_WELCOME
+;!insertmacro MUI_PAGE_COMPONENTS
+!insertmacro MUI_PAGE_DIRECTORY
+!insertmacro MUI_PAGE_INSTFILES
+!insertmacro MUI_PAGE_FINISH
+
+!insertmacro MUI_UNPAGE_CONFIRM
+!insertmacro MUI_UNPAGE_INSTFILES
+
+; Set languages (first is default language)
+!insertmacro MUI_LANGUAGE "English"
+!insertmacro MUI_RESERVEFILE_LANGDLL
+
+; https://nsis.sourceforge.io/Download_and_Install_dotNET_45
+Function CheckAndDownloadDotNet48
+ # Set up our Variables
+ Var /GLOBAL dotNET48IsThere
+ Var /GLOBAL dotNET_CMD_LINE
+ Var /GLOBAL EXIT_CODE
+
+ # We are reading a version release DWORD that Microsoft says is the documented
+ # way to determine if .NET Framework 4.8 is installed
+ ReadRegDWORD $dotNET48IsThere HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" "Release"
+ IntCmp $dotNET48IsThere 528049 is_equal is_less is_greater
+
+ is_equal:
+ Goto done_compare_not_needed
+ is_greater:
+ Goto done_compare_not_needed
+ is_less:
+ Goto done_compare_needed
+
+ done_compare_needed:
+ #.NET Framework 4.8 install is *NEEDED*
+
+ # Microsoft Download Center EXE:
+ # Web Bootstrapper: https://go.microsoft.com/fwlink/?LinkId=2085155
+ # Full Download: https://go.microsoft.com/fwlink/?linkid=2088631
+
+ # Setup looks for components\dotNET48Full.exe relative to the install EXE location
+ # This allows the installer to be placed on a USB stick (for computers without internet connections)
+ # If the .NET Framework 4.8 installer is *NOT* found, Setup will connect to Microsoft's website
+ # and download it for you
+
+ # Reboot Required with these Exit Codes:
+ # 1641 or 3010
+
+ # Command Line Switches:
+ # /showrmui /passive /norestart
+
+ # Silent Command Line Switches:
+ # /q /norestart
+
+
+ # Let's see if the user is doing a Silent install or not
+ IfSilent is_quiet is_not_quiet
+
+ is_quiet:
+ StrCpy $dotNET_CMD_LINE "/q /norestart"
+ Goto LookForLocalFile
+ is_not_quiet:
+ StrCpy $dotNET_CMD_LINE "/showrmui /passive /norestart"
+ Goto LookForLocalFile
+
+ LookForLocalFile:
+ # Let's see if the user stored the Full Installer
+ IfFileExists "$EXEPATH\components\dotNET48Full.exe" do_local_install do_network_install
+
+ do_local_install:
+ # .NET Framework found on the local disk. Use this copy
+
+ ExecWait '"$EXEPATH\components\dotNET48Full.exe" $dotNET_CMD_LINE' $EXIT_CODE
+ Goto is_reboot_requested
+
+ # Now, let's Download the .NET
+ do_network_install:
+
+ Var /GLOBAL dotNetDidDownload
+ NSISdl::download "https://go.microsoft.com/fwlink/?linkid=2088631" "$TEMP\dotNET48Web.exe" $dotNetDidDownload
+
+ StrCmp $dotNetDidDownload success fail
+ success:
+ ExecWait '"$TEMP\dotNET45Web.exe" $dotNET_CMD_LINE' $EXIT_CODE
+ Goto is_reboot_requested
+
+ fail:
+ MessageBox MB_OK|MB_ICONEXCLAMATION "Unable to download .NET Framework. ${PRODUCT_NAME} will be installed, but will not function without the Framework!"
+ Goto done_dotNET_function
+
+ # $EXIT_CODE contains the return codes. 1641 and 3010 means a Reboot has been requested
+ is_reboot_requested:
+ ${If} $EXIT_CODE = 1641
+ ${OrIf} $EXIT_CODE = 3010
+ SetRebootFlag true
+ ${EndIf}
+
+ done_compare_not_needed:
+ # Done dotNET Install
+ Goto done_dotNET_function
+
+ #exit the function
+ done_dotNET_function:
+
+FunctionEnd
+
+
+Section "" CoreSection
+; Set Section properties
+ SetOverwrite on
+
+ ; Set Section Files and Shortcuts
+ SetOutPath "$INSTDIR\"
+
+ File "${BUILDPATH}\${ASSEMBLY}"
+ File "${BUILDPATH}\${APPNAME}.pdb"
+ File "${BUILDPATH}\UtilityBelt.Service.Installer.exe"
+
+SectionEnd
+
+Section -FinishSection
+
+ WriteRegStr HKLM "Software\${SOFTWARECOMPANY}\${APPNAME}" "" "$INSTDIR"
+ WriteRegStr HKLM "Software\${SOFTWARECOMPANY}\${APPNAME}" "Version" "${VERSION}"
+
+ ;Register in decal
+ ClearErrors
+ ReadRegStr $0 HKLM "Software\Decal\Plugins\${APPGUID}" ""
+ ${If} ${Errors}
+ WriteRegStr HKLM "Software\Decal\Plugins\${APPGUID}" "" "${APPNAME}"
+ WriteRegDWORD HKLM "Software\Decal\Plugins\${APPGUID}" "Enabled" "1"
+ WriteRegStr HKLM "Software\Decal\Plugins\${APPGUID}" "Object" "${CLASSNAME}"
+ WriteRegStr HKLM "Software\Decal\Plugins\${APPGUID}" "Assembly" "${ASSEMBLY}"
+ WriteRegStr HKLM "Software\Decal\Plugins\${APPGUID}" "Path" "$INSTDIR"
+ WriteRegStr HKLM "Software\Decal\Plugins\${APPGUID}" "Surrogate" "{71A69713-6593-47EC-0002-0000000DECA1}"
+ WriteRegStr HKLM "Software\Decal\Plugins\${APPGUID}" "Uninstaller" "${APPNAME}"
+ ${Else}
+ ${IF} $0 != "${APPNAME}"
+ MESSAGEBOX MB_OK|MB_ICONSTOP "Skipped decal plugin registration. A decal plugin with this GUID already exists ($0), and is not ${APPNAME}."
+ ${ENDIF}
+ ${EndIf}
+
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayName" "${APPNAME}"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "UninstallString" "$INSTDIR\uninstall.exe"
+ WriteUninstaller "$INSTDIR\uninstall.exe"
+
+ ; make sure dotnet 4.8 is installed
+ Call CheckAndDownloadDotNet48
+
+ ; make sure UtilityBelt.Service is installed
+ ; TODO: try and pull UtilityBelt.Service version from the registry and check it against the version required for this plugin
+ ExecWait '"$instdir\UtilityBelt.Service.Installer.exe"'
+
+SectionEnd
+
+; Modern install component descriptions
+!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
+ !insertmacro MUI_DESCRIPTION_TEXT ${CoreSection} ""
+!insertmacro MUI_FUNCTION_DESCRIPTION_END
+
+;Uninstall section
+Section Uninstall
+
+ ;Remove from registry...
+ DeleteRegKey HKLM "Software\${SOFTWARECOMPANY}\${APPNAME}"
+ DeleteRegKey HKLM "Software\Decal\Plugins\${APPGUID}"
+ DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}"
+
+ ; Delete self
+ Delete "$INSTDIR\uninstall.exe"
+
+ ;Clean up
+ Delete "$INSTDIR\${ASSEMBLY}"
+ Delete "$INSTDIR\${APPNAME}.pdb"
+ Delete "$INSTDIR\UtilityBelt.Service.Installer.exe"
+
+ ;RMDir "$INSTDIR\"
+
+SectionEnd
+
+; eof
\ No newline at end of file
diff --git a/MosswartMassacreNG/scripts/post-build.ps1 b/MosswartMassacreNG/scripts/post-build.ps1
new file mode 100644
index 0000000..baf2d73
--- /dev/null
+++ b/MosswartMassacreNG/scripts/post-build.ps1
@@ -0,0 +1,17 @@
+param(
+ [string]$NuGetPackageRoot,
+ [string]$ProjectDir
+)
+
+if ($Env:OS -and $Env:OS -like '*Windows*') {
+
+ $makensis = Join-Path $NuGetPackageRoot 'nsis-tool\3.0.8\tools\makensis.exe'
+ $installer = Join-Path $ProjectDir 'scripts\installer.nsi'
+
+ Write-Verbose "Using makensis at $makensis"
+ & $makensis $installer
+}
+else {
+ # Only runs when building on Linux/macOS with makensis in PATH
+ & makensis "$ProjectDir/scripts/installer.nsi"
+}
diff --git a/README.md b/README.md
index 2805752..48ccd18 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,54 @@
+<<<<<<< HEAD
+<<<<<<< HEAD
+# MosswartMassacreNG
+
+=======
+# 🚀 MosswartMassacreNG
+
+A hot-reloadable [Decal](https://decaldev.com/) v2.9.8.3 plugin that builds against .NET Framework 4.8 and [UtilityBelt.Service](https://gitlab.com/utilitybelt/utilitybelt.service) for the views.
+
+
+## 🔧 Developing
+
+ * The plugin code is located in the `MosswartMassacreNG` project. You can ignore the `MosswartMassacreNG.Loader` project, it contains the hot-reloader and doesn't need to be edited unless you are doing something out of the ordinary.
+ * `MosswartMassacreNG/PluginCore.cs` is the main Plugin class. It contains the plugin startup / shutdown methods.
+ * `MosswartMassacreNG/ExampleUI.cs` includes some demo UI code.
+ * `MosswartMassacreNG/scripts/installer.nsi` is the NSIS script used to generate the installer.
+ * Build the new plugin solution in Visual Studio with `Ctrl+Shift+B`, or by going to `Build -> Build Solution`.
+ * Add both `MosswartMassacreNG.dll` and `MosswartMassacreNG.Loader.dll` to decal by opening decal from the tray and selecting `Add -> Browse -> /bin/Release/net481/`. (Select each dll file individually.)
+ * Disable `MosswartMassacreNG` in decal by unchecking it under the `Plugins` list.
+ * During development you should have `MosswartMassacreNG` (under `Plugins`) disabled, and `MosswartMassacreNG.Loader` enabled (under `Network Filters`) in decal. This allows for hot-reloading of the plugin without logging out / restarting the client.
+ * To hot-reload, just recompile the plugin while ingame. You should see a message in the chat window showing that the plugin has reloaded.
+
+## 📦 Releasing
+
+ * Right click the Plugin project and choose `Properties`. Scroll down and update the version number.
+ * Build the latest release version.
+ * In decal, enable your Plugin under the `Plugins` section, and disable `MosswartMassacreNG.Loader` under Network Filters. This allows you to test the plugin with hot-reloading disabled.
+ * Ensure the plugin works as expected ingame.
+ * Test the installer in `bin/Release/`.
+ * Distribute the installer.
+
+## Build Server Requirements
+ * Either use the docker image at `TODO` or use a build server with the following requirements:
+ * All build servers:
+ * [Powershell](https://learn.microsoft.com/en-us/powershell/) is in the environment `PATH` by calling `powershell`.
+ * Non-Windows build servers:
+ * [NSIS](https://nsis.sourceforge.io/Main_Page) is in the environment `PATH` by calling `makensis`.
+ * DotNet 6 SDK installed.
+
+## 💡 Tips
+
+ * If you need to reference more decal dlls, make sure to copy them to `deps/` and reference from there to maintain linux build compatibility.
+ * When hot-reloading, events like `CharacterFilter.LoginComplete` have already triggered when the plugin reloads so the plugin will never see them. During plugin startup, you can check the current login state to determine if this is a normal load, or a hot one.
+ ```csharp
+ protected override void Startup() {
+ var isHotReload = CoreManager.Current.CharacterFilter.LoginStatus == 3;
+ }
+ ```
+ * If hot-reloading is being prevented because `.Loader` is trying to be recompiled and is locked by acclient, you can right click the `.Loader` project in the Visual Studio `Solution Explorer` and select `Unload Project` to prevent it from being rebuilt. **Note:** You must build `.Loader` at least once before unloading the project, if you want to use hot-reloading.
+>>>>>>> bae77fb (init)
+=======
# MosswartMassacreNG
+>>>>>>> 5022342fd81a32611a921fcd07267023dacc6eca
diff --git a/deps/Decal.Adapter.dll b/deps/Decal.Adapter.dll
new file mode 100644
index 0000000..69b4609
Binary files /dev/null and b/deps/Decal.Adapter.dll differ
diff --git a/deps/Decal.Interop.Core.dll b/deps/Decal.Interop.Core.dll
new file mode 100644
index 0000000..0f94119
Binary files /dev/null and b/deps/Decal.Interop.Core.dll differ