From 9dd15ef9222d00ade7b1cb19f381bfb2db6a0137 Mon Sep 17 00:00:00 2001 From: kittnz Date: Mon, 23 Feb 2026 16:30:49 +0100 Subject: [PATCH] Make this compatible to build with MSVS 2022 --- CMakeLists.txt | 98 ++++++++++++++++++++++++++++++------ src/auth/crypto.cpp | 4 +- src/core/window.cpp | 31 ++++++++++++ src/game/warden_memory.cpp | 4 +- src/game/warden_module.cpp | 64 ++++++++++++++--------- src/game/world_packets.cpp | 14 +++++- src/network/tcp_socket.cpp | 10 ++-- src/network/world_socket.cpp | 10 ++-- vcpkg.json | 15 ++++++ 9 files changed, 201 insertions(+), 49 deletions(-) create mode 100644 vcpkg.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 049d58a8..ff956973 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # Options option(BUILD_SHARED_LIBS "Build shared libraries" OFF) option(WOWEE_BUILD_TESTS "Build tests" OFF) +option(WOWEE_ENABLE_ASAN "Enable AddressSanitizer (Debug builds)" OFF) # Opcode registry generation/validation find_package(Python3 COMPONENTS Interpreter QUIET) @@ -50,7 +51,7 @@ else() find_package(PkgConfig REQUIRED) endif() if(PkgConfig_FOUND) - pkg_check_modules(FFMPEG REQUIRED libavformat libavcodec libswscale libavutil) + pkg_check_modules(FFMPEG libavformat libavcodec libswscale libavutil) else() # Fallback for MSVC/vcpkg — find FFmpeg libraries manually find_path(FFMPEG_INCLUDE_DIRS libavformat/avformat.h) @@ -59,9 +60,15 @@ else() find_library(AVUTIL_LIB NAMES avutil) find_library(SWSCALE_LIB NAMES swscale) set(FFMPEG_LIBRARIES ${AVFORMAT_LIB} ${AVCODEC_LIB} ${AVUTIL_LIB} ${SWSCALE_LIB}) - if(NOT AVFORMAT_LIB) - message(FATAL_ERROR "FFmpeg not found. On Windows install via MSYS2: mingw-w64-x86_64-ffmpeg") - endif() +endif() +if(FFMPEG_INCLUDE_DIRS AND AVFORMAT_LIB) + set(HAVE_FFMPEG TRUE) + message(STATUS "Found FFmpeg: ${AVFORMAT_LIB}") +elseif(FFMPEG_FOUND) + set(HAVE_FFMPEG TRUE) +else() + set(HAVE_FFMPEG FALSE) + message(WARNING "FFmpeg not found — video_player will be disabled. Install via vcpkg: ffmpeg:x64-windows") endif() # Unicorn Engine (x86 emulator for cross-platform Warden module execution) @@ -83,6 +90,9 @@ if(NOT glm_FOUND) endif() # GLM GTX extensions (quaternion, norm, etc.) require this flag on newer GLM versions add_compile_definitions(GLM_ENABLE_EXPERIMENTAL GLM_FORCE_DEPTH_ZERO_TO_ONE) +if(WIN32) + add_compile_definitions(NOMINMAX _CRT_SECURE_NO_WARNINGS) +endif() # SPIR-V shader compilation via glslc find_program(GLSLC glslc HINTS ${Vulkan_GLSLC_EXECUTABLE} "$ENV{VULKAN_SDK}/bin") @@ -292,7 +302,7 @@ set(WOWEE_SOURCES src/rendering/levelup_effect.cpp src/rendering/charge_effect.cpp src/rendering/loading_screen.cpp - src/rendering/video_player.cpp + $<$:${CMAKE_CURRENT_SOURCE_DIR}/src/rendering/video_player.cpp> # UI src/ui/ui_manager.cpp @@ -438,8 +448,10 @@ target_include_directories(wowee PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/extern ${CMAKE_CURRENT_SOURCE_DIR}/extern/vk-bootstrap/src - ${FFMPEG_INCLUDE_DIRS} ) +if(HAVE_FFMPEG) + target_include_directories(wowee PRIVATE ${FFMPEG_INCLUDE_DIRS}) +endif() # Link libraries target_link_libraries(wowee PRIVATE @@ -460,9 +472,12 @@ if(TARGET GLEW::GLEW) target_link_libraries(wowee PRIVATE GLEW::GLEW) endif() -target_link_libraries(wowee PRIVATE ${FFMPEG_LIBRARIES}) -if (FFMPEG_LIBRARY_DIRS) - target_link_directories(wowee PRIVATE ${FFMPEG_LIBRARY_DIRS}) +if(HAVE_FFMPEG) + target_compile_definitions(wowee PRIVATE HAVE_FFMPEG) + target_link_libraries(wowee PRIVATE ${FFMPEG_LIBRARIES}) + if(FFMPEG_LIBRARY_DIRS) + target_link_directories(wowee PRIVATE ${FFMPEG_LIBRARY_DIRS}) + endif() endif() # Platform-specific libraries @@ -508,6 +523,47 @@ else() target_compile_options(wowee PRIVATE -Wall -Wextra -Wpedantic) endif() +# Debug build flags +if(MSVC) + # /ZI — Edit-and-Continue debug info (works with hot-reload in VS 2022) + # /RTC1 — stack-frame and uninitialised-variable runtime checks (Debug only) + # /sdl — additional SDL security checks + # /Od — disable optimisation so stepping matches source lines exactly + target_compile_options(wowee PRIVATE + $<$:/ZI /RTC1 /sdl /Od> + ) + # Ensure the linker emits a .pdb alongside the .exe for every config + target_link_options(wowee PRIVATE + $<$:/DEBUG:FULL> + $<$:/DEBUG:FASTLINK> + ) +else() + # -g3 — maximum DWARF debug info (includes macro definitions) + # -Og — optimise for debugging (better than -O0, keeps most frames) + # -fno-omit-frame-pointer — preserve frame pointers so stack traces are clean + target_compile_options(wowee PRIVATE + $<$:-g3 -Og -fno-omit-frame-pointer> + $<$:-g -fno-omit-frame-pointer> + ) +endif() + +# AddressSanitizer — catch buffer overflows, use-after-free, etc. +# Enable with: cmake ... -DWOWEE_ENABLE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug +if(WOWEE_ENABLE_ASAN) + if(MSVC) + target_compile_options(wowee PRIVATE /fsanitize=address) + # ASAN on MSVC requires the dynamic CRT (/MD or /MDd) + target_compile_options(wowee PRIVATE + $<$:/MDd> + $<$:/MD> + ) + else() + target_compile_options(wowee PRIVATE -fsanitize=address -fno-omit-frame-pointer) + target_link_options(wowee PRIVATE -fsanitize=address) + endif() + message(STATUS "AddressSanitizer: ENABLED") +endif() + # Release build optimizations include(CheckIPOSupported) check_ipo_supported(RESULT _ipo_supported OUTPUT _ipo_error) @@ -522,14 +578,27 @@ if(NOT MSVC) target_compile_options(wowee PRIVATE $<$:-fvisibility=hidden -fvisibility-inlines-hidden>) endif() -# Copy assets to build directory (runs every build, not just configure) -add_custom_target(copy_assets ALL +# Copy assets next to the executable (runs every build, not just configure). +# Uses $ so MSVC multi-config generators place assets +# in bin/Debug/ or bin/Release/ alongside the exe, not the common bin/ parent. +add_custom_command(TARGET wowee POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/assets - ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets - COMMENT "Syncing assets to build directory" + $/assets + COMMENT "Syncing assets to $/assets" ) -add_dependencies(wowee copy_assets) + +# On Windows, SDL 2.28+ uses LoadLibraryExW with LOAD_LIBRARY_SEARCH_DEFAULT_DIRS +# which does NOT include System32. Copy vulkan-1.dll into the output directory so +# SDL_Vulkan_LoadLibrary can locate it without needing a full system PATH search. +if(WIN32) + add_custom_command(TARGET wowee POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$ENV{SystemRoot}/System32/vulkan-1.dll" + "$/vulkan-1.dll" + COMMENT "Copying vulkan-1.dll to output directory" + ) +endif() # Install targets install(TARGETS wowee @@ -685,6 +754,7 @@ message(STATUS " Build Type: ${CMAKE_BUILD_TYPE}") message(STATUS " SDL2: ${SDL2_VERSION}") message(STATUS " OpenSSL: ${OPENSSL_VERSION}") message(STATUS " ImGui: ${IMGUI_DIR}") +message(STATUS " ASAN: ${WOWEE_ENABLE_ASAN}") message(STATUS "") # ---- CPack packaging ---- diff --git a/src/auth/crypto.cpp b/src/auth/crypto.cpp index 91690ecf..b4508393 100644 --- a/src/auth/crypto.cpp +++ b/src/auth/crypto.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace wowee { namespace auth { @@ -19,7 +20,8 @@ std::vector Crypto::sha1(const std::string& data) { std::vector Crypto::md5(const std::vector& data) { std::vector hash(MD5_DIGEST_LENGTH); - MD5(data.data(), data.size(), hash.data()); + unsigned int length = 0; + EVP_Digest(data.data(), data.size(), hash.data(), &length, EVP_md5(), nullptr); return hash; } diff --git a/src/core/window.cpp b/src/core/window.cpp index 658cd10b..f533689b 100644 --- a/src/core/window.cpp +++ b/src/core/window.cpp @@ -2,6 +2,9 @@ #include "core/logger.hpp" #include "rendering/vk_context.hpp" #include +#ifdef _WIN32 +#include +#endif namespace wowee { namespace core { @@ -29,6 +32,33 @@ bool Window::initialize() { return false; } + // Explicitly load the Vulkan library before creating the window. + // SDL_CreateWindow with SDL_WINDOW_VULKAN fails on some platforms/drivers + // if the Vulkan loader hasn't been located yet; calling this first gives a + // clear error and avoids the misleading "not configured in SDL" message. + // SDL 2.28+ uses LoadLibraryExW(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) which does + // not search System32, so fall back to the explicit path on Windows if needed. + bool vulkanLoaded = (SDL_Vulkan_LoadLibrary(nullptr) == 0); +#ifdef _WIN32 + if (!vulkanLoaded) { + const char* sysRoot = std::getenv("SystemRoot"); + if (sysRoot && *sysRoot) { + std::string fallbackPath = std::string(sysRoot) + "\\System32\\vulkan-1.dll"; + vulkanLoaded = (SDL_Vulkan_LoadLibrary(fallbackPath.c_str()) == 0); + if (vulkanLoaded) { + LOG_INFO("Loaded Vulkan library via explicit path: ", fallbackPath); + } + } + } +#endif + if (!vulkanLoaded) { + LOG_ERROR("Failed to load Vulkan library: ", SDL_GetError()); + LOG_ERROR("Ensure the Vulkan runtime (vulkan-1.dll) is installed. " + "Install the latest GPU drivers or the Vulkan Runtime from https://vulkan.lunarg.com/"); + SDL_Quit(); + return false; + } + // Create Vulkan window (no GL attributes needed) Uint32 flags = SDL_WINDOW_VULKAN | SDL_WINDOW_SHOWN; if (config.fullscreen) { @@ -74,6 +104,7 @@ void Window::shutdown() { window = nullptr; } + SDL_Vulkan_UnloadLibrary(); SDL_Quit(); LOG_INFO("Window shutdown complete"); } diff --git a/src/game/warden_memory.cpp b/src/game/warden_memory.cpp index 257b3d58..c5a0afd2 100644 --- a/src/game/warden_memory.cpp +++ b/src/game/warden_memory.cpp @@ -182,6 +182,7 @@ void WardenMemory::patchRuntimeGlobals() { // uint32 dwAllocationGranularity // uint16 wProcessorLevel // uint16 wProcessorRevision +#pragma pack(push, 1) struct { uint16_t wProcessorArchitecture; uint16_t wReserved; @@ -194,7 +195,7 @@ void WardenMemory::patchRuntimeGlobals() { uint32_t dwAllocationGranularity; uint16_t wProcessorLevel; uint16_t wProcessorRevision; - } __attribute__((packed)) sysInfo = { + } sysInfo = { 0, // x86 0, 4096, // 4K page size @@ -207,6 +208,7 @@ void WardenMemory::patchRuntimeGlobals() { 6, // P6 family 0x3A09 // revision }; +#pragma pack(pop) static_assert(sizeof(sysInfo) == 36, "SYSTEM_INFO must be 36 bytes"); uint32_t rva = sysInfoAddr - imageBase_; if (rva + 36 <= imageSize_) { diff --git a/src/game/warden_module.cpp b/src/game/warden_module.cpp index 262453a8..fba39960 100644 --- a/src/game/warden_module.cpp +++ b/src/game/warden_module.cpp @@ -8,6 +8,10 @@ #include #include #include +#include +#include +#include +#include #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -342,37 +346,47 @@ bool WardenModule::verifyRSASignature(const std::vector& data) { std::vector expectedHash = auth::Crypto::sha1(dataToHash); - // Create RSA public key structure - RSA* rsa = RSA_new(); - if (!rsa) { - std::cerr << "[WardenModule] Failed to create RSA structure" << '\n'; - return false; - } - + // Create RSA public key using EVP_PKEY_fromdata (OpenSSL 3.0 compatible) BIGNUM* n = BN_bin2bn(modulus, 256, nullptr); BIGNUM* e = BN_new(); BN_set_word(e, exponent); - #if OPENSSL_VERSION_NUMBER >= 0x10100000L - // OpenSSL 1.1.0+ - RSA_set0_key(rsa, n, e, nullptr); - #else - // OpenSSL 1.0.x - rsa->n = n; - rsa->e = e; - #endif - - // Decrypt signature using public key + EVP_PKEY* pkey = nullptr; + EVP_PKEY_CTX* ctx = nullptr; std::vector decryptedSig(256); - int decryptedLen = RSA_public_decrypt( - 256, - signature.data(), - decryptedSig.data(), - rsa, - RSA_NO_PADDING - ); + int decryptedLen = -1; - RSA_free(rsa); + { + OSSL_PARAM_BLD* bld = OSSL_PARAM_BLD_new(); + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, n); + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e); + OSSL_PARAM* params = OSSL_PARAM_BLD_to_param(bld); + OSSL_PARAM_BLD_free(bld); + + EVP_PKEY_CTX* fromCtx = EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr); + if (fromCtx && EVP_PKEY_fromdata_init(fromCtx) > 0) { + EVP_PKEY_fromdata(fromCtx, &pkey, EVP_PKEY_PUBLIC_KEY, params); + } + if (fromCtx) EVP_PKEY_CTX_free(fromCtx); + OSSL_PARAM_free(params); + + if (pkey) { + ctx = EVP_PKEY_CTX_new(pkey, nullptr); + if (ctx && EVP_PKEY_verify_recover_init(ctx) > 0 && + EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) > 0) { + size_t outLen = decryptedSig.size(); + if (EVP_PKEY_verify_recover(ctx, decryptedSig.data(), &outLen, + signature.data(), 256) > 0) { + decryptedLen = static_cast(outLen); + } + } + } + } + + BN_free(n); + BN_free(e); + if (ctx) EVP_PKEY_CTX_free(ctx); + if (pkey) EVP_PKEY_free(pkey); if (decryptedLen < 0) { std::cerr << "[WardenModule] RSA public decrypt failed" << '\n'; diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index e044d78a..5d2581b0 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -11,6 +11,16 @@ #include #include +namespace { + inline uint32_t bswap32(uint32_t v) { + return ((v & 0xFF000000u) >> 24) | ((v & 0x00FF0000u) >> 8) + | ((v & 0x0000FF00u) << 8) | ((v & 0x000000FFu) << 24); + } + inline uint16_t bswap16(uint16_t v) { + return static_cast(((v & 0xFF00u) >> 8) | ((v & 0x00FFu) << 8)); + } +} + namespace wowee { namespace game { @@ -3744,10 +3754,10 @@ bool TalentsInfoParser::parse(network::Packet& packet, TalentsInfoData& data) { // These two counts are big-endian (network byte order) uint32_t talentCountBE = packet.readUInt32(); - uint32_t talentCount = __builtin_bswap32(talentCountBE); + uint32_t talentCount = bswap32(talentCountBE); uint16_t entryCountBE = packet.readUInt16(); - uint16_t entryCount = __builtin_bswap16(entryCountBE); + uint16_t entryCount = bswap16(entryCountBE); // Sanity check: prevent corrupt packets from allocating excessive memory if (entryCount > 64) { diff --git a/src/network/tcp_socket.cpp b/src/network/tcp_socket.cpp index a83ef190..38a1cf6a 100644 --- a/src/network/tcp_socket.cpp +++ b/src/network/tcp_socket.cpp @@ -28,8 +28,11 @@ bool TCPSocket::connect(const std::string& host, uint16_t port) { net::setNonBlocking(sockfd); // Resolve host - struct hostent* server = gethostbyname(host.c_str()); - if (server == nullptr) { + struct addrinfo hints{}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + struct addrinfo* res = nullptr; + if (getaddrinfo(host.c_str(), nullptr, &hints, &res) != 0 || res == nullptr) { LOG_ERROR("Failed to resolve host: ", host); net::closeSocket(sockfd); sockfd = INVALID_SOCK; @@ -40,8 +43,9 @@ bool TCPSocket::connect(const std::string& host, uint16_t port) { struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; - memcpy(&serverAddr.sin_addr.s_addr, server->h_addr, server->h_length); + serverAddr.sin_addr = reinterpret_cast(res->ai_addr)->sin_addr; serverAddr.sin_port = htons(port); + freeaddrinfo(res); int result = ::connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); if (result < 0) { diff --git a/src/network/world_socket.cpp b/src/network/world_socket.cpp index 02698146..ab29a271 100644 --- a/src/network/world_socket.cpp +++ b/src/network/world_socket.cpp @@ -100,8 +100,11 @@ bool WorldSocket::connect(const std::string& host, uint16_t port) { net::setNonBlocking(sockfd); // Resolve host - struct hostent* server = gethostbyname(host.c_str()); - if (server == nullptr) { + struct addrinfo hints{}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + struct addrinfo* res = nullptr; + if (getaddrinfo(host.c_str(), nullptr, &hints, &res) != 0 || res == nullptr) { LOG_ERROR("Failed to resolve host: ", host); net::closeSocket(sockfd); sockfd = INVALID_SOCK; @@ -112,8 +115,9 @@ bool WorldSocket::connect(const std::string& host, uint16_t port) { struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; - memcpy(&serverAddr.sin_addr.s_addr, server->h_addr, server->h_length); + serverAddr.sin_addr = reinterpret_cast(res->ai_addr)->sin_addr; serverAddr.sin_port = htons(port); + freeaddrinfo(res); int result = ::connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); if (result < 0) { diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 00000000..60b8639e --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,15 @@ +{ + "name": "wowee", + "version": "1.0.0", + "dependencies": [ + { + "name": "sdl2", + "features": [ "vulkan" ] + }, + "openssl", + "glew", + "glm", + "zlib", + "ffmpeg" + ] +}