From 9e3e9d2997501522ea75884d3b2b403686024e07 Mon Sep 17 00:00:00 2001 From: Evan Martine Date: Wed, 5 Aug 2026 17:45:33 +0000 Subject: [PATCH] build: Ninja Multi-Config, schema codegen, and the offline-mod FPS cap endpoint Replaces the Visual Studio 17 2022 generator with Ninja Multi-Config so the build works against any toolset (2022, 2026+) without pinning a platform toolset. Adds paired Debug/Release build presets. Wires the packet-generator into CMake: gimuserver/packets/all.hpp and gimuserver/archive/archive.hpp are regenerated from the KDL schemas whenever a .kdl file changes, replacing the hand-run generate.py step. standalone_frontend accepts an optional config path as argv[1] and chdirs to its parent so relative paths in config.json resolve regardless of the launch working directory. stdout is unbuffered so redirected and headless boot logs survive a crash instead of dying in the CRT block buffer. Adds OfflineModController with a single route, GET /offline_mod/fps_cap, which returns the configured render-loop cap as a bare integer. It is read by the offline-proxy render hook at startup, not by the game client; the proxy falls back to its compile-time default if the endpoint is unreachable. game_frontend/bootstrap_windows.cpp: MSVC requires __declspec(dllexport) before the return type. The __APPX__-gated block had never been compiled because debug-win64 builds STANDALONE, so the C2059 only surfaced on the first release build. vcpkg.json drops the drogon[ctl] feature. The port marks ctl as supports:"native" and vcpkg refuses to install it on x86-windows-static. drogon_ctl is a scaffolding CLI that this codebase never references. Bumps the packet-generator submodule to 1e1682e, and switches BadgeInfo to serialise ::BadgeInfoResp rather than ::BadgeInfo so the response carries the h23iRjGN dispatch key at its root. The dispatcher also now records handler exceptions into the request log alongside the existing LOG_ERROR. --- .gitignore | 20 +++- CMakeLists.txt | 75 ++++++++++++++ CMakePresets.json | 52 ++++++++-- deploy/config.json | 5 +- game_frontend/bootstrap_windows.cpp | 11 ++- gimuserver/App.hpp | 1 + gimuserver/CMakeLists.txt | 11 ++- .../controller/OfflineModController.cpp | 19 ++++ .../controller/OfflineModController.hpp | 26 +++++ gimuserver/drogon/GimuServer.hpp | 1 + gimuserver/drogon/ServerConfig.hpp | 7 ++ gimuserver/gme/handlers/BadgeInfo.cpp | 2 + .../gme/handlers/GmeControllerHandlers.cpp | 3 + launch.vs.json | 21 ++++ packet-generator | 2 +- rebuild.bat | 99 +++++++++++++++++++ standalone_frontend/CMakeLists.txt | 17 ++++ standalone_frontend/main.cpp | 68 ++++++++++++- vcpkg.json | 1 - 19 files changed, 417 insertions(+), 24 deletions(-) create mode 100644 gimuserver/controller/OfflineModController.cpp create mode 100644 gimuserver/controller/OfflineModController.hpp create mode 100644 launch.vs.json create mode 100644 rebuild.bat diff --git a/.gitignore b/.gitignore index 1cfe52f..a1a778d 100644 --- a/.gitignore +++ b/.gitignore @@ -479,8 +479,18 @@ Makefile .ninja_log *.ninja -deploy/ +# Per-deploy state and large game assets stay untracked, but +# `deploy/system/*.json` (decoded MST data the server reads at boot) +# must be committed — without it, fresh checkouts can't run the server. +deploy/gme.sqlite +deploy/log/ +deploy/game_content/ +deploy/config.json config.json + +# IDA Pro audit tooling — kept off the public repo because the binary +# pseudocode dumps are private reverse-engineering artifacts. +tools/ida/ vcpkg_installed/ .idea .vscode @@ -490,3 +500,11 @@ generated/ # Project-specific CONTEXT.md out/ + +# Local dev tooling + docs — not for upstream +tools/ +*.md + +# Local-only debug CLI (dev tool, kept out of upstream) +standalone_frontend/DebugCli.cpp +standalone_frontend/DebugCli.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d2efb7..5e2a57c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,82 @@ find_package(Drogon CONFIG REQUIRED) find_package(unofficial-sqlite3 CONFIG REQUIRED) find_package(glaze CONFIG REQUIRED) # c++ JSON abstraction +# glaze's vcpkg port pins /GL (whole-program opt) and /LTCG on its imported +# target for Release/MinSizeRel. Those flags propagate through gimuserver to +# every consumer, and MSVC's LTCG code-gen pass chokes on the coroutine +# HANDLEF bodies (UserInfo.cpp, GachaAction.cpp) with C4737 -> LNK1257. +# Drop the LTCG flags so Release links cleanly. We give up cross-TU inlining +# from glaze; the tradeoff is the build actually completing. +if (MSVC AND TARGET glaze::glaze) + set_property(TARGET glaze::glaze PROPERTY INTERFACE_COMPILE_OPTIONS + "/Zc:preprocessor;/permissive-;/Zc:lambda") + set_property(TARGET glaze::glaze PROPERTY INTERFACE_LINK_OPTIONS + "$<$:/INCREMENTAL:NO>;$<$:/INCREMENTAL:NO>") +endif() + add_subdirectory(packet-generator/assets/runtime/cpp) + +# KDL -> C++ codegen. The packet-generator Rust CLI compiles assets/all.kdl +# into a single flat header (all.hpp) that gimuserver includes. The first +# build compiles the Rust generator itself (one-time, a few minutes); every +# subsequent build only re-runs it when a .kdl file has changed. +find_program(CARGO_EXECUTABLE cargo + DOC "Rust/Cargo toolchain, required to build packet-generator" +) +if (NOT CARGO_EXECUTABLE) + message(FATAL_ERROR + "cargo not found on PATH. Install Rust from https://rustup.rs/ " + "and re-run CMake configure." + ) +endif() + +set(PKGEN_SRC_DIR "${CMAKE_SOURCE_DIR}/packet-generator") +set(PKGEN_ENTRY "assets/all.kdl") +# App.hpp includes , so emit directly into the +# source tree under gimuserver/packets/. That directory has a .gitignore +# (*.hpp) that keeps the generated header out of version control. +set(PKGEN_OUT_DIR "${CMAKE_SOURCE_DIR}/gimuserver/packets") +set(PKGEN_OUT_HEADER "${PKGEN_OUT_DIR}/all.hpp") + +file(GLOB_RECURSE KDL_SCHEMAS CONFIGURE_DEPENDS + "${PKGEN_SRC_DIR}/assets/*.kdl" +) + +file(MAKE_DIRECTORY "${PKGEN_OUT_DIR}") + +# The archive schema (assets/archive.kdl) generates a second header consumed +# by gimuserver/archive/*Archiver; gimuserver/archive/.gitignore keeps it out +# of version control just like packets/all.hpp. +set(PKGEN_ARCHIVE_ENTRY "assets/archive.kdl") +set(PKGEN_ARCHIVE_OUT_DIR "${CMAKE_SOURCE_DIR}/gimuserver/archive") +set(PKGEN_ARCHIVE_OUT_HEADER "${PKGEN_ARCHIVE_OUT_DIR}/archive.hpp") + +add_custom_command( + OUTPUT "${PKGEN_OUT_HEADER}" "${PKGEN_ARCHIVE_OUT_HEADER}" + COMMAND "${CARGO_EXECUTABLE}" run --release -- + generate --cxx --glaze + -i "${PKGEN_ENTRY}" + -o "${PKGEN_OUT_DIR}" + COMMAND "${CARGO_EXECUTABLE}" run --release -- + generate --cxx --glaze + -i "${PKGEN_ARCHIVE_ENTRY}" + -o "${PKGEN_ARCHIVE_OUT_DIR}" + WORKING_DIRECTORY "${PKGEN_SRC_DIR}" + DEPENDS ${KDL_SCHEMAS} + COMMENT "Regenerating C++ packet headers from KDL schemas" + VERBATIM +) + +add_custom_target(pkgen_generate ALL + DEPENDS "${PKGEN_OUT_HEADER}" "${PKGEN_ARCHIVE_OUT_HEADER}" + SOURCES ${KDL_SCHEMAS} +) + +message(STATUS + "packet-generator: first build compiles the Rust generator (~2-5 min); " + "subsequent builds only re-run it when .kdl files change." +) + add_subdirectory(gimuserver) if (STANDALONE) diff --git a/CMakePresets.json b/CMakePresets.json index 7dac0e0..0eaa67f 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -16,7 +16,7 @@ { "name": "default-debug", "displayName": "Config Debug", - "description": "General default debug configuration", + "description": "General default debug configuration (single-config generators only)", "inherits": "default-base", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" @@ -26,7 +26,7 @@ { "name": "default-release", "displayName": "Config Release", - "description": "General default release configuration", + "description": "General default release configuration (single-config generators only)", "inherits": "default-base", "cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" @@ -35,10 +35,11 @@ }, { "name": "debug-win64", - "displayName": "Development config for Windows (64-bit)", - "inherits": "default-debug", - "generator": "Visual Studio 17 2022", - "architecture": "x64", + "displayName": "Development config for Windows (Ninja Multi-Config)", + "description": "Multi-config (Debug + Release) build for Windows x64. Must be run from an x64 Native Tools environment (VsDevCmd.bat -arch=amd64 -host_arch=amd64) so the x64-hosted cl.exe is on PATH. Do NOT set CMAKE_C/CXX_COMPILER here — let CMake find the compiler from the environment so it inherits the correct host/target architecture.", + "inherits": "default-base", + "generator": "Ninja Multi-Config", + "binaryDir": "${sourceDir}/out/build/${presetName}", "condition": { "type": "equals", "lhs": "${hostSystemName}", @@ -78,10 +79,15 @@ }, { "name": "release-win32", - "displayName": "Deployment for Brave Frontier for Windows", - "inherits": "default-release", - "generator": "Visual Studio 17 2022", - "architecture": "x64", + "displayName": "Deployment for Brave Frontier for Windows (APPX)", + "description": "Multi-config build producing the APPX-embedded server. Requires running from a Developer Command Prompt for Visual Studio.", + "inherits": "default-base", + "generator": "Ninja Multi-Config", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "architecture": { + "value": "x64", + "strategy": "external" + }, "condition": { "type": "equals", "lhs": "${hostSystemName}", @@ -98,5 +104,31 @@ } } } + ], + "buildPresets": [ + { + "name": "debug-win64-debug", + "configurePreset": "debug-win64", + "configuration": "Debug" + }, + { + "name": "debug-win64-release", + "configurePreset": "debug-win64", + "configuration": "Release" + }, + { + "name": "release-win32-debug", + "configurePreset": "release-win32", + "configuration": "Debug" + }, + { + "name": "release-win32-release", + "configurePreset": "release-win32", + "configuration": "Release" + }, + { + "name": "debug-lnx64", + "configurePreset": "debug-lnx64" + } ] } diff --git a/deploy/config.json b/deploy/config.json index 46c1fc6..58513ca 100644 --- a/deploy/config.json +++ b/deploy/config.json @@ -60,7 +60,10 @@ "initial_free_gems": 5, "initial_zel": 10000000, "initial_karma": 10000000, - "initial_bcoins": 0 + "initial_bcoins": 0, + // Client render-loop cap delivered to the offline-proxy via + // GET /offline_mod/fps_cap. 0 disables the client-side cap. + "fps_cap": 60 } } } diff --git a/game_frontend/bootstrap_windows.cpp b/game_frontend/bootstrap_windows.cpp index 0cacca5..b49c000 100644 --- a/game_frontend/bootstrap_windows.cpp +++ b/game_frontend/bootstrap_windows.cpp @@ -4,9 +4,16 @@ #include #include "gamefrontend.h" -extern "C" void __stdcall __declspec(dllexport) OfflineMod_startup(void) +// MSVC requires __declspec(dllexport) BEFORE the return type, not after +// __stdcall. The previous declaration order ("extern \"C\" void __stdcall +// __declspec(dllexport) OfflineMod_startup") produced +// error C2059: syntax error: '__declspec(dllexport)' +// on the first real release-win32 build (the only preset that defines +// __APPX__ and reaches this file). The debug-win64 preset doesn't define +// __APPX__, so the #ifdef'd block had never been compiled before. +extern "C" __declspec(dllexport) void __stdcall OfflineMod_startup(void) { // TODO: populate -} +} #endif diff --git a/gimuserver/App.hpp b/gimuserver/App.hpp index b02521d..526c96a 100644 --- a/gimuserver/App.hpp +++ b/gimuserver/App.hpp @@ -25,6 +25,7 @@ #include #include #include +#include // utility #include #include diff --git a/gimuserver/CMakeLists.txt b/gimuserver/CMakeLists.txt index adf08cf..287005e 100644 --- a/gimuserver/CMakeLists.txt +++ b/gimuserver/CMakeLists.txt @@ -1,7 +1,13 @@ file(GLOB_RECURSE SRC "*.cpp" "*.hpp") source_group(TREE "${CMAKE_CURRENT_LIST_DIR}" FILES ${SRC}) add_library(gimuserver STATIC ${SRC}) -target_link_libraries(gimuserver PUBLIC + +# The generated header (packets/all.hpp) is produced by pkgen_generate, +# declared at the project root. Everything in gimuserver transitively +# depends on it via App.hpp, so pin the build-order edge here. +add_dependencies(gimuserver pkgen_generate) + +target_link_libraries(gimuserver PUBLIC # third-party libraries Drogon::Drogon cryptopp::cryptopp @@ -11,10 +17,9 @@ target_link_libraries(gimuserver PUBLIC pkgen_cpp ) -target_include_directories(gimuserver +target_include_directories(gimuserver PUBLIC ../ - ${CMAKE_CURRENT_BINARY_DIR}/generated/ PRIVATE . ) diff --git a/gimuserver/controller/OfflineModController.cpp b/gimuserver/controller/OfflineModController.cpp new file mode 100644 index 0000000..85fb725 --- /dev/null +++ b/gimuserver/controller/OfflineModController.cpp @@ -0,0 +1,19 @@ +#include "App.hpp" +#include "OfflineModController.hpp" + +using namespace drogon; + +void OfflineModController::HandleFpsCap(const HttpRequestPtr& rq, + std::function&& callback) +{ + (void)rq; + const auto cap = theServer()->cache().serverConfig().fpsCap; + + auto resp = HttpResponse::newHttpResponse(); + resp->setContentTypeCode(ContentType::CT_TEXT_PLAIN); + resp->setStatusCode(k200OK); + resp->setBody(std::to_string(cap)); + resp->setCloseConnection(true); + + callback(resp); +} diff --git a/gimuserver/controller/OfflineModController.hpp b/gimuserver/controller/OfflineModController.hpp new file mode 100644 index 0000000..68d89f6 --- /dev/null +++ b/gimuserver/controller/OfflineModController.hpp @@ -0,0 +1,26 @@ +#pragma once + +/*! +* Endpoints consumed by the offline-proxy libcurl shim (not by the BF +* client itself). These deliver server-owned configuration that the +* client-side hooks need at startup. +* +*

