diff options
Diffstat (limited to 'primedev/primelauncher/main.cpp')
-rw-r--r-- | primedev/primelauncher/main.cpp | 478 |
1 files changed, 478 insertions, 0 deletions
diff --git a/primedev/primelauncher/main.cpp b/primedev/primelauncher/main.cpp new file mode 100644 index 00000000..ae745672 --- /dev/null +++ b/primedev/primelauncher/main.cpp @@ -0,0 +1,478 @@ +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <tlhelp32.h> +#include <filesystem> +#include <sstream> +#include <fstream> +#include <shlwapi.h> +#include <iostream> + +#pragma comment(lib, "Ws2_32.lib") + +#include <winsock2.h> +#include <WS2tcpip.h> + +namespace fs = std::filesystem; + +extern "C" +{ + __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; + __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; +} + +HMODULE hLauncherModule; +HMODULE hHookModule; +HMODULE hTier0Module; + +wchar_t exePath[4096]; +wchar_t buffer[8192]; + +DWORD GetProcessByName(std::wstring processName) +{ + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + + PROCESSENTRY32 processSnapshotEntry = {0}; + processSnapshotEntry.dwSize = sizeof(PROCESSENTRY32); + + if (snapshot == INVALID_HANDLE_VALUE) + return 0; + + if (!Process32First(snapshot, &processSnapshotEntry)) + return 0; + + while (Process32Next(snapshot, &processSnapshotEntry)) + { + if (!wcscmp(processSnapshotEntry.szExeFile, processName.c_str())) + { + CloseHandle(snapshot); + return processSnapshotEntry.th32ProcessID; + } + } + + CloseHandle(snapshot); + return 0; +} + +bool GetExePathWide(wchar_t* dest, DWORD destSize) +{ + if (!dest) + return NULL; + if (destSize < MAX_PATH) + return NULL; + + DWORD length = GetModuleFileNameW(NULL, dest, destSize); + return length && PathRemoveFileSpecW(dest); +} + +FARPROC GetLauncherMain() +{ + static FARPROC Launcher_LauncherMain; + if (!Launcher_LauncherMain) + Launcher_LauncherMain = GetProcAddress(hLauncherModule, "LauncherMain"); + return Launcher_LauncherMain; +} + +void LibraryLoadError(DWORD dwMessageId, const wchar_t* libName, const wchar_t* location) +{ + char text[8192]; + std::string message = std::system_category().message(dwMessageId); + + sprintf_s( + text, + "Failed to load the %ls at \"%ls\" (%lu):\n\n%hs\n\nMake sure you followed the Northstar installation instructions carefully " + "before reaching out for help.", + libName, + location, + dwMessageId, + message.c_str()); + + if (dwMessageId == 126 && std::filesystem::exists(location)) + { + sprintf_s( + text, + "%s\n\nThe file at the specified location DOES exist, so this error indicates that one of its *dependencies* failed to be " + "found.\n\nTry the following steps:\n1. Install Visual C++ 2022 Redistributable: " + "https://aka.ms/vs/17/release/vc_redist.x64.exe\n2. Repair game files", + text); + } + else if (!fs::exists("Titanfall2.exe") && (fs::exists("..\\Titanfall2.exe") || fs::exists("..\\..\\Titanfall2.exe"))) + { + auto curDir = std::filesystem::current_path().filename().string(); + auto aboveDir = std::filesystem::current_path().parent_path().filename().string(); + sprintf_s( + text, + "%s\n\nWe detected that in your case you have extracted the files into a *subdirectory* of your Titanfall 2 " + "installation.\nPlease move all the files and folders from current folder (\"%s\") into the Titanfall 2 installation directory " + "just above (\"%s\").\n\nPlease try out the above steps by yourself before reaching out to the community for support.", + text, + curDir.c_str(), + aboveDir.c_str()); + } + else if (!fs::exists("Titanfall2.exe")) + { + sprintf_s( + text, + "%s\n\nRemember: you need to unpack the contents of this archive into your Titanfall 2 game installation directory, not just " + "to any random folder.", + text); + } + else if (fs::exists("Titanfall2.exe")) + { + sprintf_s( + text, + "%s\n\nTitanfall2.exe has been found in the current directory: is the game installation corrupted or did you not unpack all " + "Northstar files here?", + text); + } + + MessageBoxA(GetForegroundWindow(), text, "Northstar Launcher Error", 0); +} + +void AwaitOriginStartup() +{ + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); + SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (sock != INVALID_SOCKET) + { + const int LSX_PORT = 3216; + + sockaddr_in lsxAddr; + lsxAddr.sin_family = AF_INET; + inet_pton(AF_INET, "127.0.0.1", &(lsxAddr.sin_addr)); + lsxAddr.sin_port = htons(LSX_PORT); + + std::cout << "LSX: connect()" << std::endl; + connect(sock, (struct sockaddr*)&lsxAddr, sizeof(lsxAddr)); + + char buf[4096]; + memset(buf, 0, sizeof(buf)); + + do + { + recv(sock, buf, 4096, 0); + std::cout << buf << std::endl; + + // honestly really shit, this isn't needed for origin due to being able to check OriginClientService + // but for ea desktop we don't have anything like this, so atm we just have to wait to ensure that we start after logging in + Sleep(8000); + } while (!strstr(buf, "<LSX>")); // ensure we're actually getting data from lsx + } + + WSACleanup(); // cleanup sockets and such so game can contact lsx itself +} + +void EnsureOriginStarted() +{ + if (GetProcessByName(L"Origin.exe") || GetProcessByName(L"EADesktop.exe")) + return; // already started + + // unpacked exe will crash if origin isn't open on launch, so launch it + // get origin path from registry, code here is reversed from OriginSDK.dll + HKEY key; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\WOW6432Node\\Origin", 0, KEY_READ, &key) != ERROR_SUCCESS) + { + MessageBoxA(0, "Error: failed reading Origin path!", "Northstar Launcher Error", MB_OK); + return; + } + + char originPath[520]; + DWORD originPathLength = 520; + if (RegQueryValueExA(key, "ClientPath", 0, 0, (LPBYTE)&originPath, &originPathLength) != ERROR_SUCCESS) + { + MessageBoxA(0, "Error: failed reading Origin path!", "Northstar Launcher Error", MB_OK); + return; + } + + std::cout << "[*] Starting Origin..." << std::endl; + + PROCESS_INFORMATION pi; + memset(&pi, 0, sizeof(pi)); + STARTUPINFO si; + memset(&si, 0, sizeof(si)); + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_MINIMIZE; + CreateProcessA( + originPath, + (char*)"", + NULL, + NULL, + false, + CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_PROCESS_GROUP, + NULL, + NULL, + (LPSTARTUPINFOA)&si, + &pi); + + std::cout << "[*] Waiting for Origin..." << std::endl; + + // wait for origin process to boot + do + { + Sleep(500); + } while (!GetProcessByName(L"OriginClientService.exe") && !GetProcessByName(L"EADesktop.exe")); + + // wait for origin to be ready to start + AwaitOriginStartup(); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); +} + +void PrependPath() +{ + wchar_t* pPath; + size_t len; + errno_t err = _wdupenv_s(&pPath, &len, L"PATH"); + if (!err) + { + swprintf_s(buffer, L"PATH=%s\\bin\\x64_retail\\;%s", exePath, pPath); + auto result = _wputenv(buffer); + if (result == -1) + { + MessageBoxW( + GetForegroundWindow(), + L"Warning: could not prepend the current directory to app's PATH environment variable. Something may break because of " + L"that.", + L"Northstar Launcher Warning", + 0); + } + free(pPath); + } + else + { + MessageBoxW( + GetForegroundWindow(), + L"Warning: could not get current PATH environment variable in order to prepend the current directory to it. Something may " + L"break because of that.", + L"Northstar Launcher Warning", + 0); + } +} + +bool ShouldLoadNorthstar(int argc, char* argv[]) +{ + for (int i = 0; i < argc; i++) + if (!strcmp(argv[i], "-nonorthstardll")) + return false; + + auto runNorthstarFile = std::ifstream("run_northstar.txt"); + if (runNorthstarFile) + { + std::stringstream runNorthstarFileBuffer; + runNorthstarFileBuffer << runNorthstarFile.rdbuf(); + runNorthstarFile.close(); + if (runNorthstarFileBuffer.str().starts_with("0")) + return false; + } + return true; +} + +bool LoadNorthstar() +{ + FARPROC Hook_Init = nullptr; + { + std::string strProfile = "R2Northstar"; + char* clachar = strstr(GetCommandLineA(), "-profile="); + if (clachar) + { + std::string cla = std::string(clachar); + if (strncmp(cla.substr(9, 1).c_str(), "\"", 1)) + { + int space = cla.find(" "); + std::string dirname = cla.substr(9, space - 9); + std::cout << "[*] Found profile in command line arguments: " << dirname << std::endl; + strProfile = dirname.c_str(); + } + else + { + std::string quote = "\""; + int quote1 = cla.find(quote); + int quote2 = (cla.substr(quote1 + 1)).find(quote); + std::string dirname = cla.substr(quote1 + 1, quote2); + std::cout << "[*] Found profile in command line arguments: " << dirname << std::endl; + strProfile = dirname; + } + } + else + { + std::cout << "[*] Profile was not found in command line arguments. Using default: R2Northstar" << std::endl; + strProfile = "R2Northstar"; + } + + // Check if "Northstar.dll" exists in profile directory, if it doesnt fall back to root + swprintf_s(buffer, L"%s\\%s\\Northstar.dll", exePath, std::wstring(strProfile.begin(), strProfile.end()).c_str()); + + if (!fs::exists(fs::path(buffer))) + swprintf_s(buffer, L"%s\\Northstar.dll", exePath); + + std::wcout << L"[*] Using: " << buffer << std::endl; + + hHookModule = LoadLibraryExW(buffer, 0, 8u); + if (hHookModule) + Hook_Init = GetProcAddress(hHookModule, "InitialiseNorthstar"); + if (!hHookModule || Hook_Init == nullptr) + { + LibraryLoadError(GetLastError(), L"Northstar.dll", buffer); + return false; + } + } + ((bool (*)())Hook_Init)(); + + return true; +} + +HMODULE LoadDediStub(const char* name) +{ + // this works because materialsystem_dx11.dll uses relative imports, and even a DLL loaded with an absolute path will take precedence + std::cout << "[*] Loading " << name << std::endl; + swprintf_s(buffer, L"%s\\bin\\x64_dedi\\%hs", exePath, name); + HMODULE h = LoadLibraryExW(buffer, 0, LOAD_WITH_ALTERED_SEARCH_PATH); + if (!h) + { + wprintf(L"[*] Failed to load stub %hs from \"%ls\": %hs\n", name, buffer, std::system_category().message(GetLastError()).c_str()); + } + return h; +} + +int main(int argc, char* argv[]) +{ + + if (strstr(GetCommandLineA(), "-waitfordebugger")) + { + while (!IsDebuggerPresent()) + { + // Sleep 100ms to give debugger time to attach. + Sleep(100); + } + } + + if (!GetExePathWide(exePath, sizeof(exePath))) + { + MessageBoxA( + GetForegroundWindow(), + "Failed getting game directory.\nThe game cannot continue and has to exit.", + "Northstar Launcher Error", + 0); + return 1; + } + + SetCurrentDirectoryW(exePath); + + bool noOriginStartup = false; + bool dedicated = false; + bool nostubs = false; + + for (int i = 0; i < argc; i++) + if (!strcmp(argv[i], "-noOriginStartup")) + noOriginStartup = true; + else if (!strcmp(argv[i], "-dedicated")) // also checked by Northstar.dll + dedicated = true; + else if (!strcmp(argv[i], "-nostubs")) + nostubs = true; + + if (!noOriginStartup && !dedicated) + { + EnsureOriginStarted(); + } + + if (dedicated && !nostubs) + { + std::cout << "[*] Loading stubs" << std::endl; + HMODULE gssao, gtxaa, d3d11; + if (!(gssao = GetModuleHandleA("GFSDK_SSAO.win64.dll")) && !(gtxaa = GetModuleHandleA("GFSDK_TXAA.win64.dll")) && + !(d3d11 = GetModuleHandleA("d3d11.dll"))) + { + if (!(gssao = LoadDediStub("GFSDK_SSAO.win64.dll")) || !(gtxaa = LoadDediStub("GFSDK_TXAA.win64.dll")) || + !(d3d11 = LoadDediStub("d3d11.dll"))) + { + if ((!gssao || FreeLibrary(gssao)) && (!gtxaa || FreeLibrary(gtxaa)) && (!d3d11 || FreeLibrary(d3d11))) + { + std::cout << "[*] WARNING: Failed to load d3d11/gfsdk stubs from bin/x64_dedi. " + "The stubs have been unloaded and the original libraries will be used instead" + << std::endl; + } + else + { + // this is highly unlikely + MessageBoxA( + GetForegroundWindow(), + "Failed to load one or more stubs, but could not unload them either.\n" + "The game cannot continue and has to exit.", + "Northstar Launcher Error", + 0); + return 1; + } + } + } + else + { + // this should never happen + std::cout << "[*] WARNING: Failed to load stubs because conflicting modules are already loaded, so those will be used instead " + "(did Northstar initialize too late?)." + << std::endl; + } + } + + { + PrependPath(); + + if (!fs::exists("ns_startup_args.txt")) + { + std::ofstream file("ns_startup_args.txt"); + std::string defaultArgs = "-multiple"; + file.write(defaultArgs.c_str(), defaultArgs.length()); + file.close(); + } + if (!fs::exists("ns_startup_args_dedi.txt")) + { + std::ofstream file("ns_startup_args_dedi.txt"); + std::string defaultArgs = "+setplaylist private_match"; + file.write(defaultArgs.c_str(), defaultArgs.length()); + file.close(); + } + + std::cout << "[*] Loading tier0.dll" << std::endl; + swprintf_s(buffer, L"%s\\bin\\x64_retail\\tier0.dll", exePath); + hTier0Module = LoadLibraryExW(buffer, 0, LOAD_WITH_ALTERED_SEARCH_PATH); + if (!hTier0Module) + { + LibraryLoadError(GetLastError(), L"tier0.dll", buffer); + return 1; + } + + bool loadNorthstar = ShouldLoadNorthstar(argc, argv); + if (loadNorthstar) + { + std::cout << "[*] Loading Northstar" << std::endl; + if (!LoadNorthstar()) + return 1; + } + else + std::cout << "[*] Going to load the vanilla game" << std::endl; + + std::cout << "[*] Loading launcher.dll" << std::endl; + swprintf_s(buffer, L"%s\\bin\\x64_retail\\launcher.dll", exePath); + hLauncherModule = LoadLibraryExW(buffer, 0, LOAD_WITH_ALTERED_SEARCH_PATH); + if (!hLauncherModule) + { + LibraryLoadError(GetLastError(), L"launcher.dll", buffer); + return 1; + } + } + + std::cout << "[*] Launching the game..." << std::endl; + auto LauncherMain = GetLauncherMain(); + if (!LauncherMain) + MessageBoxA( + GetForegroundWindow(), + "Failed loading launcher.dll.\nThe game cannot continue and has to exit.", + "Northstar Launcher Error", + 0); + + std::cout.flush(); + return ((int(/*__fastcall*/*)(HINSTANCE, HINSTANCE, LPSTR, int))LauncherMain)( + NULL, NULL, NULL, 0); // the parameters aren't really used anyways +} |