diff --git a/deps/PackageList.txt b/deps/PackageList.txt index ce7024616..e9ec688f9 100644 --- a/deps/PackageList.txt +++ b/deps/PackageList.txt @@ -86,6 +86,6 @@ fkYAML (A C++ header-only YAML library) https://github.com/fktn-k/fkYAML Version: 721edb3e1a817e527fd9e1e18a3bea300822522e -libsidecar (enables interaction with other components when the worldserver is running in cluster mode. It requires building a shared library first and putting it in the corresponding folder.) - https://github.com/walkline/ToCloud9/tree/master/game-server/libsidecar - Version: master +libsidecar (enables interaction with other components when the worldserver is running in cluster mode. Place the shared library from the release assets in deps/libsidecar for builds. UNIX: BUILD_RPATH finds deps/libsidecar; install copies to ${prefix}/lib; runtime also searches next to worldserver and ../lib. Windows: no rpath - build copies the DLL next to worldserver; install places it next to worldserver. Headers under deps/libsidecar/include - including tc9_version.h - must come from the same libsidecar build/release as the linked binary. worldserver checks ABI early when Cluster.Enabled.) + https://github.com/walkline/ToCloud9/releases/tag/libsidecar-v1.0.0 + Version: 1.0.0 (TC9_VERSION in deps/libsidecar/include/tc9_version.h) diff --git a/deps/libsidecar/CMakeLists.txt b/deps/libsidecar/CMakeLists.txt index 170e0cc3b..78b5cc5fd 100644 --- a/deps/libsidecar/CMakeLists.txt +++ b/deps/libsidecar/CMakeLists.txt @@ -37,30 +37,54 @@ else() set(LIBSIDECAR_EXTENSION "so") endif() + # Visible to worldserver CMake (rpath on UNIX; POST_BUILD copy on Windows). + set(LIBSIDECAR_SHARED_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/libsidecar.${LIBSIDECAR_EXTENSION}" + CACHE INTERNAL "Path to the real libsidecar shared library file") + + if (NOT EXISTS "${LIBSIDECAR_SHARED_FILE}") + message(WARNING + "USE_REAL_LIBSIDECAR is ON but ${LIBSIDECAR_SHARED_FILE} was not found. " + "Place the matching shared library (and headers) from the same libsidecar " + "build/release before linking or installing.") + endif() + if(WIN32) set_target_properties(libsidecar PROPERTIES IMPORTED_LOCATION - ${CMAKE_CURRENT_SOURCE_DIR}/libsidecar.${LIBSIDECAR_EXTENSION} + ${LIBSIDECAR_SHARED_FILE} IMPORTED_IMPLIB ${CMAKE_CURRENT_SOURCE_DIR}/${LIBSIDECAR_IMPORT_LIB} INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/include) + + # worldserver installs to CMAKE_INSTALL_PREFIX root; DLL must sit next to it. + # Build-tree runs: apps/CMakeLists.txt copies this DLL next to worldserver + # (Windows has no rpath; the loader only searches the exe directory / PATH). + install(FILES "${LIBSIDECAR_SHARED_FILE}" + DESTINATION "${CMAKE_INSTALL_PREFIX}") else() set_target_properties(libsidecar PROPERTIES IMPORTED_LOCATION - ${CMAKE_CURRENT_SOURCE_DIR}/libsidecar.${LIBSIDECAR_EXTENSION} + ${LIBSIDECAR_SHARED_FILE} INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/include IMPORTED_NO_SONAME true) - set_target_properties(libsidecar PROPERTIES - IMPORTED_LOCATION_NOCONFIG - "${CMAKE_INSTALL_RPATH}/libsidecar.${LIBSIDECAR_EXTENSION}" - IMPORTED_SONAME_NOCONFIG - "libsidecar.${LIBSIDECAR_EXTENSION}") + # Source-tree dir for BUILD_RPATH only (dev runs from the build tree). + # Do not put this path on INSTALL_RPATH — that would bake the build + # machine's source tree into installed binaries. + # Do not use INTERFACE_LINK_OPTIONS -rpath: game, scripts, and + # worldserver all link libsidecar and would get duplicate -rpath warnings. + set(LIBSIDECAR_RUNTIME_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE INTERNAL + "Directory containing the real libsidecar shared library (build tree)") + + # Install next to the rest of the prefix libraries (worldserver → bin/, + # so runtime rpath uses $ORIGIN/../lib and CMAKE_INSTALL_RPATH = prefix/lib). + install(FILES "${LIBSIDECAR_SHARED_FILE}" DESTINATION lib) endif() endif() diff --git a/deps/libsidecar/include/libsidecar.h b/deps/libsidecar/include/libsidecar.h index ef477c76d..b5a2e94bb 100644 --- a/deps/libsidecar/include/libsidecar.h +++ b/deps/libsidecar/include/libsidecar.h @@ -19,6 +19,9 @@ #endif #endif +/* Compile-time version macros (must match the libsidecar you link) */ +#include "tc9_version.h" + /* Include all API headers */ #include "battleground-api.h" #include "events-group.h" @@ -33,6 +36,13 @@ extern "C" { #endif +/* ABI / package version of the *linked* library (not the caller's headers). + * Pass TC9_VERSION_MAJOR / TC9_VERSION_MINOR from the headers you compiled + * against into TC9CheckAbiCompatible. Returns 0 if compatible. */ +TC9_API void TC9GetVersion(int* major, int* minor, int* patch); +TC9_API const char* TC9GetVersionString(void); +TC9_API int TC9CheckAbiCompatible(int required_major, int required_minor); + /* Main library functions */ TC9_API void TC9InitLib(uint16_t port, uint32_t realmID, uint8_t isCrossRealm, char* availableMaps, uint32_t** assignedMaps, int* assignedMapsSize); TC9_API void TC9GracefulShutdown(); diff --git a/deps/libsidecar/include/tc9_version.h b/deps/libsidecar/include/tc9_version.h new file mode 100644 index 000000000..c2aa588a4 --- /dev/null +++ b/deps/libsidecar/include/tc9_version.h @@ -0,0 +1,20 @@ +#ifndef TC9_VERSION_H +#define TC9_VERSION_H + +/* Vendored snapshot of libsidecar-cpp generated version header. + * Keep in sync with the libsidecar binary in this deps folder. + * Upstream source of truth: project(libsidecar VERSION ...) in + * game-server/libsidecar-cpp/CMakeLists.txt (generates this file). + */ + +#define TC9_VERSION_MAJOR 1 +#define TC9_VERSION_MINOR 0 +#define TC9_VERSION_PATCH 0 + +#define TC9_VERSION_STRING "1.0.0" + +/* major*10000 + minor*100 + patch */ +#define TC9_VERSION_NUMBER \ + (TC9_VERSION_MAJOR * 10000 + TC9_VERSION_MINOR * 100 + TC9_VERSION_PATCH) + +#endif /* TC9_VERSION_H */ diff --git a/deps/libsidecar/stub/libsidecar.c b/deps/libsidecar/stub/libsidecar.c index ed13fedd5..21d378cfe 100644 --- a/deps/libsidecar/stub/libsidecar.c +++ b/deps/libsidecar/stub/libsidecar.c @@ -7,6 +7,32 @@ void panicWithTC9Unavailable(const char* message) { exit(EXIT_FAILURE); } +// Version APIs work without a real libsidecar so callers can always query +// the header version this tree was built against. +void TC9GetVersion(int* major, int* minor, int* patch) +{ + if (major) + *major = TC9_VERSION_MAJOR; + if (minor) + *minor = TC9_VERSION_MINOR; + if (patch) + *patch = TC9_VERSION_PATCH; +} + +const char* TC9GetVersionString(void) +{ + return TC9_VERSION_STRING; +} + +int TC9CheckAbiCompatible(int required_major, int required_minor) +{ + if (TC9_VERSION_MAJOR != required_major) + return 1; + if (TC9_VERSION_MINOR < required_minor) + return 2; + return 0; +} + // TC9SetBattlegroundStartHandler sets handler for starting battleground. // extern void TC9SetBattlegroundStartHandler(BattlegroundStartHandler h) { panicWithTC9Unavailable("TC9SetBattlegroundStartHandler"); } diff --git a/deps/libsidecar/stub/libsidecar.h b/deps/libsidecar/stub/libsidecar.h index 220d3fa94..8903dd8f3 100644 --- a/deps/libsidecar/stub/libsidecar.h +++ b/deps/libsidecar/stub/libsidecar.h @@ -4,6 +4,9 @@ #include #include +/* Compile-time version macros (vendored with real headers under include/) */ +#include "tc9_version.h" + /* Include all API headers */ #include "battleground-api.h" #include "events-group.h" @@ -18,6 +21,11 @@ extern "C" { #endif +/* Version APIs (implemented by stub without panic; real lib reports its build) */ +void TC9GetVersion(int* major, int* minor, int* patch); +const char* TC9GetVersionString(void); +int TC9CheckAbiCompatible(int required_major, int required_minor); + /* Main library functions */ void TC9InitLib(uint16_t port, uint32_t realmID, uint8_t isCrossRealm, char* availableMaps, uint32_t** assignedMaps, int* assignedMapsSize); void TC9GracefulShutdown(); diff --git a/deps/libsidecar/stub/tc9_version.h b/deps/libsidecar/stub/tc9_version.h new file mode 100644 index 000000000..c9009f17b --- /dev/null +++ b/deps/libsidecar/stub/tc9_version.h @@ -0,0 +1,21 @@ +#ifndef TC9_VERSION_H +#define TC9_VERSION_H + +/* Vendored snapshot of libsidecar-cpp generated version header. + * Keep in sync with include/tc9_version.h and the linked libsidecar binary. + * Built only when USE_REAL_LIBSIDECAR is OFF (static stub, not the real .so/.dll). + */ + +#define TC9_LIBSIDECAR_IS_STUB 1 + +#define TC9_VERSION_MAJOR 1 +#define TC9_VERSION_MINOR 0 +#define TC9_VERSION_PATCH 0 + +#define TC9_VERSION_STRING "1.0.0" + +/* major*10000 + minor*100 + patch */ +#define TC9_VERSION_NUMBER \ + (TC9_VERSION_MAJOR * 10000 + TC9_VERSION_MINOR * 100 + TC9_VERSION_PATCH) + +#endif /* TC9_VERSION_H */ diff --git a/src/cmake/showoptions.cmake b/src/cmake/showoptions.cmake index f8e41d361..a3c3da186 100644 --- a/src/cmake/showoptions.cmake +++ b/src/cmake/showoptions.cmake @@ -123,6 +123,16 @@ endif() if ( USE_REAL_LIBSIDECAR ) message("* Use stub for libsidecar : No") + # Required ABI only applies when linking the real shared library (include/). + if (EXISTS "${CMAKE_SOURCE_DIR}/deps/libsidecar/include/tc9_version.h") + file(STRINGS "${CMAKE_SOURCE_DIR}/deps/libsidecar/include/tc9_version.h" _tc9_ver_line + REGEX "^#define TC9_VERSION_STRING ") + if (_tc9_ver_line) + string(REGEX REPLACE "^#define TC9_VERSION_STRING \"([^\"]+)\".*" "\\1" TC9_VENDORED_VERSION "${_tc9_ver_line}") + message("* libsidecar required ABI : ${TC9_VENDORED_VERSION}") + endif() + unset(_tc9_ver_line) + endif() else() message("* Use stub for libsidecar : Yes") endif() diff --git a/src/server/apps/CMakeLists.txt b/src/server/apps/CMakeLists.txt index ebd7f3a65..8befd845f 100644 --- a/src/server/apps/CMakeLists.txt +++ b/src/server/apps/CMakeLists.txt @@ -148,6 +148,45 @@ foreach(APPLICATION_NAME ${APPLICATIONS_BUILD_LIST}) set_target_properties(${APP_PROJECT_NAME} PROPERTIES LINK_FLAGS "${${APP_PROJECT_NAME}_LINK_FLAGS}") + # Real libsidecar runtime location (not via imported-target INTERFACE -rpath: + # game, scripts, and worldserver all link libsidecar and would get duplicates). + # + # UNIX/macOS — rpath search order: + # BUILD: deps/libsidecar → next to binary → ../lib + # INSTALL: target inherits CMAKE_INSTALL_RPATH (${prefix}/lib) first, then + # APPEND next to binary and ../lib (no source-tree path) + # + # Windows — no rpath; loader only searches the exe directory (then PATH). + # BUILD: POST_BUILD copy DLL next to worldserver + # INSTALL: install(FILES) places DLL next to worldserver (see deps/libsidecar) + if (USE_REAL_LIBSIDECAR) + if (WIN32 AND LIBSIDECAR_SHARED_FILE) + add_custom_command(TARGET ${APP_PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${LIBSIDECAR_SHARED_FILE}" + "$" + COMMENT "Copy libsidecar.dll next to worldserver (Windows build tree)") + elseif (LIBSIDECAR_RUNTIME_DIR) + if (APPLE) + set_property(TARGET ${APP_PROJECT_NAME} APPEND PROPERTY BUILD_RPATH + "${LIBSIDECAR_RUNTIME_DIR}" + "@loader_path" + "@loader_path/../lib") + set_property(TARGET ${APP_PROJECT_NAME} APPEND PROPERTY INSTALL_RPATH + "@loader_path" + "@loader_path/../lib") + elseif (UNIX) + set_property(TARGET ${APP_PROJECT_NAME} APPEND PROPERTY BUILD_RPATH + "${LIBSIDECAR_RUNTIME_DIR}" + "$ORIGIN" + "$ORIGIN/../lib") + set_property(TARGET ${APP_PROJECT_NAME} APPEND PROPERTY INSTALL_RPATH + "$ORIGIN" + "$ORIGIN/../lib") + endif() + endif() + endif() + # Add all dynamic projects as dependency to the worldserver if (WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES) add_dependencies(${APP_PROJECT_NAME} ${WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES}) diff --git a/src/server/apps/worldserver/Main.cpp b/src/server/apps/worldserver/Main.cpp index e3182d65d..4dac150fd 100644 --- a/src/server/apps/worldserver/Main.cpp +++ b/src/server/apps/worldserver/Main.cpp @@ -205,6 +205,11 @@ int main(int argc, char** argv) LOG_INFO("server.worldserver", "> Using Boost version: {}.{}.{}", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100); }); + // Cluster.Enabled is known from config here. Fail before DB/network if the + // loaded libsidecar is the stub or does not match the headers we built with. + if (!sToCloud9Sidecar->CheckLibsidecarAbi()) + return 1; + OpenSSLCrypto::threadsSetup(); std::shared_ptr opensslHandle(nullptr, [](void*) { OpenSSLCrypto::threadsCleanup(); }); diff --git a/src/server/game/TC9Sidecar/TC9Sidecar.cpp b/src/server/game/TC9Sidecar/TC9Sidecar.cpp index ef808a9c5..05fa9f23d 100644 --- a/src/server/game/TC9Sidecar/TC9Sidecar.cpp +++ b/src/server/game/TC9Sidecar/TC9Sidecar.cpp @@ -44,6 +44,71 @@ ToCloud9Sidecar::ToCloud9Sidecar() : _clusterModeEnabled(false), _isCrossrealm(f { } +bool ToCloud9Sidecar::CheckLibsidecarAbi() +{ + if (!sConfigMgr->GetOption("Cluster.Enabled", false)) + return true; + + int libMajor = 0; + int libMinor = 0; + int libPatch = 0; + TC9GetVersion(&libMajor, &libMinor, &libPatch); + char const* libVersionStr = TC9GetVersionString(); + +#if defined(TC9_LIBSIDECAR_IS_STUB) + char const* libKind = "stub"; +#else + char const* libKind = "shared library"; +#endif + +#if defined(TC9_LIBSIDECAR_IS_STUB) + // Stub always "matches" its own macros; fail here instead of after the realm + // is online when TC9InitLib panics. + // SetSynchronous first: this runs before the io_context pool starts, so async + // LOG_* would never print on early return. Success path leaves async alone. + sLog->SetSynchronous(); + LOG_INFO("server", "libsidecar ({}) runtime {}.{}.{} ({}) - headers {}.{}.{} ({})", + libKind, + libMajor, libMinor, libPatch, + libVersionStr ? libVersionStr : "?", + TC9_VERSION_MAJOR, TC9_VERSION_MINOR, TC9_VERSION_PATCH, + TC9_VERSION_STRING); + LOG_ERROR("server", + "Cluster.Enabled requires the real libsidecar shared library. " + "This worldserver was built with the stub (default). " + "Rebuild with -DUSE_REAL_LIBSIDECAR=ON and place matching headers + library " + "from the same libsidecar release under deps/libsidecar."); + return false; +#else + if (TC9CheckAbiCompatible(TC9_VERSION_MAJOR, TC9_VERSION_MINOR) != 0) + { + sLog->SetSynchronous(); + LOG_INFO("server", "libsidecar ({}) runtime {}.{}.{} ({}) - headers {}.{}.{} ({})", + libKind, + libMajor, libMinor, libPatch, + libVersionStr ? libVersionStr : "?", + TC9_VERSION_MAJOR, TC9_VERSION_MINOR, TC9_VERSION_PATCH, + TC9_VERSION_STRING); + LOG_ERROR("server", + "libsidecar ABI mismatch: worldserver was built for {}.{}.{} (headers {}), " + "but loaded library is {}.{}.{} ({}). " + "Copy matching headers + library from the same libsidecar build/release.", + TC9_VERSION_MAJOR, TC9_VERSION_MINOR, TC9_VERSION_PATCH, TC9_VERSION_STRING, + libMajor, libMinor, libPatch, + libVersionStr ? libVersionStr : "?"); + return false; + } + + LOG_INFO("server", "libsidecar ({}) runtime {}.{}.{} ({}) - headers {}.{}.{} ({})", + libKind, + libMajor, libMinor, libPatch, + libVersionStr ? libVersionStr : "?", + TC9_VERSION_MAJOR, TC9_VERSION_MINOR, TC9_VERSION_PATCH, + TC9_VERSION_STRING); + return true; +#endif +} + void ToCloud9Sidecar::Init(uint16 port, int realmId) { _clusterModeEnabled = sConfigMgr->GetOption("Cluster.Enabled", false); diff --git a/src/server/game/TC9Sidecar/TC9Sidecar.h b/src/server/game/TC9Sidecar/TC9Sidecar.h index 417c88914..599d78240 100644 --- a/src/server/game/TC9Sidecar/TC9Sidecar.h +++ b/src/server/game/TC9Sidecar/TC9Sidecar.h @@ -34,6 +34,11 @@ private: public: static ToCloud9Sidecar* instance(); + /// When Cluster.Enabled, validate libsidecar (real library required; ABI match). + /// Call after config and logging are ready, before DB/network startup. + /// @return false if worldserver should exit with code 1. + bool CheckLibsidecarAbi(); + void Init(uint16 port, int realmId); void Deinit();