Initial commit of MosswartMassacreNG

This commit is contained in:
erik 2025-06-01 13:51:27 +02:00
commit d4edb67594
13 changed files with 847 additions and 0 deletions

15
.gitignore vendored Normal file
View file

@ -0,0 +1,15 @@
# Visual Studio
.vs/
*.user
*.suo
# Build output
bin/
obj/
# NuGet packages
packages/
# OS-specific
Thumbs.db
.DS_Store

9
LICENSE Normal file
View file

@ -0,0 +1,9 @@
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.

View file

@ -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; }
/// <summary>
/// Assembly directory (contains both loader and plugin dlls)
/// </summary>
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 { }
}
}
}

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<OutputType>Library</OutputType>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<OutputPath>..\bin\</OutputPath>
<PlatformTarget>x86</PlatformTarget>
<LangVersion>10</LangVersion>
<GenerateAssemblyInfo>True</GenerateAssemblyInfo>
<RootNamespace>MosswartMassacreNG</RootNamespace>
<AssemblyName>MosswartMassacreNG.Loader</AssemblyName>
</PropertyGroup>
<ItemGroup>
<None Include="LoaderCore.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Decal.Adapter">
<HintPath>.\..\deps\Decal.Adapter.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Decal.Interop.Core, Version=2.9.8.2, Culture=neutral, PublicKeyToken=481f17d392f1fb65, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<EmbedInteropTypes>True</EmbedInteropTypes>
<HintPath>.\..\deps\Decal.Interop.Core.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
</ItemGroup>
</Project>

31
MosswartMassacreNG.sln Normal file
View file

@ -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

View file

@ -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
{
/// <summary>
/// The UBService Hud
/// </summary>
private readonly Hud hud;
/// <summary>
/// The default value for TestText.
/// </summary>
public const string DefaultTestText = "Some Test Text";
/// <summary>
/// Some test text. This value is used to the text input in our UI.
/// </summary>
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;
}
/// <summary>
/// Called every time the ui is redrawing.
/// </summary>
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);
}
}
/// <summary>
/// Called when our print test text button is pressed
/// </summary>
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();
}
}
}

View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<OutputType>Library</OutputType>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<OutputPath>..\bin\</OutputPath>
<PlatformTarget>x86</PlatformTarget>
<Version>1.0.0</Version>
<LangVersion>12</LangVersion>
<GenerateAssemblyInfo>True</GenerateAssemblyInfo>
<ProjectGuid>4b1f02bb-9b95-46f0-ad5b-223fea7392fb</ProjectGuid>
<RootNamespace>MosswartMassacreNG</RootNamespace>
<AssemblyName>MosswartMassacreNG</AssemblyName>
</PropertyGroup>
<ItemGroup>
<None Include="PluginCore.cs" />
<None Include="ExampleUI.cs" />
<Content Include="scripts\installer.nsi" />
<Content Include="scripts\post-build.ps1" />
</ItemGroup>
<ItemGroup>
<Reference Include="Decal.Adapter">
<HintPath>.\..\deps\Decal.Adapter.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="NSIS-Tool" Version="3.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="UtilityBelt.Service" Version="2.2.6">
<ExcludeAssets>runtime; build;</ExcludeAssets>
</PackageReference>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="powershell -ExecutionPolicy RemoteSigned -NoProfile ^&#xD;&#xA; -File &quot;$(MSBuildProjectDirectory)\scripts\post-build.ps1&quot; ^&#xD;&#xA; -NuGetPackageRoot &quot;$(NuGetPackageRoot)\&quot; ^&#xD;&#xA; -ProjectDir &quot;$(MSBuildProjectDirectory)&quot;&#xD;&#xA;" />
</Target>
</Project>

View file

@ -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
{
/// <summary>
/// This is the main plugin class. When your plugin is loaded, Startup() is called, and when it's unloaded Shutdown() is called.
/// </summary>
[FriendlyName("MosswartMassacreNG")]
public class PluginCore : PluginBase
{
private static string _assemblyDirectory = null;
private ExampleUI ui;
/// <summary>
/// Assembly directory containing the plugin dll
/// </summary>
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;
}
}
/// <summary>
/// Called when your plugin is first loaded.
/// </summary>
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;
}
/// <summary>
/// CharacterFilter_LoginComplete event handler.
/// </summary>
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);
}
}
/// <summary>
/// Called when your plugin is unloaded. Either when logging out, closing the client, or hot reloading.
/// </summary>
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
/// <summary>
/// Log an exception to log.txt in the same directory as the plugin.
/// </summary>
/// <param name="ex"></param>
internal static void Log(Exception ex)
{
Log(ex.ToString());
}
/// <summary>
/// Log a string to log.txt in the same directory as the plugin.
/// </summary>
/// <param name="message"></param>
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
}
}

View file

@ -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

View file

@ -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"
}

49
README.md Normal file
View file

@ -0,0 +1,49 @@
<<<<<<< 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 -> <YourProjectPath>/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 `<YourPlugin>.Loader` is trying to be recompiled and is locked by acclient, you can right click the `<YourPlugin>.Loader` project in the Visual Studio `Solution Explorer` and select `Unload Project` to prevent it from being rebuilt. **Note:** You must build `<YourPlugin>.Loader` at least once before unloading the project, if you want to use hot-reloading.
>>>>>>> bae77fb (init)

BIN
deps/Decal.Adapter.dll vendored Normal file

Binary file not shown.

BIN
deps/Decal.Interop.Core.dll vendored Normal file

Binary file not shown.