-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
823 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.10.35013.160 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiBloxy", "MultiBloxy\MultiBloxy.csproj", "{9CBD2E5E-735B-4FE4-A56B-33032253C305}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{9CBD2E5E-735B-4FE4-A56B-33032253C305}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{9CBD2E5E-735B-4FE4-A56B-33032253C305}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{9CBD2E5E-735B-4FE4-A56B-33032253C305}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{9CBD2E5E-735B-4FE4-A56B-33032253C305}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {9AF4AC17-D81F-4776-8363-7AF4F68E69AA} | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
using System; | ||
using System.IO; | ||
using System.Xml.Linq; | ||
|
||
namespace MultiBloxy | ||
{ | ||
public class Config | ||
{ | ||
private static readonly string ConfigFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.xml"); | ||
private static XDocument configDocument; | ||
|
||
static Config() | ||
{ | ||
Load(); | ||
} | ||
|
||
public static void Load() | ||
{ | ||
if (File.Exists(ConfigFilePath)) | ||
{ | ||
configDocument = XDocument.Load(ConfigFilePath); | ||
} | ||
else | ||
{ | ||
configDocument = new XDocument(new XElement("Config")); | ||
} | ||
} | ||
|
||
public static void Save() | ||
{ | ||
if (!configDocument.Root.HasElements) | ||
{ | ||
if (File.Exists(ConfigFilePath)) | ||
{ | ||
File.Delete(ConfigFilePath); | ||
} | ||
} | ||
else | ||
{ | ||
configDocument.Save(ConfigFilePath); | ||
} | ||
} | ||
|
||
public static void Set(string key, object value) | ||
{ | ||
var element = configDocument.Root.Element(key); | ||
if (element != null) | ||
{ | ||
element.Value = value.ToString(); | ||
} | ||
else | ||
{ | ||
configDocument.Root.Add(new XElement(key, value)); | ||
} | ||
Save(); | ||
} | ||
|
||
public static T Get<T>(string key, T defaultValue = default) | ||
{ | ||
var element = configDocument.Root.Element(key); | ||
if (element != null) | ||
{ | ||
return (T)Convert.ChangeType(element.Value, typeof(T)); | ||
} | ||
return defaultValue; | ||
} | ||
|
||
public static void Remove(string key) | ||
{ | ||
var element = configDocument.Root.Element(key); | ||
if (element != null) | ||
{ | ||
element.Remove(); | ||
Save(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
using System; | ||
using System.Diagnostics; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace MultiBloxy | ||
{ | ||
public class HandleCloser | ||
{ | ||
[StructLayout(LayoutKind.Sequential)] | ||
private struct SYSTEM_HANDLE_INFORMATION | ||
{ | ||
public uint ProcessId; | ||
public byte ObjectTypeNumber; | ||
public byte Flags; | ||
public ushort Handle; | ||
public uint Object; | ||
public uint GrantedAccess; | ||
} | ||
|
||
[StructLayout(LayoutKind.Sequential)] | ||
private struct UNICODE_STRING | ||
{ | ||
public ushort Length; | ||
public ushort MaximumLength; | ||
public IntPtr Buffer; | ||
} | ||
|
||
[StructLayout(LayoutKind.Sequential)] | ||
private struct OBJECT_NAME_INFORMATION | ||
{ | ||
public UNICODE_STRING Name; | ||
} | ||
|
||
[DllImport("kernel32.dll", SetLastError = true)] | ||
private static extern IntPtr OpenEvent(uint dwDesiredAccess, bool bInheritHandle, string lpName); | ||
|
||
[DllImport("ntdll.dll")] | ||
private static extern uint NtQuerySystemInformation(int systemInformationClass, IntPtr systemInformation, uint systemInformationLength, out uint returnLength); | ||
|
||
[DllImport("ntdll.dll")] | ||
private static extern uint NtQueryObject(IntPtr handle, int objectInformationClass, IntPtr objectInformation, uint objectInformationLength, out uint returnLength); | ||
|
||
[DllImport("kernel32.dll", SetLastError = true)] | ||
private static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId); | ||
|
||
[DllImport("kernel32.dll", SetLastError = true)] | ||
[return: MarshalAs(UnmanagedType.Bool)] | ||
private static extern bool CloseHandle(IntPtr hObject); | ||
|
||
[DllImport("kernel32.dll", SetLastError = true)] | ||
private static extern IntPtr GetCurrentProcess(); | ||
|
||
[DllImport("kernel32.dll", SetLastError = true)] | ||
[return: MarshalAs(UnmanagedType.Bool)] | ||
private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions); | ||
|
||
private const int SystemHandleInformation = 16; | ||
private const int ObjectNameInformation = 1; | ||
private const uint PROCESS_ALL = 0x001F0FFF; | ||
private const uint DUPLICATE_CLOSE_SOURCE = 0x0001; | ||
private const uint DUPLICATE_SAME_ACCESS = 0x0002; | ||
|
||
// I don't think this is the most ideal and optimized solution. Feel free to create a pull request with improvements / refactoring | ||
public static void CloseAllHandles() | ||
{ | ||
// Get all processes with the name "RobloxPlayerBeta" | ||
Process[] processes = Process.GetProcessesByName("RobloxPlayerBeta"); | ||
|
||
// Iterate through each process | ||
foreach (var process in processes) | ||
{ | ||
uint size = 0x10000; | ||
IntPtr buffer = Marshal.AllocHGlobal((int)size); | ||
|
||
// Loop to query system information until the buffer is large enough | ||
while (true) | ||
{ | ||
// Query system information for handles | ||
uint status = NtQuerySystemInformation(SystemHandleInformation, buffer, size, out uint returnLength); | ||
|
||
// If the buffer is too small, double its size and retry | ||
if (status == 0xC0000004) | ||
{ | ||
size *= 2; | ||
Marshal.FreeHGlobal(buffer); | ||
buffer = Marshal.AllocHGlobal((int)size); | ||
} | ||
else | ||
{ | ||
break; | ||
} | ||
} | ||
|
||
// Read the number of handles from the buffer | ||
int handleCount = Marshal.ReadInt32(buffer); | ||
IntPtr ptr = new IntPtr(buffer.ToInt64() + Marshal.SizeOf(typeof(int))); | ||
|
||
// Iterate through each handle | ||
for (int i = 0; i < handleCount; i++) | ||
{ | ||
// Get handle information from the buffer | ||
SYSTEM_HANDLE_INFORMATION handleInfo = (SYSTEM_HANDLE_INFORMATION)Marshal.PtrToStructure(ptr, typeof(SYSTEM_HANDLE_INFORMATION)); | ||
|
||
// Check if the handle belongs to the current process | ||
if (handleInfo.ProcessId == process.Id) | ||
{ | ||
// Open the process to get a handle | ||
IntPtr processHandle = OpenProcess(PROCESS_ALL, false, process.Id); | ||
if (processHandle == IntPtr.Zero) | ||
{ | ||
// Move to the next handle if opening the process fails | ||
ptr = new IntPtr(ptr.ToInt64() + Marshal.SizeOf(typeof(SYSTEM_HANDLE_INFORMATION))); | ||
continue; | ||
} | ||
|
||
IntPtr dupHandle = IntPtr.Zero; | ||
// Duplicate the handle to the current process | ||
bool success = DuplicateHandle(processHandle, new IntPtr(handleInfo.Handle), GetCurrentProcess(), out dupHandle, 0, false, DUPLICATE_SAME_ACCESS); | ||
if (!success) | ||
{ | ||
// Close the process handle and move to the next handle if duplication fails | ||
CloseHandle(processHandle); | ||
ptr = new IntPtr(ptr.ToInt64() + Marshal.SizeOf(typeof(SYSTEM_HANDLE_INFORMATION))); | ||
continue; | ||
} | ||
|
||
uint bufferSize = 0x1000; | ||
IntPtr nameBuffer = Marshal.AllocHGlobal((int)bufferSize); | ||
|
||
// Query the object name information for the duplicated handle | ||
uint status = NtQueryObject(dupHandle, ObjectNameInformation, nameBuffer, bufferSize, out uint returnLength); | ||
|
||
if (status != 0) | ||
{ | ||
// Free resources and move to the next handle if querying the object name fails | ||
Marshal.FreeHGlobal(nameBuffer); | ||
CloseHandle(dupHandle); | ||
CloseHandle(processHandle); | ||
ptr = new IntPtr(ptr.ToInt64() + Marshal.SizeOf(typeof(SYSTEM_HANDLE_INFORMATION))); | ||
continue; | ||
} | ||
|
||
// Get the object name information from the buffer | ||
OBJECT_NAME_INFORMATION objectNameInfo = (OBJECT_NAME_INFORMATION)Marshal.PtrToStructure(nameBuffer, typeof(OBJECT_NAME_INFORMATION)); | ||
if (objectNameInfo.Name.Length > 0) | ||
{ | ||
// Convert the object name to a string | ||
string name = Marshal.PtrToStringUni(objectNameInfo.Name.Buffer, objectNameInfo.Name.Length / 2); | ||
if (name.Contains("ROBLOX_singletonEvent")) | ||
{ | ||
// Close the handle if it matches the target name | ||
bool success2 = DuplicateHandle(processHandle, new IntPtr(handleInfo.Handle), IntPtr.Zero, out _, 0, false, DUPLICATE_CLOSE_SOURCE); | ||
} | ||
} | ||
|
||
// Free resources | ||
Marshal.FreeHGlobal(nameBuffer); | ||
CloseHandle(dupHandle); | ||
CloseHandle(processHandle); | ||
} | ||
// Move to the next handle | ||
ptr = new IntPtr(ptr.ToInt64() + Marshal.SizeOf(typeof(SYSTEM_HANDLE_INFORMATION))); | ||
} | ||
|
||
// Free the allocated buffer | ||
Marshal.FreeHGlobal(buffer); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
using System.Collections.Generic; | ||
using System.Globalization; | ||
|
||
namespace MultiBloxy | ||
{ | ||
public class Localization | ||
{ | ||
private readonly Dictionary<string, Dictionary<string, string>> _translations; | ||
private string _currentLocale; | ||
|
||
public Localization() | ||
{ | ||
_translations = new Dictionary<string, Dictionary<string, string>>(); | ||
_currentLocale = CultureInfo.CurrentCulture.Name; | ||
LoadTranslations(); | ||
} | ||
|
||
private void LoadTranslations() | ||
{ | ||
_translations["en"] = new Dictionary<string, string> | ||
{ | ||
{ "ContextMenu.StatusMenuItem.Running", "Status: Running" }, | ||
{ "ContextMenu.StatusMenuItem.Paused", "Status: Paused" }, | ||
{ "ContextMenu.StatusMenuItem.Error", "Status: Error creating Mutex" }, | ||
{ "ContextMenu.PauseMenuItem.Pause", "Pause" }, | ||
{ "ContextMenu.PauseMenuItem.Resume", "Resume" }, | ||
{ "ContextMenu.ReloadMenuItem.Reload", "Reload" }, | ||
{ "ContextMenu.StartNewInstanceMenuItem.StartNewInstance", "Start New Roblox Instance" }, | ||
{ "ContextMenu.StopAllInstancesMenuItem.StopAllInstances", "Stop All Roblox Instances" }, | ||
{ "ContextMenu.ShowInExplorerMenuItem.ShowInExplorer", "Show In Explorer" }, | ||
{ "ContextMenu.SettingsMenuItem.Settings", "Settings" }, | ||
{ "ContextMenu.SettingsMenuItem.PauseOnLaunchMenuItem.PauseOnLaunch", "Pause on Launch" }, | ||
{ "ContextMenu.SettingsMenuItem.ResetRememberedMenuItem.ResetRemembered", "Reset Remembered Options" }, | ||
{ "ContextMenu.ExitMenuItem.Exit", "Exit" }, | ||
{ "Error.Mutex.Caption", "Failed to Create Mutex" }, | ||
{ "Error.Mutex.Message", "An error occurred while creating the Mutex. This likely happened because when {0} was launched, Roblox was already running and had registered its handle. You can do the following:" }, | ||
{ "Error.Mutex.Action.Fix", "Close the handle for all instances of Roblox" }, | ||
{ "Error.Mutex.Action.Abort", "Stop all Roblox instances" }, | ||
{ "Error.Mutex.Action.Retry", "Try again" }, | ||
{ "Error.Mutex.Action.Ignore", "Ignore the error and continue" }, | ||
{ "Error.Mutex.Action.Remember", "Remember this choice" }, | ||
{ "Error.Mutex.Action.Confirm", "Confirm" }, | ||
{ "Error.Singleton.Caption", "Singleton Error" }, | ||
{ "Error.Singleton.Message", "{0} is already running. Try looking in the system tray." } | ||
}; | ||
|
||
_translations["ru"] = new Dictionary<string, string> | ||
{ | ||
{ "ContextMenu.StatusMenuItem.Running", "Статус: Работает" }, | ||
{ "ContextMenu.StatusMenuItem.Paused", "Статус: Приостановлено" }, | ||
{ "ContextMenu.StatusMenuItem.Error", "Статус: Ошибка создания Mutex" }, | ||
{ "ContextMenu.PauseMenuItem.Pause", "Приостановить" }, | ||
{ "ContextMenu.PauseMenuItem.Resume", "Возобновить" }, | ||
{ "ContextMenu.ReloadMenuItem.Reload", "Перезагрузить" }, | ||
{ "ContextMenu.StartNewInstanceMenuItem.StartNewInstance", "Запустить новый экземпляр Roblox" }, | ||
{ "ContextMenu.StopAllInstancesMenuItem.StopAllInstances", "Закрыть все экземпляры Roblox" }, | ||
{ "ContextMenu.ShowInExplorerMenuItem.ShowInExplorer", "Показать в проводнике" }, | ||
{ "ContextMenu.SettingsMenuItem.Settings", "Настройки" }, | ||
{ "ContextMenu.SettingsMenuItem.PauseOnLaunchMenuItem.PauseOnLaunch", "Приостановить при запуске" }, | ||
{ "ContextMenu.SettingsMenuItem.ResetRememberedMenuItem.ResetRemembered", "Сбросить запомненные параметры" }, | ||
{ "ContextMenu.ExitMenuItem.Exit", "Выход" }, | ||
{ "Error.Mutex.Caption", "Не удалось создать Mutex" }, | ||
{ "Error.Mutex.Message", "Произошла ошибка при создании Mutex. Скорее всего, это связано с тем, что при запуске {0} Roblox уже был запущен и успел зарегистрировать свой дескриптор. Вы можете сделать следующее:" }, | ||
{ "Error.Mutex.Action.Fix", "Закрыть дескриптор для всех экземпляров Roblox" }, | ||
{ "Error.Mutex.Action.Abort", "Закрыть все экземпляры Roblox" }, | ||
{ "Error.Mutex.Action.Retry", "Попробовать снова" }, | ||
{ "Error.Mutex.Action.Ignore", "Игнорировать ошибку и продолжить" }, | ||
{ "Error.Mutex.Action.Remember", "Запомнить этот выбор" }, | ||
{ "Error.Mutex.Action.Confirm", "Подтвердить" }, | ||
{ "Error.Singleton.Caption", "Ошибка одиночного экземпляра" }, | ||
{ "Error.Singleton.Message", "{0} уже запущен. Попробуйте поискать в области уведомлений." } | ||
}; | ||
} | ||
|
||
public string GetTranslation(string key) | ||
{ | ||
string locale = GetLocaleWithoutRegion(_currentLocale); | ||
|
||
if (_translations.ContainsKey(locale) && _translations[locale].ContainsKey(key)) | ||
{ | ||
return _translations[locale][key]; | ||
} | ||
|
||
// Fallback to English if the translation is not found | ||
if (_translations.ContainsKey("en") && _translations["en"].ContainsKey(key)) | ||
{ | ||
return _translations["en"][key]; | ||
} | ||
|
||
// Fallback to the key if the translation is not found | ||
return key; | ||
} | ||
|
||
private string GetLocaleWithoutRegion(string locale) | ||
{ | ||
int index = locale.IndexOf('-'); | ||
if (index != -1) | ||
{ | ||
return locale.Substring(0, index); | ||
} | ||
return locale; | ||
} | ||
} | ||
} |
Oops, something went wrong.