diff --git a/NativeLauncher.cpp b/NativeLauncher.cpp index c120ed5..fe357f3 100644 --- a/NativeLauncher.cpp +++ b/NativeLauncher.cpp @@ -2,34 +2,28 @@ #include #include -#include -#include +#include +#include #include #include +#include -using namespace std; - -bool disable_debug_privilege() -{ +bool disable_debug_privilege() { HANDLE hToken = NULL; LUID luidDebugPrivilege; PRIVILEGE_SET RequiredPrivileges; BOOL bResult; - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) - { - std::cout << "OpenProcessToken failed: " << GetLastError() << std::endl; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) { + std::cerr << "OpenProcessToken failed: " << GetLastError() << std::endl; return false; } - - if (!LookupPrivilegeValue(NULL, L"SeDebugPrivilege", &luidDebugPrivilege)) - { - std::cout << "LookupPrivilegeValue failed: " << GetLastError() << std::endl; + if (!LookupPrivilegeValue(NULL, L"SeDebugPrivilege", &luidDebugPrivilege)) { + std::cerr << "LookupPrivilegeValue failed: " << GetLastError() << std::endl; return false; } - RequiredPrivileges.PrivilegeCount = 1; RequiredPrivileges.Control = PRIVILEGE_SET_ALL_NECESSARY; @@ -38,15 +32,14 @@ bool disable_debug_privilege() PrivilegeCheck(hToken, &RequiredPrivileges, &bResult); - if (bResult) // SeDebugPrivilege is enabled; try disabling it - { + if (bResult) { // SeDebugPrivilege is enabled; try disabling it TOKEN_PRIVILEGES TokenPrivileges; TokenPrivileges.PrivilegeCount = 1; TokenPrivileges.Privileges[0].Luid = luidDebugPrivilege; TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_REMOVED; if (!AdjustTokenPrivileges(hToken, FALSE, &TokenPrivileges, 0, NULL, 0)) { - std::cout << "AdjustTokenPrivileges failed: " << GetLastError() << std::endl; + std::cerr << "AdjustTokenPrivileges failed: " << GetLastError() << std::endl; return false; } } @@ -59,47 +52,39 @@ struct handle_data { HWND window_handle; }; -BOOL CALLBACK enum_windows_callback(HWND handle, LPARAM lParam) -{ +BOOL CALLBACK enum_windows_callback(HWND handle, LPARAM lParam) { handle_data& data = *(handle_data*)lParam; unsigned long process_id = 0; GetWindowThreadProcessId(handle, &process_id); if (data.process_id != process_id) return TRUE; + data.window_handle = handle; + return FALSE; } -bool has_window(unsigned long process_id) -{ +bool has_window(unsigned long process_id) { handle_data data; data.process_id = process_id; data.window_handle = 0; EnumWindows(enum_windows_callback, (LPARAM)&data); + return data.window_handle != nullptr; } -int launch_game(char* appC, char* argC) -{ - std::string app(appC); - std::string arg(argC); +int launch_game(wchar_t* appC, wchar_t* argC) { + std::wstring app(appC); + std::wstring arg(argC); - //Prepare CreateProcess args - std::wstring app_w(app.length(), L' '); // Make room for characters - std::copy(app.begin(), app.end(), app_w.begin()); // Copy string to wstring. - - std::wstring arg_w(arg.length(), L' '); // Make room for characters - std::copy(arg.begin(), arg.end(), arg_w.begin()); // Copy string to wstring. - - std::wstring input = app_w + L" " + arg_w; + std::wstring input = app + L" " + arg; wchar_t* arg_concat = const_cast(input.c_str()); - const wchar_t* app_const = app_w.c_str(); + const wchar_t* app_const = app.c_str(); TCHAR username[256]; DWORD size = 256; - if (!GetUserName((TCHAR*)username, &size)) - { - std::cout << "GetUserName failed: " << GetLastError() << std::endl; + if (!GetUserName((TCHAR*)username, &size)) { + std::cerr << "GetUserName failed: " << GetLastError() << std::endl; return -1; } @@ -112,15 +97,13 @@ int launch_game(char* appC, char* argC) SECURITY_DESCRIPTOR secDes; ZeroMemory(&secDes, sizeof(secDes)); - if (!InitializeSecurityDescriptor(&secDes, 1u)) - { - std::cout << "InitializeSecurityDescriptor failed: " << GetLastError() << std::endl; + if (!InitializeSecurityDescriptor(&secDes, 1u)) { + std::cerr << "InitializeSecurityDescriptor failed: " << GetLastError() << std::endl; return -1; } - if (!SetSecurityDescriptorDacl(&secDes, true, NewAcl, false)) - { - std::cout << "SetSecurityDescriptorDacl failed: " << GetLastError() << std::endl; + if (!SetSecurityDescriptorDacl(&secDes, true, NewAcl, false)) { + std::cerr << "SetSecurityDescriptorDacl failed: " << GetLastError() << std::endl; return -1; } @@ -137,30 +120,25 @@ int launch_game(char* appC, char* argC) pSec.lpSecurityDescriptor = &secDes; pSec.bInheritHandle = false; - if (!CreateProcess(nullptr, arg_concat, &pSec, nullptr, false, 0x20, nullptr, nullptr, &si, &pi)) - { - std::cout << "CreateProcess failed: " << GetLastError() << std::endl; + if (!CreateProcess(nullptr, arg_concat, &pSec, nullptr, false, 0x20, nullptr, nullptr, &si, &pi)) { + std::cerr << "CreateProcess failed: " << GetLastError() << std::endl; return -1; } - - while (!has_window(pi.dwProcessId)) - { + while (!has_window(pi.dwProcessId)) { Sleep(10); } PACL myAcl; auto gsi = GetSecurityInfo(GetCurrentProcess(), SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &myAcl, nullptr, nullptr); - if (gsi != ERROR_SUCCESS) - { - std::cout << "GetSecurityInfo failed: " << gsi << std::endl; + if (gsi != ERROR_SUCCESS) { + std::cerr << "GetSecurityInfo failed: " << gsi << std::endl; return -1; } auto ssi = SetSecurityInfo(pi.hProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION, nullptr, nullptr, myAcl, nullptr); - if (ssi != ERROR_SUCCESS) - { - std::cout << "SetSecurityInfo failed: " << ssi << std::endl; + if (ssi != ERROR_SUCCESS) { + std::cerr << "SetSecurityInfo failed: " << ssi << std::endl; return -1; } @@ -170,19 +148,36 @@ int launch_game(char* appC, char* argC) return pi.dwProcessId; } -int main(int argc, char* argv[]) -{ - if (argc < 3) - { - std::cout << "needs game and arguments"; +void pipe_to_ipc(int port, int code) { + WSADATA wsaData {}; + WSAStartup(MAKEWORD(2,2), &wsaData); + + SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + sockaddr_in serverAddr {}; + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(port); + serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); + + connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)); + + char buffer[50] {}; + sprintf(buffer, "%d", code); + + send(clientSocket, buffer, strlen(buffer), 0 ); +} + +int wmain(int argc, wchar_t* argv[]) { + if (argc < 4) { + std::cerr << "Usage: NativeLauncher.exe [port] [executable] [game args]" << std::endl; return -1; } disable_debug_privilege(); - auto pid = launch_game(argv[1], argv[2]); + auto pid = launch_game(argv[2], argv[3]); - std::cout << pid; + pipe_to_ipc(wcstol(argv[1], 0, 10), pid); - return pid; -} \ No newline at end of file + return 1; +} diff --git a/README.md b/README.md index d350db6..65abc2b 100644 --- a/README.md +++ b/README.md @@ -5,18 +5,29 @@ mirror](https://img.shields.io/badge/mirror-GitHub-black.svg?logo=github)](https [![ryne.moe mirror](https://img.shields.io/badge/mirror-ryne.moe-red.svg?logo=git)](https://git.ryne.moe/redstrate/nativelauncher) -Full credit goes to the XIVQuickLauncher team, this is their old ACL bypass before it was rewritten in C#, but -it still works! +This is an ACL bypass for Win32 apps, but specifically is used for [Dalamud](https://github.com/goatcorp/Dalamud) injection into FFXIV. This is a C++ alternative for +[FFXIVQuickLauncher's own bypass which is in C#](https://github.com/goatcorp/FFXIVQuickLauncher/blob/master/src/XIVLauncher.Common.Windows/NativeAclFix.cs). This makes it usable for launchers which aren't in C# like [Astra](https://sr.ht/~redstrate/astra). This is mainly used for external use (i.e. calling NativeLauncher as a wrapper). -If you plan on using this on Linux or macOS, don't fear as I graciously built an [EXE for you](https://github.com/redstrate/nativelauncher/releases/latest). Right now they are built with VS2020 if that means anything to you. - -## What is this? -This is a workaround used by Dalamud to implement a specific ACL bypass to allow them to freely read/write memory, or something? Currently, this is written in nice-looking C# but this was unusable for [xivlauncher](https://github.com/redstrate/xivlauncher) because I was using C++, but lo and behold they were actually using _this_ before! - -The usage is extremely useful if you want to use this in your own launcher or game projects: - -* The first argument is the executable (usually ffxiv_dx11.exe) +## Usage +* The first argument is the port number of your TCP server +* The second argument is the executable (usually ffxiv_dx11.exe) * The rest of the arguments is passed as-is to your chosen executable -* If this is done successfully, it will spit out a PID of the new process for you to use. If it fails, it'll tell you why. +* If an error occured, it is printed into stderr + +This communicates via IPC through TCP sockets. This is to handle usecases such as running within Wine which not only clogs up stdout but also throws away +the error code of the application which makes those two communication methods impossible. + +The only data it will send over the TCP socket (and the port is specified as the second argument) is the PID number, which you can then use to +bootstrap Dalamud or any other injector. I've been testing this under latest Wine Staging (as of writing, 6.22) and the process still works out-of-the box. + +## Building +You can compile this using MinGW or MSVC. If you're using MSVC, just use the vcxproj. If you're trying to compile it using MinGW, use this command: + +`x86_64-w64-mingw32-g++ -municode NativeLauncher.cpp -static -lwsock32 -o NativeLauncher.exe` + +Of course there's always [a prebuilt EXE for you](https://github.com/redstrate/nativelauncher/releases/latest) if can't compile it. + +## Credit +* The XIVQuickLauncher team, who originally wrote this.