|
| 1 | +#include "main_win_uwp.hpp" |
| 2 | + |
| 3 | +#include <filesystem> |
| 4 | +#include <functional> |
| 5 | +#include <print> |
| 6 | +#include <thread> |
| 7 | + |
| 8 | +#include <winrt/windows.applicationmodel.h> |
| 9 | +#include <winrt/windows.foundation.h> |
| 10 | +#include <winrt/windows.storage.h> |
| 11 | + |
| 12 | +#include <runtime.hpp> |
| 13 | + |
| 14 | +std::thread::id mc_thread_id; |
| 15 | +HANDLE mc_thread_handle = nullptr; |
| 16 | + |
| 17 | +DWORD WINAPI SelauraRuntimeLoaderProc() { |
| 18 | + AllocConsole(); |
| 19 | + |
| 20 | + AttachConsole(GetCurrentProcessId()); |
| 21 | + SetConsoleTitleA("Selaura Client Console"); |
| 22 | + |
| 23 | + FILE* fp; |
| 24 | + freopen_s(&fp, "CONOUT$", "w", stdout); |
| 25 | + freopen_s(&fp, "CONOUT$", "w", stderr); |
| 26 | + freopen_s(&fp, "CONIN$", "r", stdin); |
| 27 | + |
| 28 | + std::println("[Selaura Runtime Loader] Thread ID: {}, Thread Handle: {}", mc_thread_id, mc_thread_handle); |
| 29 | + std::println("[Selaura Runtime Loader] Press Numpad1 to End"); |
| 30 | + |
| 31 | + auto winrt_folder = winrt::Windows::Storage::ApplicationData::Current().RoamingFolder(); |
| 32 | + auto folder = std::filesystem::path(winrt::to_string(winrt_folder.Path())) / "Selaura"; |
| 33 | + |
| 34 | + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); |
| 35 | + if (hOut != INVALID_HANDLE_VALUE) { |
| 36 | + DWORD mode = 0; |
| 37 | + if (GetConsoleMode(hOut, &mode)) { |
| 38 | + SetConsoleMode(hOut, mode | 0x0004); |
| 39 | + } |
| 40 | + } |
| 41 | + |
| 42 | + const auto runtime_path = folder / "selaura_runtime.dll"; |
| 43 | + if (!std::filesystem::exists(runtime_path)) { |
| 44 | + std::println("\x1b[91m[Selaura Runtime Loader] ERROR: '{}' not found. The runtime is required.\x1b[0m", |
| 45 | + runtime_path.string()); |
| 46 | + } else { |
| 47 | + std::function<void(selaura::runtime*)> load_mods = [=](selaura::runtime* rt) { |
| 48 | + // To load mods we need LoadLibrary, and other Windows-specific |
| 49 | + // functions, and this will be called in the runtime. However, |
| 50 | + // due to the goal of cross-platform, the function must be |
| 51 | + // created here, and then further passed to the runtime as an |
| 52 | + // argument in the Init function. |
| 53 | + std::filesystem::path mods_folder = folder/ "mods"; |
| 54 | + |
| 55 | + if (!std::filesystem::exists(mods_folder)) { |
| 56 | + std::filesystem::create_directory(mods_folder); |
| 57 | + return; |
| 58 | + } |
| 59 | + |
| 60 | + int plugins_loaded = 0; |
| 61 | + |
| 62 | + using init_fn = void(*)(selaura::runtime*); |
| 63 | + std::vector<init_fn> init_fn_list = {}; |
| 64 | + |
| 65 | + for (const auto& entry : std::filesystem::directory_iterator(mods_folder)) { |
| 66 | + if (!entry.is_regular_file()) continue; |
| 67 | + const auto& path = entry.path(); |
| 68 | + if (path.extension() == ".dll") { |
| 69 | + HMODULE mod = LoadLibraryExW(entry.path().c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES); |
| 70 | + if (mod) { |
| 71 | + FreeLibrary(mod); |
| 72 | + mod = LoadLibraryW(entry.path().c_str()); |
| 73 | + } |
| 74 | + |
| 75 | + auto init = reinterpret_cast<init_fn>(GetProcAddress(mod, "SelauraPluginInit")); |
| 76 | + init_fn_list.push_back(init); |
| 77 | + |
| 78 | + if (mod) { |
| 79 | + plugins_loaded++; |
| 80 | + std::println("[Selaura Plugin Loader] Loaded: {}", path.filename().string()); |
| 81 | + } else { |
| 82 | + std::println("[Selaura Plugin Loader] Failed to load: {} (Error {})", |
| 83 | + path.filename().string(), GetLastError()); |
| 84 | + } |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + std::println("[Selaura Plugin Loader] Loaded {} plugin(s).", plugins_loaded); |
| 89 | + |
| 90 | + for (auto fn : init_fn_list) { |
| 91 | + fn(rt); |
| 92 | + } |
| 93 | + }; |
| 94 | + |
| 95 | + SuspendThread(mc_thread_handle); |
| 96 | + |
| 97 | + auto* ctx = new selaura::runtime_context; |
| 98 | + ctx->thread_id = mc_thread_id; |
| 99 | + |
| 100 | + const winrt::Windows::ApplicationModel::Package pkg = winrt::Windows::ApplicationModel::Package::Current(); |
| 101 | + ctx->version_major = pkg.Id().Version().Major; |
| 102 | + ctx->version_minor = pkg.Id().Version().Minor; |
| 103 | + ctx->version_build = pkg.Id().Version().Build; |
| 104 | + ctx->version_revision = pkg.Id().Version().Revision; |
| 105 | + |
| 106 | + HMODULE mod = LoadLibraryExW((folder / "selaura_runtime.dll").c_str(), nullptr, 0); |
| 107 | + using runtime_init_fn = void(*)(selaura::runtime_context*, std::function<void(selaura::runtime*)>); |
| 108 | + auto runtime_init = reinterpret_cast<runtime_init_fn>(GetProcAddress(mod, "SelauraRuntimeInit")); |
| 109 | + runtime_init(ctx, load_mods); |
| 110 | + |
| 111 | + ResumeThread(mc_thread_handle); |
| 112 | + } |
| 113 | + |
| 114 | + while (true) { |
| 115 | + Sleep(10); |
| 116 | + if (GetAsyncKeyState(VK_NUMPAD1)) break; |
| 117 | + } |
| 118 | + |
| 119 | + fclose(fp); |
| 120 | + FreeConsole(); |
| 121 | + |
| 122 | + ExitProcess(0); |
| 123 | + |
| 124 | + return 0; |
| 125 | +} |
| 126 | + |
| 127 | +BOOL APIENTRY DllMain(HINSTANCE, DWORD fdwReason, LPVOID) { |
| 128 | + // This DLL is the Selaura Runtime Loader, which is injected |
| 129 | + // immediately as the game loads by replacing the dxgi.dll. |
| 130 | + // This is why in the headers it exports the necessary |
| 131 | + // functions used in that library, so it can be loaded as it |
| 132 | + // is essential to the game's startup. |
| 133 | + |
| 134 | + if (fdwReason == DLL_PROCESS_ATTACH) { |
| 135 | + mc_thread_id = std::this_thread::get_id(); |
| 136 | + mc_thread_handle = OpenThread(THREAD_ALL_ACCESS, FALSE, GetCurrentThreadId()); |
| 137 | + |
| 138 | + CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)SelauraRuntimeLoaderProc, nullptr, 0, nullptr); |
| 139 | + } |
| 140 | + |
| 141 | + return TRUE; |
| 142 | +} |
0 commit comments