#include #include #include #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include "Config.h" #include "Logger.h" #include "Overlay/GameOverlay.h" #include "Overlay/OverlayWindow.h" #include "Runtime/DayZRuntimeService.h" #include "Web/WebRadarServer.h" // Shared stop flag so the console Ctrl+C / close handler can reach it. static std::atomic* g_stopFlag = nullptr; static BOOL WINAPI ConsoleCtrlHandler(DWORD signal) { if (signal == CTRL_C_EVENT || signal == CTRL_CLOSE_EVENT || signal == CTRL_BREAK_EVENT) { if (g_stopFlag) g_stopFlag->store(true); return TRUE; } return FALSE; } int main() { std::filesystem::create_directories("logs"); AppLogger::Init(); auto log = AppLogger::Get(); log->info("DayZ Memory Reader - Starting"); RuntimeConfig rtCfg = RuntimeConfig::Load("config/config.cfg"); DayZRuntimeService service(rtCfg); // ------------------------------------------------------------------------- // Config — loaded from config/overlay.json (defaults if missing). // ------------------------------------------------------------------------- static const std::string kCfgPath = "config/overlay.json"; OverlayConfig cfg = OverlayConfig::Load(kCfgPath); // ------------------------------------------------------------------------- // Web radar server — settings come from config. // ------------------------------------------------------------------------- WebRadarConfig webCfg; webCfg.bindAddress = cfg.webBindAddress; webCfg.port = cfg.webPort; webCfg.password = cfg.webPassword; WebRadarServer webRadar(webCfg); webRadar.Start(); // ------------------------------------------------------------------------- // Runtime service callback: log stats + push snapshot to web radar. // ------------------------------------------------------------------------- service.SetUpdateCallback([&log, &webRadar](const RuntimeUpdate& update) { // Always push to the web radar (even when not yet attached) so the // browser client sees connection/status changes immediately. webRadar.PushSnapshot(update); if (!update.areBaseObjectsReady) { // Status messages are already de-duplicated inside WaitForSession. return; } // Periodic heartbeat at most once every 10 seconds. Kept terse — the // interesting per-event detail (admins detected, skeletons lost) is logged // as it happens by the runtime service, not polled here. using Clock = std::chrono::steady_clock; static auto nextLog = Clock::time_point{}; auto now = Clock::now(); if (now < nextLog) return; nextLog = now + std::chrono::seconds(10); // Skeleton coverage: how many in-range players currently have valid bones. size_t skelOk = 0, admins = 0; for (const auto& p : update.players) { if (p.skeleton.valid) ++skelOk; if (p.isAdmin) ++admins; } log->info("[Live] Players={} (bones {}/{}, admins {}) Zombies={} Vehicles={} Server='{}'", update.players.size(), skelOk, update.players.size(), admins, update.zombies.size(), update.carsAndBoats.size(), update.serverName.value_or("?")); }); service.Start(); // Overlay polls GetLatestUpdate() each frame; no extra callback needed. std::atomic stopFlag{false}; g_stopFlag = &stopFlag; GameOverlay overlay(service, cfg, kCfgPath); overlay.SetWebRadarPort(webCfg.port); overlay.SetExitCallback([&stopFlag]() { stopFlag.store(true); }); OverlayWindow window; window.SetResolutionOverride(cfg.overlayWidth, cfg.overlayHeight); overlay.SetResizeCallback([&window](int w, int h) { window.ResizeTo(w, h); }); window.SetDrawCallback([&overlay](float w, float h) { overlay.Draw(w, h); }); window.SetInputQueryCallback([&overlay]() { return overlay.IsMenuOpen(); }); window.SetFontReadyCallback([&overlay](ImFont* close, ImFont* lootFar) { overlay.SetLootFonts(close, lootFar); }); SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); // Blocks until the overlay window is closed or stopFlag is set externally. window.Run(stopFlag); webRadar.Stop(); service.Stop(); log->info("Stopped."); spdlog::shutdown(); return 0; }