zJ2KMg>y}EK>d0Y@C5mCi6usUH^432pgThI`x)%advStN_9_f6pi*$xEQLIr=ICK@R zf^DhuRF93a9sg!%XOYT#d!n}icRwh*NGf8`)YL@=osk-6(3z=sXWok;C?=O2Rrock zn~JFfD9|~2Xa+80Vaa0bd_pNJR0yH*T4*YFSRPPYAXhpMM!DNAxe@(SYI0__QD1oD z=%Hs&IZ*#Q>};m4X2gG6#~(ED^z87~vvA}uyT=)*N_^Hj!#O-^z&cUZU7pRB2y2NG zYzkS%L_DBDJRscjb(rq)g3sYliU}{+ZKJH`zzfL13n(UDa3=sV)n32@26%zM)KaV2 z46RUlH{!K5#k^893yRh{Sad^Dz>C^Bc|A5bO zI)gbC((`VbA*Rm%D{}6X6Mb2FfS286naMilWyZ)!A2GaymBBU!*WZH}n9rR`oorAb zb)i88sX3WTjp4kEz%No^)K4E9)maGJ78dXqse~xhL@0A6`9S70iFQ-!lla2J90(VZ z7VqrgplLLO!dw4{Bijn-!YG8NE__ALcb>5+=^41nnvhS1c}>N+&g$*0nmD3ZdXT3q zS8~#EBuB#!`QD9d!*)6Djmxi+F)Dqmuv4lpmL9~Z-qe>V)c|`HF49psXq0^rM+0yd zNKG-QAT{5hP^xk0qn^y9s$$kqTqjs7coKrrF}?A3W>#Ag(zv-8J)-#iW7cnj{CR$P zEHmbuX>5$B?;8VYF ec? plYcoDxeBPSRCt7FsGD+Jix5h~GO>Wi|B8x8;!fi+BvO5xPWuW3aHc zqpe6 nR~ATya@l(6Q#wKKj1C##Xv ;^}Z?u4%Pxd$eDU z?fis3?k(a{P>jJ%*^RQoI30tIqU!RR{+Pn*=2nTqZ-yO){F+DB!!zGz7^_3T> 235a zys8c~2}q}y45U-=og~dIakvjV+|3TR*WvDWxZgP3ki(r^VD-HNNat~l!` xV-QjTm>2NPOTsUO&I1xzaQQ>fWTUm4K z9FA`uYwiw*yW8O&akv48JF(E_F$GAcJz L9Q~ROZj6U9n1M%&2haM&6PRaEQf1w zxP-$^D6*R0=b)Dy#FbdI=L} y z;Bb@SXeW!Wod(1wJ3$oznk*;^G)0hICj;O4g5y(>peh48P0&oBcM7TmI!Vwxpc4hf zfbi@HDXM|^%6bs1#01U_s)>P9)z~Bm1U_D};avdwt9gx3Ir6 !IpiI7R(^$WC zh|Byi`D-nSZ__#wDUFqiAQc0Bg(2iHY*`(w%(<3uWaj!9e8EvZ8}Es7O%>V| !uw(25QvZK4wl1Q<=liLSL$ivK*#7hnWH9 z=ia<3X!AH!m1A>-*z9G>XVV}AE#v()I1DdnL0!{$2fp&~QW2C*+(41>efNQ32dQ`# zVffa#>svJ9c8!4| 8Nv4&hP$`+J8J!q)W4&a;$waPZ>sqo-e>;^8h 4Z0XD`57Fxn)|IZv~8z-|wo%_*TO@ zL&*15fXv?OXuTcbtpGZ-H^lf>fahaY#{CkWaUJ}06(2DoaG;OyX@AyJd^&9srzdZa zr}J4)@f#q(A5NKUyqG5#{S@E-Izl&WE5d24m!mrZT`i{SVoqHPtNCXkGic;kq>-NX z{~j_gWy@tf?Pob&GcqO%d1o7K@DF&}|8Znl|Mz>^zpiur`b7KmwslwG)BAZ7xH{Wm zoPy6$>05Xz*83&RjqPn6ZR=B!(&cAImL*foo0_k~?+iv3;zvT8@uPvP(aZe{fSR>P z&06BP^TOJ_9IhnD+M_Ck8(w)nAQNKvX(QFGALG_W?GNS1+zS(D4(XL5oyCN`Ya!dE zD&+=`x=Zy>GrUjVk8+7{F+jB81|2t^yurlX9&QNxVkNj0<8Ax4suH~{@OKZvN>Y&{ zG*?@h_AhJf b#D9QF7(XX+HMrKY9G(5TrwUeX-Y3X zi>K*vR#)dK$gOtTQApvgwiv6OHnH={Q^zI%uDjZlv(RwvI>R)o8dab5+h*O+aGX`} z_3XHKj+rwh?$RwQ_1?vn?R!|)r~G=jPg nWxhCf30KVF?&mt7}Vo}bS^ptaJS z0<)$K58#(dvNCk6?um9IGp8Z7>*a6Dq08XnBiAXb%&k+$`2n2YTG$N{U8@lwPpwLw z?7+gjrsbtHs8MtVK1~8Di*GUW#28;UgE!TFJ`~01Mx`B-vPhlY$gxL@T-jz &H!KBZ #?i7AoG;?X*}IkgawiX4}BGV zHS>UDAEs-2VUN+E5879AiSi`ZA(tV50(!``(sm7Ybc(X72V#iF2vvsPnUdiYqXv_h z3PhQ#p %Ath!Ds>u*Zun%*Mu^TLKJXJ5NZ23Y(x_%bzBp|J)bez!oL0( ze_cfe0@#7GVyp;}VpQZ{tVClgkTckC*D2-XWrT7xq7 74(vvPHIMMto;Ls?xw$4Pl262`F2vKeSpxE>K)ehLDL!V 0 z&>UU{GaMJr&}rkia^%HSNNoYSK+r8frwHN_4m$P|K)ihlsa-%i_Hx`c>Db=^@wP3b zUI5au^Wo{d+zhG5fV3^|#l6po!rchO3rs9448)0ukP1LZb9V#j6!$ymaUd>X6jDzD z={y2>Gpitpk$RL_9+FEva*0Lm+p#|r`=_xlD^RDrRD!*`Y-Hf9pc)@IQH?@+1{m&y z%N>Na^WZS*&4!yTz`n@C^Ot^XcoH>f17D!kQzaVF?WPLHGV6&Nx}Qd@rD+m78CyMh z!>vXUpm*aBx9iyI37t|0od <`5T~tKi+Y)|d4jsY%@>4?6R6wY9vq$A z$-IF(cFoxP>65Sj>cMU0fBgO*JMX{r+NzJu?*8nA6@U5kUvJ& @~9_hiNbjCVY*|)nH2S=Q2GwO8S^j;OMB2;q(rlYq_cRg4t~tmQ62*z6X#pnt2v% z2}W?2`JG5|W9@l0hjEvcr3@w&>2MOLRi+R*%$QObW+oY33d8WEVoKplB{q}s&T_Eb z++{`v;%{8b)H>M3tVG5)`G%q)RRgy4>`#C6Rt~@>lo>}C8R7U)hrXfpkZJ_O5!{vG zLmsw{D{~tdYIkLLbw b|aoSkN@lApT*9EAAw~M8A1OEhPk^8 zCqe6c%=us e{*u3VE`lGxPN ze(i!5e4gz22!F-39jU~on%4Dg{%_pzjaXZwpYnX+Z$5;_XIA*s`tSdZH^0HGYfo%u zj>{8QHn+8!54F2e9tz*}d&;DzhZk;0G;YM_o{byvsUC{SNn+Zkzv<$Uoy~PV9?LP` zOb_Ss&PRmJ(Ad1TrTMyq`Gnmh@qR3AV@x8cPkc;nY5g&6Hfw}EpTHZ<9;PPuqj=AR z_Xx4}!^IJzbHBUGBWOP&%omm0Vji3FM_QBe$L3m;D`ySL6GMt?D7fyDWlwIT*Ph(i o{Py_ey!K?p_ +#include +#include "MinHook/MinHook.h" + +const uintptr_t SetSubGameOffset = 0x2E4F760; + +HMODULE handle; + +template +static T* Offset(uintptr_t offset) +{ + return reinterpret_cast (reinterpret_cast (GetModuleHandle(0)) + offset); +} + +void (*SetSubGame)(void* thisref, uint8_t SubGame) = nullptr; + +void SetSubGameHook(void* thisref, uint8_t SubGame) +{ + if (SubGame != 0) // not campaign, ignore + { + SetSubGame(thisref, SubGame); + return; + } + + PWSTR rawLocalAppData; + SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &rawLocalAppData); + std::wstring localAppData(rawLocalAppData); + + CoTaskMemFree(rawLocalAppData); + + std::wstring dllPath(localAppData + L"\\Mercury\\Mercury-1.8.dll"); + + // reference https://github.com/ZeroMemoryEx/Dll-Injector/blob/master/DLL-Injector/Dll-Injector.cpp#L43 + HANDLE fnHandle = OpenProcess(PROCESS_ALL_ACCESS, 0, GetCurrentProcessId()); + void* location = VirtualAllocEx(fnHandle, 0, MAX_PATH, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + BOOL write = WriteProcessMemory(fnHandle, location, dllPath.c_str(), wcslen(dllPath.c_str()) * sizeof(wchar_t) + sizeof(wchar_t), 0); + + if (!write) + { + std::cerr << "DLL failed to inject (WriteProcessMemory failed).\n"; + + MH_DisableHook(Offset (SetSubGameOffset)); + SetSubGame(thisref, SubGame); + FreeLibraryAndExitThread(handle, 0); + return; + } + + HANDLE thread = CreateRemoteThread(fnHandle, 0, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, location, 0, 0); + + if (!thread) + { + std::cerr << "DLL failed to inject (CreateRemoteThread failed).\n"; + + MH_DisableHook(Offset (SetSubGameOffset)); + SetSubGame(thisref, SubGame); + FreeLibraryAndExitThread(handle, 0); + return; + } + + WaitForSingleObject(thread, INFINITE); + VirtualFree(location, 0, MEM_RELEASE); + + MH_DisableHook(Offset (SetSubGameOffset)); + SetSubGame(thisref, SubGame); + return; +} + +DWORD MainThread(LPVOID) +{ + AllocConsole(); + ShowWindow(GetConsoleWindow(), SW_SHOW); + FILE* fp; + freopen_s(&fp, "CONOUT$", "w", stderr); + + MH_Initialize(); + + LPVOID off = Offset (SetSubGameOffset); + MH_CreateHook(off, SetSubGameHook, reinterpret_cast (&SetSubGame)); + MH_EnableHook(off); + + return 0; +} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + handle = hModule; + CreateThread(0, 0, MainThread, 0, 0, 0); + break; + } + + return TRUE; +} + diff --git a/MercuryInjector/framework.h b/MercuryInjector/framework.h new file mode 100644 index 0000000..54b83e9 --- /dev/null +++ b/MercuryInjector/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include diff --git a/MercuryLauncher.sln b/MercuryLauncher.sln new file mode 100644 index 0000000..aec7cf8 --- /dev/null +++ b/MercuryLauncher.sln @@ -0,0 +1,23 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35527.113 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MercuryLauncher", "MercuryLauncher\MercuryLauncher.vcxproj", "{3F0A6ADD-95A1-4D26-B6CD-578FE4432269}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MercuryInjector", "MercuryInjector\MercuryInjector.vcxproj", "{6DB67076-1DD3-4754-B0E7-780185F9AAB5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3F0A6ADD-95A1-4D26-B6CD-578FE4432269}.Release|x64.ActiveCfg = Release|x64 + {3F0A6ADD-95A1-4D26-B6CD-578FE4432269}.Release|x64.Build.0 = Release|x64 + {6DB67076-1DD3-4754-B0E7-780185F9AAB5}.Release|x64.ActiveCfg = Release|x64 + {6DB67076-1DD3-4754-B0E7-780185F9AAB5}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/MercuryLauncher/MercuryLauncher.cpp b/MercuryLauncher/MercuryLauncher.cpp new file mode 100644 index 0000000..813ca2a --- /dev/null +++ b/MercuryLauncher/MercuryLauncher.cpp @@ -0,0 +1,503 @@ +#include +#include +#include +#include +#include +#include +#include "cURL/curl.h" +#include "libzip/zip.h" + +#define LAWIN_URL "https://github.com/Lawin0129/LawinServer/zipball/master" + +#define BINARY_PATH_OT L".\\FortniteGame\\Binaries\\Win32\\FortniteClient-Win32-Shipping.exe" +#define CONTENT_PATH_OT L".\\FortniteGame\\Content\\" +#define CONFIG_PATH_OT L".\\FortniteGame\\Config\\" +#define DEFAULTENGINE_URL "https://github.com/absoluteSpacehead/testtesttest/raw/refs/heads/main/DefaultEngine.ini" +#define DEFAULTGAME_URL "https://github.com/absoluteSpacehead/testtesttest/raw/refs/heads/main/DefaultGame.ini" +#define ABILITIES_URL "https://github.com/absoluteSpacehead/testtesttest/raw/refs/heads/main/GE_AllAbilities.uasset" +#define ACTOR_URL "https://github.com/absoluteSpacehead/testtesttest/raw/refs/heads/main/InGame_Actor.uasset" +#define GAMEMODE_URL "https://github.com/absoluteSpacehead/testtesttest/raw/refs/heads/main/InGame_Gamemode.uasset" + +#define BINARY_PATH_1_8 L".\\FortniteGame\\Binaries\\Win64\\FortniteClient-Win64-Shipping.exe" +#define PAKS_PATH_1_8 L".\\FortniteGame\\Content\\Paks\\" +#define PRODUCT_VERSION_1_8 L"3724489" +#define DLL_URL "https://github.com/absoluteSpacehead/testtesttest/raw/refs/heads/main/Mercury-1.8.dll" +#define PAK_URL "https://github.com/absoluteSpacehead/testtesttest/raw/refs/heads/main/zzz_LawinServer.pak" +#define SIG_URL "https://github.com/absoluteSpacehead/testtesttest/raw/refs/heads/main/zzz_LawinServer.sig" + +HANDLE job; + +void Exit() +{ + std::cout << "Press any key to exit.\n"; + _getch(); +} + +// https://stackoverflow.com/a/1636415 +size_t WriteData(void* ptr, size_t size, size_t nmemb, FILE* stream) +{ + return fwrite(ptr, size, nmemb, stream); +} + +int DownloadFile(const char* URL, const wchar_t outName[FILENAME_MAX]) +{ + CURL* curl; + FILE* fp; + CURLcode res; + + curl = curl_easy_init(); + + if (!curl) + { + std::cout << "cURL init failed.\n"; + return 1; + } + + errno_t fpOpen = _wfopen_s(&fp, outName, L"wb"); + if (fpOpen != 0) + { + char out[128]; + strerror_s(out, fpOpen); + std::cout << "_wfopen_s failed (" << out << ") .\n"; + return 2; + } + + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_URL, URL); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteData); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + res = curl_easy_perform(curl); + + if (res != 0) + { + std::cout << "cURL failed (" << curl_easy_strerror(res) << ").\n"; + return 3; + } + + curl_easy_cleanup(curl); + fclose(fp); + + return 0; +} + +void GetLocalAppData(std::wstring& out) +{ + PWSTR rawLocalAppData; + SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &rawLocalAppData); + out = *(new std::wstring(rawLocalAppData)); + + out += L"\\Mercury"; + + CoTaskMemFree(rawLocalAppData); +} + +int RunLawin() +{ + std::wstring localAppData; + GetLocalAppData(localAppData); + + // make sure the root mercury folder exists + if (!std::filesystem::exists(localAppData)) + { + std::filesystem::create_directory(localAppData); + } + + // Check that lawinserver is installed to begin with + // we delete the zip at the end, so if it still exists that means something went wrong. assume download fucked up, delete it and try again + if (std::filesystem::exists(localAppData + L"\\LawinServer.zip")) + { + std::cout << "LawinServer.zip already exists! Last attempt may have failed. If this happens repeatedly, seek support on Discord.\nTrying again...\n"; + + std::filesystem::remove(localAppData + L"\\LawinServer.zip"); + + if (std::filesystem::exists(localAppData + L"\\LawinServer")) + { + std::filesystem::remove_all(localAppData + L"\\LawinServer"); + } + } + + if (!std::filesystem::exists(localAppData + L"\\LawinServer")) + { + std::cout << "LawinServer is missing, downloading it now...\n"; + + if (DownloadFile(LAWIN_URL, (localAppData + L"\\LawinServer.zip").c_str()) != 0) + { + std::cerr << "LawinServer failed to download.\n"; + return 1; + } + + std::wcout << "LawinServer downloaded. Extracting to " << localAppData << "\\LawinServer...\n"; + + std::filesystem::create_directory(localAppData + L"\\LawinServer"); + + zip_error_t err; + + zip_source_t* src = zip_source_win32w_create((localAppData + L"\\LawinServer.zip").c_str(), 0, -1, &err); + if (!src) + { + std::cerr << "Source creation failed (" << zip_error_strerror(&err) << ").\n"; + return 2; + } + + zip* file = zip_open_from_source(src, 0, &err); + if (!file) + { + std::cerr << "File opening failed (" << zip_error_strerror(&err) << ").\n"; + zip_source_free(src); + return 3; + } + + // loop thru entries + for (int i = 0; i < zip_get_num_entries(file, 0); i++) + { + const char* name = zip_get_name(file, i, 0); + + // strip out the top dir, its ANNOYING and i HATE IT + const char* sName = strchr(name, '/'); + if (!sName || strlen(sName) <= 1) + { + continue; + } + sName++; + + // const char* -> wstring + std::wstring wName(sName, sName + strlen(sName)); + std::wstring targ = localAppData + L"\\LawinServer\\" + wName; + + // dir? this setup is probably a bit sloppy tho + if (name[strlen(name) - 1] == '/') + { + _wmkdir(targ.c_str()); + continue; + } + + // not a dir, get the file out + zip_file* zFile = zip_fopen_index(file, i, 0); + if (!zFile) + { + std::cerr << "Failed to open file " << name << " (file may be corrupt).\n"; + return 4; + } + + HANDLE fileHandle = CreateFileW(targ.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + + if (fileHandle == INVALID_HANDLE_VALUE) + { + std::wcerr << "Failed to create file " << targ << ".\n"; + return 5; + } + + char buf[4096]; + DWORD written; + zip_int64_t read; + while ((read = zip_fread(zFile, buf, sizeof(buf))) > 0) + { + if (!WriteFile(fileHandle, buf, (DWORD)read, &written, nullptr)) + { + std::wcerr << "Error writing file " << targ << ".\n"; + return 6; + } + } + + zip_fclose(zFile); + CloseHandle(fileHandle); + } + + zip_close(file); + std::cout << "Extraction complete.\n"; + + // delete the zip + std::filesystem::remove(localAppData + L"\\LawinServer.zip"); + } + + // packages + if (!std::filesystem::exists(localAppData + L"\\LawinServer\\node_modules")) // just to be fancy + std::cout << "Installing required packages...\n"; + else + std::cout << "Checking for package updates...\n"; + + STARTUPINFOW startupInfo = { 0 }; + startupInfo.cb = sizeof(STARTUPINFO); + PROCESS_INFORMATION processInformation = { 0 }; + std::wstring cmd = L"cmd /c npm i"; + CreateProcessW(nullptr, &cmd[0], nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, (localAppData + L"\\LawinServer").c_str(), &startupInfo, &processInformation); + + WaitForSingleObject(processInformation.hProcess, INFINITE); + + CloseHandle(processInformation.hProcess); + CloseHandle(processInformation.hThread); + + // make sure that it actually. installed the packages + if (!std::filesystem::exists(localAppData + L"\\LawinServer\\node_modules")) + { + std::cerr << "Packages were not installed correctly (node_modules doesn't exist).\n"; + return 7; + } + + // server + std::cout << "Starting LawinServer...\n"; + + // we already dealt with these earlier, we can reuse them + startupInfo = { 0 }; + startupInfo.cb = sizeof(STARTUPINFO); + processInformation = { 0 }; + cmd = L"node index.js"; + CreateProcessW(nullptr, &cmd[0], nullptr, nullptr, FALSE, 0x0, nullptr, (localAppData + L"\\LawinServer").c_str(), &startupInfo, &processInformation); + + AssignProcessToJobObject(job, processInformation.hProcess); + + CloseHandle(processInformation.hProcess); + CloseHandle(processInformation.hThread); + + std::cout << "LawinServer has been started.\n"; + + return 0; +} + +// The setup for OT / 1.8 are *completely* different, to the point where theres no code shared between them + +int SetupOT() +{ + int lawinStatus = RunLawin(); + if (lawinStatus != 0) + return 1; + + // are we pakless? + if (!std::filesystem::exists(CONTENT_PATH_OT)) + { + std::cerr << "Build may not be pakless (Content folder not found). Ensure you downloaded the build from the Mercury server.\n"; + return 2; + } + + std::wstring pathAsWstring(CONTENT_PATH_OT); + + // check if we have InGame_Gamemode.uasset. we download this last so if we're missing this we can download everything just to be safe + if (!std::filesystem::exists(pathAsWstring + L"InGame_Gamemode.uasset")) + { + std::cout << "Required Mercury files are missing. Downloading...\n"; + + pathAsWstring = CONFIG_PATH_OT; + if (DownloadFile(DEFAULTENGINE_URL, (pathAsWstring + L"DefaultEngine.ini").c_str()) != 0) + { + std::cerr << "Failed to download DefaultEngine.ini.\n"; + return 3; + } + + if (DownloadFile(DEFAULTGAME_URL, (pathAsWstring + L"DefaultGame.ini").c_str()) != 0) + { + std::cerr << "Failed to download DefaultGame.ini.\n"; + return 3; + } + + pathAsWstring = CONTENT_PATH_OT; + if (DownloadFile(ABILITIES_URL, (pathAsWstring + L"GE_AllAbilities.uasset").c_str()) != 0) + { + std::cerr << "Failed to download GE_AllAbilities.uasset.\n"; + return 3; + } + + if (DownloadFile(ACTOR_URL, (pathAsWstring + L"InGame_Actor.uasset").c_str()) != 0) + { + std::cerr << "Failed to download InGame_Actor.uasset.\n"; + return 3; + } + + if (DownloadFile(GAMEMODE_URL, (pathAsWstring + L"InGame_Gamemode.uasset").c_str()) != 0) + { + std::cerr << "Failed to download InGame_Gamemode.uasset.\n"; + return 3; + } + + std::cout << "All files downloaded.\n"; + } + + std::cout << "Starting Fortnite...\nLoading may take a while. Enter anything on the login screen.\n"; + + STARTUPINFOW startupInfo = { 0 }; + startupInfo.cb = sizeof(STARTUPINFO); + PROCESS_INFORMATION processInformation = { 0 }; + CreateProcessW((LPWSTR)BINARY_PATH_OT, nullptr, nullptr, nullptr, FALSE, 0x0, nullptr, nullptr, &startupInfo, &processInformation); + + AssignProcessToJobObject(job, processInformation.hProcess); + + WaitForSingleObject(processInformation.hProcess, INFINITE); + + CloseHandle(processInformation.hProcess); + CloseHandle(processInformation.hThread); + + return 0; +} + +int Setup18() +{ + int lawinStatus = RunLawin(); + if (lawinStatus != 0) + return 1; + + std::wstring localAppData; + + GetLocalAppData(localAppData); + + // check that we have our dll + if (!std::filesystem::exists(".\\MercuryInjector.dll")) + { + std::cerr << "Required DLL (MercuryInjector.dll) is missing. Ensure all files were extracted properly.\n"; + return 4; + } + + // check that we have the mercury dll AND the pak. someone could be using the same build folder but have wiped their appdata, etc + std::wstring pathAsWstring(PAKS_PATH_1_8); + if (!std::filesystem::exists(localAppData + L"\\Mercury-1.8.dll") || !std::filesystem::exists(pathAsWstring + L"zzz_LawinServer.pak")) + { + std::cout << "Required Mercury files are missing. Downloading...\n"; + + if (DownloadFile(DLL_URL, (localAppData + L"\\Mercury-1.8.dll").c_str()) != 0) + { + std::cerr << "Failed to download Mercury-1.8.dll.\n"; + return 2; + } + + if (DownloadFile(PAK_URL, (pathAsWstring + L"zzz_LawinServer.pak").c_str()) != 0) + { + std::cerr << "Failed to download zzz_LawinServer.pak.\n"; + return 2; + } + + if (DownloadFile(SIG_URL, (pathAsWstring + L"zzz_LawinServer.sig").c_str()) != 0) + { + std::cerr << "Failed to download zzz_LawinServer.sig.\n"; + return 2; + } + + std::cout << "All files downloaded.\n"; + } + + std::cout << "Starting Fortnite... Type anything in the login screen.\n"; + + // open fortnite + STARTUPINFOW startupInfo = { 0 }; + startupInfo.cb = sizeof(STARTUPINFO); + PROCESS_INFORMATION processInformation = { 0 }; + + // args + std::wstring cmdl(BINARY_PATH_1_8); + cmdl += L" -skippatchcheck -epicportal -HTTP=WinInet -log"; + + CreateProcessW((LPWSTR)BINARY_PATH_1_8, &cmdl[0], nullptr, nullptr, FALSE, 0x0, nullptr, nullptr, &startupInfo, &processInformation); + + AssignProcessToJobObject(job, processInformation.hProcess); + + std::wstring dllPath(std::filesystem::current_path().c_str()); + dllPath += L"\\MercuryInjector.dll"; + + // reference https://github.com/ZeroMemoryEx/Dll-Injector/blob/master/DLL-Injector/Dll-Injector.cpp#L43 + HANDLE fnHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processInformation.dwProcessId); + void* location = VirtualAllocEx(fnHandle, 0, MAX_PATH, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + BOOL write = WriteProcessMemory(fnHandle, location, dllPath.c_str(), wcslen(dllPath.c_str()) * sizeof(wchar_t) + sizeof(wchar_t), 0); + + if (!write) + { + std::cerr << "DLL failed to inject (WriteProcessMemory failed).\n"; + + TerminateProcess(processInformation.hProcess, 0); + return 3; + } + + HANDLE thread = CreateRemoteThread(fnHandle, 0, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, location, 0, 0); + + if (!thread) + { + std::cerr << "DLL failed to inject (CreateRemoteThread failed).\n"; + + TerminateProcess(processInformation.hProcess, 0); + return 3; + } + + WaitForSingleObject(thread, INFINITE); + VirtualFree(location, 0, MEM_RELEASE); + + CloseHandle(thread); + CloseHandle(fnHandle); + + WaitForSingleObject(processInformation.hProcess, INFINITE); + + CloseHandle(processInformation.hProcess); + CloseHandle(processInformation.hThread); + + return 0; +} + + +int main() +{ + job = CreateJobObjectW(nullptr, nullptr); + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = { 0 }; + jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + SetInformationJobObject(job, JobObjectExtendedLimitInformation, &jobInfo, sizeof(jobInfo)); + + // a lil hacky i dont care. + std::wstring cmd = L"cmd /c npm --version"; + STARTUPINFOW startupInfo = { 0 }; + startupInfo.cb = sizeof(STARTUPINFO); + PROCESS_INFORMATION processInformation = { 0 }; + + if (!CreateProcessW(nullptr, &cmd[0], nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &startupInfo, &processInformation)) + { + std::cerr << "npm is either not installed or has not been added to PATH. Ensure node.js and npm have been installed correctly.\nIf node.js has not yet been installed, visit https://nodejs.org to download and install it.\n"; + CloseHandle(processInformation.hProcess); + CloseHandle(processInformation.hThread); + Exit(); + return 3; + } + + CloseHandle(processInformation.hProcess); + CloseHandle(processInformation.hThread); + + if (std::filesystem::exists(BINARY_PATH_OT)) + { + // we only have 1 32bit build lol + int status = SetupOT(); + if (status != 0) + { + Exit(); + return 4; + } + } + else if (std::filesystem::exists(BINARY_PATH_1_8)) + { + // get cl + DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(BINARY_PATH_1_8, NULL); + char* fileVersionInfoBuffer = new char[fileVersionInfoSize]; + GetFileVersionInfoW(BINARY_PATH_1_8, NULL, fileVersionInfoSize, fileVersionInfoBuffer); + + wchar_t* rawVersion; + UINT discard; + VerQueryValueW(fileVersionInfoBuffer, L"\\StringFileInfo\\040904b0\\ProductVersion", (LPVOID*)&rawVersion, &discard); // could get language but blehh the exe only has one so i dotn care. hardcode it :P + + std::wstring version(rawVersion + 0x7, 7); + + if (version == PRODUCT_VERSION_1_8) + { + int status = Setup18(); + if (status != 0) + { + Exit(); + return 4; + } + } + else + { + std::wcerr << "Binary ProductVersion is wrong (got " << version << ", expected " << PRODUCT_VERSION_1_8 << "). Ensure you're using Fortnite 1.8.\n"; + Exit(); + return 2; + } + } + else + { + std::cerr << "No binaries could be found. Ensure the launcher is placed alongside the FortniteGame and Content folders.\n"; + Exit(); + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/MercuryLauncher/MercuryLauncher.rc b/MercuryLauncher/MercuryLauncher.rc new file mode 100644 index 0000000000000000000000000000000000000000..dac6d70421e70894786368acfa16c8260272f7a6 GIT binary patch literal 3268 zcmdT`ZEq4m5Pm+J_#amIVxs|HenEkX1X@iGnl>RJ4M-({gK(fR@wcnbGke=!xdS8m z;mqc4c4udHXJ==gou5DIsKenLGmNpu5(&R4HsoEAmtc!q%sFb}1)IYiJ|X6}CVxXZ zA#Y78y)AoV_8muBZH5(PYIuq#+@)@`N^Z>AIi)8iTgmCy@EH}8mS)xDrlzO0Pw^2E z`WT>v4nAOnOPrD0V*dg?+>jITe~tz@J@&)?vY~t}SQn^6n||5#@swU#V!`uo=pFf? z&r@IX-{Gm7Jg59|M*bUqyEpRtJNRn;^?Hx}Up|ioi+pr!=_^Ijl;=J!HiGBFy5*w5 zI>+0MnSXQMRKgf=c+NYIISP5$wL83{Hxwx+1#ysMvT9}?d-f>1CS3Cr?-*A;t0HF5 zfce$r-Xq3Wk4@`-XLEZs`&{FaQ9Pp71?h?H+9Ow^0cqPJHE@DlkFgx#JzjF{h;j|? z=5=-@Mx5>LoMnFuC_}Dzk}h{`{dZ_koaZ4bMD@L<&x$3T%{O@Vwqb>&UD}GVcZgRh z{_6g`$D}1>IcAR{%DiI28 + + \ No newline at end of file diff --git a/MercuryLauncher/MercuryLauncher.vcxproj.filters b/MercuryLauncher/MercuryLauncher.vcxproj.filters new file mode 100644 index 0000000..dececa4 --- /dev/null +++ b/MercuryLauncher/MercuryLauncher.vcxproj.filters @@ -0,0 +1,37 @@ + ++ ++ +Debug +Win32 ++ +Release +Win32 ++ +Debug +x64 ++ +Release +x64 ++ +17.0 +Win32Proj +{3f0a6add-95a1-4d26-b6cd-578fe4432269} +MercuryLauncher +10.0 ++ + +Application +true +v143 +Unicode ++ +Application +false +v143 +true +Unicode ++ +Application +true +v143 +Unicode ++ +Application +false +v143 +true +Unicode ++ + ++ ++ ++ + ++ + ++ + ++ + + +$(SolutionDir)$(Platform)\$(Configuration)\ ++ ++ + +Level3 +true +WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) +true +Console +true + ++ ++ + +Level3 +true +true +true +WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) +true +Console +true +true +true + ++ ++ + +Level3 +true +_DEBUG;_CONSOLE;%(PreprocessorDefinitions) +true +Console +true + ++ ++ + +Level3 +true +true +true +NDEBUG;_CONSOLE;CURL_STATICLIB;%(PreprocessorDefinitions) +true +stdcpp20 +Console +true +true +true +Version.lib;libzip/zip.lib;zlib/zlibstatic.lib;OpenSSL/libssl.lib;OpenSSL/libcrypto.lib;Ws2_32.lib;Crypt32.lib;Wldap32.lib;Normaliz.lib;cURL/libcurl_a.lib;%(AdditionalDependencies) + ++ ++ + ++ + ++ + ++ + + ++ \ No newline at end of file diff --git a/MercuryLauncher/OpenSSL/libcrypto.lib b/MercuryLauncher/OpenSSL/libcrypto.lib new file mode 100644 index 0000000000000000000000000000000000000000..ff1bb6fcc90ec5fc5ba0ffcf8b91956a4c3c842e GIT binary patch literal 47120722 zcmeF434kP3egCT}NDLwpL_|e`iUbjL=iC`Yrn+ ++ +{4FC737F1-C7A5-4376-A066-2A32D752A2FF} +cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx ++ +{93995380-89BD-4b04-88EB-625FBE52EBFB} +h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd ++ +{67DA6AB6-F800-4c08-8B7A-83BB121AAD01} +rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms ++ ++ +Source Files ++ ++ +Header Files ++ ++ +Resource Files ++ ++ +Resource Files +@rj3R$jL_+?b_v)^$K4y1z*@cPuF1uat ze!utL@BOa#k|T%aM#hiY<246G|F2_pS5I$uPj5$O^!HWnKYLeq^!D_Lf1I#~q7=?h z_Pz7~AJ5BwL}`kJ6O`w*iTz&q9i q(u4SXf7ys#C1vzPM15MH$Vn6f0iO;KL7J#gjY%8SDI$?nRF!uZQA z$^nNSr5sScbmzlq$0-L~cD!=HSI%z_KVMJ|SVp|~ul7+|;?w6VFK&SYC)X%#;d^%| z2QD-I?$38Bt#Hu)zExSl_8)_TXHQky!OM3)MA?xz EJCsEH>{ey 6=qe>%GPrgY>g8f`wSu$>WM6sLT^>dq*ruf1>%IjCK(~rU0&p)cPgLT8FDRubf z*~+>Vtc<}54eT;BF!ZZ)lsJa-XDJcfvR)ZpB1SsyQ6jiZQAS*hKIaZ)XT#VjXDJfb zpR0^*BR2foAxb+K|LP`1U}E>p4oZh869S*;bnr`lZ-Enkbf3}+8z+aAZN=mZ&Q&I7 ztO%xF(5WQiokeA;38oLcLTQSx?59j`17=P=T#@*7uQHqMZjC3-P-bH|X>@nxq+6em zm^9-gAXWg{`DhGaoW`n zE8B{Hx@EJ{2&W%;wbBZ2+50r*E%W=g#9J@jQ)w4(JN$M<; uZ;RoK*YB@1!`oLC zmA8N8@d(a5=6vPMFXSS4N97nL31{^^u6X$81I|&l1!q6+Fl9&LowvMH@o>(OM<`3i z|0>EkA-wD5=PFCW% Q$a>T(st4WxL>s$CQhnX87;{n(}1eBS(Kt*&(>QpnPPB z_~^msDLW%BIsQk=vf _9T>ZQ|mF zSH8HFxZwrgQJUi4x|JJNurdZahz4#v<`(72!k1rpg(C6(bCfT)z)gpLSCRO@naWLD zgNJWdZhDI1mO~9?yWpd{E4MsFaclSS%98N?y_H+r#8<};R@%nTuTj3*CcgHj>y>T7 z)%z%4+e&=BdVnJFg@=`|gKr#s)po}>H(aG`4Q|snE6akru2;VGtiuCLUaNd-JK*-i zu2dx6cd>GN4By`4d_`h$f92b|AnrJRR9O;!d#!TEcEW!Qo~u0F@SU3 PY+jq zx(xW)@y9F6iJw37NTn6-J#-JHDgOCH nw{S1NyMf=6$@P1#ZS^Lb|~%Z <~^0ipCtV6uP;`X z3;&lrOxX@lm6shD;ERW;N(}11tWwqc?_3hJ#e-BI7d@nEOTm}!RkdZnU%g?Knuzy2 zp#D`0?Dp*6sL%M@E0bZ*sn2MJcPi@c@7_!O>;Ly)BA)r0Nwq09o}&KEsTZqz+}Z2k zf6h^#wfk=BvtGPjeOB+`>a(hc1i1bL^;rUcSe1x9-?&O`i7#EE?zu!f`*p{tP4Qo6 ztIrN$uYDd+>+nx6RQK8rc+P82R3o@>L4D3LWAD3fSC jF7|o!F|`$*`;A^T zj(rd6Reh9Rt?rwIvyW2u-5Kzxt?s*mw#MN39k$vC`&V-63SwjMiHFqvA6-}yUU2Ru zYD4_;boB+>h!=HSq3(2e !3vahKxzWKm99N2M->SOR?^}vtZtseNPQT4#D zAF3YsgX`1-@4Lar-|c&wx>dL!r~aM5!<)A&4*Kw@D)F8B)Pr_Hy!5zh)E$oZ-=)4( z;8$a9<7Jt1)mAuo|Cg$XIQ3BV;2nsUU;i1kZT$FA_2tWezyH$NY8;22aF`mwIR~nT z#_)=Rf1*Aqc;iLtE0zg|ZMjrU#P1(a58KIc_ H)p6z#p>Su)jHh#pt^bq=(EmHcV_hOakje6xb8l+e<#ChKXR2C z#*w?NQX`lss7HqJ?Yq?@cOYK(=VR3Eg`@7-SM@N^->W_qpx^!*wG~qD{+aru;^s|i zY6Xd7ka_z<>ej$mb(p#&+<&=hECaIFU9Rp5$Squ@HpHW2YR*OeRr{#B8VWUCU3R?Z z61A`$VV+P>SMc;1gW~7+RhywSaG@H(hYD)R!{_c(ODk9zgB2QBp@9_|SfPOx8d#x$ z6&hHfffX89p@9_|SfPOx8d#x$6&hHfffX89p@9_|SfPOx8d#x$6&hHfffX89p@9_| zSfPOx8rZHhu+F+dZ3jo6aE*HOKVP68eeK~99P_$vbxHWfuzJj|Z)zKFc#Ewz#c%Ib z->{Wf|C6KCW;nJwr%If)pdRbtt~1nQo8Y+gajL|YtJLEJu9 gHb@7r~ajc2l?XET~&vzo{Mrt;(8{${P>RXoq zZ!2G-Zcm)?+*x%6D`T*|XyEOG=c#Su50|KKZ-O)5Tu@iA X$LmxN?|gnv-7dIqm3q!A9#PLZ{qz7oKTSPnC&jydxuCYfxg$rY&G7CG zKU3p4@61corg&_#dR`3gIc`#|!_8Ny?^!0C|II(D0`EQilGgajp6Yv>;DYCER<{|a zzEHiO5iTB9FIXbp_v&L*i4Tpb@7vM1eNuhjHsbvcU8siff$7hwKJJ@SKd^&v;h$$! z4<8&{uYT~F8xnERA>UEA8UJ~WdeIKRhu(0Y`k|@a9GrcQ`k_k>4{-H&)eqfzsDsDv zRX? 1EDK63vP>aL8B{&IJ9SHQ<^cuZX?E ek}pFaMm{ z6q~P7Ki&kF9e29ABk_s7FH?P#9#KCryMO!m@}&BShrhAQ;9uS|rmo Wk5jiNKDX*xb-D2Q?pxH) zU$MXX`CmP z{nYFJ(5rr-=M41=ryit!;fwdEU--*~64xJnk-9VDi)TKdE;nvC z^8j^w;!7XhLv0r~_ME0J8-8@IdgFG&mw&al+6p(B7pOZcZvNA~>T= 9nU{djo|i8>VF(N?BF{uRR5y|zVqHg)V6TjKI(rSVMOu6 zbJYLbN__Wcm#azm-lacL!?<&LS`Fj-d+O?T!`IJHzaPR~qvxql7VbXr6jk7dYwnHU zM}7CJPZivAUPWz*C-znE*@f|wU)-q*{PbDZ?O5DIoli0R;^6nI%Yt{^sQw~``<{8e z`jo@ZFIMjh;g{Dws0!S#T^rzi52^RJfnW6;r0xhju=aAbE&T3o^??@n_1>qcI~A^a zsru^}9^CsPwJAPwh5BF|KO9pZjN!jmU9Rp>eDqrNzwbz|;AuJr|MSy_)MohYai^$F z@%>k;ziku08@*d?il6qWziWbrj=e=~3;%VR`cM=6{tb7kZQ;k~slVR}Jbdw+)K>U^ z=YK@?@W|ivs!PVZ$rHmL78cYUjX#zQb!WhzUcHCf6jvUi{%MJL^fhzpPKDvS)kl{J ze>NUb+reYI?W?whD{fK$vRkn|-1C6?mmQ4%)lXG-20T$Hsu7GHuRgIA_`my$>XQIX zU31$m3GLrIH6PzHG_4K%)gA|EI~u!PdZ5+}&v@<=T1#yCt@eyv0J}e`XpQjK#}>49 z@XU{YM_X>}xtF1}#G4M)_FPWfb+)!=n|Sv8Ls}g^cA@s{R`}WJ+OxM2d%gEoEsp2x z{w7Ug;C$^l9 3w`9Dc{1L4ot~*eBMGS|Xw4k-ZD-XL|+o5>( zW!fvB1RP!))g*p;pLRHSRpx-k7(YaN)y)?-#H)|IQcJ{zuhw3@1ia>euW7BY>hBAh zk9S ZDt>yI*0~Mnx@ccbp!;u5XpGH|Y29%=c)r%X zjOcmcg<8+_QCp4P{Vvp6;)XR^?-H=Oc)PYW=-Ygk)_3{o4sJhJ>uZ4izdu{+&mN@p z4=!l^?>JrS|LmyN|2_Ww`%dkMmt5_@zE3;C!@`x?5$7J-5U)Mta_zO}{#JYK)xUA@ zm%Fti4?A8vviz8X4_~Mq>EXwGYl7DmPtl%q{BBHp-7@2-H|?e^3BUfDcGOm4;M6{C zxuKii(6$LP_h@<}-2ORDk0JGvUhS!f^!xA9mK`_mqor4{G6uT<4H*A^hSm^&UZ)u@ zvM2OvI}>uhU8Cho-`(X rnQ|F)sOD3HG=)R+q8!G^In?0i(}1)Z_$ <3@f)>YduUw~XH+=JI z?YI@Jj6nho9RIA_v=waq82rQ8du!VZZ#wQ6ZCAqJz@gf%h7&IRuGTJwUcZ~Rf*msk zBbOekwS&>utkHH9Y6sk>?JO9pT%fJsNgRXEo}rDcU}X%Jfd kWX2060&EEB3fd9Ef zoBhju+DUuu5#W`FY9}S(y_abxwZPn8k7;uU{iZSQxm%k*>@;mY{mcM=I#HVk3uj)_ z7>``9Ei}O5@mFa+K6RkB=wg#{ofbjwBibepS3a(7is9tLI<>ZN_7U329{zBrc5(=t zCvMVO;*YDe%`t2_W3$!@r>Mth%Z5Am)=mlGACLHnwiKMY`Z}#$ym^m{wWhf70`1Kq zoc7|wwXMRpirQ)0h<`fyIPK|-w;X EDhJ#+Ph*nH@myG zEqM2TKd3DQ=l#!>S`yxK!S!0ZIDgBmwp@7cN!MvfxZqXmwQa_&wsygG#QXkne{HFF z|1l3}PbNN~oUW~)`50VybEmee;e)%Kp|yqc_t8GM%(&<|YqX~;K6KTYnum*Dd#)yN z^V! r?MR>mMs16REFLaizOa)Wlo4#vMeP|zCT z$~T^^HN>@7YF8%U-?$?z2d=vEa;*_Q`P|#II{f3|+9y5y;$rQSTY;+=(%QD--@6~u zo=jY`&pK_%xa2VHnk3x2uXfE6@u{J^w6^ev$F)zzaP9GDX^HsmdhOaJ;?pPIp!vAX z(mve;pE-9QErLhR(>~+kvwa6?P4P!f`)mVz?&wprmiWb}_PG!~|Hl2a6$E4Oh0HUx zrvx58O#8wP!1Z@st_ghc=o@x9+_2#qZ3R2o82sP~?S>tQFa6*?tr>27+kKkEkJ8$W zP4MM4>$ODO`Wx-bI{|L`&S~0Gar231YTJriUO%ig!&m
0j+q9;5>@n>d zF21>;pw;2ZC$w*da9jRhZK?Ry^yS*^>mSl?|ML?P-+sf1n#7f#)4qMfOC6wF`gQ{D z$nL4#@y780oB4NR3=9lr^ubl>ik>&J`FtUr*5~IZ2Ri3!BQtZO1D#W~h4Gou`GL-b z$@#(Aq0xcv5nUlg*Wk$T$lzRUYGzYS{60D{yLRBdu4{sZzR{w)EM#nN1U;wK@7YF*Q3`o2pGO z3@uE|O!I>*$7Qov#-h5hTmoqb8e_j*XAZhlB)DSkuhFj_*;E*LSO5e z<9}P2YgA}$WVq3%a3?m5ObreVjSeo(jt(u >owahdPQm`KJr$#zPiJhCq?5I2|M%WpzF010vl)>(@}ZEbR@2p7PG??d zN{|C$v@%99rEjcl3Fqi5 y{5wSb zqNyS3uWxyfD? kKMEx>#cq8$*rGlO@O`F;FX3aTX7N?n8d5#7%Qq)V? zRH2$G7Idd)PSS BT9(16`$5IaAD71$|^8U<$hP<$Sf2Vl_?8j0UVn zcco|>xoWYb57k0TVzrsIY|BcQ1knui!}j>auV%5P%0&ut86*EoUpieZ*k;Ps7w2mO z{lgQX>ibGrvrw+&*%Jg6vT+63$|XV5)vWVrVmjDK`g56FrfB7}`pDw^!pxLIoNn*? z%lTX>lS}8_Ho^@}_=UpyWQ}6AlrNilc#6CoGrqhny_zazs-{^=OfCpGw8OzrRv@PW z>rr<~^0#7 0*tCne#O{PP#vqTOv%y*h+i-H8 zU`q(+vCFAkxyb&MG>f6rHpr9LVZ6(wqUrJs?gVWbnj3V^3!j7V^T07@&1}{*i|s6V zm;TCnCQ~fs4a+6KO>o&T)_$R7y_l_}s@Y;$A0J{7&2fRC0si-jUNExdD$J0-$Oif& zynjVE?MkUyu*C5?&@X2m4v_6TJQ!l(ife$wa0gseeIkzZril_rD^f`GdzizFEduAC ziFv~ZNR*(%XG!!+Xqw}f39m`P0!pm4)$oLhX?Sn((l~oTVm0CuQ=Is1uI93QiC>f0 z!Pua-JA0u8>s|}u1x1e2FexRhk%S9eG*ul%xyz%Etvi*ra#ekJdZ4E^H#alqlRg|* zwwyDH88-c?iTQcX;DMfz+Qxw%uAbpkd81%fD%q;esW3FjrL<>i!&nD@1}qF^6^yi9 zvhr4tYIbpWVtQ;QT$Gu%)1`DJ6I8f)WCIIxJe-JwFlUR4cx`Hapl5n!ftF8;i)^Sq z_Sl@Aw$cR-teF|wGBmwqpvMs@1m`WHhKy~ctSo1!EN5t-hgMBaj12UIcLCDnZKGmj zI2!qVpoeK*oE_*HgEfiPS`=JKI_DNQF+IO9G`%q4><8|cQ&@%VGF^nPT$Jq|=vknN zg7%_?*{MvrV8IUs!;efrlo$+0TDLRs=ILBYhs)luFfIzCw8^2l4FLy{&gf}7TQ0+R z2OXW8*hIr(J J)wvbCzf(d2+>v0+pPFLn) zn$6}*`pC@K(8$8foapLFixYF<&SvyVrChR$WkYuc)fp4_x)c;==;=%~pULOgZ9Qt^ zN>X31p=XQ&=W^8wMsL0b70*EE6Cp|=Vct&VbCxMYDl>Bv3*%GJ%f$TD(85TF?nq}< zi&@S_mjn uJlPIFDDGTn>Q^NX{yGjp6oNvk%iXG&(aY8A8k<^|? TqC?`&A{}8e z6UMWgw=?NnxSie%#B1mNmbMWl2IQ99qa1{p&SY(PUcc`X)3b{S!<2)Ns#YpfNJpm= zP9MtKG8 PfjluEz6?+(L$YlEi$Azy=0Wr>1??aG}XON zutLH&6!`m0HY4N-Au4gN19h{XhsP|NrF 7`5u4i-cZ`9a zVKH07^D~o+3!y>C>sG#C+h)OvY$79!huG(Y*fyY6I8d5lXW`su=f&;@rBZwHk|uGU zL!z87N49KHqTC~;2-e%*^Liz1Kr w_N< #E+i69ffEqc->jtdVNsU2Ble6;PSDhWo;VwvCbe0x zt!zr1;uEA8VKEH!P&j|k1tjsc5Nl9?HK>*i!wjf>ip_Os1H&BO7D!t$A jQua5bN4bwvlj9Prj7uXy)F|ypK!UH`!BZj+f zqzhb!G8|Z`$Ut*9V{jbiE41qsbgRe#Qppx0q$JtP`a{!1uxhcPnR(rtj^V|zu^LC6 zJd9%pgsB&b#bOn%3%!FkV9bugg2Xp )G0zR6o=+IHq37q-~>I3Nd?ZbCM^i-)K#}YO&L0 zs?|u#;UgNf+)fp<93UYcaRS!EZOh2aVwmwUb-0Cm!DQo?_v=<9Th!B8+qAh2vXjio zRUnw~5X*FOZ78%q6!k0zlx fYk&CSdP*U&V{%jIk}W2U^;xSf^~hWeo|>So!h2=>>x0k$5UV$sg# zs#bIxa*1qyes+9rXg;(~i+Z_`FPYU+=v-bH7Y7ehJ2S)95g(nhkdu@lGw5(}5=s>E zmkqF6oK$sdwaDaFirEy4)uZ{*#W`mf(GbD6hA;7O@9@bL!!THX(>0+%Y1QA7o~cw} znvs9YRVNpsU`_p9Cw>x@^rDfovze@+(-Mv!#QB0-r1>FUD_B@1y;`tgd(%~2=q5#j zhZq+3sCRgD$STNS>FF|CnI(mG4uOjE#oZ<8QQalSf|V#+`8?aO!^9yX6|E8b_XPV% z(nY{R%bGIMMl~ 6i*J2vJhC^s -o1DHTzz z7R5F(&rUco;+#98V3FEccR}CFEH-YDRza|iqk<82=Ato$2*C{Jl?{Q1IbCF|!(!VN z`|2DU#%O3yuOM^cMo^?9;d_@96>x7w99n-4RP{9K)?7KytcUJu-sT(GMuP5CIcn@& zu4Lw7L&jbikV5RbO(srYF>c~emA4Ov;Af_WCg4kgtC(Mr5I?5#86(4f%=zrD3np=K zdPESMAU8n_&Kinj%VrA&18$MMeqK=K#Js!BGC8u^dJ3hsN-CWy=Tafob#|^M?&pHf zZotEFI_C^jiLiU_wgv^vEY1mz+oJ@RR@s4rMy7SMl!hcw2Sw?QQs}GLk=>0}P+yQQ z!{s7ll**Z4qaPWXmaAM`i6&XFK|2DX&17{mo3C(l%fd~F{y2>k1TT3`S;F|xCYmk8 z1fiB@b-TiDW!W&alasX#Vx0^6jZmh~I0r3-X-?VL6rd!VuMN$Oz~0VGi}r=CZq9n- zvK$EaGkHB{rYr>c=$PE2% 5#Ps6K zV$jSY^Jk{3$ZzxzYv2#4d&iUwT^J9f`>19rxitJ|C3GLGjgHPUdcmDbW*b@v?H(w@ zQt2|P!-B)O#x`P0w=tS9OwMs3pzy>oesjy5^G3E(fUo!WauS8V=PT8ekwY>h1P4w( zLZS^JGA tA$a=Ku!BOz^Dgc_s!NSS*L3#X7Vg8HNR%ZNKt;-=`AA65aE z^$Hd-mH{8*f8RJ7n8#EYo+^T=bg2B {>&q}i4@V4Q^%Z6Mz(NXv_`*8TvA z%a=cb732t}VcQl`Nx@H`6`v8tJ>dl9G(0N?2Yv~W>o6r$O66P?eYM|*h+Kiwqhe(% zh81EpBf2%t3KRWY1qRA5K`_vvHeei4;$jLz;|WJ2^1G^zH4~cjN{9|e7UwnvMO1aM zWx!#EC?R@~R?%Q(Sq(*(OY!#wzZo{1Pl~%lHq?>W1 AB_nzrIR+SADJ!SQ!O0nPF7}%0F~SA_`kD8%(i~)^ zJd)-j>erBxeq?59iZip*`$-tENMkcuqWD#sw2aNwT>BpXC)j~igIKH828SnR1|5Sn zrybEG T3N0qAF1e87FshZuI|sst{FUeaLwA(+O?@c zd+pk|fe|}dF_+E>;ky_b*HX@#EgFiI96gzvA3}HN&VPe4oxV9AeQB>b%4s+;HbEIQ zqKzb?k-;bpF? w;@=bID>7RN=Y8aR zBF7O+(W;KYB#MZZQAZE4ek6S_l~WmU#Ifi~S>GcgMBtGGb!&?nU{>nJerhzfy^;>H zuAc^RtS4||;^%|`5i28Mti{iXL+pHLP*CE4${b1K#D~T|H>#W*gAMwzv6eL0)FM9& zBDty?RQa)CaeS!H#RXdO;O_mY3-gyk7O5eEo*w^V-CeTFR;H4#>SE{hf9}I>I8(xg zLHyh~&`)QAZ`oohlSc*aeq&aGZ)Gc&vN0ldztOPZn~mwRiT$