catastropy averted?
This commit is contained in:
@@ -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")]
|
||||
Reference in New Issue
Block a user