1
Fork 0

Communicate through TCP instead of stdout and exit codes

This now makes it way more stable under scenarios where you're
running it under Wine, which is kind of finnicky with stdout and exit
codes. Now the first argument is a port number, which recieves the PID
upon completion.

It now successfully compiles under MinGW as well.
This commit is contained in:
Joshua Goins 2022-04-06 13:11:45 -04:00
parent 209649ffa2
commit 9c54121a73
2 changed files with 81 additions and 75 deletions

View file

@ -2,34 +2,28 @@
#include <iostream> #include <iostream>
#include <windows.h> #include <windows.h>
#include <AccCtrl.h> #include <accctrl.h>
#include <AclAPI.h> #include <aclapi.h>
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
#include <winsock2.h>
using namespace std; bool disable_debug_privilege() {
bool disable_debug_privilege()
{
HANDLE hToken = NULL; HANDLE hToken = NULL;
LUID luidDebugPrivilege; LUID luidDebugPrivilege;
PRIVILEGE_SET RequiredPrivileges; PRIVILEGE_SET RequiredPrivileges;
BOOL bResult; BOOL bResult;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) {
{ std::cerr << "OpenProcessToken failed: " << GetLastError() << std::endl;
std::cout << "OpenProcessToken failed: " << GetLastError() << std::endl;
return false; return false;
} }
if (!LookupPrivilegeValue(NULL, L"SeDebugPrivilege", &luidDebugPrivilege)) {
if (!LookupPrivilegeValue(NULL, L"SeDebugPrivilege", &luidDebugPrivilege)) std::cerr << "LookupPrivilegeValue failed: " << GetLastError() << std::endl;
{
std::cout << "LookupPrivilegeValue failed: " << GetLastError() << std::endl;
return false; return false;
} }
RequiredPrivileges.PrivilegeCount = 1; RequiredPrivileges.PrivilegeCount = 1;
RequiredPrivileges.Control = PRIVILEGE_SET_ALL_NECESSARY; RequiredPrivileges.Control = PRIVILEGE_SET_ALL_NECESSARY;
@ -38,15 +32,14 @@ bool disable_debug_privilege()
PrivilegeCheck(hToken, &RequiredPrivileges, &bResult); PrivilegeCheck(hToken, &RequiredPrivileges, &bResult);
if (bResult) // SeDebugPrivilege is enabled; try disabling it if (bResult) { // SeDebugPrivilege is enabled; try disabling it
{
TOKEN_PRIVILEGES TokenPrivileges; TOKEN_PRIVILEGES TokenPrivileges;
TokenPrivileges.PrivilegeCount = 1; TokenPrivileges.PrivilegeCount = 1;
TokenPrivileges.Privileges[0].Luid = luidDebugPrivilege; TokenPrivileges.Privileges[0].Luid = luidDebugPrivilege;
TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_REMOVED; TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_REMOVED;
if (!AdjustTokenPrivileges(hToken, FALSE, &TokenPrivileges, 0, NULL, 0)) { if (!AdjustTokenPrivileges(hToken, FALSE, &TokenPrivileges, 0, NULL, 0)) {
std::cout << "AdjustTokenPrivileges failed: " << GetLastError() << std::endl; std::cerr << "AdjustTokenPrivileges failed: " << GetLastError() << std::endl;
return false; return false;
} }
} }
@ -59,47 +52,39 @@ struct handle_data {
HWND window_handle; 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; handle_data& data = *(handle_data*)lParam;
unsigned long process_id = 0; unsigned long process_id = 0;
GetWindowThreadProcessId(handle, &process_id); GetWindowThreadProcessId(handle, &process_id);
if (data.process_id != process_id) if (data.process_id != process_id)
return TRUE; return TRUE;
data.window_handle = handle; data.window_handle = handle;
return FALSE; return FALSE;
} }
bool has_window(unsigned long process_id) bool has_window(unsigned long process_id) {
{
handle_data data; handle_data data;
data.process_id = process_id; data.process_id = process_id;
data.window_handle = 0; data.window_handle = 0;
EnumWindows(enum_windows_callback, (LPARAM)&data); EnumWindows(enum_windows_callback, (LPARAM)&data);
return data.window_handle != nullptr; return data.window_handle != nullptr;
} }
int launch_game(char* appC, char* argC) int launch_game(wchar_t* appC, wchar_t* argC) {
{ std::wstring app(appC);
std::string app(appC); std::wstring arg(argC);
std::string arg(argC);
//Prepare CreateProcess args std::wstring input = app + L" " + arg;
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;
wchar_t* arg_concat = const_cast<wchar_t*>(input.c_str()); wchar_t* arg_concat = const_cast<wchar_t*>(input.c_str());
const wchar_t* app_const = app_w.c_str(); const wchar_t* app_const = app.c_str();
TCHAR username[256]; TCHAR username[256];
DWORD size = 256; DWORD size = 256;
if (!GetUserName((TCHAR*)username, &size)) if (!GetUserName((TCHAR*)username, &size)) {
{ std::cerr << "GetUserName failed: " << GetLastError() << std::endl;
std::cout << "GetUserName failed: " << GetLastError() << std::endl;
return -1; return -1;
} }
@ -112,15 +97,13 @@ int launch_game(char* appC, char* argC)
SECURITY_DESCRIPTOR secDes; SECURITY_DESCRIPTOR secDes;
ZeroMemory(&secDes, sizeof(secDes)); ZeroMemory(&secDes, sizeof(secDes));
if (!InitializeSecurityDescriptor(&secDes, 1u)) if (!InitializeSecurityDescriptor(&secDes, 1u)) {
{ std::cerr << "InitializeSecurityDescriptor failed: " << GetLastError() << std::endl;
std::cout << "InitializeSecurityDescriptor failed: " << GetLastError() << std::endl;
return -1; return -1;
} }
if (!SetSecurityDescriptorDacl(&secDes, true, NewAcl, false)) if (!SetSecurityDescriptorDacl(&secDes, true, NewAcl, false)) {
{ std::cerr << "SetSecurityDescriptorDacl failed: " << GetLastError() << std::endl;
std::cout << "SetSecurityDescriptorDacl failed: " << GetLastError() << std::endl;
return -1; return -1;
} }
@ -137,30 +120,25 @@ int launch_game(char* appC, char* argC)
pSec.lpSecurityDescriptor = &secDes; pSec.lpSecurityDescriptor = &secDes;
pSec.bInheritHandle = false; pSec.bInheritHandle = false;
if (!CreateProcess(nullptr, arg_concat, &pSec, nullptr, false, 0x20, nullptr, nullptr, &si, &pi)) if (!CreateProcess(nullptr, arg_concat, &pSec, nullptr, false, 0x20, nullptr, nullptr, &si, &pi)) {
{ std::cerr << "CreateProcess failed: " << GetLastError() << std::endl;
std::cout << "CreateProcess failed: " << GetLastError() << std::endl;
return -1; return -1;
} }
while (!has_window(pi.dwProcessId)) {
while (!has_window(pi.dwProcessId))
{
Sleep(10); Sleep(10);
} }
PACL myAcl; PACL myAcl;
auto gsi = GetSecurityInfo(GetCurrentProcess(), SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &myAcl, nullptr, nullptr); auto gsi = GetSecurityInfo(GetCurrentProcess(), SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &myAcl, nullptr, nullptr);
if (gsi != ERROR_SUCCESS) if (gsi != ERROR_SUCCESS) {
{ std::cerr << "GetSecurityInfo failed: " << gsi << std::endl;
std::cout << "GetSecurityInfo failed: " << gsi << std::endl;
return -1; return -1;
} }
auto ssi = SetSecurityInfo(pi.hProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION, nullptr, nullptr, myAcl, nullptr); auto ssi = SetSecurityInfo(pi.hProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION, nullptr, nullptr, myAcl, nullptr);
if (ssi != ERROR_SUCCESS) if (ssi != ERROR_SUCCESS) {
{ std::cerr << "SetSecurityInfo failed: " << ssi << std::endl;
std::cout << "SetSecurityInfo failed: " << ssi << std::endl;
return -1; return -1;
} }
@ -170,19 +148,36 @@ int launch_game(char* appC, char* argC)
return pi.dwProcessId; return pi.dwProcessId;
} }
int main(int argc, char* argv[]) void pipe_to_ipc(int port, int code) {
{ WSADATA wsaData {};
if (argc < 3) WSAStartup(MAKEWORD(2,2), &wsaData);
{
std::cout << "needs game and arguments"; 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; return -1;
} }
disable_debug_privilege(); 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; return 1;
} }

View file

@ -5,18 +5,29 @@ mirror](https://img.shields.io/badge/mirror-GitHub-black.svg?logo=github)](https
[![ryne.moe [![ryne.moe
mirror](https://img.shields.io/badge/mirror-ryne.moe-red.svg?logo=git)](https://git.ryne.moe/redstrate/nativelauncher) 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 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
it still works! [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. ## Usage
* The first argument is the port number of your TCP server
## What is this? * The second argument is the executable (usually ffxiv_dx11.exe)
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)
* The rest of the arguments is passed as-is to your chosen executable * 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. 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.