~redstrate/nativelauncher

9c54121a73d49d96c2e3d51e79c87f1c07704b73 — Joshua Goins 2 years ago 209649f
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.
2 files changed, 80 insertions(+), 74 deletions(-)

M NativeLauncher.cpp
M README.md
M NativeLauncher.cpp => NativeLauncher.cpp +59 -64
@@ 2,34 2,28 @@

#include <iostream>
#include <windows.h>
#include <AccCtrl.h>
#include <AclAPI.h>
#include <accctrl.h>
#include <aclapi.h>
#include <iomanip>
#include <sstream>
#include <winsock2.h>

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);

   //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.
int launch_game(wchar_t* appC, wchar_t* argC) {
   std::wstring app(appC);
   std::wstring arg(argC);

   std::wstring input = app_w + L" " + arg_w;
   std::wstring input = app + L" " + arg;
   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];
   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;
}

M README.md => README.md +21 -10
@@ 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!
## 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 an error occured, it is printed into stderr

The usage is extremely useful if you want to use this in your own launcher or game projects:
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 first 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.
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.