URL prefix: /offline_mod/ +*/ +class OfflineModController : public drogon::HttpController +{ +public: + /*! + * Returns the desired client render-loop cap as a bare integer in + * the response body (e.g. "60"). Read by the offline-proxy FPS hook + * on its first MyRender call; falls back to the proxy's compile-time + * default if this endpoint is unreachable. + * @param[in] rq HTTP request + * @param[in] callback Callback to send the response + */ + void HandleFpsCap(const drogon::HttpRequestPtr& rq, std::function&& callback); + + METHOD_LIST_BEGIN + ADD_METHOD_TO(OfflineModController::HandleFpsCap, "/offline_mod/fps_cap", drogon::Get); + METHOD_LIST_END +}; diff --git a/gimuserver/drogon/GimuServer.hpp b/gimuserver/drogon/GimuServer.hpp index b9656f8..af19b80 100644 --- a/gimuserver/drogon/GimuServer.hpp +++ b/gimuserver/drogon/GimuServer.hpp @@ -66,6 +66,7 @@ class GimuServer final : public drogon::Plugin } private: + /*! * DLC error file. */ diff --git a/gimuserver/drogon/ServerConfig.hpp b/gimuserver/drogon/ServerConfig.hpp index 939d7db..d9c31b0 100644 --- a/gimuserver/drogon/ServerConfig.hpp +++ b/gimuserver/drogon/ServerConfig.hpp @@ -24,4 +24,11 @@ struct ServerConfig * Initial brave coins. */ uint32_t initialBraveCoins; + + /*! + * Frame-rate cap delivered to the offline-proxy client at startup. 0 + * disables the client-side cap. Read from plugins[0].config.server.fps_cap + * in deploy/config.json; defaults to 60 if absent. + */ + uint32_t fpsCap; }; diff --git a/gimuserver/gme/handlers/BadgeInfo.cpp b/gimuserver/gme/handlers/BadgeInfo.cpp index 2d8c691..73990fe 100644 --- a/gimuserver/gme/handlers/BadgeInfo.cpp +++ b/gimuserver/gme/handlers/BadgeInfo.cpp @@ -3,6 +3,8 @@ HANDLEF(BadgeInfo) { + // Use BadgeInfoResp (the wrapper): it carries the "h23iRjGN" dispatch key the + // client requires (see decompfrontier/server PR #23). ::BadgeInfoResp resp{}; std::string buffer{}; const auto& ec = glz::write_json(resp, buffer); diff --git a/gimuserver/gme/handlers/GmeControllerHandlers.cpp b/gimuserver/gme/handlers/GmeControllerHandlers.cpp index 1271f8f..59d9608 100644 --- a/gimuserver/gme/handlers/GmeControllerHandlers.cpp +++ b/gimuserver/gme/handlers/GmeControllerHandlers.cpp @@ -87,6 +87,7 @@ static GmeHandler getHandler(std::string_view cmd) REGISTER("ynB7X5P9", UpdateInfoLight, "7kH9NXwC"); REGISTER("cTZ3W2JG", UserInfo, "ScJx6ywWEb0A3njT"); + } } @@ -150,6 +151,7 @@ drogon::Task GmeController::Handle(drogon::SessionPtr session, const catch (const drogon::orm::DrogonDbException& ex) { LOG_ERROR << "Handler error " << header.id << " (" << handler.name << ") database exception: " << ex.base().what(); + logReq << "EXCEPTION (db): " << ex.base().what() << "\n"; GmeError err{}; err.cmd = GmeErrorCommand::Close; err.flag = GmeErrorFlags::IsInError; @@ -159,6 +161,7 @@ drogon::Task GmeController::Handle(drogon::SessionPtr session, const catch (const std::exception& ex) { LOG_ERROR << "Handler error " << header.id << " (" << handler.name << ") exception: " << ex.what(); + logReq << "EXCEPTION (std): " << ex.what() << "\n"; GmeError err{}; err.cmd = GmeErrorCommand::Close; err.flag = GmeErrorFlags::IsInError; diff --git a/launch.vs.json b/launch.vs.json new file mode 100644 index 0000000..a144b15 --- /dev/null +++ b/launch.vs.json @@ -0,0 +1,21 @@ +{ + "version": "0.2.1", + "configurations": [ + { + "type": "native", + "name": "gimuserverw Debug", + "project": "CMakeLists.txt", + "projectTarget": "gimuserverw.exe (standalone_frontend\\gimuserverw.exe)", + "args": [ "${workspaceRoot}\\deploy\\config.json" ], + "env": {} + }, + { + "type": "native", + "name": "gimuserverw Release", + "project": "CMakeLists.txt", + "projectTarget": "gimuserverw.exe (standalone_frontend\\gimuserverw.exe)", + "args": [ "${workspaceRoot}\\deploy\\config.json" ], + "env": {} + } + ] +} diff --git a/packet-generator b/packet-generator index cf604c0..1e1682e 160000 --- a/packet-generator +++ b/packet-generator @@ -1 +1 @@ -Subproject commit cf604c09963ce2b2e499bcbff776af1dfa5c328e +Subproject commit 1e1682e9c24f76dc7f4110b9229cb1c2c21f498d diff --git a/rebuild.bat b/rebuild.bat new file mode 100644 index 0000000..adbc8a5 --- /dev/null +++ b/rebuild.bat @@ -0,0 +1,99 @@ +@echo off +setlocal + +set "SERVER_DIR=%~dp0" +set "VCPKG_ROOT=C:\Users\Evan\BF\vcpkg" +set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" +set "BUILD_DIR=%SERVER_DIR%out\build\debug-win64" + +:: ── Locate VsDevCmd.bat via vswhere (VS 2017-2026+) ────────────────────────── +if not exist "%VSWHERE%" ( + echo ERROR: vswhere.exe not found. Visual Studio does not appear to be installed. + pause & exit /b 1 +) + +for /f "usebackq tokens=*" %%i in ( + `"%VSWHERE%" -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath` +) do set "VS_PATH=%%i" + +if not defined VS_PATH ( + echo ERROR: No Visual Studio installation with C++ tools found. + echo Install the "Desktop development with C++" workload from the VS Installer. + pause & exit /b 1 +) + +set "VSDEVCMD=%VS_PATH%\Common7\Tools\VsDevCmd.bat" +if not exist "%VSDEVCMD%" ( + echo ERROR: VsDevCmd.bat not found at: + echo %VSDEVCMD% + pause & exit /b 1 +) + +:: ── Check vcpkg ─────────────────────────────────────────────────────────────── +if not exist "%VCPKG_ROOT%\vcpkg.exe" ( + echo ERROR: vcpkg not found at %VCPKG_ROOT% + echo Run the BF installer first to set up vcpkg. + pause & exit /b 1 +) + +echo ============================================================ +echo BF Server Rebuild (Ninja Multi-Config) +echo VS: %VS_PATH% +echo vcpkg: %VCPKG_ROOT% +echo Build dir: %BUILD_DIR% +echo ============================================================ +echo. + +:: ── Config choice ───────────────────────────────────────────────────────────── +set CONFIG=Debug +set /p CONFIG="Build configuration? (Debug/Release, default Debug): " +if /i "%CONFIG%"=="" set CONFIG=Debug +if /i "%CONFIG%"=="d" set CONFIG=Debug +if /i "%CONFIG%"=="r" set CONFIG=Release + +:: Map to the build preset name +if /i "%CONFIG%"=="Debug" set BUILD_PRESET=debug-win64-debug +if /i "%CONFIG%"=="Release" set BUILD_PRESET=debug-win64-release + +if not defined BUILD_PRESET ( + echo ERROR: Unknown configuration "%CONFIG%". Use Debug or Release. + pause & exit /b 1 +) + +:: ── Configure choice ────────────────────────────────────────────────────────── +:: Auto-suggest reconfigure if the build directory doesn't exist yet. +set RECONFIGURE=N +if not exist "%BUILD_DIR%\build.ninja" set RECONFIGURE=Y + +set /p RECONFIGURE="Re-run CMake configure? (y/N, default %RECONFIGURE%): " +if /i "%RECONFIGURE%"=="y" goto :configure +if /i "%RECONFIGURE%"=="Y" goto :configure +goto :build_only + +:configure +echo. +echo [1/2] Configuring (debug-win64 preset)... +echo NOTE: First build will compile the Rust packet-generator (~2-5 min). +echo. +cmd /c ""%VSDEVCMD%" -arch=amd64 -host_arch=amd64 && cd /d "%SERVER_DIR%" && cmake --preset debug-win64" +if errorlevel 1 ( + echo. + echo ERROR: CMake configure failed. + pause & exit /b 1 +) +echo. + +:build_only +echo [Building %CONFIG%]... +cmd /c ""%VSDEVCMD%" -arch=amd64 -host_arch=amd64 && cd /d "%SERVER_DIR%" && cmake --build --preset %BUILD_PRESET%" + +:done +if errorlevel 1 ( + echo. + echo BUILD FAILED. Check the output above for errors. +) else ( + echo. + echo Build succeeded. Artifacts: %BUILD_DIR%\%CONFIG%\ +) +pause +endlocal diff --git a/standalone_frontend/CMakeLists.txt b/standalone_frontend/CMakeLists.txt index 6dc396f..661b0d7 100644 --- a/standalone_frontend/CMakeLists.txt +++ b/standalone_frontend/CMakeLists.txt @@ -1,3 +1,20 @@ file(GLOB SRC "*.cpp" "*.hpp") add_executable(gimuserverw ${SRC}) target_link_libraries(gimuserverw PRIVATE gimuserver) + +# The debug CLI (DebugCli.cpp/.hpp) is a local-only dev tool kept out of the +# repo (git-ignored). When it's present, the GLOB above compiles it and we +# define BF_DEBUG_CLI so main.cpp wires it in; upstream builds without the file +# just omit the CLI. Re-run CMake configure after adding/removing the file. +if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/DebugCli.cpp") + target_compile_definitions(gimuserverw PRIVATE BF_DEBUG_CLI) + message(STATUS "standalone_frontend: debug CLI enabled (DebugCli.cpp present)") +endif() + +# In Debug builds, bake the absolute path to deploy/config.json into the +# binary so F5 from VS (or any launcher) always finds the right config +# regardless of working directory. Release builds keep the default +# "./config.json" so the deployed package still works portably. +target_compile_definitions(gimuserverw PRIVATE + $<$:GIMU_DEFAULT_CONFIG_PATH="${CMAKE_SOURCE_DIR}/deploy/config.json"> +) diff --git a/standalone_frontend/main.cpp b/standalone_frontend/main.cpp index 7337535..d3d704a 100644 --- a/standalone_frontend/main.cpp +++ b/standalone_frontend/main.cpp @@ -4,25 +4,83 @@ #include #include +// The debug CLI is a local-only dev tool (git-ignored). When DebugCli.cpp is +// present, standalone_frontend/CMakeLists.txt defines BF_DEBUG_CLI; upstream +// builds without the file simply omit it. +#ifdef BF_DEBUG_CLI +#include "DebugCli.hpp" +#endif + +#include + int main(int argc, char** argv) { + // Unbuffered stdout so log lines reach redirected files immediately - + // otherwise a wedged/killed headless run loses everything still sitting + // in the CRT block buffer and boot failures become undiagnosable. + setvbuf(stdout, nullptr, _IONBF, 0); + +#ifdef BF_DEBUG_CLI + // CLI-client mode: launched by the server to host the debug console window. + // Must be checked before any Drogon / config setup so this process never + // starts the web server or runs migrations. + if (argc >= 3 && std::string(argv[1]) == "--debug-cli") + { + RunDebugCliClient(argv[2]); + return 0; + } +#endif + #ifdef _WIN32 SetConsoleTitleW(L"GimuFrontier standalone server"); #endif + // Config path resolution priority: + // 1. Command-line argument (argv[1]) — explicit always wins. + // 2. GIMU_DEFAULT_CONFIG_PATH — baked in at compile time for Debug + // builds so F5 from VS works without touching the launch config. + // 3. "./config.json" — fallback for Release / command-line usage where + // the caller cd's into deploy/ first (e.g. rebuild.bat). +#ifndef GIMU_DEFAULT_CONFIG_PATH +#define GIMU_DEFAULT_CONFIG_PATH "./config.json" +#endif + const char* configArg = (argc > 1) ? argv[1] : GIMU_DEFAULT_CONFIG_PATH; + try { + // Resolve to an absolute path so the chdir below works regardless of + // what the current directory was at launch. + auto absConfig = std::filesystem::absolute(configArg); + + // Change into the config file's directory. Every path in config.json + // is relative to its own location (mst_root, document_root, etc.) so + // this makes the server portable without editing config.json. + std::filesystem::current_path(absConfig.parent_path()); + drogon::app() - .loadConfigFile("./config.json") + .loadConfigFile(absConfig.filename().string()) .registerBeginningAdvice([]() { - MigrationManager::RunMigrations(drogon::app().getDbClient()); - }) - .run() - ; + auto db = drogon::app().getDbClient(); + MigrationManager::RunMigrations(db); + // Legacy SeedDefaultUnits/SeedDefaultTown removed: the + // tutorial (CreateUser + UnitArchiver) provisions the user + // under the fresh-DB model adopted from upstream dev. +#ifdef BF_DEBUG_CLI + StartDebugCli(); +#endif + }) + .run(); } catch (const std::exception& ex) { printf("Fatal exception during execution: %s\n", ex.what()); +#ifdef _WIN32 + // Also write to the VS Output window so the message is visible + // without needing to watch the console window. + OutputDebugStringA("Fatal exception during execution: "); + OutputDebugStringA(ex.what()); + OutputDebugStringA("\n"); +#endif } drogon::HttpAppFramework::instance().getLoop()->queueInLoop([]() diff --git a/vcpkg.json b/vcpkg.json index 351e7c8..78e351c 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -4,7 +4,6 @@ "name": "drogon", "features": [ "sqlite3", - "ctl", "orm" ] },