catastropy averted?

This commit is contained in:
2026-04-30 11:18:02 +03:00
parent 3e19aac2c2
commit 585abc7132
149 changed files with 25785 additions and 14 deletions
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>MultiboxMutexer</AssemblyName>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<TargetFramework>net7.0</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup>
<LangVersion>14.0</LangVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
</PropertyGroup>
<PropertyGroup />
<ItemGroup />
<ItemGroup>
<Reference Include="Dalamud" />
<Reference Include="ImGui.NET" />
</ItemGroup>
</Project>
@@ -0,0 +1,53 @@
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
namespace MultiboxMutexer.dll.Resourcer;
[StructLayout(LayoutKind.Auto, CharSet = CharSet.Auto)]
internal static class ResourceHelper
{
private static Assembly assembly;
static ResourceHelper()
{
assembly = typeof(ResourceHelper).GetTypeInfo().Assembly;
}
public static Stream AsStream(string P_0)
{
return assembly.GetManifestResourceStream(P_0);
}
public static StreamReader AsStreamReader(string P_0)
{
Stream manifestResourceStream = assembly.GetManifestResourceStream(P_0);
if (manifestResourceStream == null)
{
return null;
}
return new StreamReader(manifestResourceStream);
}
public static string AsString(string P_0)
{
StreamReader streamReader = null;
Stream stream = null;
try
{
stream = assembly.GetManifestResourceStream(P_0);
if (stream == null)
{
throw new Exception("Could not find a resource named '" + P_0 + "'.");
}
streamReader = new StreamReader(stream);
return streamReader.ReadToEnd();
}
finally
{
streamReader?.Dispose();
stream?.Dispose();
}
}
}
@@ -0,0 +1,289 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace MultiboxMutexer;
public static class HandleManager
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct SYSTEM_HANDLE_INFORMATION
{
public uint OwnerPID;
public byte ObjectType;
public byte HandleFlags;
public ushort HandleValue;
public nuint ObjectPointer;
public nint AccessMask;
}
private struct OBJECT_BASIC_INFORMATION
{
public uint Attributes;
public uint GrantedAccess;
public uint HandleCount;
public uint PointerCount;
public uint PagedPoolUsage;
public uint NonPagedPoolUsage;
public uint Reserved1;
public uint Reserved2;
public uint Reserved3;
public uint NameInformationLength;
public uint TypeInformationLength;
public uint SecurityDescriptorLength;
public FILETIME CreateTime;
}
private struct IO_STATUS_BLOCK
{
public uint Status;
public ulong Information;
}
[Flags]
private enum DuplicateOptions : uint
{
DUPLICATE_CLOSE_SOURCE = 1u,
DUPLICATE_SAME_ACCESS = 2u
}
[Flags]
private enum ProcessAccessFlags : uint
{
All = 0x1F0FFFu,
Terminate = 1u,
CreateThread = 2u,
VMOperation = 8u,
VMRead = 0x10u,
VMWrite = 0x20u,
DupHandle = 0x40u,
SetInformation = 0x200u,
QueryInformation = 0x400u,
Synchronize = 0x100000u
}
[Flags]
private enum NTSTATUS : uint
{
STATUS_SUCCESS = 0u,
STATUS_INFO_LENGTH_MISMATCH = 0xC0000004u
}
[Flags]
private enum OBJECT_INFORMATION_CLASS : uint
{
ObjectBasicInformation = 0u,
ObjectNameInformation = 1u,
ObjectTypeInformation = 2u,
ObjectAllTypesInformation = 3u,
ObjectHandleInformation = 4u
}
[Flags]
private enum SYSTEM_INFORMATION_CLASS : uint
{
SystemHandleInformation = 0x10u
}
[Flags]
private enum FILE_INFORMATION_CLASS
{
FileNameInformation = 9
}
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(nint hObject);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DuplicateHandle(nint hSourceProcessHandle, nint hSourceHandle, nint hTargetProcessHandle, out nint lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, DuplicateOptions dwOptions);
[DllImport("kernel32.dll")]
private static extern nint OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessID);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern NTSTATUS NtQueryInformationFile(nint FileHandle, ref IO_STATUS_BLOCK IoStatusBlock, nint FileInformation, int FileInformationLength, FILE_INFORMATION_CLASS FileInformationClass);
[DllImport("ntdll.dll")]
private static extern NTSTATUS NtQueryObject(nint ObjectHandle, OBJECT_INFORMATION_CLASS ObjectInformationClass, nint ObjectInformation, int ObjectInformationLength, out int ReturnLength);
[DllImport("ntdll.dll")]
private static extern NTSTATUS NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass, nint SystemInformation, int SystemInformationLength, out int ReturnLength);
public static bool ClearMutex()
{
bool result = false;
Process[] processes = Process.GetProcesses();
foreach (Process process in processes)
{
if (process.ProcessName.Equals("ffxiv_dx11", StringComparison.OrdinalIgnoreCase) && KillHandle(process, "6AA83AB5-BAC4-4a36-9F66-A309770760CB_ffxiv_game00", isFile: false))
{
result = true;
}
}
return result;
}
public static bool KillHandle(Process targetProcess, string handleName, bool isFile)
{
bool result = false;
nint allHandles = GetAllHandles();
if (allHandles == IntPtr.Zero)
{
return result;
}
List<SYSTEM_HANDLE_INFORMATION> handles = GetHandles(targetProcess, allHandles);
Marshal.FreeHGlobal(allHandles);
nint num = OpenProcess(ProcessAccessFlags.DupHandle, bInheritHandle: false, (uint)targetProcess.Id);
foreach (SYSTEM_HANDLE_INFORMATION item in handles)
{
string text = ((!isFile) ? GetHandleName(item, num) : GetFileName(item, num));
if (text.Contains(handleName) && CloseHandleEx(item.OwnerPID, new IntPtr(item.HandleValue)))
{
result = true;
}
}
CloseHandle(num);
return result;
}
private static bool CloseHandleEx(uint processID, nint handleToClose)
{
nint num = OpenProcess(ProcessAccessFlags.All, bInheritHandle: false, processID);
nint lpTargetHandle;
bool result = DuplicateHandle(num, handleToClose, IntPtr.Zero, out lpTargetHandle, 0u, bInheritHandle: false, DuplicateOptions.DUPLICATE_CLOSE_SOURCE);
CloseHandle(num);
return result;
}
private static string ConvertToString(nint pStringBuffer)
{
long num = ((IntPtr)pStringBuffer).ToInt64();
int num2 = IntPtr.Size * 2;
return Marshal.PtrToStringAnsi(new IntPtr(num + num2));
}
private static nint GetAllHandles()
{
int num = 65536;
nint num2 = Marshal.AllocHGlobal(num);
int ReturnLength;
NTSTATUS nTSTATUS = NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS.SystemHandleInformation, num2, num, out ReturnLength);
while (true)
{
switch (nTSTATUS)
{
case NTSTATUS.STATUS_INFO_LENGTH_MISMATCH:
break;
case NTSTATUS.STATUS_SUCCESS:
return num2;
default:
Marshal.FreeHGlobal(num2);
return IntPtr.Zero;
}
Marshal.FreeHGlobal(num2);
num *= 2;
num2 = Marshal.AllocHGlobal(num);
nTSTATUS = NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS.SystemHandleInformation, num2, num, out ReturnLength);
}
}
private static List<SYSTEM_HANDLE_INFORMATION> GetHandles(Process targetProcess, nint pSysHandles)
{
List<SYSTEM_HANDLE_INFORMATION> list = new List<SYSTEM_HANDLE_INFORMATION>();
long num = ((IntPtr)pSysHandles).ToInt64();
int num2 = Marshal.ReadInt32(pSysHandles);
for (int i = 0; i < num2; i++)
{
long num3 = IntPtr.Size + i * Marshal.SizeOf(typeof(SYSTEM_HANDLE_INFORMATION));
SYSTEM_HANDLE_INFORMATION item = (SYSTEM_HANDLE_INFORMATION)Marshal.PtrToStructure(new IntPtr(num + num3), typeof(SYSTEM_HANDLE_INFORMATION));
if (item.OwnerPID == (uint)targetProcess.Id)
{
list.Add(item);
}
}
return list;
}
private static string GetFileName(SYSTEM_HANDLE_INFORMATION handleInfo, nint hProcess)
{
try
{
nint handle = Process.GetCurrentProcess().Handle;
DuplicateHandle(hProcess, new IntPtr(handleInfo.HandleValue), handle, out var lpTargetHandle, 0u, bInheritHandle: false, DuplicateOptions.DUPLICATE_SAME_ACCESS);
int num = 512;
nint num2 = Marshal.AllocHGlobal(num);
IO_STATUS_BLOCK IoStatusBlock = default(IO_STATUS_BLOCK);
NtQueryInformationFile(lpTargetHandle, ref IoStatusBlock, num2, num, FILE_INFORMATION_CLASS.FileNameInformation);
CloseHandle(lpTargetHandle);
int num3 = 4;
string? result = Marshal.PtrToStringUni(new IntPtr(((IntPtr)num2).ToInt64() + num3));
Marshal.FreeHGlobal(num2);
return result;
}
catch (Exception)
{
return string.Empty;
}
}
private static string GetHandleName(SYSTEM_HANDLE_INFORMATION targetHandleInfo, nint hProcess)
{
if (((IntPtr)targetHandleInfo.AccessMask).ToInt64() == 1180063)
{
return string.Empty;
}
nint handle = Process.GetCurrentProcess().Handle;
DuplicateHandle(hProcess, new IntPtr(targetHandleInfo.HandleValue), handle, out var lpTargetHandle, 0u, bInheritHandle: false, DuplicateOptions.DUPLICATE_SAME_ACCESS);
int ReturnLength = GetHandleNameLength(lpTargetHandle);
nint num = Marshal.AllocHGlobal(ReturnLength);
NtQueryObject(lpTargetHandle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, num, ReturnLength, out ReturnLength);
nint zero = IntPtr.Zero;
zero = Marshal.AllocHGlobal(ReturnLength);
string result = "";
if (NtQueryObject(lpTargetHandle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, zero, ReturnLength, out ReturnLength) == NTSTATUS.STATUS_SUCCESS)
{
result = Marshal.PtrToStringUni((nint)((long)zero + (long)(2 * IntPtr.Size)));
Marshal.FreeHGlobal(zero);
}
CloseHandle(lpTargetHandle);
ConvertToString(num);
Marshal.FreeHGlobal(num);
return result;
}
private static int GetHandleNameLength(nint handle)
{
int ReturnLength = Marshal.SizeOf(typeof(OBJECT_BASIC_INFORMATION));
nint num = Marshal.AllocHGlobal(ReturnLength);
NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectBasicInformation, num, ReturnLength, out ReturnLength);
OBJECT_BASIC_INFORMATION oBJECT_BASIC_INFORMATION = (OBJECT_BASIC_INFORMATION)Marshal.PtrToStructure(num, typeof(OBJECT_BASIC_INFORMATION));
Marshal.FreeHGlobal(num);
if (oBJECT_BASIC_INFORMATION.NameInformationLength == 0)
{
return 256;
}
return (int)oBJECT_BASIC_INFORMATION.NameInformationLength;
}
}
@@ -0,0 +1,107 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Dalamud.Game.Command;
using Dalamud.Game.Gui;
using Dalamud.IoC;
using Dalamud.Logging;
using Dalamud.Plugin;
namespace MultiboxMutexer;
public sealed class Plugin : IDalamudPlugin, IDisposable
{
[Flags]
private enum ProcessAccessFlags : uint
{
All = 0x1F0FFFu,
Terminate = 1u,
CreateThread = 2u,
VMOperation = 8u,
VMRead = 0x10u,
VMWrite = 0x20u,
DupHandle = 0x40u,
SetInformation = 0x200u,
QueryInformation = 0x400u,
Synchronize = 0x100000u
}
[Flags]
private enum DuplicateOptions : uint
{
DUPLICATE_CLOSE_SOURCE = 1u,
DUPLICATE_SAME_ACCESS = 2u
}
private const uint PROCESS_DUP_HANDLE = 64u;
public string Name => "Multibox Mutexer";
public DalamudPluginInterface PluginInterface { get; }
public CommandManager CommandManager { get; }
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(nint hObject);
[DllImport("kernel32.dll")]
private static extern nint OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, nint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DuplicateHandle(nint hSourceProcessHandle, nint hSourceHandle, nint hTargetProcessHandle, out nint lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, DuplicateOptions dwOptions);
public static bool CloseHandleEx(nint pid, nint handle)
{
nint num = OpenProcess(ProcessAccessFlags.DupHandle, bInheritHandle: false, pid);
nint lpTargetHandle = IntPtr.Zero;
bool result = DuplicateHandle(num, handle, IntPtr.Zero, out lpTargetHandle, 0u, bInheritHandle: false, DuplicateOptions.DUPLICATE_CLOSE_SOURCE);
CloseHandle(num);
return result;
}
public Plugin([RequiredVersion("1.0")] DalamudPluginInterface pluginInterface, [RequiredVersion("1.0")] CommandManager commandManager, [RequiredVersion("1.0")] GameGui gameGui)
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_0030: Expected O, but got Unknown
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
//IL_0030: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Expected O, but got Unknown
PluginInterface = pluginInterface;
CommandManager = commandManager;
CommandManager.AddHandler("/mutex", new CommandInfo(new HandlerDelegate(OnCommand))
{
HelpMessage = "Fix mutex"
});
PluginLog.Debug("LOADED?", Array.Empty<object>());
Process currentProcess = Process.GetCurrentProcess();
PluginLog.Debug("CURRENT PROCESS PID: " + currentProcess.Id, Array.Empty<object>());
ReleaseAllMutexes(currentProcess);
}
public void ReleaseAllMutexes(Process proc)
{
PluginLog.Debug("MUTEXER CALLED", Array.Empty<object>());
if (HandleManager.ClearMutex())
{
PluginLog.Debug("Succesfully killed the mutex", Array.Empty<object>());
}
else
{
PluginLog.Warning("Failed to kill mutex!", Array.Empty<object>());
}
}
private void OnCommand(string command, string args)
{
PluginLog.Debug("RUNNING MUTEXER", Array.Empty<object>());
Process currentProcess = Process.GetCurrentProcess();
ReleaseAllMutexes(currentProcess);
}
public void Dispose()
{
CommandManager.RemoveHandler("/mutex");
}
}
@@ -0,0 +1,28 @@
using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.Gui;
using Dalamud.IoC;
namespace MultiboxMutexer;
public class Service
{
[PluginService]
public static ClientState ClientState { get; private set; }
[PluginService]
public static Framework Framework { get; private set; }
[PluginService]
public static GameGui GameGui { get; private set; }
[PluginService]
public static SigScanner SigScanner { get; private set; }
[PluginService]
public static ChatGui Chat { get; private set; }
[PluginService]
public static DataManager DataManager { get; private set; }
}
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Dalamud.Game;
using Dalamud.Interface;
using ImGuiNET;
namespace MultiboxMutexer;
internal static class Util
{
internal static bool TryScanText(this SigScanner scanner, string sig, out nint result)
{
result = IntPtr.Zero;
try
{
result = scanner.ScanText(sig);
return true;
}
catch (KeyNotFoundException)
{
return false;
}
}
private unsafe static byte[] ReadTerminatedBytes(byte* ptr)
{
if (ptr == null)
{
return new byte[0];
}
List<byte> list = new List<byte>();
while (*ptr != 0)
{
list.Add(*ptr);
ptr++;
}
return list.ToArray();
}
internal unsafe static string ReadTerminatedString(byte* ptr)
{
return Encoding.UTF8.GetString(ReadTerminatedBytes(ptr));
}
internal static bool ContainsIgnoreCase(this string haystack, string needle)
{
return CultureInfo.InvariantCulture.CompareInfo.IndexOf(haystack, needle, CompareOptions.IgnoreCase) >= 0;
}
internal static bool IconButton(FontAwesomeIcon icon, string id)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
ImGui.PushFont(UiBuilder.IconFont);
bool result = ImGui.Button(FontAwesomeExtensions.ToIconString(icon) + "##" + id);
ImGui.PopFont();
return result;
}
}
@@ -0,0 +1,6 @@
internal class MultiboxMutexer_ProcessedByFody
{
internal const string FodyVersion = "6.6.4.0";
internal const string Resourcer = "1.8.0.0";
}
@@ -0,0 +1,15 @@
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
[assembly: AssemblyCompany("aRkker")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Why are we still here? Just to suffer?")]
[assembly: AssemblyFileVersion("1.0.1.2")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("MultiboxMutexer")]
[assembly: AssemblyTitle("MultiboxMutexer")]
[assembly: AssemblyVersion("1.0.1.2")]