文件
XLZEN/native/loader/src/main.cpp
T
Administrator 0da7d49fd6
Build Loader / build (push) Canceled after 0s
Import OpenZen deobfuscated source
2026-09-15 03:14:09 +08:00

243 行
9.0 KiB
C++

//
// main.cpp — Win32 entry point and window orchestration.
//
// Two modes:
//
// GUI (no arguments): splash plays its ~1 s intro, the main window fades
// in, and clicking a row's Inject opens the progress overlay which runs
// the injection on a worker thread; on completion the loader fades out.
//
// Headless (command line): inject without any UI and exit. Progress is
// printed to the parent console when present (exit code 0 = bootstrap
// complete); failures pop a message box. Useful for launcher integrations
// and scripted starts:
// OpenZenLoader.exe 34028 --nogui inject into PID 34028, no UI
// OpenZenLoader.exe 34028 same (--nogui implied by the pid)
// OpenZenLoader.exe --nogui inject into every detected
// Minecraft instance
// OpenZenLoader.exe --help print usage, exit 0
//
// The GUI itself is pure Win32 + GDI+ (no Qt): the whole UI lives in wgfx /
// splash / main / overlay and links only system libraries.
//
#include "loader.h"
#include "main_win.h"
#include "overlay_win.h"
#include "splash_win.h"
#include "wgfx.h"
#include <cstdlib>
#include <gdiplus.h>
#include <vector>
namespace {
const wchar_t* kUsage =
L"OpenZenLoader - command line mode\n"
L"\n"
L"Usage:\n"
L" OpenZenLoader.exe <JavaPID> [--nogui]\n"
L" Inject OpenZen into the running Java process (must be a\n"
L" javaw.exe/java.exe process). Prints progress; exit code 0 = ok.\n"
L" OpenZenLoader.exe --nogui\n"
L" Inject into every detected Minecraft instance.\n"
L" OpenZenLoader.exe --help\n"
L" Show this help (exit code 0).\n"
L"\n"
L"Exit codes: 0 = success, 1 = injection failed, 2 = bad arguments\n";
// A /SUBSYSTEM:WINDOWS binary has no console of its own. When launched from a
// terminal we attach to the parent's console so WriteConsoleW output lands
// back in the caller's window. Best-effort: when the parent has no console
// (double-click launch) output is simply dropped and the exit code carries
// the result.
bool AttachParentConsole() {
if (!AttachConsole(ATTACH_PARENT_PROCESS)) return false;
HANDLE conout = CreateFileW(L"CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, 0, nullptr);
if (conout == INVALID_HANDLE_VALUE) return false;
SetStdHandle(STD_OUTPUT_HANDLE, conout);
SetStdHandle(STD_ERROR_HANDLE, conout);
return true;
}
void Write(const std::wstring& text) {
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
if (!out || out == INVALID_HANDLE_VALUE) return;
DWORD written = 0;
WriteConsoleW(out, text.c_str(), static_cast<DWORD>(text.size()),
&written, nullptr);
}
// Headless: inject one pid, report via exit code (+ message box on error).
int RunHeadless(unsigned long pid) {
Write(L"Injecting into PID " + std::to_wstring(pid) + L"...\n");
std::wstring err = loader::inject(pid);
if (err.empty()) {
Write(L"Injection complete: OpenZen bootstrap finished; "
L"the client should appear on the next tick.\n");
ui::LogLine(L"headless inject ok, pid=" + std::to_wstring(pid));
return 0;
}
Write(L"Injection failed: " + err + L"\n");
ui::LogLine(L"headless inject FAILED, pid=" + std::to_wstring(pid) +
L": " + err);
MessageBoxW(nullptr,
(L"Injection failed (PID " + std::to_wstring(pid) + L"):\n" +
err)
.c_str(),
L"OpenZen Loader", MB_OK | MB_ICONERROR);
return 1;
}
// Headless: inject into every Minecraft instance we can find.
int RunHeadlessAll() {
auto procs = loader::list_java_processes();
std::vector<unsigned long> targets;
std::vector<std::wstring> labels;
for (const auto& jp : procs) {
if (!ui::LooksLikeMinecraft(jp)) continue;
targets.push_back(jp.pid);
labels.push_back(jp.window_title.empty() ? jp.window_class
: jp.window_title);
}
if (targets.empty()) {
ui::LogLine(L"headless inject: no Minecraft instance detected");
Write(L"No Minecraft instances detected.\n");
MessageBoxW(nullptr, L"No Minecraft instances detected.",
L"OpenZen Loader", MB_OK | MB_ICONWARNING);
return 1;
}
int failed = 0;
for (size_t i = 0; i < targets.size(); ++i) {
std::wstring err = loader::inject(targets[i]);
if (err.empty()) {
Write(L"Injected " + labels[i] + L" (PID " +
std::to_wstring(targets[i]) + L"): bootstrap complete.\n");
ui::LogLine(L"headless inject ok, pid=" +
std::to_wstring(targets[i]));
} else {
++failed;
Write(L"Inject FAILED for " + labels[i] + L" (PID " +
std::to_wstring(targets[i]) + L"): " + err + L"\n");
ui::LogLine(L"headless inject FAILED, pid=" +
std::to_wstring(targets[i]) + L": " + err);
MessageBoxW(nullptr,
(L"Injection failed (" + labels[i] + L", PID " +
std::to_wstring(targets[i]) + L"):\n" + err)
.c_str(),
L"OpenZen Loader", MB_OK | MB_ICONERROR);
}
}
return failed == 0 ? 0 : 1;
}
} // namespace
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
unsigned long cliPid = 0;
bool nogui = false;
bool help = false;
bool bad = false;
std::wstring badArg;
for (int i = 1; i < __argc; ++i) {
std::wstring a = __wargv[i];
if (_wcsicmp(a.c_str(), L"--nogui") == 0 ||
_wcsicmp(a.c_str(), L"/nogui") == 0) {
nogui = true;
} else if (_wcsicmp(a.c_str(), L"--help") == 0 ||
_wcsicmp(a.c_str(), L"-h") == 0 ||
_wcsicmp(a.c_str(), L"/?") == 0) {
help = true;
} else {
wchar_t* end = nullptr;
unsigned long v = wcstoul(a.c_str(), &end, 10);
if (end && *end == L'\0' && v != 0) {
cliPid = v;
} else {
bad = true;
badArg = a;
}
}
}
bool haveCli = help || bad || cliPid != 0 || nogui;
if (haveCli) {
// Don't run the message loop below: cli.cpp holds the console-attach
// helpers too, but the GUI stays out of the headless path entirely.
AttachParentConsole();
if (help && !bad) {
Write(kUsage);
return 0;
}
if (bad) {
Write(L"Unknown argument: " + badArg + L"\n" + kUsage);
return 2;
}
if (cliPid != 0) {
// Only ever inject into a Java process: a typo'd PID must not end
// up mapping our DLL into notepad.exe.
bool isJava = false;
std::wstring image;
for (const auto& p : loader::list_java_processes()) {
if (p.pid == cliPid) {
isJava = true;
image = p.image_name;
break;
}
}
if (!isJava) {
Write(L"Injection failed: PID " + std::to_wstring(cliPid) +
L" is not a running Java (javaw.exe/java.exe) process\n");
return 1;
}
return RunHeadless(cliPid);
}
// --nogui without a PID: every Minecraft instance it can find.
return RunHeadlessAll();
}
ui::InitDpi();
ui::LogLine(L"--- loader start (gui), scale=" +
std::to_wstring(ui::g_scale));
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken = 0;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
int rc = 0;
{
ui::SplashWindow splash;
ui::MainWindow main;
// Splash done -> reveal the main window with its entrance animation.
splash.onFinished = [&main] { main.PlayEntrance(); };
// Row Inject click -> show the progress overlay and run the real
// injection on the overlay's worker thread.
std::unique_ptr<ui::OverlayWindow> overlay;
main.onInjectRequested = [&](unsigned long pid,
const std::wstring& title) {
if (main.IsInjecting()) return;
main.SetInjectionInFlight(true);
overlay = std::make_unique<ui::OverlayWindow>();
overlay->onCompleted = [&main](bool) { main.PlayExit(); };
overlay->Show(main.hwnd(), pid, title);
};
// Main window gone -> end the process.
main.onClosed = [] { PostQuitMessage(0); };
splash.Show();
MSG msg{};
while (GetMessageW(&msg, nullptr, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
rc = static_cast<int>(msg.wParam);
}
Gdiplus::GdiplusShutdown(gdiplusToken);
return rc;
}