From 6a3594675b2fe694a7f5c040eba134d4a70ba62f Mon Sep 17 00:00:00 2001 From: ApfelTeeSaft <91074565+ApfelTeeSaft@users.noreply.github.com> Date: Sun, 1 Feb 2026 11:33:54 +0100 Subject: [PATCH] first commit --- CMakeLists.txt | 233 ++++ Core/Common.h | 111 ++ Core/Hooks/HookTypes.h | 376 ++++++ Core/Logging/Log.cpp | 167 +++ Core/Logging/Log.h | 58 + Core/Memory/Memory.cpp | 171 +++ Core/Memory/Memory.h | 114 ++ Core/Memory/PatternScanner.h | 1 + Core/Versioning/VersionInfo.h | 111 ++ Core/Versioning/VersionResolver.cpp | 573 +++++++++ Core/Versioning/VersionResolver.h | 82 ++ Engine/CoreTypes/NamePool.cpp | 330 ++++++ Engine/CoreTypes/NamePool.h | 168 +++ Engine/CoreTypes/ObjectArray.cpp | 286 +++++ Engine/CoreTypes/ObjectArray.h | 150 +++ Engine/CoreTypes/OffsetResolver.cpp | 319 +++++ Engine/CoreTypes/OffsetResolver.h | 246 ++++ Engine/EngineCore.cpp | 465 ++++++++ Engine/EngineCore.h | 138 +++ Engine/Events/ProcessEventDispatcher.cpp | 492 ++++++++ Engine/Events/ProcessEventDispatcher.h | 403 +++++++ Engine/Reflection/PropertyIterator.cpp | 620 ++++++++++ Engine/Reflection/PropertyIterator.h | 374 ++++++ Engine/Replication/FastArraySerializer.cpp | 566 +++++++++ Engine/Replication/FastArraySerializer.h | 329 ++++++ Engine/UObject/UObjectWrapper.cpp | 548 +++++++++ Engine/UObject/UObjectWrapper.h | 193 ++++ Entry/DllMain.cpp | 336 ++++++ README.md | 3 + STW/Building/BuildingManager.cpp | 830 +++++++++++++ STW/Building/BuildingManager.h | 322 ++++++ STW/Building/BuildingTypes.h | 382 ++++++ STW/GameMode/STWGameMode.cpp | 374 ++++++ STW/GameMode/STWGameMode.h | 192 +++ STW/Inventory/InventoryManager.cpp | 639 ++++++++++ STW/Inventory/InventoryManager.h | 137 +++ STW/Inventory/InventoryTypes.h | 273 +++++ STW/Missions/MissionManager.cpp | 425 +++++++ STW/Missions/MissionManager.h | 99 ++ STW/Missions/MissionObjective.cpp | 321 ++++++ STW/Missions/MissionObjective.h | 150 +++ STW/Missions/MissionTypes.h | 316 +++++ STW/Player/STWPlayerController.cpp | 397 +++++++ STW/Player/STWPlayerController.h | 190 +++ STW/Player/STWPlayerPawn.cpp | 400 +++++++ STW/Player/STWPlayerPawn.h | 209 ++++ UniversalSlashingSimulator.sln | 24 + UniversalSlashingSimulator.vcxproj | 168 +++ UniversalSlashingSimulator.vcxproj.filters | 212 ++++ UniversalSlashingSimulator.vcxproj.user | 4 + external/memcury/memcury.h | 1217 ++++++++++++++++++++ external/minhook/include/MinHook.h | 185 +++ external/minhook/lib/libMinHook.x64.lib | Bin 0 -> 548944 bytes external/minhook/lib/libMinHook.x86.lib | Bin 0 -> 528266 bytes 54 files changed, 15429 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 Core/Common.h create mode 100644 Core/Hooks/HookTypes.h create mode 100644 Core/Logging/Log.cpp create mode 100644 Core/Logging/Log.h create mode 100644 Core/Memory/Memory.cpp create mode 100644 Core/Memory/Memory.h create mode 100644 Core/Memory/PatternScanner.h create mode 100644 Core/Versioning/VersionInfo.h create mode 100644 Core/Versioning/VersionResolver.cpp create mode 100644 Core/Versioning/VersionResolver.h create mode 100644 Engine/CoreTypes/NamePool.cpp create mode 100644 Engine/CoreTypes/NamePool.h create mode 100644 Engine/CoreTypes/ObjectArray.cpp create mode 100644 Engine/CoreTypes/ObjectArray.h create mode 100644 Engine/CoreTypes/OffsetResolver.cpp create mode 100644 Engine/CoreTypes/OffsetResolver.h create mode 100644 Engine/EngineCore.cpp create mode 100644 Engine/EngineCore.h create mode 100644 Engine/Events/ProcessEventDispatcher.cpp create mode 100644 Engine/Events/ProcessEventDispatcher.h create mode 100644 Engine/Reflection/PropertyIterator.cpp create mode 100644 Engine/Reflection/PropertyIterator.h create mode 100644 Engine/Replication/FastArraySerializer.cpp create mode 100644 Engine/Replication/FastArraySerializer.h create mode 100644 Engine/UObject/UObjectWrapper.cpp create mode 100644 Engine/UObject/UObjectWrapper.h create mode 100644 Entry/DllMain.cpp create mode 100644 README.md create mode 100644 STW/Building/BuildingManager.cpp create mode 100644 STW/Building/BuildingManager.h create mode 100644 STW/Building/BuildingTypes.h create mode 100644 STW/GameMode/STWGameMode.cpp create mode 100644 STW/GameMode/STWGameMode.h create mode 100644 STW/Inventory/InventoryManager.cpp create mode 100644 STW/Inventory/InventoryManager.h create mode 100644 STW/Inventory/InventoryTypes.h create mode 100644 STW/Missions/MissionManager.cpp create mode 100644 STW/Missions/MissionManager.h create mode 100644 STW/Missions/MissionObjective.cpp create mode 100644 STW/Missions/MissionObjective.h create mode 100644 STW/Missions/MissionTypes.h create mode 100644 STW/Player/STWPlayerController.cpp create mode 100644 STW/Player/STWPlayerController.h create mode 100644 STW/Player/STWPlayerPawn.cpp create mode 100644 STW/Player/STWPlayerPawn.h create mode 100644 UniversalSlashingSimulator.sln create mode 100644 UniversalSlashingSimulator.vcxproj create mode 100644 UniversalSlashingSimulator.vcxproj.filters create mode 100644 UniversalSlashingSimulator.vcxproj.user create mode 100644 external/memcury/memcury.h create mode 100644 external/minhook/include/MinHook.h create mode 100644 external/minhook/lib/libMinHook.x64.lib create mode 100644 external/minhook/lib/libMinHook.x86.lib diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b57472d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,233 @@ +# UniversalSlashingSimulator - CMake Build Configuration +# +# This project builds a DLL that can be injected into Fortnite +# to provide STW (Save the World) gameserver functionality. +# +# Requirements: +# - Windows SDK +# - MinHook library (user provides) +# - Memcury library (user provides) +# - C++17 compiler + +cmake_minimum_required(VERSION 3.16) + +project(UniversalSlashingSimulator + VERSION 0.1.0 + DESCRIPTION "Version-agnostic STW Gameserver Framework" + LANGUAGES CXX +) + +# C++ Standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Windows-specific settings +if(WIN32) + add_definitions(-DWIN32_LEAN_AND_MEAN -DNOMINMAX) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF) +endif() + +# Build type +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# Debug definitions +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_definitions(-DUSS_DEBUG) +endif() + +# ============================================================================ +# Source Files +# ============================================================================ + +# Core sources +set(CORE_SOURCES + Core/Logging/Log.cpp + Core/Memory/Memory.cpp + Core/Versioning/VersionResolver.cpp +) + +set(CORE_HEADERS + Core/Common.h + Core/Logging/Log.h + Core/Memory/Memory.h + Core/Versioning/VersionInfo.h + Core/Versioning/VersionResolver.h + Core/Hooks/HookTypes.h +) + +# Engine sources +set(ENGINE_SOURCES + Engine/CoreTypes/ObjectArray.cpp + Engine/CoreTypes/NamePool.cpp + Engine/CoreTypes/OffsetResolver.cpp + Engine/UObject/UObjectWrapper.cpp + Engine/Reflection/PropertyIterator.cpp + Engine/Replication/FastArraySerializer.cpp + Engine/Events/ProcessEventDispatcher.cpp + Engine/EngineCore.cpp +) + +set(ENGINE_HEADERS + Engine/CoreTypes/ObjectArray.h + Engine/CoreTypes/NamePool.h + Engine/CoreTypes/OffsetResolver.h + Engine/UObject/UObjectWrapper.h + Engine/Reflection/PropertyIterator.h + Engine/Replication/FastArraySerializer.h + Engine/Events/ProcessEventDispatcher.h + Engine/EngineCore.h +) + +# STW sources +set(STW_SOURCES + STW/GameMode/STWGameMode.cpp + STW/Missions/MissionManager.cpp + STW/Missions/MissionObjective.cpp + STW/Player/STWPlayerController.cpp + STW/Player/STWPlayerPawn.cpp + STW/Inventory/InventoryManager.cpp + STW/Building/BuildingManager.cpp +) + +set(STW_HEADERS + STW/GameMode/STWGameMode.h + STW/Missions/MissionManager.h + STW/Missions/MissionObjective.h + STW/Missions/MissionTypes.h + STW/Player/STWPlayerController.h + STW/Player/STWPlayerPawn.h + STW/Inventory/InventoryManager.h + STW/Inventory/InventoryTypes.h + STW/Building/BuildingManager.h + STW/Building/BuildingTypes.h +) + +# Entry point +set(ENTRY_SOURCES + Entry/DllMain.cpp +) + +# ============================================================================ +# Library Target +# ============================================================================ + +add_library(${PROJECT_NAME} SHARED + ${CORE_SOURCES} + ${CORE_HEADERS} + ${ENGINE_SOURCES} + ${ENGINE_HEADERS} + ${STW_SOURCES} + ${STW_HEADERS} + ${ENTRY_SOURCES} +) + +# Include directories +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +# ============================================================================ +# External Dependencies (User Provides) +# ============================================================================ + +# MinHook - User must provide +# Add to: external/minhook/include and external/minhook/lib +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/minhook") + target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/external/minhook/include + ) + find_library(MINHOOK_LIB + NAMES MinHook libMinHook.x64 + PATHS ${CMAKE_CURRENT_SOURCE_DIR}/external/minhook/lib + ) + if(MINHOOK_LIB) + target_link_libraries(${PROJECT_NAME} PRIVATE ${MINHOOK_LIB}) + message(STATUS "Found MinHook: ${MINHOOK_LIB}") + endif() +endif() + +# Memcury - User must provide (header-only typically) +# Add to: external/memcury/include +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/memcury") + target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/external/memcury/include + ) + message(STATUS "Found Memcury: ${CMAKE_CURRENT_SOURCE_DIR}/external/memcury") +endif() + +# Windows libraries +target_link_libraries(${PROJECT_NAME} PRIVATE + psapi +) + +# ============================================================================ +# Compiler Settings +# ============================================================================ + +if(MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE + /W4 # Warning level 4 + /WX- # Warnings not as errors (for development) + /MP # Multi-processor compilation + /permissive- # Strict conformance + /Zc:__cplusplus # Report correct C++ version + ) + + # Release optimizations + if(CMAKE_BUILD_TYPE STREQUAL "Release") + target_compile_options(${PROJECT_NAME} PRIVATE + /O2 + /Ob2 + /GL + ) + target_link_options(${PROJECT_NAME} PRIVATE + /LTCG + ) + endif() +else() + target_compile_options(${PROJECT_NAME} PRIVATE + -Wall + -Wextra + -Wpedantic + ) +endif() + +# ============================================================================ +# Output Settings +# ============================================================================ + +set_target_properties(${PROJECT_NAME} PROPERTIES + OUTPUT_NAME "USS" + SUFFIX ".dll" + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib +) + +# ============================================================================ +# Installation +# ============================================================================ + +install(TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) + +# ============================================================================ +# Summary +# ============================================================================ + +message(STATUS "") +message(STATUS "UniversalSlashingSimulator Configuration:") +message(STATUS " Version: ${PROJECT_VERSION}") +message(STATUS " Build Type: ${CMAKE_BUILD_TYPE}") +message(STATUS " C++ Standard: ${CMAKE_CXX_STANDARD}") +message(STATUS " Output: ${CMAKE_BINARY_DIR}/bin/USS.dll") +message(STATUS "") +message(STATUS "External Dependencies:") +message(STATUS " MinHook: ${MINHOOK_LIB}") +message(STATUS " Memcury: (header-only, check external/memcury)") +message(STATUS "") diff --git a/Core/Common.h b/Core/Common.h new file mode 100644 index 0000000..67d7943 --- /dev/null +++ b/Core/Common.h @@ -0,0 +1,111 @@ +/** + * UniversalSlashingSimulator - Common Types and Definitions + * + * This file contains common type definitions, macros, and includes + * used throughout the project. All platform-specific definitions + * are centralized here. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +namespace USS +{ + using int8 = int8_t; + using int16 = int16_t; + using int32 = int32_t; + using int64 = int64_t; + using uint8 = uint8_t; + using uint16 = uint16_t; + using uint32 = uint32_t; + using uint64 = uint64_t; + + using uintptr = uintptr_t; + using intptr = intptr_t; + + enum class EResult : uint8 + { + Success = 0, + Failed, + NotSupported, + InvalidVersion, + PatternNotFound, + HookFailed, + AlreadyInitialized, + NotInitialized, + + InvalidState, + InvalidParameter, + + InsufficientResources, + InventoryFull, + ItemNotFound, + + BuildingNotFound, + BuildLimitReached, + InvalidPlacement, + + TrapNotFound, + TrapNotReady + }; + + inline const char* ResultToString(EResult Result) + { + switch (Result) + { + case EResult::Success: return "Success"; + case EResult::Failed: return "Failed"; + case EResult::NotSupported: return "NotSupported"; + case EResult::InvalidVersion: return "InvalidVersion"; + case EResult::PatternNotFound: return "PatternNotFound"; + case EResult::HookFailed: return "HookFailed"; + case EResult::AlreadyInitialized: return "AlreadyInitialized"; + case EResult::NotInitialized: return "NotInitialized"; + case EResult::InvalidState: return "InvalidState"; + case EResult::InvalidParameter: return "InvalidParameter"; + case EResult::InsufficientResources:return "InsufficientResources"; + case EResult::InventoryFull: return "InventoryFull"; + case EResult::ItemNotFound: return "ItemNotFound"; + case EResult::BuildingNotFound: return "BuildingNotFound"; + case EResult::BuildLimitReached: return "BuildLimitReached"; + case EResult::InvalidPlacement: return "InvalidPlacement"; + case EResult::TrapNotFound: return "TrapNotFound"; + case EResult::TrapNotReady: return "TrapNotReady"; + default: return "Unknown"; + } + } + + #define USS_INTERFACE class + + #define USS_NON_COPYABLE(ClassName) \ + ClassName(const ClassName&) = delete; \ + ClassName& operator=(const ClassName&) = delete; + + #define USS_NON_MOVABLE(ClassName) \ + ClassName(ClassName&&) = delete; \ + ClassName& operator=(ClassName&&) = delete; + + #ifdef USS_DEBUG + #define USS_LOG(fmt, ...) ::USS::Log::Write(::USS::ELogLevel::Info, fmt, ##__VA_ARGS__) + #define USS_WARN(fmt, ...) ::USS::Log::Write(::USS::ELogLevel::Warning, fmt, ##__VA_ARGS__) + #define USS_ERROR(fmt, ...) ::USS::Log::Write(::USS::ELogLevel::Error, fmt, ##__VA_ARGS__) + #else + #define USS_LOG(fmt, ...) ((void)0) + #define USS_WARN(fmt, ...) ((void)0) + #define USS_ERROR(fmt, ...) ((void)0) + #endif + + #define USS_FATAL(fmt, ...) ::USS::Log::Write(::USS::ELogLevel::Fatal, fmt, ##__VA_ARGS__) + +} \ No newline at end of file diff --git a/Core/Hooks/HookTypes.h b/Core/Hooks/HookTypes.h new file mode 100644 index 0000000..617e8f0 --- /dev/null +++ b/Core/Hooks/HookTypes.h @@ -0,0 +1,376 @@ +/** + * UniversalSlashingSimulator - Hook Types + * + * Hooking system using MinHook for function detouring. + * Requires MinHook library to be linked. + */ + +#pragma once + +#include "../Common.h" +#include "../Logging/Log.h" +#include +#include +#include +#include + +namespace USS +{ + using ProcessEventFn = void(*)(void* Object, void* Function, void* Params); + + namespace Hook + { + // Internal state + namespace Detail + { + inline bool g_bInitialized = false; + inline std::mutex g_Mutex; + } + + /** + * Initialize the hook library (MinHook) + * Must be called before any other Hook functions + */ + inline EResult Initialize() + { + std::lock_guard Lock(Detail::g_Mutex); + + if (Detail::g_bInitialized) + return EResult::AlreadyInitialized; + + MH_STATUS Status = MH_Initialize(); + if (Status != MH_OK) + { + USS_ERROR("MinHook initialization failed: %s", MH_StatusToString(Status)); + return EResult::Failed; + } + + Detail::g_bInitialized = true; + USS_LOG("MinHook initialized successfully"); + return EResult::Success; + } + + /** + * Shutdown the hook library + * Removes all hooks and cleans up + */ + inline void Shutdown() + { + std::lock_guard Lock(Detail::g_Mutex); + + if (!Detail::g_bInitialized) + return; + + MH_DisableHook(MH_ALL_HOOKS); + + MH_STATUS Status = MH_Uninitialize(); + if (Status != MH_OK) + { + USS_WARN("MinHook shutdown warning: %s", MH_StatusToString(Status)); + } + else + { + USS_LOG("MinHook shutdown successfully"); + } + + Detail::g_bInitialized = false; + } + + /** + * Check if hook system is initialized + */ + inline bool IsInitialized() + { + return Detail::g_bInitialized; + } + + /** + * Create and enable a hook in one call + * @param Target - Address of function to hook + * @param Detour - Your detour function + * @param OutOriginal - Receives pointer to original function (trampoline) + * @return EResult::Success on success + */ + template + inline EResult CreateAndEnable(uintptr Target, T Detour, T* OutOriginal) + { + if (!Detail::g_bInitialized) + { + USS_ERROR("Hook::CreateAndEnable called before Initialize"); + return EResult::NotInitialized; + } + + if (Target == 0 || Detour == nullptr) + { + USS_ERROR("Hook::CreateAndEnable - invalid parameters"); + return EResult::InvalidParameter; + } + + void* pTarget = reinterpret_cast(Target); + void* pDetour = reinterpret_cast(Detour); + void** ppOriginal = reinterpret_cast(OutOriginal); + + MH_STATUS Status = MH_CreateHook(pTarget, pDetour, ppOriginal); + if (Status != MH_OK) + { + USS_ERROR("MH_CreateHook failed at 0x%llX: %s", Target, MH_StatusToString(Status)); + return EResult::HookFailed; + } + + Status = MH_EnableHook(pTarget); + if (Status != MH_OK) + { + USS_ERROR("MH_EnableHook failed at 0x%llX: %s", Target, MH_StatusToString(Status)); + MH_RemoveHook(pTarget); + return EResult::HookFailed; + } + + USS_LOG("Hook created and enabled at 0x%llX", Target); + return EResult::Success; + } + + /** + * Create a hook without enabling it + */ + template + inline EResult Create(uintptr Target, T Detour, T* OutOriginal) + { + if (!Detail::g_bInitialized) + return EResult::NotInitialized; + + void* pTarget = reinterpret_cast(Target); + void* pDetour = reinterpret_cast(Detour); + void** ppOriginal = reinterpret_cast(OutOriginal); + + MH_STATUS Status = MH_CreateHook(pTarget, pDetour, ppOriginal); + if (Status != MH_OK) + { + USS_ERROR("MH_CreateHook failed at 0x%llX: %s", Target, MH_StatusToString(Status)); + return EResult::HookFailed; + } + + USS_LOG("Hook created (disabled) at 0x%llX", Target); + return EResult::Success; + } + + /** + * Enable a previously created hook + */ + inline EResult Enable(uintptr Target) + { + if (!Detail::g_bInitialized) + return EResult::NotInitialized; + + void* pTarget = reinterpret_cast(Target); + MH_STATUS Status = MH_EnableHook(pTarget); + + if (Status != MH_OK) + { + USS_ERROR("MH_EnableHook failed at 0x%llX: %s", Target, MH_StatusToString(Status)); + return EResult::HookFailed; + } + + return EResult::Success; + } + + /** + * Disable a hook (can be re-enabled later) + */ + inline EResult Disable(uintptr Target) + { + if (!Detail::g_bInitialized) + return EResult::NotInitialized; + + void* pTarget = reinterpret_cast(Target); + MH_STATUS Status = MH_DisableHook(pTarget); + + if (Status != MH_OK) + { + USS_ERROR("MH_DisableHook failed at 0x%llX: %s", Target, MH_StatusToString(Status)); + return EResult::HookFailed; + } + + return EResult::Success; + } + + /** + * Remove a hook completely + */ + inline EResult Remove(uintptr Target) + { + if (!Detail::g_bInitialized) + return EResult::NotInitialized; + + void* pTarget = reinterpret_cast(Target); + + MH_DisableHook(pTarget); + + MH_STATUS Status = MH_RemoveHook(pTarget); + if (Status != MH_OK) + { + USS_ERROR("MH_RemoveHook failed at 0x%llX: %s", Target, MH_StatusToString(Status)); + return EResult::HookFailed; + } + + USS_LOG("Hook removed at 0x%llX", Target); + return EResult::Success; + } + + /** + * Enable all hooks + */ + inline EResult EnableAll() + { + if (!Detail::g_bInitialized) + return EResult::NotInitialized; + + MH_STATUS Status = MH_EnableHook(MH_ALL_HOOKS); + return (Status == MH_OK) ? EResult::Success : EResult::HookFailed; + } + + /** + * Disable all hooks + */ + inline EResult DisableAll() + { + if (!Detail::g_bInitialized) + return EResult::NotInitialized; + + MH_STATUS Status = MH_DisableHook(MH_ALL_HOOKS); + return (Status == MH_OK) ? EResult::Success : EResult::HookFailed; + } + + /** + * Queue a hook to be enabled (for batch enabling) + */ + inline EResult QueueEnable(uintptr Target) + { + if (!Detail::g_bInitialized) + return EResult::NotInitialized; + + void* pTarget = reinterpret_cast(Target); + MH_STATUS Status = MH_QueueEnableHook(pTarget); + return (Status == MH_OK) ? EResult::Success : EResult::HookFailed; + } + + /** + * Queue a hook to be disabled (for batch disabling) + */ + inline EResult QueueDisable(uintptr Target) + { + if (!Detail::g_bInitialized) + return EResult::NotInitialized; + + void* pTarget = reinterpret_cast(Target); + MH_STATUS Status = MH_QueueDisableHook(pTarget); + return (Status == MH_OK) ? EResult::Success : EResult::HookFailed; + } + + /** + * Apply all queued enable/disable operations + */ + inline EResult ApplyQueued() + { + if (!Detail::g_bInitialized) + return EResult::NotInitialized; + + MH_STATUS Status = MH_ApplyQueued(); + return (Status == MH_OK) ? EResult::Success : EResult::HookFailed; + } + } + + // ======================================================================== + // ProcessEvent Dispatcher + // ======================================================================== + + /** + * Simple ProcessEvent hook manager + * Manages callbacks for ProcessEvent interception + */ + class FSimpleProcessEventDispatcher + { + public: + using PreCallback = std::function; + using PostCallback = std::function; + + static FSimpleProcessEventDispatcher& Get() + { + static FSimpleProcessEventDispatcher Instance; + return Instance; + } + + // Register a pre-ProcessEvent callback + // Return false from callback to block the original function + void RegisterPre(PreCallback Callback) + { + std::lock_guard Lock(m_Mutex); + m_PreCallbacks.push_back(std::move(Callback)); + } + + // Register a post-ProcessEvent callback + void RegisterPost(PostCallback Callback) + { + std::lock_guard Lock(m_Mutex); + m_PostCallbacks.push_back(std::move(Callback)); + } + + // Called from the hook detour - dispatch to pre-callbacks + bool DispatchPre(void* Object, void* Function, void* Params) + { + bool bCallOriginal = true; + + std::vector Callbacks; + { + std::lock_guard Lock(m_Mutex); + Callbacks = m_PreCallbacks; + } + + for (const auto& Cb : Callbacks) + { + if (!Cb(Object, Function, Params)) + bCallOriginal = false; + } + return bCallOriginal; + } + + // Called from the hook detour - dispatch to post-callbacks + void DispatchPost(void* Object, void* Function, void* Params) + { + std::vector Callbacks; + { + std::lock_guard Lock(m_Mutex); + Callbacks = m_PostCallbacks; + } + + for (const auto& Cb : Callbacks) + { + Cb(Object, Function, Params); + } + } + + // Set/get original function pointer + void SetOriginal(ProcessEventFn Original) { m_pOriginal = Original; } + ProcessEventFn GetOriginal() const { return m_pOriginal; } + + void ClearCallbacks() + { + std::lock_guard Lock(m_Mutex); + m_PreCallbacks.clear(); + m_PostCallbacks.clear(); + } + + private: + FSimpleProcessEventDispatcher() : m_pOriginal(nullptr) {} + + std::mutex m_Mutex; + std::vector m_PreCallbacks; + std::vector m_PostCallbacks; + ProcessEventFn m_pOriginal; + }; + + inline FSimpleProcessEventDispatcher& GetSimpleProcessEventDispatcher() + { + return FSimpleProcessEventDispatcher::Get(); + } + +} \ No newline at end of file diff --git a/Core/Logging/Log.cpp b/Core/Logging/Log.cpp new file mode 100644 index 0000000..5bf9c25 --- /dev/null +++ b/Core/Logging/Log.cpp @@ -0,0 +1,167 @@ +/** + * UniversalSlashingSimulator - Logging System Implementation + */ + +#include "Log.h" +#include +#include + +namespace USS +{ + std::mutex Log::s_Mutex; + std::ofstream Log::s_FileStream; + ELogLevel Log::s_MinLevel = ELogLevel::Info; + bool Log::s_bConsoleEnabled = false; + bool Log::s_bFileEnabled = false; + bool Log::s_bInitialized = false; + + EResult Log::Initialize(bool bEnableConsole, const char* LogFilePath) + { + std::lock_guard Lock(s_Mutex); + + if (s_bInitialized) + return EResult::AlreadyInitialized; + + if (bEnableConsole) + { + if (AllocConsole()) + { + FILE* pFile = nullptr; + freopen_s(&pFile, "CONOUT$", "w", stdout); + freopen_s(&pFile, "CONOUT$", "w", stderr); + s_bConsoleEnabled = true; + } + } + + if (LogFilePath != nullptr) + { + s_FileStream.open(LogFilePath, std::ios::out | std::ios::trunc); + if (s_FileStream.is_open()) + { + s_bFileEnabled = true; + } + } + + s_bInitialized = true; + return EResult::Success; + } + + void Log::Shutdown() + { + std::lock_guard Lock(s_Mutex); + + if (!s_bInitialized) + return; + + if (s_bFileEnabled && s_FileStream.is_open()) + { + s_FileStream.close(); + } + + if (s_bConsoleEnabled) + { + FreeConsole(); + } + + s_bConsoleEnabled = false; + s_bFileEnabled = false; + s_bInitialized = false; + } + + void Log::Write(ELogLevel Level, const char* Format, ...) + { + va_list Args; + va_start(Args, Format); + WriteV(Level, Format, Args); + va_end(Args); + } + + void Log::WriteV(ELogLevel Level, const char* Format, va_list Args) + { + if (Level < s_MinLevel) + return; + + char Buffer[4096]; + vsnprintf(Buffer, sizeof(Buffer), Format, Args); + Buffer[sizeof(Buffer) - 1] = '\0'; + + WriteInternal(Level, Buffer); + } + + void Log::SetMinLevel(ELogLevel Level) + { + std::lock_guard Lock(s_Mutex); + s_MinLevel = Level; + } + + const char* Log::GetLevelName(ELogLevel Level) + { + switch (Level) + { + case ELogLevel::Trace: return "TRACE"; + case ELogLevel::Debug: return "DEBUG"; + case ELogLevel::Info: return "INFO"; + case ELogLevel::Warning: return "WARN"; + case ELogLevel::Error: return "ERROR"; + case ELogLevel::Fatal: return "FATAL"; + default: return "UNKNOWN"; + } + } + + void Log::WriteInternal(ELogLevel Level, const char* Message) + { + std::lock_guard Lock(s_Mutex); + + if (!s_bInitialized) + return; + + time_t Now = time(nullptr); + struct tm TimeInfo; + localtime_s(&TimeInfo, &Now); + + char Timestamp[32]; + strftime(Timestamp, sizeof(Timestamp), "%H:%M:%S", &TimeInfo); + + // Format: [HH:MM:SS] [LEVEL] Message + char FormattedMessage[4200]; + snprintf(FormattedMessage, sizeof(FormattedMessage), + "[%s] [%s] %s\n", Timestamp, GetLevelName(Level), Message); + + if (s_bConsoleEnabled) + { + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + WORD Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + + switch (Level) + { + case ELogLevel::Trace: + case ELogLevel::Debug: + Color = FOREGROUND_INTENSITY; + break; + case ELogLevel::Info: + Color = FOREGROUND_GREEN | FOREGROUND_INTENSITY; + break; + case ELogLevel::Warning: + Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; + break; + case ELogLevel::Error: + case ELogLevel::Fatal: + Color = FOREGROUND_RED | FOREGROUND_INTENSITY; + break; + } + + SetConsoleTextAttribute(hConsole, Color); + printf("%s", FormattedMessage); + SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + } + + if (s_bFileEnabled && s_FileStream.is_open()) + { + s_FileStream << FormattedMessage; + s_FileStream.flush(); + } + + OutputDebugStringA(FormattedMessage); + } + +} \ No newline at end of file diff --git a/Core/Logging/Log.h b/Core/Logging/Log.h new file mode 100644 index 0000000..8786add --- /dev/null +++ b/Core/Logging/Log.h @@ -0,0 +1,58 @@ +/** + * UniversalSlashingSimulator - Logging System + * + * Provides thread-safe logging with multiple output targets. + * Supports console, file, and debug output logging. + */ + +#pragma once + +#include "../Common.h" +#include +#include +#include + +namespace USS +{ + enum class ELogLevel : uint8 + { + Trace = 0, + Debug, + Info, + Warning, + Error, + Fatal + }; + + class Log + { + public: + USS_NON_COPYABLE(Log) + USS_NON_MOVABLE(Log) + + static EResult Initialize(bool bEnableConsole = true, const char* LogFilePath = nullptr); + static void Shutdown(); + + static void Write(ELogLevel Level, const char* Format, ...); + + static void WriteV(ELogLevel Level, const char* Format, va_list Args); + + static void SetMinLevel(ELogLevel Level); + + static const char* GetLevelName(ELogLevel Level); + + private: + Log() = default; + ~Log() = default; + + static void WriteInternal(ELogLevel Level, const char* Message); + + static std::mutex s_Mutex; + static std::ofstream s_FileStream; + static ELogLevel s_MinLevel; + static bool s_bConsoleEnabled; + static bool s_bFileEnabled; + static bool s_bInitialized; + }; + +} \ No newline at end of file diff --git a/Core/Memory/Memory.cpp b/Core/Memory/Memory.cpp new file mode 100644 index 0000000..4daee78 --- /dev/null +++ b/Core/Memory/Memory.cpp @@ -0,0 +1,171 @@ +/** + * UniversalSlashingSimulator - Memory Utilities Implementation + */ + +#include "Memory.h" +#include "../Logging/Log.h" +#include +#include + +namespace USS +{ + FModuleInfo Memory::s_BaseModule = { 0, 0, nullptr }; + bool Memory::s_bInitialized = false; + + EResult Memory::Initialize() + { + if (s_bInitialized) + return EResult::AlreadyInitialized; + + HMODULE hModule = GetModuleHandle(nullptr); + if (!hModule) + { + USS_ERROR("Failed to get base module handle"); + return EResult::Failed; + } + + MODULEINFO ModInfo = {}; + if (!GetModuleInformation(GetCurrentProcess(), hModule, &ModInfo, sizeof(ModInfo))) + { + USS_ERROR("Failed to get module information"); + return EResult::Failed; + } + + s_BaseModule.BaseAddress = reinterpret_cast(ModInfo.lpBaseOfDll); + s_BaseModule.Size = ModInfo.SizeOfImage; + s_BaseModule.Name = "FortniteClient-Win64-Shipping.exe"; + + USS_LOG("Memory initialized - Base: 0x%llX, Size: 0x%llX", + s_BaseModule.BaseAddress, s_BaseModule.Size); + + s_bInitialized = true; + return EResult::Success; + } + + const FModuleInfo& Memory::GetBaseModule() + { + return s_BaseModule; + } + + bool Memory::MaskCompare(const uint8* Data, const char* Pattern, const char* Mask) + { + for (; *Mask; ++Mask, ++Data, ++Pattern) + { + if (*Mask == 'x' && *Data != static_cast(*Pattern)) + return false; + } + return true; + } + + FPatternResult Memory::FindPattern(const char* Pattern, const char* Mask) + { + if (!s_bInitialized) + return { false, 0 }; + + return FindPattern(s_BaseModule.BaseAddress, s_BaseModule.Size, Pattern, Mask); + } + + FPatternResult Memory::FindPattern(uintptr Start, size_t Size, const char* Pattern, const char* Mask) + { + FPatternResult Result = { false, 0 }; + + if (!Pattern || !Mask) + return Result; + + size_t MaskLength = strlen(Mask); + if (Size < MaskLength) + return Result; + + size_t SearchSize = Size - MaskLength; + + for (size_t i = 0; i < SearchSize; ++i) + { + const uint8* Address = reinterpret_cast(Start + i); + + if (MaskCompare(Address, Pattern, Mask)) + { + Result.bFound = true; + Result.Address = Start + i; + return Result; + } + } + + return Result; + } + + FPatternResult Memory::FindPatternIDA(const char* Signature) + { + FPatternResult Result = { false, 0 }; + + if (!Signature || !s_bInitialized) + return Result; + + std::vector Pattern; + std::vector Mask; + + std::istringstream Stream(Signature); + std::string Token; + + while (Stream >> Token) + { + if (Token == "?" || Token == "??") + { + Pattern.push_back(0x00); + Mask.push_back('?'); + } + else + { + try + { + int Value = std::stoi(Token, nullptr, 16); + Pattern.push_back(static_cast(Value)); + Mask.push_back('x'); + } + catch (...) + { + USS_ERROR("Invalid signature token: %s", Token.c_str()); + return Result; + } + } + } + + if (Pattern.empty()) + return Result; + + Pattern.push_back('\0'); + Mask.push_back('\0'); + + return FindPattern(Pattern.data(), Mask.data()); + } + + uintptr Memory::ResolveRelative(uintptr Address, int32 InstructionSize, int32 OffsetPosition) + { + if (!IsValidAddress(Address)) + return 0; + + int32 Offset = 0; + if (!Read(Address + OffsetPosition, Offset)) + return 0; + + return Address + InstructionSize + Offset; + } + + bool Memory::IsValidAddress(uintptr Address) + { + if (Address == 0) + return false; + + MEMORY_BASIC_INFORMATION MemInfo = {}; + if (VirtualQuery(reinterpret_cast(Address), &MemInfo, sizeof(MemInfo)) == 0) + return false; + + if (MemInfo.State != MEM_COMMIT) + return false; + + if (MemInfo.Protect & (PAGE_NOACCESS | PAGE_GUARD)) + return false; + + return true; + } + +} \ No newline at end of file diff --git a/Core/Memory/Memory.h b/Core/Memory/Memory.h new file mode 100644 index 0000000..7c638b7 --- /dev/null +++ b/Core/Memory/Memory.h @@ -0,0 +1,114 @@ +/** + * UniversalSlashingSimulator - Memory Utilities + * + * Provides memory manipulation, pattern scanning, and module + * information utilities. All pattern scanning is abstracted + * for future external offset finder integration. + */ + +#pragma once + +#include "../Common.h" +#include + +namespace USS +{ + struct FModuleInfo + { + uintptr BaseAddress; + size_t Size; + const char* Name; + }; + + struct FPatternResult + { + bool bFound; + uintptr Address; + + explicit operator bool() const { return bFound; } + }; + + class Memory + { + public: + USS_NON_COPYABLE(Memory) + USS_NON_MOVABLE(Memory) + + static EResult Initialize(); + + static const FModuleInfo& GetBaseModule(); + + // Pattern scanning with mask + // Pattern: raw bytes to match + // Mask: 'x' = must match, '?' = wildcard + static FPatternResult FindPattern(const char* Pattern, const char* Mask); + + static FPatternResult FindPattern(uintptr Start, size_t Size, const char* Pattern, const char* Mask); + + // Pattern scanning with IDA-style signature + // Example: "48 8B 05 ?? ?? ?? ?? 48 85 C0" + static FPatternResult FindPatternIDA(const char* Signature); + + // Resolve relative address (for RIP-relative instructions) + // Address: Address containing the relative offset + // InstructionSize: Total size of the instruction + // OffsetPosition: Position of the offset within the instruction + static uintptr ResolveRelative(uintptr Address, int32 InstructionSize, int32 OffsetPosition); + + template + static bool Read(uintptr Address, T& OutValue); + + template + static bool Write(uintptr Address, const T& Value); + + static bool IsValidAddress(uintptr Address); + + private: + Memory() = default; + ~Memory() = default; + + static bool MaskCompare(const uint8* Data, const char* Pattern, const char* Mask); + + static FModuleInfo s_BaseModule; + static bool s_bInitialized; + }; + + template + bool Memory::Read(uintptr Address, T& OutValue) + { + if (!IsValidAddress(Address)) + return false; + + __try + { + OutValue = *reinterpret_cast(Address); + return true; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return false; + } + } + + template + bool Memory::Write(uintptr Address, const T& Value) + { + DWORD OldProtect; + if (!VirtualProtect(reinterpret_cast(Address), sizeof(T), PAGE_EXECUTE_READWRITE, &OldProtect)) + return false; + + __try + { + *reinterpret_cast(Address) = Value; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + VirtualProtect(reinterpret_cast(Address), sizeof(T), OldProtect, &OldProtect); + return false; + } + + VirtualProtect(reinterpret_cast(Address), sizeof(T), OldProtect, &OldProtect); + return true; + } + +} \ No newline at end of file diff --git a/Core/Memory/PatternScanner.h b/Core/Memory/PatternScanner.h new file mode 100644 index 0000000..2369ccf --- /dev/null +++ b/Core/Memory/PatternScanner.h @@ -0,0 +1 @@ +// TODO: @timmie replace with ur offset finder or whatever lol \ No newline at end of file diff --git a/Core/Versioning/VersionInfo.h b/Core/Versioning/VersionInfo.h new file mode 100644 index 0000000..af766e3 --- /dev/null +++ b/Core/Versioning/VersionInfo.h @@ -0,0 +1,111 @@ +/** + * UniversalSlashingSimulator - Version Information + * + * Contains version structures and feature flags used throughout + * the project. The FVersionInfo struct represents detected + * engine and Fortnite version information. + */ + +#pragma once + +#include "../Common.h" + +namespace USS +{ + // Engine version ranges for feature detection + enum class EEngineGeneration : uint8 + { + Unknown = 0, + UE4_16_19, // 4.16 - 4.19 (GNames, UProperty, fixed objects) + UE4_20_22, // 4.20 - 4.22 (chunked objects) + UE4_23_24, // 4.23 - 4.24 (FNamePool) + UE4_25, // 4.25 (FField/FProperty) + UE4_26_27, // 4.26 - 4.27 (TObjectPtr prep) + UE5_0, // 5.0+ (TObjectPtr) + UE5_1_Plus // 5.1+ (further changes) + }; + + // Version information structure + struct FVersionInfo + { + // Engine version + uint32 EngineVersionMajor; // 4 or 5 + uint32 EngineVersionMinor; // 16, 19, 23, 25, 27, etc. + uint32 EngineVersionPatch; // Patch number + + // Fortnite version + double FortniteVersion; // e.g., 1.8, 8.30, 16.00 + uint32 FortniteSeasonMajor; // Season number + uint32 FortniteSeasonMinor; // Minor patch + uint32 FortniteCL; // Changelist number + + // Computed generation + EEngineGeneration Generation; + + // Feature flags (computed from version) + bool bUseFNamePool; // >= 4.23 + bool bUseFField; // >= 4.25 + bool bUseChunkedObjects; // >= 4.21 + bool bUseNewFastArraySerializer;// FN >= 8.30 + bool bUseTObjectPtr; // >= 5.0 + bool bSupportsSTW; // Has STW support + + // Initialize with defaults + FVersionInfo() + : EngineVersionMajor(0) + , EngineVersionMinor(0) + , EngineVersionPatch(0) + , FortniteVersion(0.0) + , FortniteSeasonMajor(0) + , FortniteSeasonMinor(0) + , FortniteCL(0) + , Generation(EEngineGeneration::Unknown) + , bUseFNamePool(false) + , bUseFField(false) + , bUseChunkedObjects(false) + , bUseNewFastArraySerializer(false) + , bUseTObjectPtr(false) + , bSupportsSTW(true) + {} + + // Check if version is valid + bool IsValid() const + { + return EngineVersionMajor > 0 && Generation != EEngineGeneration::Unknown; + } + + // Get engine version as string (e.g., "4.23.1") + std::string GetEngineVersionString() const + { + char Buffer[32]; + snprintf(Buffer, sizeof(Buffer), "%u.%u.%u", + EngineVersionMajor, EngineVersionMinor, EngineVersionPatch); + return Buffer; + } + + // Get Fortnite version as string (e.g., "8.30") + std::string GetFortniteVersionString() const + { + char Buffer[32]; + snprintf(Buffer, sizeof(Buffer), "%.2f", FortniteVersion); + return Buffer; + } + + // Get generation name + const char* GetGenerationName() const + { + switch (Generation) + { + case EEngineGeneration::UE4_16_19: return "UE4.16-4.19"; + case EEngineGeneration::UE4_20_22: return "UE4.20-4.22"; + case EEngineGeneration::UE4_23_24: return "UE4.23-4.24"; + case EEngineGeneration::UE4_25: return "UE4.25"; + case EEngineGeneration::UE4_26_27: return "UE4.26-4.27"; + case EEngineGeneration::UE5_0: return "UE5.0"; + case EEngineGeneration::UE5_1_Plus: return "UE5.1+"; + default: return "Unknown"; + } + } + }; + +} \ No newline at end of file diff --git a/Core/Versioning/VersionResolver.cpp b/Core/Versioning/VersionResolver.cpp new file mode 100644 index 0000000..8ae72e6 --- /dev/null +++ b/Core/Versioning/VersionResolver.cpp @@ -0,0 +1,573 @@ +/** + * UniversalSlashingSimulator - Version Resolver Implementation + * + * Based on version-scanner-example.cpp patterns and comprehensive CL research. + * Complete CL-to-version mapping for Fortnite 1.2 through 19.40. + */ + +#include "VersionResolver.h" +#include "../Memory/Memory.h" +#include "../Memory/PatternScanner.h" +#include "../Logging/Log.h" +#include + +namespace USS +{ + // Complete CL to version mapping table + const FVersionResolver::FCLMapping FVersionResolver::s_CLMappings[] = { + // ======================================================================== + // Chapter 1 - Season 1 (UE 4.16) + // ======================================================================== + { 3541083, 3681159, 4, 16, 1.20 }, // 1.2 + { 3681159, 3700114, 4, 16, 1.50 }, // 1.5 + { 3700114, 3709086, 4, 16, 1.72 }, // 1.7.2 + { 3709086, 3724489, 4, 16, 1.80 }, // 1.8 + { 3724489, 3757339, 4, 16, 1.82 }, // 1.8.2 + { 3757339, 3775276, 4, 16, 1.90 }, // 1.9 + { 3775276, 3790078, 4, 16, 1.91 }, // 1.9.1 + + // ======================================================================== + // Chapter 1 - Season 2 (UE 4.19) + // ======================================================================== + { 3790078, 3807424, 4, 19, 1.10 }, // 1.10 + { 3807424, 3821117, 4, 19, 1.11 }, // 1.11 + { 3821117, 3841827, 4, 19, 2.00 }, // 2.0 + { 3841827, 3847564, 4, 19, 2.10 }, // 2.1 + { 3847564, 3858292, 4, 19, 2.20 }, // 2.2 + { 3858292, 3870737, 4, 19, 2.30 }, // 2.3 + { 3870737, 3889387, 4, 19, 2.40 }, // 2.4 + { 3889387, 3901517, 4, 19, 2.41 }, // 2.4.1 + { 3901517, 3913157, 4, 19, 2.42 }, // 2.4.2 + { 3913157, 3922182, 4, 19, 2.50 }, // 2.5 + + // ======================================================================== + // Chapter 1 - Season 3 (UE 4.20) + // ======================================================================== + { 3922182, 3935073, 4, 20, 3.00 }, // 3.0 + { 3935073, 3942182, 4, 20, 3.10 }, // 3.1 + { 3942182, 3948073, 4, 20, 3.20 }, // 3.2 + { 3948073, 3968866, 4, 20, 3.30 }, // 3.3 + { 3968866, 3989614, 4, 20, 3.40 }, // 3.4 + { 3989614, 4008490, 4, 20, 3.50 }, // 3.5 + { 4008490, 4019403, 4, 20, 3.51 }, // 3.5.1 + { 4019403, 4039451, 4, 20, 3.52 }, // 3.5.2 + { 4039451, 4072250, 4, 20, 3.60 }, // 3.6 + + // ======================================================================== + // Chapter 1 - Season 4 (UE 4.20) + // ======================================================================== + { 4072250, 4117433, 4, 20, 4.00 }, // 4.0 + { 4117433, 4127312, 4, 20, 4.10 }, // 4.1 + { 4127312, 4166199, 4, 20, 4.20 }, // 4.2 + { 4166199, 4205896, 4, 20, 4.30 }, // 4.3 + { 4205896, 4240749, 4, 20, 4.40 }, // 4.4 + { 4240749, 4276938, 4, 20, 4.50 }, // 4.5 + + // ======================================================================== + // Chapter 1 - Season 5 (UE 4.21) + // ======================================================================== + { 4276938, 4336496, 4, 21, 5.00 }, // 5.0 + { 4336496, 4352937, 4, 21, 5.01 }, // 5.0.1 + { 4352937, 4378021, 4, 21, 5.10 }, // 5.1 + { 4378021, 4395664, 4, 21, 5.20 }, // 5.2 + { 4395664, 4417689, 4, 21, 5.21 }, // 5.2.1 + { 4417689, 4442095, 4, 21, 5.30 }, // 5.3 + { 4442095, 4461277, 4, 21, 5.40 }, // 5.4 + { 4461277, 4476098, 4, 21, 5.41 }, // 5.4.1 + + // ======================================================================== + // Chapter 1 - Season 6 (UE 4.21) + // ======================================================================== + { 4476098, 4526925, 4, 21, 6.00 }, // 6.0 + { 4526925, 4541578, 4, 21, 6.01 }, // 6.0.1 + { 4541578, 4573279, 4, 21, 6.10 }, // 6.1 + { 4573279, 4612618, 4, 21, 6.20 }, // 6.2 + { 4612618, 4629139, 4, 21, 6.21 }, // 6.2.1 + { 4629139, 4667333, 4, 21, 6.30 }, // 6.3 + { 4667333, 4683176, 4, 21, 6.31 }, // 6.3.1 + + // ======================================================================== + // Chapter 1 - Season 7 (UE 4.22) + // ======================================================================== + { 4683176, 4741202, 4, 22, 7.00 }, // 7.0 + { 4741202, 4775217, 4, 22, 7.10 }, // 7.1 + { 4775217, 4801627, 4, 22, 7.20 }, // 7.2 + { 4801627, 4834550, 4, 22, 7.30 }, // 7.3 + { 4834550, 4869070, 4, 22, 7.40 }, // 7.4 + + // ======================================================================== + // Chapter 1 - Season 8 (UE 4.22) + // NOTE: 8.30 introduces new FastArraySerializer + // ======================================================================== + { 4869070, 4900175, 4, 22, 8.00 }, // 8.0 + { 4900175, 4937820, 4, 22, 8.10 }, // 8.1 + { 4937820, 4975227, 4, 22, 8.20 }, // 8.2 + { 4975227, 5027463, 4, 22, 8.30 }, // 8.3 (NEW FAS!) + { 5027463, 5046157, 4, 22, 8.40 }, // 8.4 + { 5046157, 5076327, 4, 22, 8.50 }, // 8.5 + { 5076327, 5110300, 4, 22, 8.51 }, // 8.5.1 + + // ======================================================================== + // Chapter 1 - Season 9 (UE 4.23 - FNamePool introduced) + // ======================================================================== + { 5110300, 5176700, 4, 23, 9.00 }, // 9.0 + { 5176700, 5216303, 4, 23, 9.10 }, // 9.1 + { 5216303, 5268528, 4, 23, 9.20 }, // 9.2 + { 5268528, 5332994, 4, 23, 9.30 }, // 9.3 + { 5332994, 5372160, 4, 23, 9.40 }, // 9.4 + { 5372160, 5423082, 4, 23, 9.41 }, // 9.4.1 + + // ======================================================================== + // Chapter 1 - Season 10 (X) (UE 4.23) + // ======================================================================== + { 5423082, 5492370, 4, 23, 10.00 }, // 10.0 + { 5492370, 5545976, 4, 23, 10.10 }, // 10.1 + { 5545976, 5633945, 4, 23, 10.20 }, // 10.2 + { 5633945, 5704620, 4, 23, 10.30 }, // 10.3 + { 5704620, 5826396, 4, 23, 10.31 }, // 10.3.1 + { 5826396, 5878874, 4, 23, 10.40 }, // 10.4 + + // ======================================================================== + // Chapter 2 - Season 1 (UE 4.24) + // ======================================================================== + { 5878874, 6058028, 4, 24, 11.00 }, // 11.0 + { 6058028, 6113816, 4, 24, 11.01 }, // 11.0.1 + { 6113816, 6195466, 4, 24, 11.10 }, // 11.1 + { 6195466, 6316943, 4, 24, 11.20 }, // 11.2 + { 6316943, 6394056, 4, 24, 11.21 }, // 11.2.1 + { 6394056, 6522018, 4, 24, 11.30 }, // 11.3 + { 6522018, 6639283, 4, 24, 11.31 }, // 11.3.1 + { 6639283, 6755567, 4, 24, 11.40 }, // 11.4 + { 6755567, 6870595, 4, 24, 11.50 }, // 11.5 + + // ======================================================================== + // Chapter 2 - Season 2 (UE 4.24) + // ======================================================================== + { 6870595, 7037963, 4, 24, 12.00 }, // 12.0 + { 7037963, 7095426, 4, 24, 12.10 }, // 12.1 + { 7095426, 7190182, 4, 24, 12.20 }, // 12.2 + { 7190182, 7251970, 4, 24, 12.21 }, // 12.2.1 + { 7251970, 7351410, 4, 24, 12.30 }, // 12.3 + { 7351410, 7421103, 4, 24, 12.40 }, // 12.4 + { 7421103, 7499902, 4, 24, 12.41 }, // 12.4.1 + { 7499902, 7609292, 4, 24, 12.50 }, // 12.5 + { 7609292, 7704104, 4, 24, 12.60 }, // 12.6 + { 7704104, 7834553, 4, 24, 12.61 }, // 12.6.1 + + // ======================================================================== + // Chapter 2 - Season 3 (UE 4.24) + // ======================================================================== + { 7834553, 8008725, 4, 24, 13.00 }, // 13.0 + { 8008725, 8090709, 4, 24, 13.20 }, // 13.2 + { 8090709, 8154316, 4, 24, 13.30 }, // 13.3 + { 8154316, 8297117, 4, 24, 13.40 }, // 13.4 + + // ======================================================================== + // Chapter 2 - Season 4 (UE 4.24) + // ======================================================================== + { 8297117, 8490514, 4, 24, 14.00 }, // 14.0 + { 8490514, 8606188, 4, 24, 14.10 }, // 14.1 + { 8606188, 8723043, 4, 24, 14.20 }, // 14.2 + { 8723043, 8775446, 4, 24, 14.30 }, // 14.3 + { 8775446, 8870917, 4, 24, 14.40 }, // 14.4 + { 8870917, 9034168, 4, 24, 14.50 }, // 14.5 + { 9034168, 9141206, 4, 24, 14.60 }, // 14.6 + + // ======================================================================== + // Chapter 2 - Season 5 (UE 4.25 - FField introduced) + // ======================================================================== + { 9141206, 9449003, 4, 25, 15.00 }, // 15.0 + { 9449003, 9562734, 4, 25, 15.10 }, // 15.1 + { 9562734, 9685607, 4, 25, 15.20 }, // 15.2 + { 9685607, 9822221, 4, 25, 15.21 }, // 15.2.1 + { 9822221, 9926083, 4, 25, 15.30 }, // 15.3 + { 9926083, 10033985, 4, 25, 15.40 }, // 15.4 + { 10033985, 10127509, 4, 25, 15.50 }, // 15.5 + + // ======================================================================== + // Chapter 2 - Season 6 (UE 4.26) + // ======================================================================== + { 10127509, 10466661, 4, 26, 16.00 }, // 16.0 + { 10466661, 10639200, 4, 26, 16.10 }, // 16.1 + { 10639200, 10800459, 4, 26, 16.20 }, // 16.2 + { 10800459, 10951243, 4, 26, 16.30 }, // 16.3 + { 10951243, 11100825, 4, 26, 16.40 }, // 16.4 + { 11100825, 11203632, 4, 26, 16.50 }, // 16.5 + + // ======================================================================== + // Chapter 2 - Season 7 (UE 4.26) + // ======================================================================== + { 11203632, 11556442, 4, 26, 17.00 }, // 17.0 + { 11556442, 11724923, 4, 26, 17.10 }, // 17.1 + { 11724923, 11883027, 4, 26, 17.20 }, // 17.2 + { 11883027, 12058785, 4, 26, 17.21 }, // 17.2.1 + { 12058785, 12186007, 4, 26, 17.30 }, // 17.3 + { 12186007, 12343911, 4, 26, 17.40 }, // 17.4 + { 12343911, 12493209, 4, 26, 17.50 }, // 17.5 + + // ======================================================================== + // Chapter 2 - Season 8 (UE 4.26) + // ======================================================================== + { 12493209, 12905909, 4, 26, 18.00 }, // 18.0 + { 12905909, 13039508, 4, 26, 18.10 }, // 18.1 + { 13039508, 13206842, 4, 26, 18.20 }, // 18.2 + { 13206842, 13383027, 4, 26, 18.21 }, // 18.2.1 + { 13383027, 13498980, 4, 26, 18.30 }, // 18.3 + { 13498980, 13692932, 4, 26, 18.40 }, // 18.4 + + // ======================================================================== + // Chapter 3 - Season 1 (UE 4.27) + // ======================================================================== + { 13692932, 14211857, 5, 0, 19.00 }, // 19.0 + { 14211857, 14422223, 5, 0, 19.01 }, // 19.0.1 + { 14422223, 14550713, 5, 0, 19.10 }, // 19.1 + { 14550713, 14786821, 5, 0, 19.20 }, // 19.2 + { 14786821, 14899505, 5, 0, 19.30 }, // 19.3 + { 14899505, 19215531, 5, 0, 19.40 }, // 19.4 + }; + + const size_t FVersionResolver::s_CLMappingCount = sizeof(s_CLMappings) / sizeof(s_CLMappings[0]); + + FVersionResolver::FVersionResolver() + : m_bDetected(false) + { + } + + FVersionResolver& FVersionResolver::Get() + { + static FVersionResolver Instance; + return Instance; + } + + EResult FVersionResolver::DetectVersion() + { + if (m_bDetected) + return EResult::AlreadyInitialized; + + USS_LOG("Starting version detection..."); + + if (TryDetectFromVersionInfo()) + { + USS_LOG("Version detected from embedded version info"); + } + else if (TryDetectFromCL()) + { + USS_LOG("Version detected from CL mapping"); + } + else if (TryDetectFromPatterns()) + { + USS_LOG("Version detected from memory patterns"); + } + else + { + USS_ERROR("Failed to detect version"); + + // Show error message and exit for unsupported version + MessageBoxA( + nullptr, + "UniversalSlashingSimulator could not detect the Fortnite version.\n\n" + "This build supports Fortnite versions 1.2 through 19.40.\n\n" + "Please ensure you are running a supported version.", + "Unsupported Version", + MB_OK | MB_ICONERROR + ); + + return EResult::InvalidVersion; + } + + DetermineGeneration(); + ComputeFeatureFlags(); + + if (!SupportsVersion(m_VersionInfo)) + { + USS_ERROR("Detected version is not supported: FN %.2f", m_VersionInfo.FortniteVersion); + + char ErrorMsg[512]; + snprintf(ErrorMsg, sizeof(ErrorMsg), + "UniversalSlashingSimulator detected Fortnite %.2f (CL %u)\n\n" + "This version is not supported.\n\n" + "Supported versions: 1.2 - 19.40", + m_VersionInfo.FortniteVersion, + m_VersionInfo.FortniteCL); + + MessageBoxA(nullptr, ErrorMsg, "Unsupported Version", MB_OK | MB_ICONERROR); + return EResult::InvalidVersion; + } + + USS_LOG("Detected version: Engine %s, Fortnite %.2f (CL %u)", + m_VersionInfo.GetEngineVersionString().c_str(), + m_VersionInfo.FortniteVersion, + m_VersionInfo.FortniteCL); + + USS_LOG("Generation: %s", m_VersionInfo.GetGenerationName()); + USS_LOG("Features: FNamePool=%d, FField=%d, ChunkedObjects=%d, NewFAS=%d, TObjectPtr=%d", + m_VersionInfo.bUseFNamePool, + m_VersionInfo.bUseFField, + m_VersionInfo.bUseChunkedObjects, + m_VersionInfo.bUseNewFastArraySerializer, + m_VersionInfo.bUseTObjectPtr); + + m_bDetected = true; + return EResult::Success; + } + + const FVersionInfo& FVersionResolver::GetVersionInfo() const + { + return m_VersionInfo; + } + + bool FVersionResolver::SupportsVersion(const FVersionInfo& Info) const + { + if (Info.EngineVersionMajor == 4) + { + return Info.EngineVersionMinor >= 16 && Info.EngineVersionMinor <= 27; + } + else if (Info.EngineVersionMajor == 5) + { + return Info.EngineVersionMinor <= 2; + } + return false; + } + + bool FVersionResolver::IsVersionDetected() const + { + return m_bDetected; + } + + bool FVersionResolver::TryDetectFromVersionInfo() + { + // Try to find version string in memory + // Pattern: "++Fortnite+Release-XX.XX" or similar + + const char* VersionPattern = "2B 2B 46 6F 72 74 6E 69 74 65 2B 52 65 6C 65 61 73 65"; // "++Fortnite+Release" + auto Result = Memory::FindPatternIDA(VersionPattern); + + if (!Result) + { + USS_LOG("Version string pattern not found"); + return false; + } + + // Parse version from string after pattern + // Format: "++Fortnite+Release-XX.XX-CL-XXXXXXX" + char VersionStr[64] = {}; + for (int i = 0; i < 63; ++i) + { + char c = 0; + Memory::Read(Result.Address + i, c); + if (c == 0 || c == '-' && i > 30) + break; + VersionStr[i] = c; + } + + USS_LOG("Found version string: %s", VersionStr); + + const char* CLStart = strstr(VersionStr, "CL-"); + if (CLStart) + { + uint32 CL = 0; + if (sscanf(CLStart, "CL-%u", &CL) == 1 && CL > 0) + { + return MapCLToVersion(CL); + } + } + + return false; + } + + bool FVersionResolver::TryDetectFromCL() + { + // Try to find CL using TimmiesAwesomeOffsetFinder + // This uses pattern scanning to find the GetEngineVersion function + + // Pattern for GetEngineVersion (varies by version) + // @timmie + uintptr_t CLAddr = TAOF::FindPattern( + "48 8D 05 ?? ?? ?? ?? C3 CC CC CC CC CC CC CC CC 48 8D 05 ?? ?? ?? ?? C3" + ); + + if (CLAddr) + { + uintptr_t StringAddr = TAOF::ResolveRelativeAddress(CLAddr, 7, 3); + if (StringAddr) + { + char VersionStr[64] = {}; + for (int i = 0; i < 63; ++i) + { + char c = 0; + Memory::Read(StringAddr + i, c); + if (c == 0) + break; + VersionStr[i] = c; + } + + const char* CLStart = strstr(VersionStr, "-"); + if (CLStart) + { + uint32 CL = 0; + if (sscanf(CLStart + 1, "%u", &CL) == 1 && CL > 0) + { + USS_LOG("Found CL from version function: %u", CL); + return MapCLToVersion(CL); + } + } + } + } + + USS_LOG("CL detection via pattern failed"); + return false; + } + + bool FVersionResolver::TryDetectFromPatterns() + { + // Try to detect version from characteristic patterns + + // @timmie + + // Check for FField (UE 4.25+) + uintptr_t FFieldAddr = TAOF::FindPattern(TAOF::Patterns::ProcessEvent_423Plus); + + // Check for FNamePool (UE 4.23+) + uintptr_t FNamePoolAddr = TAOF::FindPattern(TAOF::Patterns::GNames_FNamePool); + + // Check for older GNames (Pre-4.23) + uintptr_t OldGNamesAddr = TAOF::FindPattern(TAOF::Patterns::GNames_Pre423); + + if (FFieldAddr && FNamePoolAddr) + { + // UE 4.25+ + m_VersionInfo.EngineVersionMajor = 4; + m_VersionInfo.EngineVersionMinor = 25; + m_VersionInfo.FortniteVersion = 15.00; + m_VersionInfo.FortniteCL = 9500000; + USS_LOG("Detected UE 4.25+ from FField pattern"); + return true; + } + else if (FNamePoolAddr) + { + // UE 4.23-4.24 + m_VersionInfo.EngineVersionMajor = 4; + m_VersionInfo.EngineVersionMinor = 23; + m_VersionInfo.FortniteVersion = 9.00; + m_VersionInfo.FortniteCL = 5200000; + USS_LOG("Detected UE 4.23+ from FNamePool pattern"); + return true; + } + else if (OldGNamesAddr) + { + // Pre-4.23 - check for chunked objects (4.21+) + uintptr_t ChunkedAddr = TAOF::FindPattern(TAOF::Patterns::GObjects_421Plus); + if (ChunkedAddr) + { + // UE 4.21-4.22 + m_VersionInfo.EngineVersionMajor = 4; + m_VersionInfo.EngineVersionMinor = 21; + m_VersionInfo.FortniteVersion = 5.00; + m_VersionInfo.FortniteCL = 4300000; + USS_LOG("Detected UE 4.21+ from chunked GObjects pattern"); + } + else + { + // UE 4.16-4.20 + m_VersionInfo.EngineVersionMajor = 4; + m_VersionInfo.EngineVersionMinor = 16; + m_VersionInfo.FortniteVersion = 1.80; + m_VersionInfo.FortniteCL = 3724489; + USS_LOG("Detected UE 4.16-4.20 from old GNames pattern"); + } + return true; + } + + USS_WARN("Pattern-based version detection failed, using fallback..."); + + // Default to baseline version for development/testing + m_VersionInfo.EngineVersionMajor = 4; + m_VersionInfo.EngineVersionMinor = 16; + m_VersionInfo.FortniteVersion = 1.80; + m_VersionInfo.FortniteCL = 3724489; + + return true; + } + + bool FVersionResolver::MapCLToVersion(uint32 CL) + { + for (size_t i = 0; i < s_CLMappingCount; ++i) + { + const auto& Mapping = s_CLMappings[i]; + + if (CL >= Mapping.CLMin && CL < Mapping.CLMax) + { + m_VersionInfo.FortniteCL = CL; + m_VersionInfo.EngineVersionMajor = Mapping.EngineMajor; + m_VersionInfo.EngineVersionMinor = Mapping.EngineMinor; + m_VersionInfo.FortniteVersion = Mapping.FortniteVersion; + m_VersionInfo.FortniteSeasonMajor = static_cast(Mapping.FortniteVersion); + m_VersionInfo.FortniteSeasonMinor = static_cast((Mapping.FortniteVersion - m_VersionInfo.FortniteSeasonMajor) * 100); + + USS_LOG("Mapped CL %u to Fortnite %.2f (UE %u.%u)", + CL, Mapping.FortniteVersion, Mapping.EngineMajor, Mapping.EngineMinor); + return true; + } + } + + USS_WARN("Unknown CL: %u - not in mapping table", CL); + return false; + } + + void FVersionResolver::DetermineGeneration() + { + uint32 Major = m_VersionInfo.EngineVersionMajor; + uint32 Minor = m_VersionInfo.EngineVersionMinor; + + if (Major == 4) + { + if (Minor <= 19) + m_VersionInfo.Generation = EEngineGeneration::UE4_16_19; + else if (Minor <= 22) + m_VersionInfo.Generation = EEngineGeneration::UE4_20_22; + else if (Minor <= 24) + m_VersionInfo.Generation = EEngineGeneration::UE4_23_24; + else if (Minor == 25) + m_VersionInfo.Generation = EEngineGeneration::UE4_25; + else + m_VersionInfo.Generation = EEngineGeneration::UE4_26_27; + } + else if (Major == 5) + { + if (Minor == 0) + m_VersionInfo.Generation = EEngineGeneration::UE5_0; + else + m_VersionInfo.Generation = EEngineGeneration::UE5_1_Plus; + } + else + { + m_VersionInfo.Generation = EEngineGeneration::Unknown; + } + } + + void FVersionResolver::ComputeFeatureFlags() + { + uint32 Major = m_VersionInfo.EngineVersionMajor; + uint32 Minor = m_VersionInfo.EngineVersionMinor; + double FN = m_VersionInfo.FortniteVersion; + + // FNamePool introduced in UE 4.23 (FN 9.0) + m_VersionInfo.bUseFNamePool = (Major == 4 && Minor >= 23) || Major >= 5; + + // FField introduced in UE 4.25 (FN 15.0) + m_VersionInfo.bUseFField = (Major == 4 && Minor >= 25) || Major >= 5; + + // Chunked UObject array in UE 4.21+ (FN 5.0+) + m_VersionInfo.bUseChunkedObjects = (Major == 4 && Minor >= 21) || Major >= 5; + + // New FastArraySerializer in FN 8.30+ + m_VersionInfo.bUseNewFastArraySerializer = FN >= 8.30; + + // TObjectPtr in UE 5.0+ + m_VersionInfo.bUseTObjectPtr = Major >= 5; + + // STW support (check version range) + m_VersionInfo.bSupportsSTW = FN <= 20.00; + } + +} \ No newline at end of file diff --git a/Core/Versioning/VersionResolver.h b/Core/Versioning/VersionResolver.h new file mode 100644 index 0000000..6ef6dcb --- /dev/null +++ b/Core/Versioning/VersionResolver.h @@ -0,0 +1,82 @@ +/** + * UniversalSlashingSimulator - Version Resolver + * + * Detects and resolves the current engine and Fortnite version. + * Uses pattern scanning and memory analysis to determine version. + * All version-specific behavior branching is based on this resolver. + * + * Version detection strategy: + * 1. Try to read engine version from embedded version info + * 2. Fall back to CL-based detection via pattern scanning + * 3. Map CL to known Fortnite/Engine versions + * 4. Compute feature flags based on version + */ + +#pragma once + +#include "../Common.h" +#include "VersionInfo.h" + +namespace USS +{ + USS_INTERFACE IVersionResolver + { + public: + virtual ~IVersionResolver() = default; + + virtual EResult DetectVersion() = 0; + + virtual const FVersionInfo& GetVersionInfo() const = 0; + + virtual bool SupportsVersion(const FVersionInfo& Info) const = 0; + + virtual bool IsVersionDetected() const = 0; + }; + + class FVersionResolver : public IVersionResolver + { + public: + FVersionResolver(); + ~FVersionResolver() override = default; + + USS_NON_COPYABLE(FVersionResolver) + USS_NON_MOVABLE(FVersionResolver) + + EResult DetectVersion() override; + const FVersionInfo& GetVersionInfo() const override; + bool SupportsVersion(const FVersionInfo& Info) const override; + bool IsVersionDetected() const override; + + static FVersionResolver& Get(); + + private: + bool TryDetectFromVersionInfo(); + bool TryDetectFromCL(); + bool TryDetectFromPatterns(); + + bool MapCLToVersion(uint32 CL); + void ComputeFeatureFlags(); + void DetermineGeneration(); + + FVersionInfo m_VersionInfo; + bool m_bDetected; + + struct FCLMapping + { + uint32 CLMin; + uint32 CLMax; + uint32 EngineMajor; + uint32 EngineMinor; + double FortniteVersion; + }; + + static const FCLMapping s_CLMappings[]; + static const size_t s_CLMappingCount; + }; + + inline FVersionResolver& GetVersionResolver() + { + return FVersionResolver::Get(); + } + +} \ No newline at end of file diff --git a/Engine/CoreTypes/NamePool.cpp b/Engine/CoreTypes/NamePool.cpp new file mode 100644 index 0000000..3b16725 --- /dev/null +++ b/Engine/CoreTypes/NamePool.cpp @@ -0,0 +1,330 @@ +/** + * UniversalSlashingSimulator - Name Pool Implementation + */ + +#include "NamePool.h" +#include "../../Core/Memory/Memory.h" +#include "../../Core/Logging/Log.h" +#include "../../Core/Versioning/VersionResolver.h" + +namespace USS +{ + //========================================================================= + // FGNamesArray Implementation (Pre-4.23) + //========================================================================= + + FGNamesArray::FGNamesArray() + : m_BaseAddress(0) + , m_ChunksPtr(0) + , m_NumElements(0) + , m_bInitialized(false) + { + } + + EResult FGNamesArray::Initialize(uintptr Address) + { + if (m_bInitialized) + return EResult::AlreadyInitialized; + + if (Address == 0) + return EResult::Failed; + + m_BaseAddress = Address; + + // GNames is typically a pointer to TNameEntryArray + // TNameEntryArray layout: + // FNameEntry** Chunks[128]; // 0x00 - Array of chunk pointers + // int32 NumElements; // After chunks + // int32 NumChunks; + + if (!Memory::Read(Address, m_ChunksPtr)) + { + USS_ERROR("Failed to read GNames chunks pointer"); + return EResult::Failed; + } + + // Estimate number of elements by reading chunks + // Normally, we'd scan until we find null entries + m_NumElements = 0; + + for (int32 ChunkIdx = 0; ChunkIdx < 128; ++ChunkIdx) + { + uintptr ChunkPtr = 0; + if (!Memory::Read(m_ChunksPtr + (ChunkIdx * sizeof(uintptr)), ChunkPtr)) + break; + + if (ChunkPtr == 0) + break; + + m_NumElements += ElementsPerChunk; + } + + USS_LOG("FGNamesArray initialized: ~%d estimated elements", m_NumElements); + + m_bInitialized = true; + return EResult::Success; + } + + bool FGNamesArray::GetName(int32 ComparisonIndex, FResolvedName& OutName) const + { + if (!IsValidIndex(ComparisonIndex)) + return false; + + int32 ChunkIndex = ComparisonIndex / ElementsPerChunk; + int32 WithinIndex = ComparisonIndex % ElementsPerChunk; + + uintptr ChunkPtr = 0; + if (!Memory::Read(m_ChunksPtr + (ChunkIndex * sizeof(uintptr)), ChunkPtr)) + return false; + + if (ChunkPtr == 0) + return false; + + uintptr EntryPtr = 0; + if (!Memory::Read(ChunkPtr + (WithinIndex * sizeof(uintptr)), EntryPtr)) + return false; + + if (EntryPtr == 0) + return false; + + // Read FNameEntry header + // Index field at 0x00, low bit indicates wide + int32 IndexValue = 0; + if (!Memory::Read(EntryPtr, IndexValue)) + return false; + + OutName.bIsWide = (IndexValue & 1) != 0; + + // Name data starts at offset 0x10 + uintptr NameDataAddr = EntryPtr + NameOffset; + + static char AnsiBuffer[1024]; + static wchar_t WideBuffer[1024]; + + if (OutName.bIsWide) + { + for (int32 i = 0; i < 1023; ++i) + { + wchar_t Char = 0; + if (!Memory::Read(NameDataAddr + (i * sizeof(wchar_t)), Char)) + break; + + WideBuffer[i] = Char; + if (Char == 0) + { + OutName.Length = i; + break; + } + } + WideBuffer[1023] = 0; + OutName.WideName = WideBuffer; + } + else + { + for (int32 i = 0; i < 1023; ++i) + { + char Char = 0; + if (!Memory::Read(NameDataAddr + i, Char)) + break; + + AnsiBuffer[i] = Char; + if (Char == 0) + { + OutName.Length = i; + break; + } + } + AnsiBuffer[1023] = 0; + OutName.AnsiName = AnsiBuffer; + } + + return true; + } + + std::string FGNamesArray::GetNameString(int32 ComparisonIndex) const + { + FResolvedName Name; + if (GetName(ComparisonIndex, Name)) + return Name.ToString(); + return ""; + } + + bool FGNamesArray::IsValidIndex(int32 Index) const + { + return m_bInitialized && Index >= 0 && Index < m_NumElements; + } + + int32 FGNamesArray::Num() const + { + return m_NumElements; + } + + bool FGNamesArray::IsInitialized() const + { + return m_bInitialized; + } + + //========================================================================= + // FNamePoolImpl Implementation (4.23+) + //========================================================================= + + FNamePoolImpl::FNamePoolImpl() + : m_BaseAddress(0) + , m_NumBlocks(0) + , m_bInitialized(false) + { + } + + EResult FNamePoolImpl::Initialize(uintptr Address) + { + if (m_bInitialized) + return EResult::AlreadyInitialized; + + if (Address == 0) + return EResult::Failed; + + m_BaseAddress = Address; + + // FNamePool layout: + // FNameEntryAllocator Entries; + // ... + // + // FNameEntryAllocator: + // void* Lock; // 0x00 + // uint32 CurrentBlock; // 0x08 + // uint32 CurrentByteCursor;// 0x0C + // void* Blocks[8192]; // 0x10 + + uint32 CurrentBlock = 0; + if (!Memory::Read(m_BaseAddress + 0x08, CurrentBlock)) + { + USS_ERROR("Failed to read CurrentBlock from FNamePool"); + return EResult::Failed; + } + + m_NumBlocks = CurrentBlock + 1; + + USS_LOG("FNamePoolImpl initialized: %d blocks", m_NumBlocks); + + m_bInitialized = true; + return EResult::Success; + } + + bool FNamePoolImpl::GetName(int32 ComparisonIndex, FResolvedName& OutName) const + { + if (!m_bInitialized || ComparisonIndex < 0) + return false; + + // Decode ComparisonIndex + // BlockIndex = ComparisonIndex >> 16 + // NameOffset = (ComparisonIndex & 0xFFFF) * 2 + int32 BlockIndex = ComparisonIndex >> 16; + int32 NameOffset = (ComparisonIndex & 0xFFFF) * 2; + + if (BlockIndex >= m_NumBlocks) + return false; + + uintptr BlockPtr = 0; + if (!Memory::Read(m_BaseAddress + BlocksOffset + (BlockIndex * sizeof(uintptr)), BlockPtr)) + return false; + + if (BlockPtr == 0) + return false; + + uintptr EntryAddr = BlockPtr + NameOffset; + + // Read FNameEntryHeader (2 bytes) + // bIsWide : 1 + // Len : 15 + uint16 Header = 0; + if (!Memory::Read(EntryAddr, Header)) + return false; + + OutName.bIsWide = (Header & 1) != 0; + OutName.Length = Header >> 1; + + if (OutName.Length <= 0 || OutName.Length > 1023) + return false; + + uintptr NameDataAddr = EntryAddr + sizeof(uint16); + + static char AnsiBuffer[1024]; + static wchar_t WideBuffer[1024]; + + if (OutName.bIsWide) + { + for (int32 i = 0; i < OutName.Length && i < 1023; ++i) + { + wchar_t Char = 0; + if (!Memory::Read(NameDataAddr + (i * sizeof(wchar_t)), Char)) + break; + WideBuffer[i] = Char; + } + WideBuffer[OutName.Length] = 0; + OutName.WideName = WideBuffer; + } + else + { + for (int32 i = 0; i < OutName.Length && i < 1023; ++i) + { + char Char = 0; + if (!Memory::Read(NameDataAddr + i, Char)) + break; + AnsiBuffer[i] = Char; + } + AnsiBuffer[OutName.Length] = 0; + OutName.AnsiName = AnsiBuffer; + } + + return true; + } + + std::string FNamePoolImpl::GetNameString(int32 ComparisonIndex) const + { + FResolvedName Name; + if (GetName(ComparisonIndex, Name)) + return Name.ToString(); + return ""; + } + + bool FNamePoolImpl::IsValidIndex(int32 Index) const + { + // Can't easily validate without scanning + return m_bInitialized && Index >= 0; + } + + int32 FNamePoolImpl::Num() const + { + // FNamePool doesn't track count directly + // Would need to scan to count + return -1; + } + + bool FNamePoolImpl::IsInitialized() const + { + return m_bInitialized; + } + + //========================================================================= + // Factory Function + //========================================================================= + + std::unique_ptr CreateNamePool() + { + const auto& Version = GetVersionResolver().GetVersionInfo(); + + if (Version.bUseFNamePool) + { + USS_LOG("Creating FNamePoolImpl for UE %s", + Version.GetEngineVersionString().c_str()); + return std::make_unique(); + } + else + { + USS_LOG("Creating FGNamesArray for UE %s", + Version.GetEngineVersionString().c_str()); + return std::make_unique(); + } + } + +} \ No newline at end of file diff --git a/Engine/CoreTypes/NamePool.h b/Engine/CoreTypes/NamePool.h new file mode 100644 index 0000000..e0b129f --- /dev/null +++ b/Engine/CoreTypes/NamePool.h @@ -0,0 +1,168 @@ +/** + * UniversalSlashingSimulator - Name Pool Abstraction + * + * Provides a version-agnostic interface for accessing names (FName). + * Implementations differ between: + * - Pre-4.23: GNames array (TStaticIndirectArrayThreadSafeRead) + * - 4.23+: FNamePool with packed entries + */ + +#pragma once + +#include "../../Core/Common.h" + +namespace USS +{ + struct FResolvedName + { + const char* AnsiName; + const wchar_t* WideName; + bool bIsWide; + int32 Length; + + FResolvedName() + : AnsiName(nullptr) + , WideName(nullptr) + , bIsWide(false) + , Length(0) + {} + + std::string ToString() const + { + if (bIsWide && WideName) + { + std::string Result; + for (int32 i = 0; i < Length && WideName[i]; ++i) + { + Result += static_cast(WideName[i]); + } + return Result; + } + else if (AnsiName) + { + return std::string(AnsiName, Length); + } + return ""; + } + }; + + struct FNameCompact + { + int32 ComparisonIndex; + int32 Number; + + FNameCompact() : ComparisonIndex(0), Number(0) {} + FNameCompact(int32 Index, int32 Num) : ComparisonIndex(Index), Number(Num) {} + + bool operator==(const FNameCompact& Other) const + { + return ComparisonIndex == Other.ComparisonIndex && Number == Other.Number; + } + }; + + USS_INTERFACE INamePool + { + public: + virtual ~INamePool() = default; + + // Get name by comparison index + virtual bool GetName(int32 ComparisonIndex, FResolvedName& OutName) const = 0; + + // Get name as string (convenience) + virtual std::string GetNameString(int32 ComparisonIndex) const = 0; + + // Check if index is valid + virtual bool IsValidIndex(int32 Index) const = 0; + + // Get total number of names + virtual int32 Num() const = 0; + + // Initialize from memory address + virtual EResult Initialize(uintptr Address) = 0; + + // Check if initialized + virtual bool IsInitialized() const = 0; + }; + + // GNames array implementation (Pre-4.23) + class FGNamesArray : public INamePool + { + public: + FGNamesArray(); + ~FGNamesArray() override = default; + + bool GetName(int32 ComparisonIndex, FResolvedName& OutName) const override; + std::string GetNameString(int32 ComparisonIndex) const override; + bool IsValidIndex(int32 Index) const override; + int32 Num() const override; + EResult Initialize(uintptr Address) override; + bool IsInitialized() const override; + + private: + // Internal layout: + // Chunked indirect array with 0x4000 elements per chunk + // Access: GNames.Objects[index / 0x4000][index % 0x4000] + // + // FNameEntry layout (pre-4.23): + // int32 Index; // 0x00 - Upper bits: index, LSB: wide flag + // char Pad[4]; // 0x04 + // FNameEntry* HashNext; // 0x08 + // union { + // char AnsiName[1024]; + // wchar_t WideName[1024]; + // }; // 0x10 + + static constexpr int32 ElementsPerChunk = 0x4000; // 16384 + static constexpr int32 NameOffset = 0x10; // Offset to name data + + uintptr m_BaseAddress; + uintptr m_ChunksPtr; + int32 m_NumElements; + bool m_bInitialized; + }; + + // FNamePool implementation (4.23+) + class FNamePoolImpl : public INamePool + { + public: + FNamePoolImpl(); + ~FNamePoolImpl() override = default; + + bool GetName(int32 ComparisonIndex, FResolvedName& OutName) const override; + std::string GetNameString(int32 ComparisonIndex) const override; + bool IsValidIndex(int32 Index) const override; + int32 Num() const override; + EResult Initialize(uintptr Address) override; + bool IsInitialized() const override; + + private: + // FNamePool layout (4.23+): + // struct FNameEntryAllocator { + // void* Lock; // 0x00 + // uint32 CurrentBlock; // 0x08 + // uint32 CurrentByteCursor;// 0x0C + // void* Blocks[8192]; // 0x10 - Block pointers + // } + // + // ComparisonIndex encoding: + // BlockIndex = ComparisonIndex >> 16 + // NameOffset = (ComparisonIndex & 0xFFFF) * 2 + // + // FNameEntry layout (4.23+): + // struct FNameEntryHeader { + // uint16 bIsWide : 1; + // uint16 Len : 15; + // }; + // Followed by packed name data + + static constexpr int32 MaxBlocks = 8192; + static constexpr uintptr BlocksOffset = 0x10; + + uintptr m_BaseAddress; + int32 m_NumBlocks; + bool m_bInitialized; + }; + + std::unique_ptr CreateNamePool(); + +} \ No newline at end of file diff --git a/Engine/CoreTypes/ObjectArray.cpp b/Engine/CoreTypes/ObjectArray.cpp new file mode 100644 index 0000000..21d4727 --- /dev/null +++ b/Engine/CoreTypes/ObjectArray.cpp @@ -0,0 +1,286 @@ +/** + * UniversalSlashingSimulator - Object Array Implementation + */ + +#include "ObjectArray.h" +#include "../../Core/Memory/Memory.h" +#include "../../Core/Logging/Log.h" +#include "../../Core/Versioning/VersionResolver.h" + +namespace USS +{ + //========================================================================= + // FFixedObjectArray Implementation (UE4.11-4.20) + //========================================================================= + + FFixedObjectArray::FFixedObjectArray() + : m_BaseAddress(0) + , m_ObjectsPtr(0) + , m_NumElements(0) + , m_MaxElements(0) + , m_ItemSize(0x18) // Default: Object(8) + Flags(4) + ClusterIndex(4) + SerialNumber(4) + , m_bInitialized(false) + { + } + + EResult FFixedObjectArray::Initialize(uintptr Address) + { + if (m_bInitialized) + return EResult::AlreadyInitialized; + + if (Address == 0) + return EResult::Failed; + + m_BaseAddress = Address; + + // Read fixed array layout + // Offsets are approximate and may need adjustment per-version + // struct TUObjectArray { + // FUObjectItem* Objects; // 0x00 + // int32 MaxElements; // 0x08 + // int32 NumElements; // 0x0C + // } + + // The FUObjectArray contains TUObjectArray at offset 0x10 + uintptr TUObjectArrayAddr = m_BaseAddress + 0x10; + + if (!Memory::Read(TUObjectArrayAddr + 0x00, m_ObjectsPtr)) + { + USS_ERROR("Failed to read Objects pointer from FUObjectArray"); + return EResult::Failed; + } + + if (!Memory::Read(TUObjectArrayAddr + 0x08, m_MaxElements)) + { + USS_ERROR("Failed to read MaxElements from FUObjectArray"); + return EResult::Failed; + } + + if (!Memory::Read(TUObjectArrayAddr + 0x0C, m_NumElements)) + { + USS_ERROR("Failed to read NumElements from FUObjectArray"); + return EResult::Failed; + } + + USS_LOG("FFixedObjectArray initialized: NumElements=%d, MaxElements=%d", + m_NumElements, m_MaxElements); + + m_bInitialized = true; + return EResult::Success; + } + + int32 FFixedObjectArray::Num() const + { + return m_NumElements; + } + + void* FFixedObjectArray::GetByIndex(int32 Index) const + { + if (!IsValidIndex(Index)) + return nullptr; + + uintptr ItemAddr = m_ObjectsPtr + (Index * m_ItemSize); + + void* Object = nullptr; + if (!Memory::Read(ItemAddr, Object)) + return nullptr; + + return Object; + } + + bool FFixedObjectArray::GetItemByIndex(int32 Index, FObjectItem& OutItem) const + { + if (!IsValidIndex(Index)) + return false; + + uintptr ItemAddr = m_ObjectsPtr + (Index * m_ItemSize); + + if (!Memory::Read(ItemAddr + 0x00, OutItem.Object)) + return false; + if (!Memory::Read(ItemAddr + 0x08, OutItem.Flags)) + return false; + if (!Memory::Read(ItemAddr + 0x0C, OutItem.ClusterIndex)) + return false; + if (!Memory::Read(ItemAddr + 0x10, OutItem.SerialNumber)) + return false; + + return true; + } + + bool FFixedObjectArray::IsValidIndex(int32 Index) const + { + return m_bInitialized && Index >= 0 && Index < m_NumElements; + } + + bool FFixedObjectArray::IsInitialized() const + { + return m_bInitialized; + } + + //========================================================================= + // FChunkedObjectArray Implementation (UE4.21+) + //========================================================================= + + FChunkedObjectArray::FChunkedObjectArray() + : m_BaseAddress(0) + , m_ChunksPtr(0) + , m_NumElements(0) + , m_MaxElements(0) + , m_NumChunks(0) + , m_ItemSize(0x18) + , m_bInitialized(false) + { + } + + EResult FChunkedObjectArray::Initialize(uintptr Address) + { + if (m_bInitialized) + return EResult::AlreadyInitialized; + + if (Address == 0) + return EResult::Failed; + + m_BaseAddress = Address; + + // Read chunked array layout + // struct FChunkedFixedUObjectArray { + // FUObjectItem** Objects; // 0x00 - Chunks array + // FUObjectItem* PreAllocated; // 0x08 + // int32 MaxElements; // 0x10 + // int32 NumElements; // 0x14 + // int32 MaxChunks; // 0x18 + // int32 NumChunks; // 0x1C + // } + + uintptr TUObjectArrayAddr = m_BaseAddress + 0x10; + + if (!Memory::Read(TUObjectArrayAddr + 0x00, m_ChunksPtr)) + { + USS_ERROR("Failed to read Chunks pointer from FChunkedObjectArray"); + return EResult::Failed; + } + + if (!Memory::Read(TUObjectArrayAddr + 0x10, m_MaxElements)) + { + USS_ERROR("Failed to read MaxElements from FChunkedObjectArray"); + return EResult::Failed; + } + + if (!Memory::Read(TUObjectArrayAddr + 0x14, m_NumElements)) + { + USS_ERROR("Failed to read NumElements from FChunkedObjectArray"); + return EResult::Failed; + } + + if (!Memory::Read(TUObjectArrayAddr + 0x1C, m_NumChunks)) + { + USS_ERROR("Failed to read NumChunks from FChunkedObjectArray"); + return EResult::Failed; + } + + USS_LOG("FChunkedObjectArray initialized: NumElements=%d, NumChunks=%d", + m_NumElements, m_NumChunks); + + m_bInitialized = true; + return EResult::Success; + } + + int32 FChunkedObjectArray::Num() const + { + return m_NumElements; + } + + void* FChunkedObjectArray::GetByIndex(int32 Index) const + { + if (!IsValidIndex(Index)) + return nullptr; + + int32 ChunkIndex = Index / ElementsPerChunk; + int32 WithinChunkIndex = Index % ElementsPerChunk; + + // Get chunk pointer + uintptr ChunkPtrAddr = m_ChunksPtr + (ChunkIndex * sizeof(uintptr)); + uintptr ChunkPtr = 0; + + if (!Memory::Read(ChunkPtrAddr, ChunkPtr)) + return nullptr; + + if (ChunkPtr == 0) + return nullptr; + + // Get object from chunk + uintptr ItemAddr = ChunkPtr + (WithinChunkIndex * m_ItemSize); + + void* Object = nullptr; + if (!Memory::Read(ItemAddr, Object)) + return nullptr; + + return Object; + } + + bool FChunkedObjectArray::GetItemByIndex(int32 Index, FObjectItem& OutItem) const + { + if (!IsValidIndex(Index)) + return false; + + int32 ChunkIndex = Index / ElementsPerChunk; + int32 WithinChunkIndex = Index % ElementsPerChunk; + + // Get chunk pointer + uintptr ChunkPtrAddr = m_ChunksPtr + (ChunkIndex * sizeof(uintptr)); + uintptr ChunkPtr = 0; + + if (!Memory::Read(ChunkPtrAddr, ChunkPtr)) + return false; + + if (ChunkPtr == 0) + return false; + + // Get item from chunk + uintptr ItemAddr = ChunkPtr + (WithinChunkIndex * m_ItemSize); + + if (!Memory::Read(ItemAddr + 0x00, OutItem.Object)) + return false; + if (!Memory::Read(ItemAddr + 0x08, OutItem.Flags)) + return false; + if (!Memory::Read(ItemAddr + 0x0C, OutItem.ClusterIndex)) + return false; + if (!Memory::Read(ItemAddr + 0x10, OutItem.SerialNumber)) + return false; + + return true; + } + + bool FChunkedObjectArray::IsValidIndex(int32 Index) const + { + return m_bInitialized && Index >= 0 && Index < m_NumElements; + } + + bool FChunkedObjectArray::IsInitialized() const + { + return m_bInitialized; + } + + //========================================================================= + // Factory Function + //========================================================================= + + std::unique_ptr CreateObjectArray() + { + const auto& Version = GetVersionResolver().GetVersionInfo(); + + if (Version.bUseChunkedObjects) + { + USS_LOG("Creating FChunkedObjectArray for UE %s", + Version.GetEngineVersionString().c_str()); + return std::make_unique(); + } + else + { + USS_LOG("Creating FFixedObjectArray for UE %s", + Version.GetEngineVersionString().c_str()); + return std::make_unique(); + } + } + +} \ No newline at end of file diff --git a/Engine/CoreTypes/ObjectArray.h b/Engine/CoreTypes/ObjectArray.h new file mode 100644 index 0000000..ecdc1b5 --- /dev/null +++ b/Engine/CoreTypes/ObjectArray.h @@ -0,0 +1,150 @@ +/** + * UniversalSlashingSimulator - Object Array Abstraction + * + * Provides a version-agnostic interface for accessing the global + * object array (GObjects). Implementations differ between: + * - UE4.11-4.20: Fixed direct array (FUObjectItem*) + * - UE4.21+: Chunked indirect array (FUObjectItem**) + */ + +#pragma once + +#include "../../Core/Common.h" + +namespace USS +{ + // FUObjectItem flags + enum class EInternalObjectFlags : int32 + { + None = 0, + Native = 1 << 25, + Async = 1 << 26, + AsyncLoading = 1 << 27, + Unreachable = 1 << 28, + PendingKill = 1 << 29, + RootSet = 1 << 30, + NoStrongReference = 1 << 31, + }; + + // Object item wrapper (version-agnostic) + struct FObjectItem + { + void* Object; // UObject* + int32 Flags; + int32 ClusterIndex; + int32 SerialNumber; + + bool IsUnreachable() const + { + return (Flags & static_cast(EInternalObjectFlags::Unreachable)) != 0; + } + + bool IsPendingKill() const + { + return (Flags & static_cast(EInternalObjectFlags::PendingKill)) != 0; + } + + bool IsRootSet() const + { + return (Flags & static_cast(EInternalObjectFlags::RootSet)) != 0; + } + }; + + // Object array interface + USS_INTERFACE IObjectArray + { + public: + virtual ~IObjectArray() = default; + + // Get number of objects + virtual int32 Num() const = 0; + + // Get object by index (returns UObject*) + virtual void* GetByIndex(int32 Index) const = 0; + + // Get object item by index + virtual bool GetItemByIndex(int32 Index, FObjectItem& OutItem) const = 0; + + // Check if index is valid + virtual bool IsValidIndex(int32 Index) const = 0; + + // Initialize from memory address + virtual EResult Initialize(uintptr Address) = 0; + + // Check if initialized + virtual bool IsInitialized() const = 0; + }; + + // Fixed object array implementation (UE4.11-4.20) + class FFixedObjectArray : public IObjectArray + { + public: + FFixedObjectArray(); + ~FFixedObjectArray() override = default; + + int32 Num() const override; + void* GetByIndex(int32 Index) const override; + bool GetItemByIndex(int32 Index, FObjectItem& OutItem) const override; + bool IsValidIndex(int32 Index) const override; + EResult Initialize(uintptr Address) override; + bool IsInitialized() const override; + + private: + // Internal layout for fixed array + // struct FUObjectArray { + // int32 ObjFirstGCIndex; + // int32 ObjLastNonGCIndex; + // int32 MaxObjectsNotConsideredByGC; + // bool OpenForDisregardForGC; + // FUObjectItem* Objects; // Direct pointer + // int32 MaxElements; + // int32 NumElements; + // } + + uintptr m_BaseAddress; + uintptr m_ObjectsPtr; + int32 m_NumElements; + int32 m_MaxElements; + size_t m_ItemSize; + bool m_bInitialized; + }; + + // Chunked object array implementation (UE4.21+) + class FChunkedObjectArray : public IObjectArray + { + public: + FChunkedObjectArray(); + ~FChunkedObjectArray() override = default; + + int32 Num() const override; + void* GetByIndex(int32 Index) const override; + bool GetItemByIndex(int32 Index, FObjectItem& OutItem) const override; + bool IsValidIndex(int32 Index) const override; + EResult Initialize(uintptr Address) override; + bool IsInitialized() const override; + + private: + // Internal layout for chunked array + // struct FChunkedFixedUObjectArray { + // FUObjectItem** Objects; // Pointer to chunk pointers + // FUObjectItem* PreAllocatedObjects; + // int32 MaxElements; + // int32 NumElements; + // int32 MaxChunks; + // int32 NumChunks; + // } + + static constexpr int32 ElementsPerChunk = 64 * 1024; // 65536 + + uintptr m_BaseAddress; + uintptr m_ChunksPtr; + int32 m_NumElements; + int32 m_MaxElements; + int32 m_NumChunks; + size_t m_ItemSize; + bool m_bInitialized; + }; + + std::unique_ptr CreateObjectArray(); + +} \ No newline at end of file diff --git a/Engine/CoreTypes/OffsetResolver.cpp b/Engine/CoreTypes/OffsetResolver.cpp new file mode 100644 index 0000000..debe98c --- /dev/null +++ b/Engine/CoreTypes/OffsetResolver.cpp @@ -0,0 +1,319 @@ +/** + * UniversalSlashingSimulator - Offset Resolver Implementation + * + * STUB IMPLEMENTATION + * This resolver returns placeholder offsets based on version. + * @timmie, this would be replaced by your external offset finder. + */ + +#include "OffsetResolver.h" +#include "../../Core/Logging/Log.h" +#include "../../Core/Memory/Memory.h" +#include + +namespace USS +{ + FStubOffsetResolver::FStubOffsetResolver() + : m_bResolved(false) + { + } + + FStubOffsetResolver& FStubOffsetResolver::Get() + { + static FStubOffsetResolver Instance; + return Instance; + } + + EResult FStubOffsetResolver::ResolveOffsets(const FVersionInfo& Version) + { + if (m_bResolved) + return EResult::AlreadyInitialized; + + USS_LOG("Resolving offsets for %s (FN %.2f)", + Version.GetGenerationName(), + Version.FortniteVersion); + + // Start with baseline offsets + m_Offsets = FOffsetTable(); + + // Apply version-specific adjustments + ApplyVersionSpecificOffsets(Version); + + // Attempt to resolve function addresses via patterns + // These are stubbed - actual patterns would go here + + USS_WARN("Using STUB offsets - external offset finder not connected"); + + m_bResolved = true; + return EResult::Success; + } + + void FStubOffsetResolver::ApplyVersionSpecificOffsets(const FVersionInfo& Version) + { + // Apply offsets based on engine generation + switch (Version.Generation) + { + case EEngineGeneration::UE4_16_19: + // Baseline offsets (Fortnite 1.x - 2.x) + // Controller offsets from inventory_offset_fixes.h + m_Offsets.Controller.BuildPreviewMarker = 0x1788; + m_Offsets.Controller.CurrentBuildableClass = 0x1940; + m_Offsets.Controller.PreviousBuildableClass = 0x1948; + m_Offsets.Controller.EditBuildingActor = 0x1A48; + m_Offsets.Controller.QuickBars = 0x1A88; + m_Offsets.Controller.BuildPreviewMarkerMID = 0x1928; + + // UStruct (no ChildProperties in this version) + m_Offsets.UStruct.SuperStruct = 0x30; + m_Offsets.UStruct.Children = 0x38; + m_Offsets.UStruct.ChildProperties = 0x00; // Not present + m_Offsets.UStruct.PropertiesSize = 0x40; + break; + + case EEngineGeneration::UE4_20_22: + // Season 3-8 (chunked objects) + m_Offsets.Controller.BuildPreviewMarker = 0x1800; + m_Offsets.Controller.CurrentBuildableClass = 0x19C0; + m_Offsets.Controller.PreviousBuildableClass = 0x19C8; + m_Offsets.Controller.QuickBars = 0x1B10; + + m_Offsets.UStruct.SuperStruct = 0x30; + m_Offsets.UStruct.Children = 0x38; + m_Offsets.UStruct.ChildProperties = 0x00; + m_Offsets.UStruct.PropertiesSize = 0x44; + break; + + case EEngineGeneration::UE4_23_24: + // Season 9-14 (FNamePool) + m_Offsets.Controller.BuildPreviewMarker = 0x1880; + m_Offsets.Controller.CurrentBuildableClass = 0x1A40; + m_Offsets.Controller.QuickBars = 0x1B90; + + m_Offsets.UStruct.SuperStruct = 0x30; + m_Offsets.UStruct.Children = 0x38; + m_Offsets.UStruct.ChildProperties = 0x00; + m_Offsets.UStruct.PropertiesSize = 0x48; + break; + + case EEngineGeneration::UE4_25: + // Season 15 (FField introduction) + m_Offsets.Controller.BuildPreviewMarker = 0x1900; + m_Offsets.Controller.QuickBars = 0x1C00; + + // FField now present + m_Offsets.UStruct.SuperStruct = 0x30; + m_Offsets.UStruct.Children = 0x38; // UField* (non-property) + m_Offsets.UStruct.ChildProperties = 0x40; // FField* (properties) + m_Offsets.UStruct.PropertiesSize = 0x48; + + // FField offsets + m_Offsets.FField.ClassPrivate = 0x00; + m_Offsets.FField.Owner = 0x08; + m_Offsets.FField.Next = 0x10; + m_Offsets.FField.NamePrivate = 0x18; + m_Offsets.FField.FlagsPrivate = 0x20; + + // FProperty offsets + m_Offsets.FProperty.ElementSize = 0x38; + m_Offsets.FProperty.Offset = 0x44; + m_Offsets.FProperty.PropertyFlags = 0x48; + break; + + case EEngineGeneration::UE4_26_27: + // Season 16-21 (TObjectPtr prep) + m_Offsets.Controller.BuildPreviewMarker = 0x1980; + m_Offsets.Controller.QuickBars = 0x1C80; + + m_Offsets.UStruct.SuperStruct = 0x30; + m_Offsets.UStruct.Children = 0x38; + m_Offsets.UStruct.ChildProperties = 0x40; + m_Offsets.UStruct.PropertiesSize = 0x4C; + + m_Offsets.FField.ClassPrivate = 0x00; + m_Offsets.FField.Owner = 0x08; + m_Offsets.FField.Next = 0x10; + m_Offsets.FField.NamePrivate = 0x18; + m_Offsets.FField.FlagsPrivate = 0x20; + + m_Offsets.FProperty.ElementSize = 0x38; + m_Offsets.FProperty.Offset = 0x44; + m_Offsets.FProperty.PropertyFlags = 0x48; + break; + + case EEngineGeneration::UE5_0: + case EEngineGeneration::UE5_1_Plus: + // Chapter 4+ (TObjectPtr, UE5) + m_Offsets.Controller.BuildPreviewMarker = 0x1A00; + m_Offsets.Controller.QuickBars = 0x1D00; + + // UE5 adjustments (TObjectPtr adds padding) + m_Offsets.UObject.Class = 0x10; // May change to TObjectPtr + m_Offsets.UObject.Outer = 0x20; // May change to TObjectPtr + + m_Offsets.UStruct.SuperStruct = 0x30; + m_Offsets.UStruct.Children = 0x40; + m_Offsets.UStruct.ChildProperties = 0x48; + m_Offsets.UStruct.PropertiesSize = 0x50; + + m_Offsets.FField.ClassPrivate = 0x00; + m_Offsets.FField.Owner = 0x08; + m_Offsets.FField.Next = 0x10; + m_Offsets.FField.NamePrivate = 0x18; + m_Offsets.FField.FlagsPrivate = 0x20; + + m_Offsets.FProperty.ElementSize = 0x40; + m_Offsets.FProperty.Offset = 0x4C; + m_Offsets.FProperty.PropertyFlags = 0x50; + break; + + default: + USS_WARN("Unknown engine generation, using baseline offsets"); + break; + } + + USS_LOG("Applied version-specific offsets for %s", + Version.GetGenerationName()); + } + + const FOffsetTable& FStubOffsetResolver::GetOffsets() const + { + return m_Offsets; + } + + bool FStubOffsetResolver::IsResolved() const + { + return m_bResolved; + } + + int32 FStubOffsetResolver::GetOffset(EOffsetCategory Category, const char* Name) const + { + if (!m_bResolved || !Name) + return -1; + + // Simple string matching for offset lookup + // In production, this would use a hash map + + switch (Category) + { + case EOffsetCategory::UObject: + if (strcmp(Name, "Vtable") == 0) return m_Offsets.UObject.Vtable; + if (strcmp(Name, "ObjectFlags") == 0) return m_Offsets.UObject.ObjectFlags; + if (strcmp(Name, "InternalIndex") == 0) return m_Offsets.UObject.InternalIndex; + if (strcmp(Name, "Class") == 0) return m_Offsets.UObject.Class; + if (strcmp(Name, "Name") == 0) return m_Offsets.UObject.Name; + if (strcmp(Name, "Outer") == 0) return m_Offsets.UObject.Outer; + break; + + case EOffsetCategory::UField: + // UField offsets (pre-4.25) + if (strcmp(Name, "Next") == 0) return 0x30; // Default UField::Next offset + break; + + case EOffsetCategory::UStruct: + if (strcmp(Name, "SuperStruct") == 0) return m_Offsets.UStruct.SuperStruct; + if (strcmp(Name, "Children") == 0) return m_Offsets.UStruct.Children; + if (strcmp(Name, "ChildProperties") == 0) return m_Offsets.UStruct.ChildProperties; + if (strcmp(Name, "PropertiesSize") == 0) return m_Offsets.UStruct.PropertiesSize; + if (strcmp(Name, "PropertyLink") == 0) return 0x50; // Default PropertyLink offset + break; + + case EOffsetCategory::UProperty: + // UProperty offsets (pre-4.25) + if (strcmp(Name, "ArrayDim") == 0) return 0x38; + if (strcmp(Name, "ElementSize") == 0) return 0x3C; + if (strcmp(Name, "PropertyFlags") == 0) return 0x40; + if (strcmp(Name, "Offset_Internal") == 0) return 0x4C; + break; + + case EOffsetCategory::FField: + // FField offsets (4.25+) + if (strcmp(Name, "ClassPrivate") == 0) return 0x00; + if (strcmp(Name, "Owner") == 0) return 0x08; + if (strcmp(Name, "Next") == 0) return 0x20; + if (strcmp(Name, "NamePrivate") == 0) return 0x28; + break; + + case EOffsetCategory::FProperty: + // FProperty offsets (4.25+) + if (strcmp(Name, "ArrayDim") == 0) return 0x38; + if (strcmp(Name, "ElementSize") == 0) return 0x3C; + if (strcmp(Name, "PropertyFlags") == 0) return 0x40; + if (strcmp(Name, "Offset_Internal") == 0) return 0x4C; + break; + + case EOffsetCategory::FFieldClass: + // FFieldClass offsets (4.25+) + if (strcmp(Name, "Name") == 0) return 0x00; + break; + + case EOffsetCategory::Controller: + if (strcmp(Name, "BuildPreviewMarker") == 0) return m_Offsets.Controller.BuildPreviewMarker; + if (strcmp(Name, "CurrentBuildableClass") == 0) return m_Offsets.Controller.CurrentBuildableClass; + if (strcmp(Name, "QuickBars") == 0) return m_Offsets.Controller.QuickBars; + break; + + default: + break; + } + + USS_WARN("Unknown offset: %s", Name); + return -1; + } + + int32 FStubOffsetResolver::GetOffset(const char* CategoryName, const char* Name) const + { + if (!CategoryName || !Name) + return -1; + + // Map string category to enum + EOffsetCategory Category = EOffsetCategory::UObject; + + if (strcmp(CategoryName, "UObject") == 0) + Category = EOffsetCategory::UObject; + else if (strcmp(CategoryName, "UField") == 0) + Category = EOffsetCategory::UField; + else if (strcmp(CategoryName, "UStruct") == 0) + Category = EOffsetCategory::UStruct; + else if (strcmp(CategoryName, "UClass") == 0) + Category = EOffsetCategory::UClass; + else if (strcmp(CategoryName, "UFunction") == 0) + Category = EOffsetCategory::UFunction; + else if (strcmp(CategoryName, "UProperty") == 0) + Category = EOffsetCategory::UProperty; + else if (strcmp(CategoryName, "FField") == 0) + Category = EOffsetCategory::FField; + else if (strcmp(CategoryName, "FProperty") == 0) + Category = EOffsetCategory::FProperty; + else if (strcmp(CategoryName, "FFieldClass") == 0) + Category = EOffsetCategory::FFieldClass; + else if (strcmp(CategoryName, "Controller") == 0) + Category = EOffsetCategory::Controller; + else if (strcmp(CategoryName, "Pawn") == 0) + Category = EOffsetCategory::Pawn; + else if (strcmp(CategoryName, "Actor") == 0) + Category = EOffsetCategory::Actor; + else + { + USS_WARN("Unknown category: %s", CategoryName); + return -1; + } + + return GetOffset(Category, Name); + } + + uintptr FStubOffsetResolver::GetFunctionAddress(const char* Name) const + { + if (!m_bResolved || !Name) + return 0; + + if (strcmp(Name, "GObjects") == 0) return m_Offsets.Functions.GObjects; + if (strcmp(Name, "GNames") == 0) return m_Offsets.Functions.GNames; + if (strcmp(Name, "GWorld") == 0) return m_Offsets.Functions.GWorld; + if (strcmp(Name, "ProcessEvent") == 0) return m_Offsets.Functions.ProcessEvent; + if (strcmp(Name, "StaticLoadObject") == 0) return m_Offsets.Functions.StaticLoadObject; + if (strcmp(Name, "SpawnActor") == 0) return m_Offsets.Functions.SpawnActor; + + return 0; + } + +} \ No newline at end of file diff --git a/Engine/CoreTypes/OffsetResolver.h b/Engine/CoreTypes/OffsetResolver.h new file mode 100644 index 0000000..6357f71 --- /dev/null +++ b/Engine/CoreTypes/OffsetResolver.h @@ -0,0 +1,246 @@ +/** + * UniversalSlashingSimulator - Offset Resolver + * + * Provides stubbed offset resolution for engine and game structures. + * All offsets are stubbed and designed to be replaced by an external + * offset finder library in the future. @timmie + * + * IMPORTANT: No hardcoded offsets should exist outside this module. + * All offset access must go through this resolver. + */ + +#pragma once + +#include "../../Core/Common.h" +#include "../../Core/Versioning/VersionInfo.h" + +namespace USS +{ + // Offset categories + enum class EOffsetCategory : uint8 + { + UObject, + UField, // Pre-4.25 field base + UStruct, + UClass, + UFunction, + UProperty, // Pre-4.25 + FField, // 4.25+ + FProperty, // 4.25+ + FFieldClass, // 4.25+ field class info + Actor, // Actor base + Controller, // Player controller + Pawn, // Player pawn + Inventory, // Inventory system + Building, // Building system + Mission, // Mission system + }; + + // Complete offset table for all required offsets + struct FOffsetTable + { + // UObject offsets + struct + { + int32 Vtable; // 0x00 + int32 ObjectFlags; // 0x08 + int32 InternalIndex; // 0x0C + int32 Class; // 0x10 + int32 Name; // 0x18 + int32 Outer; // 0x20 + } UObject; + + // UField offsets (pre-4.25) + struct + { + int32 Next; // UField::Next + } UField; + + // UStruct offsets + struct + { + int32 SuperStruct; // UStruct::SuperStruct + int32 Children; // UStruct::Children (UField*) + int32 ChildProperties; // UStruct::ChildProperties (FField*, 4.25+) + int32 PropertiesSize; // UStruct::PropertiesSize + int32 MinAlignment; // UStruct::MinAlignment + } UStruct; + + // UClass offsets + struct + { + int32 ClassDefaultObject; + int32 ClassConstructor; + } UClass; + + // UFunction offsets + struct + { + int32 FunctionFlags; + int32 NumParms; + int32 ParmsSize; + int32 ReturnValueOffset; + int32 Func; // Native function pointer + } UFunction; + + // UProperty offsets (pre-4.25) + struct + { + int32 ElementSize; + int32 Offset; + int32 PropertyFlags; + int32 Next; + } UProperty; + + // FField offsets (4.25+) + struct + { + int32 ClassPrivate; + int32 Owner; + int32 Next; + int32 NamePrivate; + int32 FlagsPrivate; + } FField; + + // FProperty offsets (4.25+) + struct + { + int32 ElementSize; + int32 Offset; + int32 PropertyFlags; + } FProperty; + + // Controller offsets (version-specific) + struct + { + int32 BuildPreviewMarker; // 0x1788 in baseline + int32 CurrentBuildableClass; // 0x1940 in baseline + int32 PreviousBuildableClass; // 0x1948 in baseline + int32 EditBuildingActor; // 0x1A48 in baseline + int32 QuickBars; // 0x1A88 in baseline + int32 BuildPreviewMarkerMID; // 0x1928 in baseline + int32 CheatManager; + int32 PlayerState; + } Controller; + + // Pawn offsets + struct + { + int32 Controller; + int32 PlayerState; + int32 CustomizationLoadout; + int32 CharacterParts; + } Pawn; + + // Inventory offsets + struct + { + int32 WorldInventory; + int32 ItemInstances; + int32 ReplicatedEntries; + } Inventory; + + // Building offsets + struct + { + int32 BuildingActor; + int32 BuildingMaterial; + int32 BuildingEditMode; + } Building; + + // Global function addresses (stubbed) + struct + { + uintptr StaticConstructObject_Internal; + uintptr StaticLoadObject; + uintptr SpawnActor; + uintptr ProcessEvent; + uintptr GObjects; + uintptr GNames; + uintptr GWorld; + } Functions; + + // Initialize with default values + FOffsetTable() + { + memset(this, 0, sizeof(*this)); + + // Set known baseline offsets (UE 4.16) + // These are placeholders - actual offsets come from resolver + + // UObject (stable across versions) + UObject.Vtable = 0x00; + UObject.ObjectFlags = 0x08; + UObject.InternalIndex = 0x0C; + UObject.Class = 0x10; + UObject.Name = 0x18; + UObject.Outer = 0x20; + + // UField + UField.Next = 0x28; + + // UStruct (varies significantly) + UStruct.SuperStruct = 0x30; + UStruct.Children = 0x38; + UStruct.ChildProperties = 0x00; // 0 = not present (pre-4.25) + UStruct.PropertiesSize = 0x40; + UStruct.MinAlignment = 0x44; + } + }; + + // Offset resolver interface + USS_INTERFACE IOffsetResolver + { + public: + virtual ~IOffsetResolver() = default; + + // Resolve offsets for the current version + virtual EResult ResolveOffsets(const FVersionInfo& Version) = 0; + + // Get resolved offset table + virtual const FOffsetTable& GetOffsets() const = 0; + + // Check if offsets are resolved + virtual bool IsResolved() const = 0; + + // Get specific offset by category and name + virtual int32 GetOffset(EOffsetCategory Category, const char* Name) const = 0; + + // Get specific offset by category string and name (convenience) + virtual int32 GetOffset(const char* CategoryName, const char* Name) const = 0; + + // Get function address + virtual uintptr GetFunctionAddress(const char* Name) const = 0; + }; + + // Stub offset resolver (returns placeholder values) + class FStubOffsetResolver : public IOffsetResolver + { + public: + FStubOffsetResolver(); + ~FStubOffsetResolver() override = default; + + EResult ResolveOffsets(const FVersionInfo& Version) override; + const FOffsetTable& GetOffsets() const override; + bool IsResolved() const override; + int32 GetOffset(EOffsetCategory Category, const char* Name) const override; + int32 GetOffset(const char* CategoryName, const char* Name) const override; + uintptr GetFunctionAddress(const char* Name) const override; + + // Get singleton + static FStubOffsetResolver& Get(); + + private: + void ApplyVersionSpecificOffsets(const FVersionInfo& Version); + + FOffsetTable m_Offsets; + bool m_bResolved; + }; + + // Convenience function + inline IOffsetResolver& GetOffsetResolver() + { + return FStubOffsetResolver::Get(); + } + +} \ No newline at end of file diff --git a/Engine/EngineCore.cpp b/Engine/EngineCore.cpp new file mode 100644 index 0000000..98513f1 --- /dev/null +++ b/Engine/EngineCore.cpp @@ -0,0 +1,465 @@ +/** + * UniversalSlashingSimulator - Engine Core Implementation + */ + +#include "EngineCore.h" +#include "../Core/Memory/Memory.h" +#include "../Core/Logging/Log.h" +#include "../Core/Versioning/VersionResolver.h" +#include "../Core/Hooks/HookTypes.h" + +namespace USS +{ + FEngineCore::FEngineCore() + : m_GObjectsAddress(0) + , m_GNamesAddress(0) + , m_GWorldAddress(0) + { + } + + FEngineCore::~FEngineCore() + { + Shutdown(); + } + + FEngineCore& FEngineCore::Get() + { + static FEngineCore Instance; + return Instance; + } + + EResult FEngineCore::Initialize() + { + if (m_Status.bFullyInitialized) + return EResult::AlreadyInitialized; + + USS_LOG("=== UniversalSlashingSimulator Engine Core ==="); + USS_LOG("Initializing engine core..."); + + EResult Result = Memory::Initialize(); + if (Result != EResult::Success) + { + USS_ERROR("Failed to initialize memory utilities"); + return Result; + } + + Result = InitializeVersion(); + if (Result != EResult::Success) + { + USS_ERROR("Failed to detect version"); + return Result; + } + + Result = InitializeOffsets(); + if (Result != EResult::Success) + { + USS_ERROR("Failed to resolve offsets"); + return Result; + } + + Result = InitializeObjectArray(); + if (Result != EResult::Success) + { + USS_ERROR("Failed to initialize object array"); + return Result; + } + + Result = InitializeNamePool(); + if (Result != EResult::Success) + { + USS_ERROR("Failed to initialize name pool"); + return Result; + } + + Result = InitializeHooks(); + if (Result != EResult::Success) + { + USS_WARN("Hook initialization returned: %s (@timmie must implement)", ResultToString(Result)); + // Non-fatal - hooks are stubbed until @timmie implements + } + + m_Status.bFullyInitialized = true; + USS_LOG("Engine core initialized successfully"); + + return EResult::Success; + } + + void FEngineCore::Shutdown() + { + if (!m_Status.bFullyInitialized) + return; + + USS_LOG("Shutting down engine core..."); + + Hook::Shutdown(); + + m_pNamePool.reset(); + m_pObjectArray.reset(); + + m_Status = FEngineCoreStatus(); + + USS_LOG("Engine core shutdown complete"); + } + + EResult FEngineCore::InitializeVersion() + { + USS_LOG("Detecting engine version..."); + + EResult Result = GetVersionResolver().DetectVersion(); + if (Result != EResult::Success) + return Result; + + m_Status.bVersionResolved = true; + return EResult::Success; + } + + EResult FEngineCore::InitializeOffsets() + { + USS_LOG("Resolving offsets..."); + + const auto& Version = GetVersionResolver().GetVersionInfo(); + EResult Result = FStubOffsetResolver::Get().ResolveOffsets(Version); + if (Result != EResult::Success) + return Result; + + m_Status.bOffsetsResolved = true; + return EResult::Success; + } + + EResult FEngineCore::InitializeObjectArray() + { + USS_LOG("Initializing object array..."); + + m_GObjectsAddress = FindGObjectsAddress(); + if (m_GObjectsAddress == 0) + { + USS_ERROR("Failed to find GObjects address"); + return EResult::PatternNotFound; + } + + USS_LOG("GObjects at 0x%llX", m_GObjectsAddress); + + m_pObjectArray = CreateObjectArray(); + if (!m_pObjectArray) + { + USS_ERROR("Failed to create object array"); + return EResult::Failed; + } + + EResult Result = m_pObjectArray->Initialize(m_GObjectsAddress); + if (Result != EResult::Success) + { + USS_ERROR("Failed to initialize object array"); + return Result; + } + + USS_LOG("Object array initialized with %d objects", m_pObjectArray->Num()); + + m_Status.bObjectArrayInitialized = true; + return EResult::Success; + } + + EResult FEngineCore::InitializeNamePool() + { + USS_LOG("Initializing name pool..."); + + m_GNamesAddress = FindGNamesAddress(); + if (m_GNamesAddress == 0) + { + USS_ERROR("Failed to find GNames address"); + return EResult::PatternNotFound; + } + + USS_LOG("GNames at 0x%llX", m_GNamesAddress); + + m_pNamePool = CreateNamePool(); + if (!m_pNamePool) + { + USS_ERROR("Failed to create name pool"); + return EResult::Failed; + } + + EResult Result = m_pNamePool->Initialize(m_GNamesAddress); + if (Result != EResult::Success) + { + USS_ERROR("Failed to initialize name pool"); + return Result; + } + + USS_LOG("Name pool initialized"); + + m_Status.bNamePoolInitialized = true; + return EResult::Success; + } + + EResult FEngineCore::InitializeHooks() + { + USS_LOG("Initializing hooks (STUB - @timmie must implement with Memcury/MinHook)..."); + + EResult Result = Hook::Initialize(); + if (Result != EResult::Success) + { + USS_WARN("Hook::Initialize() returned %s", ResultToString(Result)); + } + + // TODO: @timmie creates ProcessEvent hook here + // See HookTypes.h for implementation guide + + m_Status.bHooksInitialized = true; + return EResult::Success; + } + + uintptr FEngineCore::FindGObjectsAddress() + { + const auto& Version = GetVersionResolver().GetVersionInfo(); + + // Pattern varies by version, @timmie might need to check + FPatternResult Result = { false, 0 }; + + if (Version.EngineVersionMajor == 4 && Version.EngineVersionMinor <= 22) + { + // UE 4.16-4.22 GObjects pattern + Result = Memory::FindPatternIDA( + "48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 8B D6" + ); + + if (Result) + { + // Resolve relative address + return Memory::ResolveRelative(Result.Address, 7, 3); + } + } + else + { + // UE 4.23+ GObjects pattern + Result = Memory::FindPatternIDA( + "48 8B 05 ?? ?? ?? ?? 48 8B 0C C8 48 8D 04 D1" + ); + + if (Result) + { + return Memory::ResolveRelative(Result.Address, 7, 3); + } + } + + USS_WARN("GObjects pattern not found, using fallback..."); + + // Fallback: Return 0 (stub - would implement alternative detection) + return 0; + } + + uintptr FEngineCore::FindGNamesAddress() + { + const auto& Version = GetVersionResolver().GetVersionInfo(); + + FPatternResult Result = { false, 0 }; + + if (Version.bUseFNamePool) + { + // UE 4.23+ FNamePool pattern + Result = Memory::FindPatternIDA( + "48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? C6 05 ?? ?? ?? ?? 01" + ); + + if (Result) + { + return Memory::ResolveRelative(Result.Address, 7, 3); + } + } + else + { + // Pre-4.23 GNames pattern + Result = Memory::FindPatternIDA( + "48 8B 05 ?? ?? ?? ?? 48 85 C0 75 50 B9" + ); + + if (Result) + { + return Memory::ResolveRelative(Result.Address, 7, 3); + } + } + + USS_WARN("GNames pattern not found, using fallback..."); + return 0; + } + + uintptr FEngineCore::FindGWorldAddress() + { + FPatternResult Result = Memory::FindPatternIDA( + "48 8B 1D ?? ?? ?? ?? ?? ?? ?? 10 4C 8D 4D ?? 4C" + ); + + if (Result) + { + return Memory::ResolveRelative(Result.Address, 7, 3); + } + + USS_WARN("GWorld pattern not found"); + return 0; + } + + const FVersionInfo& FEngineCore::GetVersionInfo() const + { + return GetVersionResolver().GetVersionInfo(); + } + + const FOffsetTable& FEngineCore::GetOffsets() const + { + return GetOffsetResolver().GetOffsets(); + } + + UObjectWrapper FEngineCore::FindObject(const char* FullName) const + { + if (!m_pObjectArray || !FullName) + return UObjectWrapper(); + + // Simple linear search - in production would use hash + int32 Num = m_pObjectArray->Num(); + for (int32 i = 0; i < Num; ++i) + { + void* Obj = m_pObjectArray->GetByIndex(i); + if (Obj) + { + UObjectWrapper Wrapper(Obj); + if (Wrapper.GetFullName() == FullName) + return Wrapper; + } + } + + return UObjectWrapper(); + } + + UObjectWrapper FEngineCore::FindObjectByName(const char* Name) const + { + if (!m_pObjectArray || !Name) + return UObjectWrapper(); + + int32 Num = m_pObjectArray->Num(); + for (int32 i = 0; i < Num; ++i) + { + void* Obj = m_pObjectArray->GetByIndex(i); + if (Obj) + { + UObjectWrapper Wrapper(Obj); + if (Wrapper.GetName() == Name) + return Wrapper; + } + } + + return UObjectWrapper(); + } + + UClassWrapper FEngineCore::FindClass(const char* ClassName) const + { + if (!m_pObjectArray || !ClassName) + return UClassWrapper(); + + // Search for Class with matching name + std::string TargetName = ClassName; + int32 Num = m_pObjectArray->Num(); + + for (int32 i = 0; i < Num; ++i) + { + void* Obj = m_pObjectArray->GetByIndex(i); + if (Obj) + { + UObjectWrapper Wrapper(Obj); + + // Check if this is a UClass + if (Wrapper.IsA("Class")) + { + if (Wrapper.GetName() == TargetName) + return UClassWrapper(Obj); + } + } + } + + return UClassWrapper(); + } + + void* FEngineCore::FindLocalPlayerController() const + { + // Search for the local player controller + // Typically named "PlayerController" or "FortPlayerController" + if (!m_pObjectArray) + return nullptr; + + int32 Num = m_pObjectArray->Num(); + for (int32 i = 0; i < Num; ++i) + { + void* Obj = m_pObjectArray->GetByIndex(i); + if (Obj) + { + UObjectWrapper Wrapper(Obj); + std::string ClassName = Wrapper.GetObjectClassName(); + + // Look for FortPlayerController or FortPlayerControllerAthena + if (ClassName.find("FortPlayerController") != std::string::npos && + ClassName.find("_C") != std::string::npos) + { + return Obj; + } + } + } + + return nullptr; + } + + void* FEngineCore::GetWorld() const + { + if (m_GWorldAddress == 0) + return nullptr; + + void* World = nullptr; + Memory::Read(m_GWorldAddress, World); + return World; + } + + std::string FEngineCore::GetObjectName(void* Object) const + { + if (!Object) + return ""; + + UObjectWrapper Wrapper(Object); + return Wrapper.GetName(); + } + + std::string FEngineCore::GetObjectClassName(void* Object) const + { + if (!Object) + return ""; + + UObjectWrapper Wrapper(Object); + return Wrapper.GetObjectClassName(); + } + + void* FEngineCore::GetObjectClass(void* Object) const + { + if (!Object) + return nullptr; + + // Read UClass* from UObject (typically at offset 0x10) + const auto& Offsets = GetOffsets(); + int32 ClassOffset = Offsets.UObject.Class; + if (ClassOffset == 0) + ClassOffset = 0x10; // Default offset + + void* Class = nullptr; + Memory::Read(reinterpret_cast(Object) + ClassOffset, Class); + return Class; + } + + std::string FEngineCore::GetNameFromIndex(int32 NameIndex) const + { + if (!m_pNamePool) + return ""; + + return m_pNamePool->GetNameString(NameIndex); + } + + IOffsetResolver& FEngineCore::GetOffsetResolver() const + { + return FStubOffsetResolver::Get(); + } + +} \ No newline at end of file diff --git a/Engine/EngineCore.h b/Engine/EngineCore.h new file mode 100644 index 0000000..4d468e4 --- /dev/null +++ b/Engine/EngineCore.h @@ -0,0 +1,138 @@ +/** + * UniversalSlashingSimulator - Engine Core + * + * Central initialization and management of all engine abstractions. + * Provides unified access to version resolver, object array, name pool, + * and other core engine systems. + */ + +#pragma once + +#include "../Core/Common.h" +#include "../Core/Versioning/VersionInfo.h" +#include "CoreTypes/ObjectArray.h" +#include "CoreTypes/NamePool.h" +#include "CoreTypes/OffsetResolver.h" +#include "UObject/UObjectWrapper.h" +#include + +namespace USS +{ + // Engine core initialization status + struct FEngineCoreStatus + { + bool bVersionResolved; + bool bOffsetsResolved; + bool bObjectArrayInitialized; + bool bNamePoolInitialized; + bool bHooksInitialized; + bool bFullyInitialized; + + FEngineCoreStatus() + : bVersionResolved(false) + , bOffsetsResolved(false) + , bObjectArrayInitialized(false) + , bNamePoolInitialized(false) + , bHooksInitialized(false) + , bFullyInitialized(false) + {} + }; + + // Engine core manager + class FEngineCore + { + public: + USS_NON_COPYABLE(FEngineCore) + USS_NON_MOVABLE(FEngineCore) + + // Get singleton instance + static FEngineCore& Get(); + + // Initialize engine core (all subsystems) + EResult Initialize(); + + // Shutdown engine core + void Shutdown(); + + // Get initialization status + const FEngineCoreStatus& GetStatus() const { return m_Status; } + + // Check if fully initialized + bool IsInitialized() const { return m_Status.bFullyInitialized; } + + // Access to core systems + const FVersionInfo& GetVersionInfo() const; + const FOffsetTable& GetOffsets() const; + IObjectArray* GetObjectArray() const { return m_pObjectArray.get(); } + INamePool* GetNamePool() const { return m_pNamePool.get(); } + + // Object lookup utilities + UObjectWrapper FindObject(const char* FullName) const; + UObjectWrapper FindObjectByName(const char* Name) const; + UClassWrapper FindClass(const char* ClassName) const; + + // Player/World utilities + void* FindLocalPlayerController() const; + void* GetWorld() const; + + // Object name utilities + std::string GetObjectName(void* Object) const; + std::string GetObjectClassName(void* Object) const; + void* GetObjectClass(void* Object) const; + std::string GetNameFromIndex(int32 NameIndex) const; + + // Offset resolver access + IOffsetResolver& GetOffsetResolver() const; + + // Object iteration + template + void ForEachObject(Callback&& Func) const + { + if (!m_pObjectArray) + return; + + int32 Num = m_pObjectArray->Num(); + for (int32 i = 0; i < Num; ++i) + { + void* Obj = m_pObjectArray->GetByIndex(i); + if (Obj) + { + if (!Func(UObjectWrapper(Obj))) + break; // Callback returned false, stop iteration + } + } + } + + private: + FEngineCore(); + ~FEngineCore(); + + // Initialization steps + EResult InitializeVersion(); + EResult InitializeOffsets(); + EResult InitializeObjectArray(); + EResult InitializeNamePool(); + EResult InitializeHooks(); + + // Find addresses via patterns + uintptr FindGObjectsAddress(); + uintptr FindGNamesAddress(); + uintptr FindGWorldAddress(); + + FEngineCoreStatus m_Status; + + std::unique_ptr m_pObjectArray; + std::unique_ptr m_pNamePool; + + uintptr m_GObjectsAddress; + uintptr m_GNamesAddress; + uintptr m_GWorldAddress; + }; + + // Convenience function + inline FEngineCore& GetEngineCore() + { + return FEngineCore::Get(); + } + +} \ No newline at end of file diff --git a/Engine/Events/ProcessEventDispatcher.cpp b/Engine/Events/ProcessEventDispatcher.cpp new file mode 100644 index 0000000..69ad3d2 --- /dev/null +++ b/Engine/Events/ProcessEventDispatcher.cpp @@ -0,0 +1,492 @@ +/** + * UniversalSlashingSimulator - ProcessEvent Dispatcher Implementation + */ + +#include "ProcessEventDispatcher.h" +#include "../../Core/Memory/Memory.h" +#include "../../Core/Logging/Log.h" +#include "../EngineCore.h" +#include + +namespace USS +{ + //========================================================================= + // FProcessEventContext Methods + //========================================================================= + + const FParsedParameter* FProcessEventContext::GetParam(const char* Name) const + { + if (!Name) + return nullptr; + + for (const auto& Param : Params) + { + if (Param.Name == Name) + { + return &Param; + } + } + return nullptr; + } + + //========================================================================= + // FEventFilter Methods + //========================================================================= + + bool FEventFilter::Matches(const FProcessEventContext& Context) const + { + if (!ObjectClassFilter.empty()) + { + if (Context.ObjectClassName.find(ObjectClassFilter) == std::string::npos) + return false; + } + + if (!FunctionNameFilter.empty()) + { + if (Context.FunctionName != FunctionNameFilter) + return false; + } + + if (!FunctionNamePrefix.empty()) + { + if (Context.FunctionName.find(FunctionNamePrefix) != 0) + return false; + } + + if (bServerOnly && !Context.bIsRPC) + return false; + + if (bClientOnly && Context.bIsRPC) + return false; + + return true; + } + + //========================================================================= + // FProcessEventDispatcher Implementation + //========================================================================= + + static FProcessEventDispatcher g_ProcessEventDispatcher; + + FProcessEventDispatcher& GetProcessEventDispatcher() + { + return g_ProcessEventDispatcher; + } + + FProcessEventDispatcher::FProcessEventDispatcher() + : m_bInitialized(false) + , m_NextHandlerId(1) + , m_bHandlersDirty(false) + , m_TotalEventsProcessed(0) + , m_TotalEventsHandled(0) + , m_TotalEventsBlocked(0) + { + } + + FProcessEventDispatcher::~FProcessEventDispatcher() + { + Shutdown(); + } + + EResult FProcessEventDispatcher::Initialize() + { + if (m_bInitialized) + return EResult::AlreadyInitialized; + + USS_LOG("Initializing ProcessEvent Dispatcher..."); + + if (!GetPropertyIterator().IsInitialized()) + { + EResult Result = GetPropertyIterator().Initialize(); + if (Result != EResult::Success) + { + USS_ERROR("Failed to initialize property iterator"); + return Result; + } + } + + m_Handlers.clear(); + m_FunctionCache.clear(); + m_TotalEventsProcessed = 0; + m_TotalEventsHandled = 0; + m_TotalEventsBlocked = 0; + + USS_LOG("ProcessEvent Dispatcher initialized"); + + m_bInitialized = true; + return EResult::Success; + } + + void FProcessEventDispatcher::Shutdown() + { + if (!m_bInitialized) + return; + + USS_LOG("Shutting down ProcessEvent Dispatcher..."); + USS_LOG(" Events processed: %llu", m_TotalEventsProcessed); + USS_LOG(" Events handled: %llu", m_TotalEventsHandled); + USS_LOG(" Events blocked: %llu", m_TotalEventsBlocked); + + m_Handlers.clear(); + m_FunctionCache.clear(); + + m_bInitialized = false; + } + + bool FProcessEventDispatcher::OnProcessEvent(void* Object, void* Function, void* Parameters) + { + if (!m_bInitialized) + return true; + + ++m_TotalEventsProcessed; + + if (m_bHandlersDirty) + { + SortHandlers(); + } + + if (m_Handlers.empty()) + return true; + + FProcessEventContext Context; + Context.Object = Object; + Context.Function = Function; + Context.Parameters = Parameters; + + Context.ObjectName = GetEngineCore().GetObjectName(Object); + Context.ObjectClassName = GetEngineCore().GetObjectClassName(Object); + Context.FunctionName = GetEngineCore().GetObjectName(Function); + + Context.bIsRPC = (Context.FunctionName.find("Server") == 0) || + (Context.FunctionName.find("Client") == 0); + Context.bIsMulticast = (Context.FunctionName.find("Multicast") != std::string::npos); + + bool bNeedsParsing = false; + for (const auto& Handler : m_Handlers) + { + if (Handler.bEnabled && Handler.Filter.Matches(Context)) + { + bNeedsParsing = true; + break; + } + } + + if (bNeedsParsing) + { + ParseParameters(Function, Parameters, Context); + } + + bool bAllowExecution = true; + + for (const auto& Handler : m_Handlers) + { + if (!Handler.bEnabled) + continue; + + if (!Handler.Filter.Matches(Context)) + continue; + + ++m_TotalEventsHandled; + + bool bContinue = Handler.Handler(Context); + + if (!bContinue) + { + bAllowExecution = false; + ++m_TotalEventsBlocked; + break; + } + } + + return bAllowExecution; + } + + int32 FProcessEventDispatcher::RegisterHandler( + const std::string& Name, + const FEventFilter& Filter, + FProcessEventHandler Handler, + int32 Priority) + { + if (!Handler) + return 0; + + FRegisteredHandler Registration; + Registration.HandlerId = m_NextHandlerId++; + Registration.Name = Name; + Registration.Filter = Filter; + Registration.Handler = std::move(Handler); + Registration.Priority = Priority; + Registration.bEnabled = true; + + m_Handlers.push_back(std::move(Registration)); + m_bHandlersDirty = true; + + USS_LOG("Registered ProcessEvent handler: %s (ID: %d, Priority: %d)", + Name.c_str(), Registration.HandlerId, Priority); + + return Registration.HandlerId; + } + + void FProcessEventDispatcher::UnregisterHandler(int32 HandlerId) + { + auto It = std::remove_if(m_Handlers.begin(), m_Handlers.end(), + [HandlerId](const FRegisteredHandler& H) { + return H.HandlerId == HandlerId; + }); + + if (It != m_Handlers.end()) + { + USS_LOG("Unregistered ProcessEvent handler ID: %d", HandlerId); + m_Handlers.erase(It, m_Handlers.end()); + } + } + + void FProcessEventDispatcher::SetHandlerEnabled(int32 HandlerId, bool bEnabled) + { + for (auto& Handler : m_Handlers) + { + if (Handler.HandlerId == HandlerId) + { + Handler.bEnabled = bEnabled; + USS_LOG("Handler %d %s", HandlerId, bEnabled ? "enabled" : "disabled"); + break; + } + } + } + + bool FProcessEventDispatcher::ParseParameters(void* Function, void* Parameters, FProcessEventContext& OutContext) + { + if (!Function || !Parameters) + return false; + + std::vector ParamInfos; + if (!GetFunctionInfo(Function, ParamInfos)) + return false; + + OutContext.Params.clear(); + OutContext.Params.reserve(ParamInfos.size()); + + for (const auto& PropInfo : ParamInfos) + { + if (!(PropInfo.PropertyFlags & EPropertyFlags::CPF_Parm)) + continue; + + FParsedParameter Param; + if (ReadParameterValue(Parameters, PropInfo, Param)) + { + if (PropInfo.PropertyFlags & EPropertyFlags::CPF_ReturnParm) + { + OutContext.bHasReturnValue = true; + } + OutContext.Params.push_back(std::move(Param)); + } + } + + return true; + } + + bool FProcessEventDispatcher::ReadParameterValue(void* Parameters, const FPropertyInfo& PropInfo, FParsedParameter& OutParam) + { + if (!Parameters) + return false; + + OutParam.Name = PropInfo.Name; + OutParam.Type = PropInfo.Type; + OutParam.Offset = PropInfo.Offset; + OutParam.Size = PropInfo.ElementSize; + OutParam.Flags = PropInfo.PropertyFlags; + + uintptr ParamAddr = reinterpret_cast(Parameters) + PropInfo.Offset; + + switch (PropInfo.Type) + { + case EPropertyType::BoolProperty: + Memory::Read(ParamAddr, OutParam.BoolValue); + break; + + case EPropertyType::ByteProperty: + case EPropertyType::Int8Property: + { + int8 Value = 0; + Memory::Read(ParamAddr, Value); + OutParam.IntValue = Value; + break; + } + + case EPropertyType::Int16Property: + case EPropertyType::UInt16Property: + { + int16 Value = 0; + Memory::Read(ParamAddr, Value); + OutParam.IntValue = Value; + break; + } + + case EPropertyType::IntProperty: + case EPropertyType::UInt32Property: + Memory::Read(ParamAddr, OutParam.IntValue); + break; + + case EPropertyType::Int64Property: + case EPropertyType::UInt64Property: + Memory::Read(ParamAddr, OutParam.Int64Value); + break; + + case EPropertyType::FloatProperty: + Memory::Read(ParamAddr, OutParam.FloatValue); + break; + + case EPropertyType::DoubleProperty: + Memory::Read(ParamAddr, OutParam.DoubleValue); + break; + + case EPropertyType::ObjectProperty: + case EPropertyType::ClassProperty: + case EPropertyType::InterfaceProperty: + case EPropertyType::WeakObjectProperty: + case EPropertyType::LazyObjectProperty: + case EPropertyType::SoftObjectProperty: + Memory::Read(ParamAddr, OutParam.PointerValue); + break; + + case EPropertyType::NameProperty: + { + // FName is typically int32 ComparisonIndex + int32 Number + int32 NameIndex = 0; + Memory::Read(ParamAddr, NameIndex); + OutParam.StringValue = GetEngineCore().GetNameFromIndex(NameIndex); + break; + } + + case EPropertyType::StrProperty: + { + // FString is TArray + // Try to read as FString + uintptr DataPtr = 0; + int32 Len = 0; + Memory::Read(ParamAddr, DataPtr); + Memory::Read(ParamAddr + sizeof(uintptr), Len); + + if (DataPtr && Len > 0 && Len < 4096) + { + // Read as wide string + std::wstring WideStr(Len, L'\0'); + for (int32 i = 0; i < Len; ++i) + { + wchar_t Ch = 0; + Memory::Read(DataPtr + (i * sizeof(wchar_t)), Ch); + WideStr[i] = Ch; + } + + // Convert to narrow + OutParam.StringValue.reserve(Len); + for (wchar_t Ch : WideStr) + { + if (Ch == 0) break; + OutParam.StringValue += static_cast(Ch); + } + } + break; + } + + case EPropertyType::StructProperty: + // Store pointer to struct data + OutParam.PointerValue = reinterpret_cast(ParamAddr); + break; + + case EPropertyType::ArrayProperty: + // Store pointer to array + OutParam.PointerValue = reinterpret_cast(ParamAddr); + break; + + default: + // Store raw pointer for unknown types + OutParam.PointerValue = reinterpret_cast(ParamAddr); + break; + } + + return true; + } + + bool FProcessEventDispatcher::WriteParameterValue(void* Parameters, const FPropertyInfo& PropInfo, const FParsedParameter& Param) + { + if (!Parameters) + return false; + + uintptr ParamAddr = reinterpret_cast(Parameters) + PropInfo.Offset; + + switch (PropInfo.Type) + { + case EPropertyType::BoolProperty: + return Memory::Write(ParamAddr, Param.BoolValue); + + case EPropertyType::IntProperty: + return Memory::Write(ParamAddr, Param.IntValue); + + case EPropertyType::Int64Property: + return Memory::Write(ParamAddr, Param.Int64Value); + + case EPropertyType::FloatProperty: + return Memory::Write(ParamAddr, Param.FloatValue); + + case EPropertyType::DoubleProperty: + return Memory::Write(ParamAddr, Param.DoubleValue); + + case EPropertyType::ObjectProperty: + return Memory::Write(ParamAddr, Param.PointerValue); + + default: + return false; + } + } + + bool FProcessEventDispatcher::GetFunctionInfo(void* Function, std::vector& OutParams) + { + if (!Function) + return false; + + // Check cache + auto It = m_FunctionCache.find(Function); + if (It != m_FunctionCache.end()) + { + OutParams = It->second; + return true; + } + + // Parse function parameters using property iterator + std::vector Params; + + GetPropertyIterator().ForEachProperty(Function, + [&Params](const FPropertyInfo& Info) -> bool { + if (Info.PropertyFlags & EPropertyFlags::CPF_Parm) + { + Params.push_back(Info); + } + return true; // Continue + }, + false); // Don't include super (functions don't inherit params) + + // Sort by offset to ensure correct parameter order + std::sort(Params.begin(), Params.end(), + [](const FPropertyInfo& A, const FPropertyInfo& B) { + return A.Offset < B.Offset; + }); + + // Cache result + m_FunctionCache[Function] = Params; + OutParams = std::move(Params); + + return true; + } + + void FProcessEventDispatcher::SortHandlers() + { + std::sort(m_Handlers.begin(), m_Handlers.end(), + [](const FRegisteredHandler& A, const FRegisteredHandler& B) { + return A.Priority > B.Priority; + }); + + m_bHandlersDirty = false; + } + +} \ No newline at end of file diff --git a/Engine/Events/ProcessEventDispatcher.h b/Engine/Events/ProcessEventDispatcher.h new file mode 100644 index 0000000..0361f84 --- /dev/null +++ b/Engine/Events/ProcessEventDispatcher.h @@ -0,0 +1,403 @@ +/** + * UniversalSlashingSimulator - ProcessEvent Dispatcher + * + * Provides version-agnostic ProcessEvent handling with automatic + * parameter parsing based on UFunction reflection. + * + * ProcessEvent signature varies slightly between versions: + * - All versions: void ProcessEvent(UFunction*, void* Parms) + * + * Parameter handling varies: + * - Pre-4.25: Parameters parsed via UProperty chain + * - 4.25+: Parameters parsed via FProperty chain + * + * This dispatcher abstracts the version differences and provides + * type-safe parameter access for hooked functions. + */ + +#pragma once + +#include "../../Core/Common.h" +#include "../../Core/Hooks/HookTypes.h" +#include "../Reflection/PropertyIterator.h" +#include +#include +#include +#include + +namespace USS +{ + /** + * Parsed function parameter + */ + struct FParsedParameter + { + std::string Name; + EPropertyType Type; + int32 Offset; + int32 Size; + uint64 Flags; + + // Value storage (for common types) + union + { + bool BoolValue; + int32 IntValue; + int64 Int64Value; + float FloatValue; + double DoubleValue; + void* PointerValue; + }; + + // For string/name types + std::string StringValue; + + FParsedParameter() + : Type(EPropertyType::Unknown) + , Offset(0) + , Size(0) + , Flags(0) + , PointerValue(nullptr) + {} + + bool IsOutParam() const + { + return (Flags & EPropertyFlags::CPF_OutParm) != 0; + } + + bool IsReturnParam() const + { + return (Flags & EPropertyFlags::CPF_ReturnParm) != 0; + } + + bool IsReferenceParam() const + { + return (Flags & EPropertyFlags::CPF_ReferenceParm) != 0; + } + }; + + /** + * Parsed ProcessEvent call context + */ + struct FProcessEventContext + { + void* Object; // UObject* calling ProcessEvent + void* Function; // UFunction* being called + void* Parameters; // Raw parameter block + + std::string ObjectName; + std::string ObjectClassName; + std::string FunctionName; + + std::vector Params; + + // Timing info + double Timestamp; + + // Flags + bool bIsRPC; // Remote procedure call + bool bIsMulticast; // Multicast delegate + bool bHasReturnValue; // Function has return value + + FProcessEventContext() + : Object(nullptr) + , Function(nullptr) + , Parameters(nullptr) + , Timestamp(0.0) + , bIsRPC(false) + , bIsMulticast(false) + , bHasReturnValue(false) + {} + + /** + * Get parameter by name + */ + const FParsedParameter* GetParam(const char* Name) const; + + /** + * Get parameter value by name (templated for type safety) + */ + template + bool GetParamValue(const char* Name, T& OutValue) const; + + /** + * Set output parameter value + */ + template + bool SetOutParamValue(const char* Name, const T& Value); + }; + + /** + * ProcessEvent handler callback type + */ + using FProcessEventHandler = std::function; + + /** + * Event filter for selective handling + */ + struct FEventFilter + { + std::string ObjectClassFilter; // Match object class name (empty = all) + std::string FunctionNameFilter; // Match function name (empty = all) + std::string FunctionNamePrefix; // Match function name prefix + bool bServerOnly; // Only server RPC calls + bool bClientOnly; // Only client RPC calls + + FEventFilter() + : bServerOnly(false) + , bClientOnly(false) + {} + + bool Matches(const FProcessEventContext& Context) const; + }; + + /** + * Registered event handler + */ + struct FRegisteredHandler + { + int32 HandlerId; + std::string Name; + FEventFilter Filter; + FProcessEventHandler Handler; + int32 Priority; // Higher = called first + bool bEnabled; + + FRegisteredHandler() + : HandlerId(0) + , Priority(0) + , bEnabled(true) + {} + }; + + /** + * ProcessEvent Dispatcher + * + * Central hub for ProcessEvent interception. Parses function + * parameters using version-appropriate reflection and dispatches + * to registered handlers. + */ + class FProcessEventDispatcher + { + public: + FProcessEventDispatcher(); + ~FProcessEventDispatcher(); + + /** + * Initialize the dispatcher + */ + EResult Initialize(); + + /** + * Shutdown and cleanup + */ + void Shutdown(); + + /** + * Main entry point - called from ProcessEvent hook + * @param Object - UObject* calling ProcessEvent + * @param Function - UFunction* being executed + * @param Parameters - Parameter block + * @return true to allow original execution, false to skip + */ + bool OnProcessEvent(void* Object, void* Function, void* Parameters); + + /** + * Register an event handler + * @param Name - Handler name for debugging + * @param Filter - Event filter criteria + * @param Handler - Callback function + * @param Priority - Execution priority (higher = first) + * @return Handler ID for later removal + */ + int32 RegisterHandler( + const std::string& Name, + const FEventFilter& Filter, + FProcessEventHandler Handler, + int32 Priority = 0); + + /** + * Unregister a handler by ID + */ + void UnregisterHandler(int32 HandlerId); + + /** + * Enable/disable a handler + */ + void SetHandlerEnabled(int32 HandlerId, bool bEnabled); + + /** + * Get handler count + */ + int32 GetHandlerCount() const { return static_cast(m_Handlers.size()); } + + /** + * Check if initialized + */ + bool IsInitialized() const { return m_bInitialized; } + + // Statistics + uint64 GetTotalEventsProcessed() const { return m_TotalEventsProcessed; } + uint64 GetTotalEventsHandled() const { return m_TotalEventsHandled; } + uint64 GetTotalEventsBlocked() const { return m_TotalEventsBlocked; } + + private: + /** + * Parse function parameters into context + */ + bool ParseParameters(void* Function, void* Parameters, FProcessEventContext& OutContext); + + /** + * Read parameter value from raw memory + */ + bool ReadParameterValue(void* Parameters, const FPropertyInfo& PropInfo, FParsedParameter& OutParam); + + /** + * Write parameter value to raw memory + */ + bool WriteParameterValue(void* Parameters, const FPropertyInfo& PropInfo, const FParsedParameter& Param); + + /** + * Get cached function info or parse it + */ + bool GetFunctionInfo(void* Function, std::vector& OutParams); + + /** + * Sort handlers by priority + */ + void SortHandlers(); + + // State + bool m_bInitialized; + int32 m_NextHandlerId; + + // Registered handlers (sorted by priority) + std::vector m_Handlers; + bool m_bHandlersDirty; + + // Function parameter cache (UFunction* -> params) + std::unordered_map> m_FunctionCache; + + // Statistics + uint64 m_TotalEventsProcessed; + uint64 m_TotalEventsHandled; + uint64 m_TotalEventsBlocked; + }; + + /** + * Global dispatcher accessor + */ + FProcessEventDispatcher& GetProcessEventDispatcher(); + + //========================================================================= + // Template Implementations + //========================================================================= + + template + bool FProcessEventContext::GetParamValue(const char* Name, T& OutValue) const + { + const FParsedParameter* Param = GetParam(Name); + if (!Param) + return false; + + // Type-specific extraction + if constexpr (std::is_same_v) + { + if (Param->Type == EPropertyType::BoolProperty) + { + OutValue = Param->BoolValue; + return true; + } + } + else if constexpr (std::is_same_v) + { + if (Param->Type == EPropertyType::IntProperty || + Param->Type == EPropertyType::ByteProperty) + { + OutValue = Param->IntValue; + return true; + } + } + else if constexpr (std::is_same_v) + { + if (Param->Type == EPropertyType::Int64Property) + { + OutValue = Param->Int64Value; + return true; + } + } + else if constexpr (std::is_same_v) + { + if (Param->Type == EPropertyType::FloatProperty) + { + OutValue = Param->FloatValue; + return true; + } + } + else if constexpr (std::is_same_v) + { + if (Param->Type == EPropertyType::DoubleProperty) + { + OutValue = Param->DoubleValue; + return true; + } + } + else if constexpr (std::is_same_v) + { + if (Param->Type == EPropertyType::StrProperty || + Param->Type == EPropertyType::NameProperty) + { + OutValue = Param->StringValue; + return true; + } + } + else if constexpr (std::is_pointer_v) + { + if (Param->Type == EPropertyType::ObjectProperty || + Param->Type == EPropertyType::ClassProperty) + { + OutValue = static_cast(Param->PointerValue); + return true; + } + } + + return false; + } + + template + bool FProcessEventContext::SetOutParamValue(const char* Name, const T& Value) + { + for (auto& Param : Params) + { + if (Param.Name == Name && Param.IsOutParam()) + { + // Write value to parameter memory + if (Parameters && Param.Offset >= 0) + { + void* ParamPtr = static_cast(Parameters) + Param.Offset; + + if constexpr (std::is_same_v) + { + *static_cast(ParamPtr) = Value; + Param.BoolValue = Value; + } + else if constexpr (std::is_same_v) + { + *static_cast(ParamPtr) = Value; + Param.IntValue = Value; + } + else if constexpr (std::is_same_v) + { + *static_cast(ParamPtr) = Value; + Param.FloatValue = Value; + } + // Add more types as needed + + return true; + } + } + } + return false; + } + +} \ No newline at end of file diff --git a/Engine/Reflection/PropertyIterator.cpp b/Engine/Reflection/PropertyIterator.cpp new file mode 100644 index 0000000..0716f59 --- /dev/null +++ b/Engine/Reflection/PropertyIterator.cpp @@ -0,0 +1,620 @@ +/** + * UniversalSlashingSimulator - Property Iterator Implementation + */ + +#include "PropertyIterator.h" +#include "../../Core/Memory/Memory.h" +#include "../../Core/Logging/Log.h" +#include "../../Core/Versioning/VersionResolver.h" +#include "../EngineCore.h" + +namespace USS +{ + //========================================================================= + // FPropertyInfo Methods + //========================================================================= + + bool FPropertyInfo::IsEditable() const + { + return (PropertyFlags & EPropertyFlags::CPF_Edit) != 0; + } + + bool FPropertyInfo::IsBlueprintVisible() const + { + return (PropertyFlags & EPropertyFlags::CPF_BlueprintVisible) != 0; + } + + bool FPropertyInfo::IsNative() const + { + return true; + } + + bool FPropertyInfo::IsReplicated() const + { + return (PropertyFlags & EPropertyFlags::CPF_Net) != 0; + } + + bool FPropertyInfo::IsSaveGame() const + { + return (PropertyFlags & EPropertyFlags::CPF_SaveGame) != 0; + } + + //========================================================================= + // FUPropertyIterator Implementation (Pre-4.25) + //========================================================================= + + FUPropertyIterator::FUPropertyIterator() + : m_ChildrenOffset(0) + , m_PropertyLinkOffset(0) + , m_SuperStructOffset(0) + , m_UProperty_ArrayDimOffset(0) + , m_UProperty_ElementSizeOffset(0) + , m_UProperty_PropertyFlagsOffset(0) + , m_UProperty_OffsetOffset(0) + , m_UProperty_NextOffset(0) + , m_bInitialized(false) + { + } + + EResult FUPropertyIterator::Initialize() + { + if (m_bInitialized) + return EResult::AlreadyInitialized; + + USS_LOG("Initializing UProperty iterator (Pre-4.25 mode)..."); + + // Get offsets from resolver + auto& Offsets = GetEngineCore().GetOffsetResolver(); + + // UStruct offsets + m_ChildrenOffset = Offsets.GetOffset("UStruct", "Children"); + m_PropertyLinkOffset = Offsets.GetOffset("UStruct", "PropertyLink"); + m_SuperStructOffset = Offsets.GetOffset("UStruct", "SuperStruct"); + + // UProperty offsets (derived from UField which is derived from UObject) + m_UProperty_NextOffset = Offsets.GetOffset("UField", "Next"); + m_UProperty_ArrayDimOffset = Offsets.GetOffset("UProperty", "ArrayDim"); + m_UProperty_ElementSizeOffset = Offsets.GetOffset("UProperty", "ElementSize"); + m_UProperty_PropertyFlagsOffset = Offsets.GetOffset("UProperty", "PropertyFlags"); + m_UProperty_OffsetOffset = Offsets.GetOffset("UProperty", "Offset_Internal"); + + // Use defaults if not resolved + if (m_ChildrenOffset == 0) m_ChildrenOffset = 0x48; // Typical for UE4.19 + if (m_PropertyLinkOffset == 0) m_PropertyLinkOffset = 0x50; + if (m_SuperStructOffset == 0) m_SuperStructOffset = 0x40; + if (m_UProperty_NextOffset == 0) m_UProperty_NextOffset = 0x30; + if (m_UProperty_ArrayDimOffset == 0) m_UProperty_ArrayDimOffset = 0x38; + if (m_UProperty_ElementSizeOffset == 0) m_UProperty_ElementSizeOffset = 0x3C; + if (m_UProperty_PropertyFlagsOffset == 0) m_UProperty_PropertyFlagsOffset = 0x40; + if (m_UProperty_OffsetOffset == 0) m_UProperty_OffsetOffset = 0x4C; + + USS_LOG("UProperty iterator offsets:"); + USS_LOG(" UStruct::Children = 0x%X", m_ChildrenOffset); + USS_LOG(" UStruct::PropertyLink = 0x%X", m_PropertyLinkOffset); + USS_LOG(" UProperty::Next = 0x%X", m_UProperty_NextOffset); + USS_LOG(" UProperty::ArrayDim = 0x%X", m_UProperty_ArrayDimOffset); + USS_LOG(" UProperty::Offset = 0x%X", m_UProperty_OffsetOffset); + + m_bInitialized = true; + return EResult::Success; + } + + bool FUPropertyIterator::IsInitialized() const + { + return m_bInitialized; + } + + void FUPropertyIterator::ForEachProperty(void* Struct, FPropertyCallback Callback, bool bIncludeSuper) + { + if (!m_bInitialized || !Struct || !Callback) + return; + + // Use PropertyLink for optimized iteration + void* Property = GetPropertyLink(Struct); + + while (Property) + { + FPropertyInfo Info; + if (ReadPropertyInfo(Property, Info)) + { + if (!Callback(Info)) + return; // Callback requested stop + } + + Property = GetNextProperty(Property); + } + + // Include super class properties if requested + if (bIncludeSuper) + { + void* SuperStruct = nullptr; + if (Memory::Read(reinterpret_cast(Struct) + m_SuperStructOffset, SuperStruct)) + { + if (SuperStruct) + { + ForEachProperty(SuperStruct, Callback, true); + } + } + } + } + + bool FUPropertyIterator::FindProperty(void* Struct, const char* PropertyName, FPropertyInfo& OutInfo) + { + if (!m_bInitialized || !Struct || !PropertyName) + return false; + + bool bFound = false; + std::string TargetName(PropertyName); + + ForEachProperty(Struct, [&](const FPropertyInfo& Info) -> bool { + if (Info.Name == TargetName) + { + OutInfo = Info; + bFound = true; + return false; // Stop iteration + } + return true; // Continue + }, true); + + return bFound; + } + + bool FUPropertyIterator::FindPropertyByOffset(void* Struct, int32 Offset, FPropertyInfo& OutInfo) + { + if (!m_bInitialized || !Struct) + return false; + + bool bFound = false; + + ForEachProperty(Struct, [&](const FPropertyInfo& Info) -> bool { + if (Info.Offset == Offset) + { + OutInfo = Info; + bFound = true; + return false; + } + return true; + }, true); + + return bFound; + } + + int32 FUPropertyIterator::GetPropertyCount(void* Struct, bool bIncludeSuper) + { + if (!m_bInitialized || !Struct) + return 0; + + int32 Count = 0; + + ForEachProperty(Struct, [&](const FPropertyInfo&) -> bool { + ++Count; + return true; + }, bIncludeSuper); + + return Count; + } + + void* FUPropertyIterator::GetPropertyListHead(void* Struct) + { + return GetPropertyLink(Struct); + } + + bool FUPropertyIterator::ReadPropertyInfo(void* Property, FPropertyInfo& OutInfo) + { + if (!Property) + return false; + + uintptr PropAddr = reinterpret_cast(Property); + + OutInfo.PropertyPtr = Property; + + // Get name from UObject (property is UObject-derived) + OutInfo.Name = GetEngineCore().GetObjectName(Property); + + // Get class name + OutInfo.ClassName = GetPropertyClassName(Property); + OutInfo.Type = ClassifyProperty(OutInfo.ClassName); + + // Read property data + Memory::Read(PropAddr + m_UProperty_ArrayDimOffset, OutInfo.ArrayDim); + Memory::Read(PropAddr + m_UProperty_ElementSizeOffset, OutInfo.ElementSize); + Memory::Read(PropAddr + m_UProperty_PropertyFlagsOffset, OutInfo.PropertyFlags); + Memory::Read(PropAddr + m_UProperty_OffsetOffset, OutInfo.Offset); + + return true; + } + + void* FUPropertyIterator::GetPropertyLink(void* Struct) + { + if (!Struct) + return nullptr; + + void* PropertyLink = nullptr; + Memory::Read(reinterpret_cast(Struct) + m_PropertyLinkOffset, PropertyLink); + return PropertyLink; + } + + void* FUPropertyIterator::GetNextProperty(void* Property) + { + if (!Property) + return nullptr; + + void* Next = nullptr; + Memory::Read(reinterpret_cast(Property) + m_UProperty_NextOffset, Next); + return Next; + } + + std::string FUPropertyIterator::GetPropertyClassName(void* Property) + { + if (!Property) + return ""; + + // Get UClass* of the property + void* Class = GetEngineCore().GetObjectClass(Property); + if (Class) + { + return GetEngineCore().GetObjectName(Class); + } + return ""; + } + + EPropertyType FUPropertyIterator::ClassifyProperty(const std::string& ClassName) + { + if (ClassName == "ByteProperty") return EPropertyType::ByteProperty; + if (ClassName == "Int8Property") return EPropertyType::Int8Property; + if (ClassName == "Int16Property") return EPropertyType::Int16Property; + if (ClassName == "IntProperty") return EPropertyType::IntProperty; + if (ClassName == "Int64Property") return EPropertyType::Int64Property; + if (ClassName == "UInt16Property") return EPropertyType::UInt16Property; + if (ClassName == "UInt32Property") return EPropertyType::UInt32Property; + if (ClassName == "UInt64Property") return EPropertyType::UInt64Property; + if (ClassName == "FloatProperty") return EPropertyType::FloatProperty; + if (ClassName == "DoubleProperty") return EPropertyType::DoubleProperty; + if (ClassName == "BoolProperty") return EPropertyType::BoolProperty; + if (ClassName == "StrProperty") return EPropertyType::StrProperty; + if (ClassName == "NameProperty") return EPropertyType::NameProperty; + if (ClassName == "TextProperty") return EPropertyType::TextProperty; + if (ClassName == "ObjectProperty") return EPropertyType::ObjectProperty; + if (ClassName == "ClassProperty") return EPropertyType::ClassProperty; + if (ClassName == "InterfaceProperty") return EPropertyType::InterfaceProperty; + if (ClassName == "WeakObjectProperty") return EPropertyType::WeakObjectProperty; + if (ClassName == "LazyObjectProperty") return EPropertyType::LazyObjectProperty; + if (ClassName == "SoftObjectProperty") return EPropertyType::SoftObjectProperty; + if (ClassName == "SoftClassProperty") return EPropertyType::SoftClassProperty; + if (ClassName == "StructProperty") return EPropertyType::StructProperty; + if (ClassName == "ArrayProperty") return EPropertyType::ArrayProperty; + if (ClassName == "MapProperty") return EPropertyType::MapProperty; + if (ClassName == "SetProperty") return EPropertyType::SetProperty; + if (ClassName == "DelegateProperty") return EPropertyType::DelegateProperty; + if (ClassName == "MulticastDelegateProperty") return EPropertyType::MulticastDelegateProperty; + if (ClassName == "MulticastInlineDelegateProperty") return EPropertyType::MulticastInlineDelegateProperty; + if (ClassName == "MulticastSparseDelegateProperty") return EPropertyType::MulticastSparseDelegateProperty; + if (ClassName == "EnumProperty") return EPropertyType::EnumProperty; + if (ClassName == "FieldPathProperty") return EPropertyType::FieldPathProperty; + return EPropertyType::Unknown; + } + + //========================================================================= + // FFFieldPropertyIterator Implementation (4.25+) + //========================================================================= + + FFFieldPropertyIterator::FFFieldPropertyIterator() + : m_ChildPropertiesOffset(0) + , m_SuperStructOffset(0) + , m_FField_ClassOffset(0) + , m_FField_OwnerOffset(0) + , m_FField_NextOffset(0) + , m_FField_NameOffset(0) + , m_FProperty_ArrayDimOffset(0) + , m_FProperty_ElementSizeOffset(0) + , m_FProperty_PropertyFlagsOffset(0) + , m_FProperty_OffsetOffset(0) + , m_FFieldClass_NameOffset(0) + , m_bInitialized(false) + { + } + + EResult FFFieldPropertyIterator::Initialize() + { + if (m_bInitialized) + return EResult::AlreadyInitialized; + + USS_LOG("Initializing FField property iterator (4.25+ mode)..."); + + // Get offsets from resolver + auto& Offsets = GetEngineCore().GetOffsetResolver(); + + // UStruct offsets (ChildProperties is new in 4.25) + m_ChildPropertiesOffset = Offsets.GetOffset("UStruct", "ChildProperties"); + m_SuperStructOffset = Offsets.GetOffset("UStruct", "SuperStruct"); + + // FField offsets + m_FField_ClassOffset = Offsets.GetOffset("FField", "ClassPrivate"); + m_FField_OwnerOffset = Offsets.GetOffset("FField", "Owner"); + m_FField_NextOffset = Offsets.GetOffset("FField", "Next"); + m_FField_NameOffset = Offsets.GetOffset("FField", "NamePrivate"); + + // FProperty offsets + m_FProperty_ArrayDimOffset = Offsets.GetOffset("FProperty", "ArrayDim"); + m_FProperty_ElementSizeOffset = Offsets.GetOffset("FProperty", "ElementSize"); + m_FProperty_PropertyFlagsOffset = Offsets.GetOffset("FProperty", "PropertyFlags"); + m_FProperty_OffsetOffset = Offsets.GetOffset("FProperty", "Offset_Internal"); + + // FFieldClass offset + m_FFieldClass_NameOffset = Offsets.GetOffset("FFieldClass", "Name"); + + // Use defaults if not resolved (typical for FN Chapter 2+) + if (m_ChildPropertiesOffset == 0) m_ChildPropertiesOffset = 0x50; + if (m_SuperStructOffset == 0) m_SuperStructOffset = 0x40; + if (m_FField_ClassOffset == 0) m_FField_ClassOffset = 0x00; + if (m_FField_OwnerOffset == 0) m_FField_OwnerOffset = 0x08; + if (m_FField_NextOffset == 0) m_FField_NextOffset = 0x20; + if (m_FField_NameOffset == 0) m_FField_NameOffset = 0x28; + if (m_FProperty_ArrayDimOffset == 0) m_FProperty_ArrayDimOffset = 0x38; + if (m_FProperty_ElementSizeOffset == 0) m_FProperty_ElementSizeOffset = 0x3C; + if (m_FProperty_PropertyFlagsOffset == 0) m_FProperty_PropertyFlagsOffset = 0x40; + if (m_FProperty_OffsetOffset == 0) m_FProperty_OffsetOffset = 0x4C; + if (m_FFieldClass_NameOffset == 0) m_FFieldClass_NameOffset = 0x00; + + USS_LOG("FField property iterator offsets:"); + USS_LOG(" UStruct::ChildProperties = 0x%X", m_ChildPropertiesOffset); + USS_LOG(" FField::Next = 0x%X", m_FField_NextOffset); + USS_LOG(" FField::NamePrivate = 0x%X", m_FField_NameOffset); + USS_LOG(" FProperty::Offset = 0x%X", m_FProperty_OffsetOffset); + + m_bInitialized = true; + return EResult::Success; + } + + bool FFFieldPropertyIterator::IsInitialized() const + { + return m_bInitialized; + } + + void FFFieldPropertyIterator::ForEachProperty(void* Struct, FPropertyCallback Callback, bool bIncludeSuper) + { + if (!m_bInitialized || !Struct || !Callback) + return; + + // Iterate ChildProperties chain (FField*) + void* Field = GetChildProperties(Struct); + + while (Field) + { + FPropertyInfo Info; + if (ReadPropertyInfo(Field, Info)) + { + if (!Callback(Info)) + return; + } + + Field = GetNextField(Field); + } + + // Include super class properties if requested + if (bIncludeSuper) + { + void* SuperStruct = nullptr; + if (Memory::Read(reinterpret_cast(Struct) + m_SuperStructOffset, SuperStruct)) + { + if (SuperStruct) + { + ForEachProperty(SuperStruct, Callback, true); + } + } + } + } + + bool FFFieldPropertyIterator::FindProperty(void* Struct, const char* PropertyName, FPropertyInfo& OutInfo) + { + if (!m_bInitialized || !Struct || !PropertyName) + return false; + + bool bFound = false; + std::string TargetName(PropertyName); + + ForEachProperty(Struct, [&](const FPropertyInfo& Info) -> bool { + if (Info.Name == TargetName) + { + OutInfo = Info; + bFound = true; + return false; + } + return true; + }, true); + + return bFound; + } + + bool FFFieldPropertyIterator::FindPropertyByOffset(void* Struct, int32 Offset, FPropertyInfo& OutInfo) + { + if (!m_bInitialized || !Struct) + return false; + + bool bFound = false; + + ForEachProperty(Struct, [&](const FPropertyInfo& Info) -> bool { + if (Info.Offset == Offset) + { + OutInfo = Info; + bFound = true; + return false; + } + return true; + }, true); + + return bFound; + } + + int32 FFFieldPropertyIterator::GetPropertyCount(void* Struct, bool bIncludeSuper) + { + if (!m_bInitialized || !Struct) + return 0; + + int32 Count = 0; + + ForEachProperty(Struct, [&](const FPropertyInfo&) -> bool { + ++Count; + return true; + }, bIncludeSuper); + + return Count; + } + + void* FFFieldPropertyIterator::GetPropertyListHead(void* Struct) + { + return GetChildProperties(Struct); + } + + bool FFFieldPropertyIterator::ReadPropertyInfo(void* Field, FPropertyInfo& OutInfo) + { + if (!Field) + return false; + + uintptr FieldAddr = reinterpret_cast(Field); + + OutInfo.PropertyPtr = Field; + + // Get name from FField (stored as FName) + // FName is at m_FField_NameOffset + int32 NameIndex = 0; + if (Memory::Read(FieldAddr + m_FField_NameOffset, NameIndex)) + { + OutInfo.Name = GetEngineCore().GetNameFromIndex(NameIndex); + } + + // Get class name from FFieldClass + OutInfo.ClassName = GetFieldClassName(Field); + OutInfo.Type = ClassifyProperty(OutInfo.ClassName); + + // Read property data + Memory::Read(FieldAddr + m_FProperty_ArrayDimOffset, OutInfo.ArrayDim); + Memory::Read(FieldAddr + m_FProperty_ElementSizeOffset, OutInfo.ElementSize); + Memory::Read(FieldAddr + m_FProperty_PropertyFlagsOffset, OutInfo.PropertyFlags); + + // Note: In 4.25+, Offset_Internal might be uint16 instead of int32 + // We read as int32 but only lower 16 bits may be valid + int32 RawOffset = 0; + Memory::Read(FieldAddr + m_FProperty_OffsetOffset, RawOffset); + OutInfo.Offset = RawOffset; + + return true; + } + + void* FFFieldPropertyIterator::GetChildProperties(void* Struct) + { + if (!Struct) + return nullptr; + + void* ChildProperties = nullptr; + Memory::Read(reinterpret_cast(Struct) + m_ChildPropertiesOffset, ChildProperties); + return ChildProperties; + } + + void* FFFieldPropertyIterator::GetNextField(void* Field) + { + if (!Field) + return nullptr; + + void* Next = nullptr; + Memory::Read(reinterpret_cast(Field) + m_FField_NextOffset, Next); + return Next; + } + + std::string FFFieldPropertyIterator::GetFieldClassName(void* Field) + { + if (!Field) + return ""; + + uintptr FieldAddr = reinterpret_cast(Field); + + // Get FFieldClass* from Field + void* FieldClass = nullptr; + if (!Memory::Read(FieldAddr + m_FField_ClassOffset, FieldClass)) + return ""; + + if (!FieldClass) + return ""; + + // FFieldClass has Name as FName at offset 0 + int32 NameIndex = 0; + if (Memory::Read(reinterpret_cast(FieldClass) + m_FFieldClass_NameOffset, NameIndex)) + { + return GetEngineCore().GetNameFromIndex(NameIndex); + } + + return ""; + } + + EPropertyType FFFieldPropertyIterator::ClassifyProperty(const std::string& ClassName) + { + // Same classification as UProperty version + if (ClassName == "ByteProperty") return EPropertyType::ByteProperty; + if (ClassName == "Int8Property") return EPropertyType::Int8Property; + if (ClassName == "Int16Property") return EPropertyType::Int16Property; + if (ClassName == "IntProperty") return EPropertyType::IntProperty; + if (ClassName == "Int64Property") return EPropertyType::Int64Property; + if (ClassName == "UInt16Property") return EPropertyType::UInt16Property; + if (ClassName == "UInt32Property") return EPropertyType::UInt32Property; + if (ClassName == "UInt64Property") return EPropertyType::UInt64Property; + if (ClassName == "FloatProperty") return EPropertyType::FloatProperty; + if (ClassName == "DoubleProperty") return EPropertyType::DoubleProperty; + if (ClassName == "BoolProperty") return EPropertyType::BoolProperty; + if (ClassName == "StrProperty") return EPropertyType::StrProperty; + if (ClassName == "NameProperty") return EPropertyType::NameProperty; + if (ClassName == "TextProperty") return EPropertyType::TextProperty; + if (ClassName == "ObjectProperty") return EPropertyType::ObjectProperty; + if (ClassName == "ClassProperty") return EPropertyType::ClassProperty; + if (ClassName == "InterfaceProperty") return EPropertyType::InterfaceProperty; + if (ClassName == "WeakObjectProperty") return EPropertyType::WeakObjectProperty; + if (ClassName == "LazyObjectProperty") return EPropertyType::LazyObjectProperty; + if (ClassName == "SoftObjectProperty") return EPropertyType::SoftObjectProperty; + if (ClassName == "SoftClassProperty") return EPropertyType::SoftClassProperty; + if (ClassName == "StructProperty") return EPropertyType::StructProperty; + if (ClassName == "ArrayProperty") return EPropertyType::ArrayProperty; + if (ClassName == "MapProperty") return EPropertyType::MapProperty; + if (ClassName == "SetProperty") return EPropertyType::SetProperty; + if (ClassName == "DelegateProperty") return EPropertyType::DelegateProperty; + if (ClassName == "MulticastDelegateProperty") return EPropertyType::MulticastDelegateProperty; + if (ClassName == "MulticastInlineDelegateProperty") return EPropertyType::MulticastInlineDelegateProperty; + if (ClassName == "MulticastSparseDelegateProperty") return EPropertyType::MulticastSparseDelegateProperty; + if (ClassName == "EnumProperty") return EPropertyType::EnumProperty; + if (ClassName == "FieldPathProperty") return EPropertyType::FieldPathProperty; + return EPropertyType::Unknown; + } + + //========================================================================= + // Factory & Global Accessor + //========================================================================= + + static std::unique_ptr g_PropertyIterator; + + std::unique_ptr CreatePropertyIterator() + { + const auto& Version = GetVersionResolver().GetVersionInfo(); + + if (Version.bUseFField) + { + USS_LOG("Creating FFFieldPropertyIterator for UE %s", + Version.GetEngineVersionString().c_str()); + return std::make_unique(); + } + else + { + USS_LOG("Creating FUPropertyIterator for UE %s", + Version.GetEngineVersionString().c_str()); + return std::make_unique(); + } + } + + IPropertyIterator& GetPropertyIterator() + { + if (!g_PropertyIterator) + { + g_PropertyIterator = CreatePropertyIterator(); + g_PropertyIterator->Initialize(); + } + return *g_PropertyIterator; + } + +} \ No newline at end of file diff --git a/Engine/Reflection/PropertyIterator.h b/Engine/Reflection/PropertyIterator.h new file mode 100644 index 0000000..73f48f1 --- /dev/null +++ b/Engine/Reflection/PropertyIterator.h @@ -0,0 +1,374 @@ +/** + * UniversalSlashingSimulator - Property Iterator Abstraction + * + * Provides version-agnostic iteration over class properties. + * Handles the UProperty vs FField/FProperty difference: + * - Pre-4.25: UProperty is UObject-derived, linked via PropertyLink + * - 4.25+: FField/FProperty is separate hierarchy, linked via Next + * + * This is critical for reflection and finding struct offsets. + */ + +#pragma once + +#include "../../Core/Common.h" +#include "../../Core/Versioning/VersionInfo.h" +#include +#include + +namespace USS +{ + /** + * Property type categories + */ + enum class EPropertyType : uint8 + { + Unknown = 0, + + // Numeric types + ByteProperty, + Int8Property, + Int16Property, + IntProperty, + Int64Property, + UInt16Property, + UInt32Property, + UInt64Property, + FloatProperty, + DoubleProperty, + + // Boolean + BoolProperty, + + // String types + StrProperty, + NameProperty, + TextProperty, + + // Object references + ObjectProperty, + ClassProperty, + InterfaceProperty, + WeakObjectProperty, + LazyObjectProperty, + SoftObjectProperty, + SoftClassProperty, + + // Structs + StructProperty, + + // Containers + ArrayProperty, + MapProperty, + SetProperty, + + // Delegates + DelegateProperty, + MulticastDelegateProperty, + MulticastInlineDelegateProperty, + MulticastSparseDelegateProperty, + + // Enum + EnumProperty, + + // Special + FieldPathProperty, + }; + + /** + * Resolved property information + */ + struct FPropertyInfo + { + std::string Name; + std::string ClassName; // Property class name (e.g., "IntProperty") + EPropertyType Type; + + int32 Offset; // Offset within struct/class + int32 ElementSize; // Size of single element + int32 ArrayDim; // Static array dimension (usually 1) + + uint64 PropertyFlags; // EPropertyFlags + + void* PropertyPtr; // Native pointer (UProperty* or FProperty*) + void* OwnerStruct; // Owning UStruct* + + // For struct properties + void* InnerStruct; // UScriptStruct* for StructProperty + + // For array properties + void* InnerProperty; // Inner property for arrays + + // For object properties + void* PropertyClass; // UClass* for object references + + FPropertyInfo() + : Type(EPropertyType::Unknown) + , Offset(0) + , ElementSize(0) + , ArrayDim(1) + , PropertyFlags(0) + , PropertyPtr(nullptr) + , OwnerStruct(nullptr) + , InnerStruct(nullptr) + , InnerProperty(nullptr) + , PropertyClass(nullptr) + {} + + bool IsValid() const { return PropertyPtr != nullptr; } + + // Common property flag checks + bool IsEditable() const; + bool IsBlueprintVisible() const; + bool IsNative() const; + bool IsReplicated() const; + bool IsSaveGame() const; + }; + + /** + * Property flags (common subset across versions) + */ + namespace EPropertyFlags + { + constexpr uint64 CPF_Edit = 0x0000000000000001; + constexpr uint64 CPF_ConstParm = 0x0000000000000002; + constexpr uint64 CPF_BlueprintVisible = 0x0000000000000004; + constexpr uint64 CPF_ExportObject = 0x0000000000000008; + constexpr uint64 CPF_BlueprintReadOnly = 0x0000000000000010; + constexpr uint64 CPF_Net = 0x0000000000000020; + constexpr uint64 CPF_EditFixedSize = 0x0000000000000040; + constexpr uint64 CPF_Parm = 0x0000000000000080; + constexpr uint64 CPF_OutParm = 0x0000000000000100; + constexpr uint64 CPF_ZeroConstructor = 0x0000000000000200; + constexpr uint64 CPF_ReturnParm = 0x0000000000000400; + constexpr uint64 CPF_DisableEditOnTemplate = 0x0000000000000800; + constexpr uint64 CPF_Transient = 0x0000000000002000; + constexpr uint64 CPF_Config = 0x0000000000004000; + constexpr uint64 CPF_DisableEditOnInstance = 0x0000000000010000; + constexpr uint64 CPF_EditConst = 0x0000000000020000; + constexpr uint64 CPF_GlobalConfig = 0x0000000000040000; + constexpr uint64 CPF_InstancedReference = 0x0000000000080000; + constexpr uint64 CPF_DuplicateTransient = 0x0000000000200000; + constexpr uint64 CPF_SaveGame = 0x0000000001000000; + constexpr uint64 CPF_NoClear = 0x0000000002000000; + constexpr uint64 CPF_ReferenceParm = 0x0000000008000000; + constexpr uint64 CPF_BlueprintAssignable = 0x0000000010000000; + constexpr uint64 CPF_Deprecated = 0x0000000020000000; + constexpr uint64 CPF_RepNotify = 0x0000000100000000; + constexpr uint64 CPF_Interp = 0x0000000200000000; + constexpr uint64 CPF_NonTransactional = 0x0000000400000000; + constexpr uint64 CPF_EditorOnly = 0x0000000800000000; + constexpr uint64 CPF_NoDestructor = 0x0000001000000000; + constexpr uint64 CPF_AutoWeak = 0x0000004000000000; + constexpr uint64 CPF_ContainsInstancedReference = 0x0000008000000000; + constexpr uint64 CPF_AssetRegistrySearchable = 0x0000010000000000; + constexpr uint64 CPF_SimpleDisplay = 0x0000020000000000; + constexpr uint64 CPF_AdvancedDisplay = 0x0000040000000000; + constexpr uint64 CPF_Protected = 0x0000080000000000; + constexpr uint64 CPF_BlueprintCallable = 0x0000100000000000; + constexpr uint64 CPF_BlueprintAuthorityOnly = 0x0000200000000000; + constexpr uint64 CPF_TextExportTransient = 0x0000400000000000; + constexpr uint64 CPF_NonPIEDuplicateTransient = 0x0000800000000000; + constexpr uint64 CPF_ExposeOnSpawn = 0x0001000000000000; + constexpr uint64 CPF_PersistentInstance = 0x0002000000000000; + constexpr uint64 CPF_UObjectWrapper = 0x0004000000000000; + constexpr uint64 CPF_HasGetValueTypeHash = 0x0008000000000000; + constexpr uint64 CPF_NativeAccessSpecifierPublic = 0x0010000000000000; + constexpr uint64 CPF_NativeAccessSpecifierProtected = 0x0020000000000000; + constexpr uint64 CPF_NativeAccessSpecifierPrivate = 0x0040000000000000; + constexpr uint64 CPF_SkipSerialization = 0x0080000000000000; + } + + /** + * Property iterator callback + */ + using FPropertyCallback = std::function; + + /** + * Property iterator interface + */ + USS_INTERFACE IPropertyIterator + { + public: + virtual ~IPropertyIterator() = default; + + /** + * Iterate all properties of a UStruct + * @param Struct - UStruct* to iterate + * @param Callback - Called for each property, return false to stop + * @param bIncludeSuper - Include inherited properties + */ + virtual void ForEachProperty(void* Struct, FPropertyCallback Callback, bool bIncludeSuper = true) = 0; + + /** + * Find property by name + * @param Struct - UStruct* to search + * @param PropertyName - Name to find + * @param OutInfo - Output property info + * @return true if found + */ + virtual bool FindProperty(void* Struct, const char* PropertyName, FPropertyInfo& OutInfo) = 0; + + /** + * Find property by offset + * @param Struct - UStruct* to search + * @param Offset - Offset to find + * @param OutInfo - Output property info + * @return true if found + */ + virtual bool FindPropertyByOffset(void* Struct, int32 Offset, FPropertyInfo& OutInfo) = 0; + + /** + * Get property count + */ + virtual int32 GetPropertyCount(void* Struct, bool bIncludeSuper = true) = 0; + + /** + * Get children/property list head + */ + virtual void* GetPropertyListHead(void* Struct) = 0; + + /** + * Initialize the iterator + */ + virtual EResult Initialize() = 0; + + /** + * Check if initialized + */ + virtual bool IsInitialized() const = 0; + }; + + /** + * UProperty-based iterator (Pre-4.25) + * + * In this version, properties are UObject-derived: + * UProperty : UField : UObject + * + * UStruct layout (relevant parts): + * - Children : UField* (first child field) + * - PropertyLink : UProperty* (optimized property iteration) + * + * UProperty layout: + * - UField base (includes UObject + Next pointer) + * - ArrayDim : int32 + * - ElementSize : int32 + * - PropertyFlags : uint64 + * - Offset_Internal : int32 + * - ... + */ + class FUPropertyIterator : public IPropertyIterator + { + public: + FUPropertyIterator(); + ~FUPropertyIterator() override = default; + + void ForEachProperty(void* Struct, FPropertyCallback Callback, bool bIncludeSuper = true) override; + bool FindProperty(void* Struct, const char* PropertyName, FPropertyInfo& OutInfo) override; + bool FindPropertyByOffset(void* Struct, int32 Offset, FPropertyInfo& OutInfo) override; + int32 GetPropertyCount(void* Struct, bool bIncludeSuper = true) override; + void* GetPropertyListHead(void* Struct) override; + EResult Initialize() override; + bool IsInitialized() const override; + + private: + bool ReadPropertyInfo(void* Property, FPropertyInfo& OutInfo); + void* GetPropertyLink(void* Struct); + void* GetNextProperty(void* Property); + std::string GetPropertyClassName(void* Property); + EPropertyType ClassifyProperty(const std::string& ClassName); + + // Offsets within UStruct + int32 m_ChildrenOffset; // Offset to Children field + int32 m_PropertyLinkOffset; // Offset to PropertyLink field + int32 m_SuperStructOffset; // Offset to SuperStruct field + + // Offsets within UProperty + int32 m_UProperty_ArrayDimOffset; + int32 m_UProperty_ElementSizeOffset; + int32 m_UProperty_PropertyFlagsOffset; + int32 m_UProperty_OffsetOffset; + int32 m_UProperty_NextOffset; // UField::Next + + bool m_bInitialized; + }; + + /** + * FField/FProperty-based iterator (4.25+) + * + * In this version, properties use a separate hierarchy: + * FProperty : FField (NOT UObject-derived) + * + * UStruct layout (relevant parts): + * - ChildProperties : FField* (new field chain) + * - Children : UField* (still exists for functions, etc.) + * + * FField layout: + * - ClassPrivate : FFieldClass* + * - Owner : FFieldVariant + * - Next : FField* + * - NamePrivate : FName + * - FlagsPrivate : EObjectFlags + * + * FProperty layout: + * - FField base + * - ArrayDim : int32 + * - ElementSize : int32 + * - PropertyFlags : EPropertyFlags + * - Offset_Internal : uint16 (changed from int32!) + * - ... + */ + class FFFieldPropertyIterator : public IPropertyIterator + { + public: + FFFieldPropertyIterator(); + ~FFFieldPropertyIterator() override = default; + + void ForEachProperty(void* Struct, FPropertyCallback Callback, bool bIncludeSuper = true) override; + bool FindProperty(void* Struct, const char* PropertyName, FPropertyInfo& OutInfo) override; + bool FindPropertyByOffset(void* Struct, int32 Offset, FPropertyInfo& OutInfo) override; + int32 GetPropertyCount(void* Struct, bool bIncludeSuper = true) override; + void* GetPropertyListHead(void* Struct) override; + EResult Initialize() override; + bool IsInitialized() const override; + + private: + bool ReadPropertyInfo(void* Field, FPropertyInfo& OutInfo); + void* GetChildProperties(void* Struct); + void* GetNextField(void* Field); + std::string GetFieldClassName(void* Field); + EPropertyType ClassifyProperty(const std::string& ClassName); + + // Offsets within UStruct + int32 m_ChildPropertiesOffset; // Offset to ChildProperties (FField*) + int32 m_SuperStructOffset; // Offset to SuperStruct + + // Offsets within FField + int32 m_FField_ClassOffset; + int32 m_FField_OwnerOffset; + int32 m_FField_NextOffset; + int32 m_FField_NameOffset; + + // Offsets within FProperty + int32 m_FProperty_ArrayDimOffset; + int32 m_FProperty_ElementSizeOffset; + int32 m_FProperty_PropertyFlagsOffset; + int32 m_FProperty_OffsetOffset; + + // FFieldClass contains class name + int32 m_FFieldClass_NameOffset; + + bool m_bInitialized; + }; + + /** + * Factory function to create appropriate property iterator + */ + std::unique_ptr CreatePropertyIterator(); + + /** + * Global property iterator accessor + */ + IPropertyIterator& GetPropertyIterator(); + +} \ No newline at end of file diff --git a/Engine/Replication/FastArraySerializer.cpp b/Engine/Replication/FastArraySerializer.cpp new file mode 100644 index 0000000..8b67f06 --- /dev/null +++ b/Engine/Replication/FastArraySerializer.cpp @@ -0,0 +1,566 @@ +/** + * UniversalSlashingSimulator - Fast Array Serializer Implementation + */ + +#include "FastArraySerializer.h" +#include "../../Core/Memory/Memory.h" +#include "../../Core/Logging/Log.h" +#include "../../Core/Versioning/VersionResolver.h" + +namespace USS +{ + //========================================================================= + // FLegacyFastArraySerializer Implementation (Pre-8.30) + //========================================================================= + + FLegacyFastArraySerializer::FLegacyFastArraySerializer() + : m_FastArrayPtr(nullptr) + , m_ItemSize(0) + , m_ItemsOffset(0) + , m_ItemsDataPtr(0) + , m_ItemsNum(0) + , m_ItemsMax(0) + , m_IDCounterOffset(0x60) // Default offset + , m_bInitialized(false) + { + } + + EResult FLegacyFastArraySerializer::Initialize(void* FastArrayPtr, size_t ItemSize, int32 ItemsOffset) + { + if (!FastArrayPtr) + return EResult::InvalidParameter; + + m_FastArrayPtr = FastArrayPtr; + m_ItemSize = ItemSize; + m_ItemsOffset = ItemsOffset; + + uintptr BaseAddr = reinterpret_cast(FastArrayPtr); + + // Read TArray structure at ItemsOffset + // TArray layout: Data*, Num, Max + uintptr TArrayAddr = BaseAddr + ItemsOffset; + + if (!Memory::Read(TArrayAddr + 0x00, m_ItemsDataPtr)) + { + USS_ERROR("Failed to read Items.Data from FastArraySerializer"); + return EResult::Failed; + } + + if (!Memory::Read(TArrayAddr + 0x08, m_ItemsNum)) + { + USS_ERROR("Failed to read Items.Num from FastArraySerializer"); + return EResult::Failed; + } + + if (!Memory::Read(TArrayAddr + 0x0C, m_ItemsMax)) + { + USS_ERROR("Failed to read Items.Max from FastArraySerializer"); + return EResult::Failed; + } + + USS_LOG("LegacyFastArraySerializer initialized: Num=%d, Max=%d, ItemSize=%zu", + m_ItemsNum, m_ItemsMax, m_ItemSize); + + m_bInitialized = true; + return EResult::Success; + } + + int32 FLegacyFastArraySerializer::Num() const + { + if (!m_bInitialized) + return 0; + + // Re-read in case it changed + int32 CurrentNum = 0; + uintptr TArrayAddr = reinterpret_cast(m_FastArrayPtr) + m_ItemsOffset; + Memory::Read(TArrayAddr + 0x08, CurrentNum); + return CurrentNum; + } + + void* FLegacyFastArraySerializer::GetItem(int32 Index) const + { + if (!m_bInitialized || Index < 0 || Index >= Num()) + return nullptr; + + // Re-read data pointer in case array was reallocated + uintptr DataPtr = 0; + uintptr TArrayAddr = reinterpret_cast(m_FastArrayPtr) + m_ItemsOffset; + Memory::Read(TArrayAddr + 0x00, DataPtr); + + if (DataPtr == 0) + return nullptr; + + return reinterpret_cast(DataPtr + (Index * m_ItemSize)); + } + + int32 FLegacyFastArraySerializer::GetItemReplicationID(int32 Index) const + { + void* Item = GetItem(Index); + if (!Item) + return -1; + + int32 ReplicationID = -1; + Memory::Read(reinterpret_cast(Item) + ReplicationIDOffset, ReplicationID); + return ReplicationID; + } + + int32 FLegacyFastArraySerializer::GetItemReplicationKey(int32 Index) const + { + void* Item = GetItem(Index); + if (!Item) + return -1; + + int32 ReplicationKey = -1; + Memory::Read(reinterpret_cast(Item) + ReplicationKeyOffset, ReplicationKey); + return ReplicationKey; + } + + bool FLegacyFastArraySerializer::IsItemDirty(int32 Index) const + { + // In legacy format, items are dirty if ReplicationKey differs from cached + // For now, assume all items need checking + return true; + } + + void FLegacyFastArraySerializer::MarkItemDirty(int32 Index) + { + void* Item = GetItem(Index); + if (!Item) + return; + + // Increment ReplicationKey + int32 CurrentKey = GetItemReplicationKey(Index); + Memory::Write(reinterpret_cast(Item) + ReplicationKeyOffset, CurrentKey + 1); + } + + void FLegacyFastArraySerializer::MarkAllDirty() + { + int32 Count = Num(); + for (int32 i = 0; i < Count; ++i) + { + MarkItemDirty(i); + } + } + + int32 FLegacyFastArraySerializer::GetArrayReplicationKey() const + { + // Legacy format doesn't have a separate array replication key + // Return max item key as approximation + int32 MaxKey = 0; + int32 Count = Num(); + for (int32 i = 0; i < Count; ++i) + { + int32 Key = GetItemReplicationKey(i); + if (Key > MaxKey) + MaxKey = Key; + } + return MaxKey; + } + + int32 FLegacyFastArraySerializer::GetIDCounter() const + { + if (!m_bInitialized) + return 0; + + int32 IDCounter = 0; + uintptr BaseAddr = reinterpret_cast(m_FastArrayPtr); + Memory::Read(BaseAddr + m_IDCounterOffset, IDCounter); + return IDCounter; + } + + void FLegacyFastArraySerializer::IncrementArrayReplicationKey() + { + // No-op for legacy format + } + + void FLegacyFastArraySerializer::RegisterChangeCallback(FFastArrayChangeCallback Callback) + { + if (Callback) + { + m_Callbacks.push_back(std::move(Callback)); + } + } + + bool FLegacyFastArraySerializer::IsInitialized() const + { + return m_bInitialized; + } + + //========================================================================= + // FNewFastArraySerializer Implementation (Post-8.30) + //========================================================================= + + FNewFastArraySerializer::FNewFastArraySerializer() + : m_FastArrayPtr(nullptr) + , m_ItemSize(0) + , m_ItemsOffset(0) + , m_ItemsDataPtr(0) + , m_ItemsNum(0) + , m_ItemsMax(0) + , m_ArrayReplicationKeyOffset(0x68) // Default for post-8.30 + , m_IDCounterOffset(0x6C) + , m_DeltaFlagsOffset(0x70) + , m_CachedArrayReplicationKey(0) + , m_bInitialized(false) + { + } + + EResult FNewFastArraySerializer::Initialize(void* FastArrayPtr, size_t ItemSize, int32 ItemsOffset) + { + if (!FastArrayPtr) + return EResult::InvalidParameter; + + m_FastArrayPtr = FastArrayPtr; + m_ItemSize = ItemSize; + m_ItemsOffset = ItemsOffset; + + uintptr BaseAddr = reinterpret_cast(FastArrayPtr); + + // Read TArray structure + uintptr TArrayAddr = BaseAddr + ItemsOffset; + + if (!Memory::Read(TArrayAddr + 0x00, m_ItemsDataPtr)) + { + USS_ERROR("Failed to read Items.Data from FastArraySerializer"); + return EResult::Failed; + } + + if (!Memory::Read(TArrayAddr + 0x08, m_ItemsNum)) + { + USS_ERROR("Failed to read Items.Num from FastArraySerializer"); + return EResult::Failed; + } + + if (!Memory::Read(TArrayAddr + 0x0C, m_ItemsMax)) + { + USS_ERROR("Failed to read Items.Max from FastArraySerializer"); + return EResult::Failed; + } + + // Read ArrayReplicationKey (new in post-8.30) + Memory::Read(BaseAddr + m_ArrayReplicationKeyOffset, m_CachedArrayReplicationKey); + + USS_LOG("NewFastArraySerializer initialized: Num=%d, Max=%d, ArrayKey=%d", + m_ItemsNum, m_ItemsMax, m_CachedArrayReplicationKey); + + m_bInitialized = true; + return EResult::Success; + } + + int32 FNewFastArraySerializer::Num() const + { + if (!m_bInitialized) + return 0; + + int32 CurrentNum = 0; + uintptr TArrayAddr = reinterpret_cast(m_FastArrayPtr) + m_ItemsOffset; + Memory::Read(TArrayAddr + 0x08, CurrentNum); + return CurrentNum; + } + + void* FNewFastArraySerializer::GetItem(int32 Index) const + { + if (!m_bInitialized || Index < 0 || Index >= Num()) + return nullptr; + + uintptr DataPtr = 0; + uintptr TArrayAddr = reinterpret_cast(m_FastArrayPtr) + m_ItemsOffset; + Memory::Read(TArrayAddr + 0x00, DataPtr); + + if (DataPtr == 0) + return nullptr; + + return reinterpret_cast(DataPtr + (Index * m_ItemSize)); + } + + int32 FNewFastArraySerializer::GetItemReplicationID(int32 Index) const + { + void* Item = GetItem(Index); + if (!Item) + return -1; + + int32 ReplicationID = -1; + Memory::Read(reinterpret_cast(Item) + ReplicationIDOffset, ReplicationID); + return ReplicationID; + } + + int32 FNewFastArraySerializer::GetItemReplicationKey(int32 Index) const + { + void* Item = GetItem(Index); + if (!Item) + return -1; + + int32 ReplicationKey = -1; + Memory::Read(reinterpret_cast(Item) + ReplicationKeyOffset, ReplicationKey); + return ReplicationKey; + } + + bool FNewFastArraySerializer::IsItemDirty(int32 Index) const + { + void* Item = GetItem(Index); + if (!Item) + return false; + + // Compare item's MostRecentArrayReplicationKey with current ArrayReplicationKey + int32 ItemArrayKey = 0; + Memory::Read(reinterpret_cast(Item) + MostRecentArrayReplicationKeyOffset, ItemArrayKey); + + return ItemArrayKey != GetArrayReplicationKey(); + } + + void FNewFastArraySerializer::MarkItemDirty(int32 Index) + { + void* Item = GetItem(Index); + if (!Item) + return; + + uintptr ItemAddr = reinterpret_cast(Item); + + // Increment ReplicationKey + int32 CurrentKey = GetItemReplicationKey(Index); + Memory::Write(ItemAddr + ReplicationKeyOffset, CurrentKey + 1); + + // Update MostRecentArrayReplicationKey to current + int32 ArrayKey = GetArrayReplicationKey(); + Memory::Write(ItemAddr + MostRecentArrayReplicationKeyOffset, ArrayKey); + + // Notify callbacks + FFastArrayChange Change; + Change.Type = FFastArrayChange::EChangeType::Modified; + Change.Index = Index; + Change.ReplicationID = GetItemReplicationID(Index); + NotifyChange(Change); + } + + void FNewFastArraySerializer::MarkAllDirty() + { + IncrementArrayReplicationKey(); + + int32 Count = Num(); + for (int32 i = 0; i < Count; ++i) + { + MarkItemDirty(i); + } + } + + int32 FNewFastArraySerializer::GetArrayReplicationKey() const + { + if (!m_bInitialized) + return 0; + + int32 ArrayKey = 0; + uintptr BaseAddr = reinterpret_cast(m_FastArrayPtr); + Memory::Read(BaseAddr + m_ArrayReplicationKeyOffset, ArrayKey); + return ArrayKey; + } + + int32 FNewFastArraySerializer::GetIDCounter() const + { + if (!m_bInitialized) + return 0; + + int32 IDCounter = 0; + uintptr BaseAddr = reinterpret_cast(m_FastArrayPtr); + Memory::Read(BaseAddr + m_IDCounterOffset, IDCounter); + return IDCounter; + } + + void FNewFastArraySerializer::IncrementArrayReplicationKey() + { + if (!m_bInitialized) + return; + + int32 CurrentKey = GetArrayReplicationKey(); + uintptr BaseAddr = reinterpret_cast(m_FastArrayPtr); + Memory::Write(BaseAddr + m_ArrayReplicationKeyOffset, CurrentKey + 1); + } + + void FNewFastArraySerializer::RegisterChangeCallback(FFastArrayChangeCallback Callback) + { + if (Callback) + { + m_Callbacks.push_back(std::move(Callback)); + } + } + + bool FNewFastArraySerializer::IsInitialized() const + { + return m_bInitialized; + } + + void FNewFastArraySerializer::NotifyChange(const FFastArrayChange& Change) + { + for (const auto& Callback : m_Callbacks) + { + if (Callback) + { + Callback(Change); + } + } + } + + //========================================================================= + // Factory Function + //========================================================================= + + std::unique_ptr CreateFastArraySerializer() + { + const auto& Version = GetVersionResolver().GetVersionInfo(); + + if (Version.bUseNewFastArraySerializer) + { + USS_LOG("Creating NewFastArraySerializer (post-8.30 format)"); + return std::make_unique(); + } + else + { + USS_LOG("Creating LegacyFastArraySerializer (pre-8.30 format)"); + return std::make_unique(); + } + } + + //========================================================================= + // FFastArrayChangeDetector Implementation + //========================================================================= + + FFastArrayChangeDetector::FFastArrayChangeDetector() + : m_Serializer(nullptr) + , m_LastArrayReplicationKey(0) + , m_LastItemCount(0) + { + } + + EResult FFastArrayChangeDetector::Initialize(IFastArraySerializer* Serializer) + { + if (!Serializer || !Serializer->IsInitialized()) + return EResult::InvalidParameter; + + m_Serializer = Serializer; + Reset(); + return EResult::Success; + } + + int32 FFastArrayChangeDetector::DetectChanges(std::vector& OutChanges) + { + OutChanges.clear(); + + if (!m_Serializer) + return 0; + + int32 CurrentCount = m_Serializer->Num(); + int32 CurrentArrayKey = m_Serializer->GetArrayReplicationKey(); + + // Check for array-level reset + if (CurrentArrayKey != m_LastArrayReplicationKey && m_Serializer->IsNewFormat()) + { + // Array key changed - could be mass update + // Check each item for changes + } + + // Detect removals (items that were present but aren't now) + for (size_t i = 0; i < m_LastItemIDs.size(); ++i) + { + int32 OldID = m_LastItemIDs[i]; + bool bFound = false; + + for (int32 j = 0; j < CurrentCount; ++j) + { + if (m_Serializer->GetItemReplicationID(j) == OldID) + { + bFound = true; + break; + } + } + + if (!bFound) + { + FFastArrayChange Change; + Change.Type = FFastArrayChange::EChangeType::Removed; + Change.Index = static_cast(i); + Change.ReplicationID = OldID; + OutChanges.push_back(Change); + } + } + + // Detect additions and modifications + for (int32 i = 0; i < CurrentCount; ++i) + { + int32 CurrentID = m_Serializer->GetItemReplicationID(i); + int32 CurrentKey = m_Serializer->GetItemReplicationKey(i); + + // Check if this item existed before + bool bIsNew = true; + bool bIsModified = false; + + for (size_t j = 0; j < m_LastItemIDs.size(); ++j) + { + if (m_LastItemIDs[j] == CurrentID) + { + bIsNew = false; + + // Check if key changed (item was modified) + if (j < m_LastItemKeys.size() && m_LastItemKeys[j] != CurrentKey) + { + bIsModified = true; + } + break; + } + } + + if (bIsNew) + { + FFastArrayChange Change; + Change.Type = FFastArrayChange::EChangeType::Added; + Change.Index = i; + Change.ReplicationID = CurrentID; + OutChanges.push_back(Change); + } + else if (bIsModified) + { + FFastArrayChange Change; + Change.Type = FFastArrayChange::EChangeType::Modified; + Change.Index = i; + Change.ReplicationID = CurrentID; + OutChanges.push_back(Change); + } + } + + // Update cached state + m_LastArrayReplicationKey = CurrentArrayKey; + m_LastItemCount = CurrentCount; + + m_LastItemIDs.clear(); + m_LastItemKeys.clear(); + m_LastItemIDs.reserve(CurrentCount); + m_LastItemKeys.reserve(CurrentCount); + + for (int32 i = 0; i < CurrentCount; ++i) + { + m_LastItemIDs.push_back(m_Serializer->GetItemReplicationID(i)); + m_LastItemKeys.push_back(m_Serializer->GetItemReplicationKey(i)); + } + + return static_cast(OutChanges.size()); + } + + void FFastArrayChangeDetector::Reset() + { + if (!m_Serializer) + return; + + m_LastArrayReplicationKey = m_Serializer->GetArrayReplicationKey(); + m_LastItemCount = m_Serializer->Num(); + + m_LastItemIDs.clear(); + m_LastItemKeys.clear(); + m_LastItemIDs.reserve(m_LastItemCount); + m_LastItemKeys.reserve(m_LastItemCount); + + for (int32 i = 0; i < m_LastItemCount; ++i) + { + m_LastItemIDs.push_back(m_Serializer->GetItemReplicationID(i)); + m_LastItemKeys.push_back(m_Serializer->GetItemReplicationKey(i)); + } + } + +} \ No newline at end of file diff --git a/Engine/Replication/FastArraySerializer.h b/Engine/Replication/FastArraySerializer.h new file mode 100644 index 0000000..bdd10ce --- /dev/null +++ b/Engine/Replication/FastArraySerializer.h @@ -0,0 +1,329 @@ +/** + * UniversalSlashingSimulator - Fast Array Serializer Abstraction + * + * Provides version-agnostic handling of FFastArraySerializer, which changed + * significantly at Fortnite 8.30 (around UE4.22). + * + * FFastArraySerializer is used for efficient replication of dynamic arrays + * like inventory items, building pieces, and other game state. + * + * Pre-8.30 Layout: + * - Simple array with per-element dirty tracking + * - ReplicationID, ReplicationKey per item + * + * Post-8.30 Layout: + * - Restructured with ArrayReplicationKey + * - Different delta serialization format + * - IDCounter for unique element identification + */ + +#pragma once + +#include "../../Core/Common.h" +#include +#include + +namespace USS +{ + /** + * Fast array item base - common to all versions + */ + struct FFastArrayItem + { + int32 ReplicationID; // Unique identifier for this item + int32 ReplicationKey; // Change counter + + // Cached state + bool bIsDirty; + bool bIsNew; + bool bIsRemoved; + + FFastArrayItem() + : ReplicationID(-1) + , ReplicationKey(-1) + , bIsDirty(false) + , bIsNew(false) + , bIsRemoved(false) + {} + }; + + /** + * Fast array change event + */ + struct FFastArrayChange + { + enum class EChangeType + { + None, + Added, + Modified, + Removed, + Reset, + }; + + EChangeType Type; + int32 Index; + int32 ReplicationID; + + FFastArrayChange() + : Type(EChangeType::None) + , Index(-1) + , ReplicationID(-1) + {} + }; + + /** + * Callback for array changes + */ + using FFastArrayChangeCallback = std::function; + + /** + * Fast array serializer interface + * + * This handles the version differences in how Fortnite + * serializes and replicates dynamic arrays. + */ + USS_INTERFACE IFastArraySerializer + { + public: + virtual ~IFastArraySerializer() = default; + + /** + * Initialize from a native FFastArraySerializer* + * @param FastArrayPtr - Pointer to native struct + * @param ItemSize - Size of each array element + * @param ItemsOffset - Offset to Items array within struct + */ + virtual EResult Initialize(void* FastArrayPtr, size_t ItemSize, int32 ItemsOffset) = 0; + + /** + * Get number of items in array + */ + virtual int32 Num() const = 0; + + /** + * Get item by index + * @param Index - Array index + * @return Pointer to item data, or nullptr if invalid + */ + virtual void* GetItem(int32 Index) const = 0; + + /** + * Get item's replication ID + */ + virtual int32 GetItemReplicationID(int32 Index) const = 0; + + /** + * Get item's replication key + */ + virtual int32 GetItemReplicationKey(int32 Index) const = 0; + + /** + * Check if item is dirty (needs replication) + */ + virtual bool IsItemDirty(int32 Index) const = 0; + + /** + * Mark item as dirty + */ + virtual void MarkItemDirty(int32 Index) = 0; + + /** + * Mark all items as dirty + */ + virtual void MarkAllDirty() = 0; + + /** + * Get array replication key (post-8.30) + */ + virtual int32 GetArrayReplicationKey() const = 0; + + /** + * Get ID counter (post-8.30) + */ + virtual int32 GetIDCounter() const = 0; + + /** + * Increment array replication key + */ + virtual void IncrementArrayReplicationKey() = 0; + + /** + * Register callback for array changes + */ + virtual void RegisterChangeCallback(FFastArrayChangeCallback Callback) = 0; + + /** + * Check if using new serializer format (post-8.30) + */ + virtual bool IsNewFormat() const = 0; + + /** + * Check if initialized + */ + virtual bool IsInitialized() const = 0; + }; + + /** + * Pre-8.30 Fast Array Serializer + * + * Layout: + * struct FFastArraySerializer { + * TArray Items; // 0x00 - Standard TArray + * TMap ItemMap; // 0x10 - ReplicationID -> Index + * int32 IDCounter; // 0x60 - Next ID to assign + * }; + * + * Each item contains: + * int32 ReplicationID; + * int32 ReplicationKey; + * int32 MostRecentArrayReplicationKey; + */ + class FLegacyFastArraySerializer : public IFastArraySerializer + { + public: + FLegacyFastArraySerializer(); + ~FLegacyFastArraySerializer() override = default; + + EResult Initialize(void* FastArrayPtr, size_t ItemSize, int32 ItemsOffset) override; + int32 Num() const override; + void* GetItem(int32 Index) const override; + int32 GetItemReplicationID(int32 Index) const override; + int32 GetItemReplicationKey(int32 Index) const override; + bool IsItemDirty(int32 Index) const override; + void MarkItemDirty(int32 Index) override; + void MarkAllDirty() override; + int32 GetArrayReplicationKey() const override; + int32 GetIDCounter() const override; + void IncrementArrayReplicationKey() override; + void RegisterChangeCallback(FFastArrayChangeCallback Callback) override; + bool IsNewFormat() const override { return false; } + bool IsInitialized() const override; + + private: + void* m_FastArrayPtr; + size_t m_ItemSize; + int32 m_ItemsOffset; + + // Cached TArray info + uintptr m_ItemsDataPtr; + int32 m_ItemsNum; + int32 m_ItemsMax; + + // Internal offsets within item + static constexpr int32 ReplicationIDOffset = 0; + static constexpr int32 ReplicationKeyOffset = 4; + + // Offset to IDCounter within FFastArraySerializer + int32 m_IDCounterOffset; + + std::vector m_Callbacks; + bool m_bInitialized; + }; + + /** + * Post-8.30 Fast Array Serializer + * + * Layout (restructured): + * struct FFastArraySerializer { + * TArray Items; // 0x00 + * FFastArraySerializerGuidReferences GuidReferencesMap; // 0x10 + * int32 ArrayReplicationKey; // 0x68 (new!) + * int32 IDCounter; // 0x6C + * EFastArraySerializerDeltaFlags DeltaFlags; // 0x70 + * }; + * + * Each item now contains: + * FFastArraySerializerItem base: + * int32 ReplicationID; + * int32 ReplicationKey; + * int32 MostRecentArrayReplicationKey; + */ + class FNewFastArraySerializer : public IFastArraySerializer + { + public: + FNewFastArraySerializer(); + ~FNewFastArraySerializer() override = default; + + EResult Initialize(void* FastArrayPtr, size_t ItemSize, int32 ItemsOffset) override; + int32 Num() const override; + void* GetItem(int32 Index) const override; + int32 GetItemReplicationID(int32 Index) const override; + int32 GetItemReplicationKey(int32 Index) const override; + bool IsItemDirty(int32 Index) const override; + void MarkItemDirty(int32 Index) override; + void MarkAllDirty() override; + int32 GetArrayReplicationKey() const override; + int32 GetIDCounter() const override; + void IncrementArrayReplicationKey() override; + void RegisterChangeCallback(FFastArrayChangeCallback Callback) override; + bool IsNewFormat() const override { return true; } + bool IsInitialized() const override; + + private: + void NotifyChange(const FFastArrayChange& Change); + + void* m_FastArrayPtr; + size_t m_ItemSize; + int32 m_ItemsOffset; + + // Cached TArray info + uintptr m_ItemsDataPtr; + int32 m_ItemsNum; + int32 m_ItemsMax; + + // Internal offsets within item + static constexpr int32 ReplicationIDOffset = 0; + static constexpr int32 ReplicationKeyOffset = 4; + static constexpr int32 MostRecentArrayReplicationKeyOffset = 8; + + // Offsets within FFastArraySerializer (post-8.30) + int32 m_ArrayReplicationKeyOffset; + int32 m_IDCounterOffset; + int32 m_DeltaFlagsOffset; + + int32 m_CachedArrayReplicationKey; + + std::vector m_Callbacks; + bool m_bInitialized; + }; + + /** + * Factory function to create appropriate serializer based on version + */ + std::unique_ptr CreateFastArraySerializer(); + + /** + * Helper to detect fast array changes between ticks + */ + class FFastArrayChangeDetector + { + public: + FFastArrayChangeDetector(); + + /** + * Initialize with a fast array serializer + */ + EResult Initialize(IFastArraySerializer* Serializer); + + /** + * Detect changes since last check + * @param OutChanges - Output list of changes + * @return Number of changes detected + */ + int32 DetectChanges(std::vector& OutChanges); + + /** + * Reset tracking state + */ + void Reset(); + + private: + IFastArraySerializer* m_Serializer; + int32 m_LastArrayReplicationKey; + std::vector m_LastItemKeys; // ReplicationKey per item + std::vector m_LastItemIDs; // ReplicationID per item + int32 m_LastItemCount; + }; + +} \ No newline at end of file diff --git a/Engine/UObject/UObjectWrapper.cpp b/Engine/UObject/UObjectWrapper.cpp new file mode 100644 index 0000000..7d963df --- /dev/null +++ b/Engine/UObject/UObjectWrapper.cpp @@ -0,0 +1,548 @@ +/** + * UniversalSlashingSimulator - UObject Wrapper Implementation + */ + +#include "UObjectWrapper.h" +#include "../../Core/Memory/Memory.h" +#include "../../Core/Versioning/VersionResolver.h" +#include "../CoreTypes/OffsetResolver.h" +#include + +namespace USS +{ + // Static name pool instance (lazy initialized) + static std::unique_ptr g_pNamePool; + + static INamePool* GetNamePoolInstance() + { + if (!g_pNamePool) + { + g_pNamePool = CreateNamePool(); + // Note: Initialization happens separately via EngineCore + } + return g_pNamePool.get(); + } + + //========================================================================= + // FNameWrapper Implementation + //========================================================================= + + std::string FNameWrapper::GetName() const + { + if (m_ComparisonIndex <= 0) + return "None"; + + INamePool* Pool = GetNamePoolInstance(); + if (!Pool || !Pool->IsInitialized()) + return ""; + + return Pool->GetNameString(m_ComparisonIndex); + } + + std::string FNameWrapper::GetFullName() const + { + std::string Name = GetName(); + + if (m_Number > 0) + { + Name += "_"; + Name += std::to_string(m_Number); + } + + return Name; + } + + //========================================================================= + // UObjectWrapper Implementation + //========================================================================= + + bool UObjectWrapper::IsValid() const + { + return m_pObject != nullptr && Memory::IsValidAddress(reinterpret_cast(m_pObject)); + } + + int32 UObjectWrapper::GetObjectFlags() const + { + if (!IsValid()) + return 0; + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + int32 Flags = 0; + + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UObject.ObjectFlags, + Flags + ); + + return Flags; + } + + int32 UObjectWrapper::GetInternalIndex() const + { + if (!IsValid()) + return -1; + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + int32 Index = -1; + + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UObject.InternalIndex, + Index + ); + + return Index; + } + + UClassWrapper UObjectWrapper::GetClass() const + { + if (!IsValid()) + return UClassWrapper(); + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + void* ClassPtr = nullptr; + + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UObject.Class, + ClassPtr + ); + + return UClassWrapper(ClassPtr); + } + + FNameWrapper UObjectWrapper::GetFName() const + { + if (!IsValid()) + return FNameWrapper(); + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + uintptr NameAddr = reinterpret_cast(m_pObject) + Offsets.UObject.Name; + + int32 ComparisonIndex = 0; + int32 Number = 0; + + Memory::Read(NameAddr + 0, ComparisonIndex); + Memory::Read(NameAddr + 4, Number); + + return FNameWrapper(ComparisonIndex, Number); + } + + UObjectWrapper UObjectWrapper::GetOuter() const + { + if (!IsValid()) + return UObjectWrapper(); + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + void* OuterPtr = nullptr; + + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UObject.Outer, + OuterPtr + ); + + return UObjectWrapper(OuterPtr); + } + + std::string UObjectWrapper::GetName() const + { + return GetFName().GetFullName(); + } + + std::string UObjectWrapper::GetFullName() const + { + if (!IsValid()) + return ""; + + UClassWrapper Class = GetClass(); + std::string ClassName = Class.IsValid() ? Class.GetName() : "Unknown"; + + return ClassName + " " + GetPathName(); + } + + std::string UObjectWrapper::GetPathName() const + { + if (!IsValid()) + return ""; + + std::string Path; + UObjectWrapper Current = GetOuter(); + + // Build path from outermost to innermost + std::vector Parts; + Parts.push_back(GetName()); + + while (Current.IsValid()) + { + Parts.push_back(Current.GetName()); + Current = Current.GetOuter(); + } + + // Reverse and join + for (auto It = Parts.rbegin(); It != Parts.rend(); ++It) + { + if (!Path.empty()) + Path += "."; + Path += *It; + } + + return Path; + } + + std::string UObjectWrapper::GetObjectClassName() const + { + if (!IsValid()) + return ""; + + UClassWrapper Class = GetClass(); + if (!Class.IsValid()) + return ""; + + return Class.GetName(); + } + + bool UObjectWrapper::IsA(const UClassWrapper& Class) const + { + if (!IsValid() || !Class.IsValid()) + return false; + + UClassWrapper MyClass = GetClass(); + return MyClass.IsChildOf(Class); + } + + bool UObjectWrapper::IsA(const char* ClassName) const + { + if (!IsValid() || !ClassName) + return false; + + UClassWrapper MyClass = GetClass(); + while (MyClass.IsValid()) + { + if (MyClass.GetName() == ClassName) + return true; + MyClass = MyClass.GetSuperClass(); + } + + return false; + } + + //========================================================================= + // UClassWrapper Implementation + //========================================================================= + + UClassWrapper UClassWrapper::GetSuperClass() const + { + if (!IsValid()) + return UClassWrapper(); + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + void* SuperPtr = nullptr; + + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UStruct.SuperStruct, + SuperPtr + ); + + return UClassWrapper(SuperPtr); + } + + UObjectWrapper UClassWrapper::GetDefaultObject() const + { + if (!IsValid()) + return UObjectWrapper(); + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + void* CDOPtr = nullptr; + + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UClass.ClassDefaultObject, + CDOPtr + ); + + return UObjectWrapper(CDOPtr); + } + + bool UClassWrapper::IsChildOf(const UClassWrapper& Parent) const + { + if (!IsValid() || !Parent.IsValid()) + return false; + + if (m_pObject == Parent.m_pObject) + return true; + + UClassWrapper Super = GetSuperClass(); + while (Super.IsValid()) + { + if (Super.m_pObject == Parent.m_pObject) + return true; + Super = Super.GetSuperClass(); + } + + return false; + } + + //========================================================================= + // UStructWrapper Implementation + //========================================================================= + + UStructWrapper UStructWrapper::GetSuperStruct() const + { + if (!IsValid()) + return UStructWrapper(); + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + void* SuperPtr = nullptr; + + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UStruct.SuperStruct, + SuperPtr + ); + + return UStructWrapper(SuperPtr); + } + + int32 UStructWrapper::GetPropertiesSize() const + { + if (!IsValid()) + return 0; + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + int32 Size = 0; + + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UStruct.PropertiesSize, + Size + ); + + return Size; + } + + int32 UStructWrapper::GetMinAlignment() const + { + if (!IsValid()) + return 1; + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + int32 Alignment = 1; + + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UStruct.MinAlignment, + Alignment + ); + + return Alignment; + } + + UStructWrapper::PropertyIterator UStructWrapper::GetProperties() const + { + if (!IsValid()) + return PropertyIterator(nullptr, false); + + const auto& Version = GetVersionResolver().GetVersionInfo(); + const auto& Offsets = GetOffsetResolver().GetOffsets(); + + void* FirstProperty = nullptr; + bool bIsFField = Version.bUseFField; + + if (bIsFField && Offsets.UStruct.ChildProperties != 0) + { + // UE 4.25+: Use ChildProperties (FField*) + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UStruct.ChildProperties, + FirstProperty + ); + } + else + { + // Pre-4.25: Use Children (UField*), filter to properties + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UStruct.Children, + FirstProperty + ); + } + + return PropertyIterator(FirstProperty, bIsFField); + } + + //========================================================================= + // PropertyIterator Implementation + //========================================================================= + + UStructWrapper::PropertyIterator::PropertyIterator(void* First, bool bIsFField) + : m_pCurrent(First) + , m_bIsFField(bIsFField) + { + } + + void UStructWrapper::PropertyIterator::Next() + { + if (!m_pCurrent) + return; + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + void* NextPtr = nullptr; + + if (m_bIsFField) + { + Memory::Read( + reinterpret_cast(m_pCurrent) + Offsets.FField.Next, + NextPtr + ); + } + else + { + Memory::Read( + reinterpret_cast(m_pCurrent) + Offsets.UField.Next, + NextPtr + ); + } + + m_pCurrent = NextPtr; + } + + std::string UStructWrapper::PropertyIterator::GetName() const + { + if (!m_pCurrent) + return ""; + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + + int32 ComparisonIndex = 0; + int32 Number = 0; + + if (m_bIsFField) + { + uintptr NameAddr = reinterpret_cast(m_pCurrent) + Offsets.FField.NamePrivate; + Memory::Read(NameAddr + 0, ComparisonIndex); + Memory::Read(NameAddr + 4, Number); + } + else + { + // UProperty is a UObject, name at standard offset + uintptr NameAddr = reinterpret_cast(m_pCurrent) + Offsets.UObject.Name; + Memory::Read(NameAddr + 0, ComparisonIndex); + Memory::Read(NameAddr + 4, Number); + } + + return FNameWrapper(ComparisonIndex, Number).GetFullName(); + } + + int32 UStructWrapper::PropertyIterator::GetOffset() const + { + if (!m_pCurrent) + return -1; + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + int32 Offset = 0; + + if (m_bIsFField) + { + Memory::Read( + reinterpret_cast(m_pCurrent) + Offsets.FProperty.Offset, + Offset + ); + } + else + { + Memory::Read( + reinterpret_cast(m_pCurrent) + Offsets.UProperty.Offset, + Offset + ); + } + + return Offset; + } + + int32 UStructWrapper::PropertyIterator::GetElementSize() const + { + if (!m_pCurrent) + return 0; + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + int32 Size = 0; + + if (m_bIsFField) + { + Memory::Read( + reinterpret_cast(m_pCurrent) + Offsets.FProperty.ElementSize, + Size + ); + } + else + { + Memory::Read( + reinterpret_cast(m_pCurrent) + Offsets.UProperty.ElementSize, + Size + ); + } + + return Size; + } + + //========================================================================= + // UFunctionWrapper Implementation + //========================================================================= + + uint32 UFunctionWrapper::GetFunctionFlags() const + { + if (!IsValid()) + return 0; + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + uint32 Flags = 0; + + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UFunction.FunctionFlags, + Flags + ); + + return Flags; + } + + uint8 UFunctionWrapper::GetNumParms() const + { + if (!IsValid()) + return 0; + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + uint8 NumParms = 0; + + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UFunction.NumParms, + NumParms + ); + + return NumParms; + } + + uint16 UFunctionWrapper::GetParmsSize() const + { + if (!IsValid()) + return 0; + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + uint16 Size = 0; + + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UFunction.ParmsSize, + Size + ); + + return Size; + } + + void* UFunctionWrapper::GetNativeFunc() const + { + if (!IsValid()) + return nullptr; + + const auto& Offsets = GetOffsetResolver().GetOffsets(); + void* Func = nullptr; + + Memory::Read( + reinterpret_cast(m_pObject) + Offsets.UFunction.Func, + Func + ); + + return Func; + } + +} \ No newline at end of file diff --git a/Engine/UObject/UObjectWrapper.h b/Engine/UObject/UObjectWrapper.h new file mode 100644 index 0000000..58e03dc --- /dev/null +++ b/Engine/UObject/UObjectWrapper.h @@ -0,0 +1,193 @@ +/** + * UniversalSlashingSimulator - UObject Wrapper + * + * Provides version-agnostic access to Unreal Engine objects. + * Wraps raw pointers and provides safe access to object members + * using the offset resolver. + * + * This is the foundation for all object manipulation in USS. + */ + +#pragma once + +#include "../../Core/Common.h" +#include "../CoreTypes/NamePool.h" +#include "../CoreTypes/OffsetResolver.h" + +namespace USS +{ + // Forward declarations + class UClassWrapper; + class UStructWrapper; + class UFunctionWrapper; + + // FName wrapper for version-agnostic name access + class FNameWrapper + { + public: + FNameWrapper() : m_ComparisonIndex(0), m_Number(0) {} + FNameWrapper(int32 Index, int32 Number) : m_ComparisonIndex(Index), m_Number(Number) {} + + // Get the name string + std::string GetName() const; + + // Get name with number suffix if non-zero + std::string GetFullName() const; + + // Check if valid + bool IsValid() const { return m_ComparisonIndex > 0; } + + // Comparison + bool operator==(const FNameWrapper& Other) const + { + return m_ComparisonIndex == Other.m_ComparisonIndex && + m_Number == Other.m_Number; + } + + bool operator!=(const FNameWrapper& Other) const + { + return !(*this == Other); + } + + // Raw access + int32 GetComparisonIndex() const { return m_ComparisonIndex; } + int32 GetNumber() const { return m_Number; } + + private: + int32 m_ComparisonIndex; + int32 m_Number; + }; + + // UObject wrapper for version-agnostic object access + class UObjectWrapper + { + public: + UObjectWrapper() : m_pObject(nullptr) {} + explicit UObjectWrapper(void* Object) : m_pObject(Object) {} + + // Check if valid + bool IsValid() const; + explicit operator bool() const { return IsValid(); } + + // Get raw pointer + void* GetRaw() const { return m_pObject; } + + // Get raw pointer as specific type + template + T* GetAs() const { return static_cast(m_pObject); } + + // Object properties + int32 GetObjectFlags() const; + int32 GetInternalIndex() const; + UClassWrapper GetClass() const; + FNameWrapper GetFName() const; + UObjectWrapper GetOuter() const; + + // Get object name as string + std::string GetName() const; + + // Get full path name (Outer.Outer.Name) + std::string GetFullName() const; + + // Get path name (/Path/To/Object) + std::string GetPathName() const; + + // Get class name (name of the UClass) + // NOTE: Named GetObjectClassName to avoid Windows macro conflict + std::string GetObjectClassName() const; + + // Alias for GetObjectClassName (avoid Windows GetClassName macro) + std::string GetClassNameStr() const { return GetObjectClassName(); } + + // Type checking + bool IsA(const UClassWrapper& Class) const; + bool IsA(const char* ClassName) const; + + // Comparison + bool operator==(const UObjectWrapper& Other) const { return m_pObject == Other.m_pObject; } + bool operator!=(const UObjectWrapper& Other) const { return m_pObject != Other.m_pObject; } + + protected: + void* m_pObject; + }; + + // UClass wrapper + class UClassWrapper : public UObjectWrapper + { + public: + UClassWrapper() : UObjectWrapper() {} + explicit UClassWrapper(void* Class) : UObjectWrapper(Class) {} + + // Get super class + UClassWrapper GetSuperClass() const; + + // Get class default object + UObjectWrapper GetDefaultObject() const; + + // Check if this class is a child of another + bool IsChildOf(const UClassWrapper& Parent) const; + }; + + // UStruct wrapper for struct iteration + class UStructWrapper : public UObjectWrapper + { + public: + UStructWrapper() : UObjectWrapper() {} + explicit UStructWrapper(void* Struct) : UObjectWrapper(Struct) {} + + // Get super struct + UStructWrapper GetSuperStruct() const; + + // Get struct size + int32 GetPropertiesSize() const; + + // Get minimum alignment + int32 GetMinAlignment() const; + + // Property iteration (version-aware) + // Pre-4.25: Iterates UProperty via Children + // 4.25+: Iterates FProperty via ChildProperties + class PropertyIterator + { + public: + PropertyIterator(void* First, bool bIsFField); + + bool IsValid() const { return m_pCurrent != nullptr; } + void Next(); + + // Property info + std::string GetName() const; + int32 GetOffset() const; + int32 GetElementSize() const; + + void* GetRaw() const { return m_pCurrent; } + + private: + void* m_pCurrent; + bool m_bIsFField; + }; + + PropertyIterator GetProperties() const; + }; + + // UFunction wrapper + class UFunctionWrapper : public UStructWrapper + { + public: + UFunctionWrapper() : UStructWrapper() {} + explicit UFunctionWrapper(void* Function) : UStructWrapper(Function) {} + + // Get function flags + uint32 GetFunctionFlags() const; + + // Get number of parameters + uint8 GetNumParms() const; + + // Get parameters size + uint16 GetParmsSize() const; + + // Get native function pointer + void* GetNativeFunc() const; + }; + +} \ No newline at end of file diff --git a/Entry/DllMain.cpp b/Entry/DllMain.cpp new file mode 100644 index 0000000..db55091 --- /dev/null +++ b/Entry/DllMain.cpp @@ -0,0 +1,336 @@ +/** + * UniversalSlashingSimulator - DLL Entry Point + * + * Main entry point for the DLL. Initializes all core systems + * + * Injection flow: + * 1. DLL_PROCESS_ATTACH + * 2. Create initialization thread + * 3. Initialize logging + * 4. Parse command-line arguments + * 5. Initialize engine core + * 6. Initialize STW systems (GameMode, Missions, Inventory, Building) + * + * Command-Line Arguments (USS-specific): + * -USS_Mission= Mission blueprint to load + * -USS_Map= Map to load + * -USS_Difficulty=<1-140> Difficulty level + * -USS_MaxPlayers=<1-4> Maximum players + * -USS_Zone= Zone name + * -USS_NoMissions Disable missions + * -USS_NoInventory Disable inventory + * -USS_NoBuilding Disable building + * -USS_Debug Enable debug mode + */ + +#include "../Core/Common.h" +#include "../Core/Logging/Log.h" +#include "../Engine/EngineCore.h" +#include "../STW/GameMode/STWGameMode.h" +#include "../STW/Missions/MissionManager.h" +#include "../STW/Inventory/InventoryManager.h" +#include "../STW/Building/BuildingManager.h" +#include +#include + +namespace USS +{ + // Forward declarations + DWORD WINAPI InitializationThread(LPVOID lpParam); + void ParseCommandLineArguments(FSTWGameConfig& Config); + bool GetCommandLineArg(const char* ArgName, std::string& OutValue); + bool HasCommandLineArg(const char* ArgName); + int GetCommandLineArgInt(const char* ArgName, int DefaultValue); + + // Global module handle + static HMODULE g_hModule = nullptr; + + // Initialization state + static bool g_bInitialized = false; + static bool g_bDebugMode = false; + + /** + * Parse command-line arguments and populate game config + */ + void ParseCommandLineArguments(FSTWGameConfig& Config) + { + // Get command line + const char* CmdLine = GetCommandLineA(); + if (!CmdLine) + return; + + USS_LOG("Parsing command line: %s", CmdLine); + + // Mission blueprint + std::string MissionArg; + if (GetCommandLineArg("-USS_Mission", MissionArg)) + { + Config.MissionBlueprint = MissionArg; + USS_LOG(" Mission: %s", MissionArg.c_str()); + } + + // Map name + std::string MapArg; + if (GetCommandLineArg("-USS_Map", MapArg)) + { + Config.MapName = MapArg; + USS_LOG(" Map: %s", MapArg.c_str()); + } + + // Zone name + std::string ZoneArg; + if (GetCommandLineArg("-USS_Zone", ZoneArg)) + { + Config.ZoneName = ZoneArg; + USS_LOG(" Zone: %s", ZoneArg.c_str()); + } + + // Difficulty level + int Difficulty = GetCommandLineArgInt("-USS_Difficulty", Config.DifficultyLevel); + if (Difficulty >= 1 && Difficulty <= 140) + { + Config.DifficultyLevel = Difficulty; + Config.DefaultDifficulty = Difficulty; + USS_LOG(" Difficulty: %d", Difficulty); + } + + // Max players + int MaxPlayers = GetCommandLineArgInt("-USS_MaxPlayers", Config.MaxPlayers); + if (MaxPlayers >= 1 && MaxPlayers <= 4) + { + Config.MaxPlayers = MaxPlayers; + USS_LOG(" MaxPlayers: %d", MaxPlayers); + } + + // Feature flags + if (HasCommandLineArg("-USS_NoMissions")) + { + Config.bEnableMissions = false; + USS_LOG(" Missions: DISABLED"); + } + + if (HasCommandLineArg("-USS_NoInventory")) + { + Config.bEnableInventory = false; + USS_LOG(" Inventory: DISABLED"); + } + + if (HasCommandLineArg("-USS_NoBuilding")) + { + Config.bEnableBuilding = false; + USS_LOG(" Building: DISABLED"); + } + + if (HasCommandLineArg("-USS_Debug")) + { + g_bDebugMode = true; + USS_LOG(" Debug Mode: ENABLED"); + } + } + + /** + * Get a command-line argument value + * Format: -ArgName=Value or -ArgName="Value With Spaces" + */ + bool GetCommandLineArg(const char* ArgName, std::string& OutValue) + { + const char* CmdLine = GetCommandLineA(); + if (!CmdLine || !ArgName) + return false; + + // Find argument + const char* ArgPos = strstr(CmdLine, ArgName); + if (!ArgPos) + return false; + + // Skip to value (after '=') + const char* ValueStart = ArgPos + strlen(ArgName); + if (*ValueStart != '=') + return false; + + ValueStart++; // Skip '=' + + // Handle quoted values + if (*ValueStart == '"') + { + ValueStart++; + const char* ValueEnd = strchr(ValueStart, '"'); + if (ValueEnd) + { + OutValue = std::string(ValueStart, ValueEnd - ValueStart); + return true; + } + } + else + { + // Unquoted - read until space or end + const char* ValueEnd = ValueStart; + while (*ValueEnd && *ValueEnd != ' ' && *ValueEnd != '\t') + ValueEnd++; + + OutValue = std::string(ValueStart, ValueEnd - ValueStart); + return true; + } + + return false; + } + + /** + * Check if a command-line flag is present + */ + bool HasCommandLineArg(const char* ArgName) + { + const char* CmdLine = GetCommandLineA(); + if (!CmdLine || !ArgName) + return false; + + return strstr(CmdLine, ArgName) != nullptr; + } + + /** + * Get an integer command-line argument + */ + int GetCommandLineArgInt(const char* ArgName, int DefaultValue) + { + std::string Value; + if (!GetCommandLineArg(ArgName, Value)) + return DefaultValue; + + try + { + return std::stoi(Value); + } + catch (...) + { + return DefaultValue; + } + } + + DWORD WINAPI InitializationThread(LPVOID lpParam) + { + EResult Result = Log::Initialize(true, "USS_Log.txt"); + if (Result != EResult::Success) + { + MessageBoxA(nullptr, "Failed to initialize logging", "USS Error", MB_ICONERROR); + return 1; + } + + USS_LOG("========================================"); + USS_LOG(" UniversalSlashingSimulator v0.1.0"); + USS_LOG(" STW Gameserver Framework"); + USS_LOG("========================================"); + USS_LOG(""); + + // Wait for game to be ready + // TODO: find trigger in log, perhaps we need a way to trick the game into going to the STW lobby in headless? + USS_LOG("Waiting for game initialization..."); + Sleep(5000); // Placeholder + + USS_LOG("Initializing engine core..."); + Result = GetEngineCore().Initialize(); + + if (Result != EResult::Success) + { + USS_FATAL("Engine core initialization failed: %s", ResultToString(Result)); + MessageBoxA(nullptr, "Failed to initialize engine core", "USS Error", MB_ICONERROR); + return 1; + } + + // Log version info + const auto& Version = GetEngineCore().GetVersionInfo(); + USS_LOG(""); + USS_LOG("=== Version Information ==="); + USS_LOG("Engine: UE %s", Version.GetEngineVersionString().c_str()); + USS_LOG("Fortnite: %.2f", Version.FortniteVersion); + USS_LOG("CL: %u", Version.FortniteCL); + USS_LOG("Generation: %s", Version.GetGenerationName()); + USS_LOG(""); + USS_LOG("=== Feature Flags ==="); + USS_LOG("FNamePool: %s", Version.bUseFNamePool ? "Yes" : "No"); + USS_LOG("FField: %s", Version.bUseFField ? "Yes" : "No"); + USS_LOG("ChunkedObjects: %s", Version.bUseChunkedObjects ? "Yes" : "No"); + USS_LOG("NewFastArray: %s", Version.bUseNewFastArraySerializer ? "Yes" : "No"); + USS_LOG("TObjectPtr: %s", Version.bUseTObjectPtr ? "Yes" : "No"); + USS_LOG(""); + + // Initialize STW systems + USS_LOG("Initializing STW systems..."); + + // GameMode configuration with defaults + FSTWGameConfig GameConfig; + GameConfig.bEnableMissions = true; + GameConfig.bEnableInventory = true; + GameConfig.bEnableBuilding = true; + GameConfig.MaxPlayers = 4; + GameConfig.DefaultDifficulty = 1; + GameConfig.DifficultyLevel = 1; + GameConfig.MissionType = EMissionType::FarmsteadDefense; + GameConfig.ZoneName = "Zone_Onboarding_FarmsteadFort"; + GameConfig.MissionBlueprint = "Mission_FarmsteadFort_C"; + + // Parse command-line arguments to override defaults + USS_LOG(""); + USS_LOG("=== Command Line Configuration ==="); + ParseCommandLineArguments(GameConfig); + USS_LOG(""); + + // Initialize GameMode (this initializes all subsystems) + Result = GetSTWGameMode().Initialize(GameConfig); + if (Result != EResult::Success) + { + USS_ERROR("STW GameMode initialization failed: %s", ResultToString(Result)); + // Continue anyway - partial functionality may still work + } + else + { + USS_LOG("STW systems initialized successfully"); + } + + USS_LOG(""); + USS_LOG("UniversalSlashingSimulator initialized successfully"); + USS_LOG("========================================"); + + g_bInitialized = true; + return 0; + } + + void Shutdown() + { + if (!g_bInitialized) + return; + + USS_LOG("Shutting down UniversalSlashingSimulator..."); + + GetSTWGameMode().Shutdown(); + + GetEngineCore().Shutdown(); + + Log::Shutdown(); + + g_bInitialized = false; + } + +} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hModule); + USS::g_hModule = hModule; + + CreateThread(nullptr, 0, USS::InitializationThread, nullptr, 0, nullptr); + break; + + case DLL_PROCESS_DETACH: + USS::Shutdown(); + break; + + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + + return TRUE; +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..8437360 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# UniversalSlashingSimulator + +## WIP Universal STW Gameserver \ No newline at end of file diff --git a/STW/Building/BuildingManager.cpp b/STW/Building/BuildingManager.cpp new file mode 100644 index 0000000..f5cd50b --- /dev/null +++ b/STW/Building/BuildingManager.cpp @@ -0,0 +1,830 @@ +/** + * UniversalSlashingSimulator - Building Manager Implementation + */ + +#include "BuildingManager.h" +#include "../Inventory/InventoryManager.h" +#include "../../Core/Logging/Log.h" +#include "../../Engine/EngineCore.h" + +namespace USS +{ + static FBuildingManager g_LocalBuildingManager; + + FBuildingManager& GetLocalBuildingManager() + { + return g_LocalBuildingManager; + } + + FBuildingManager::FBuildingManager() + { + m_BuildPreview = FBuildPreview(); + } + + FBuildingManager::~FBuildingManager() + { + Shutdown(); + } + + EResult FBuildingManager::Initialize(FInventoryManager* InventoryManager) + { + USS_LOG("Initializing Building Manager..."); + + m_pInventoryManager = InventoryManager; + + m_Buildings.clear(); + m_Traps.clear(); + m_GridToBuildingId.clear(); + m_EventCallbacks.clear(); + + m_bIsInBuildMode = false; + m_bIsPlacingTrap = false; + m_CurrentBuildType = EBuildingType::None; + m_CurrentMaterial = EBuildingMaterial::Wood; + + // Find building manager actors in world + // TODO: Locate AFortBuildingManager + + USS_LOG("Building Manager initialized"); + return EResult::Success; + } + + void FBuildingManager::Shutdown() + { + ExitBuildMode(); + ExitTrapPlacementMode(); + + m_Buildings.clear(); + m_Traps.clear(); + m_GridToBuildingId.clear(); + m_EventCallbacks.clear(); + + m_pInventoryManager = nullptr; + m_BuildingManagerActor = UObjectWrapper(); + m_TrapManagerActor = UObjectWrapper(); + } + + void FBuildingManager::Update() + { + // Update building construction progress + for (auto& Pair : m_Buildings) + { + FBuildingPiece& Building = Pair.second; + + if (Building.State == EBuildingState::Building) + { + // Progress construction + // TODO: Check actual build progress from engine + } + else if (Building.State == EBuildingState::Repairing) + { + // Progress repair + float DeltaTime = 1.0f / 30.0f; // Placeholder + Building.Stats.CurrentHealth += Building.Stats.RepairRate * DeltaTime; + + if (Building.Stats.CurrentHealth >= Building.Stats.MaxHealth) + { + Building.Stats.CurrentHealth = Building.Stats.MaxHealth; + Building.State = EBuildingState::Built; + } + } + } + + // Update traps + float DeltaTime = 1.0f / 30.0f; // Placeholder + UpdateTraps(DeltaTime); + } + + EResult FBuildingManager::EnterBuildMode(EBuildingType Type) + { + if (Type == EBuildingType::None || Type == EBuildingType::Trap) + { + return EResult::InvalidParameter; + } + + m_bIsInBuildMode = true; + m_CurrentBuildType = Type; + + // Initialize preview + m_BuildPreview = FBuildPreview(); + m_BuildPreview.Type = Type; + m_BuildPreview.Material = m_CurrentMaterial; + m_BuildPreview.Cost = GetDefaultBuildCost(m_CurrentMaterial); + + USS_LOG("Entered build mode: Type=%d, Material=%d", + static_cast(Type), static_cast(m_CurrentMaterial)); + + return EResult::Success; + } + + void FBuildingManager::ExitBuildMode() + { + if (m_bIsInBuildMode) + { + m_bIsInBuildMode = false; + m_CurrentBuildType = EBuildingType::None; + m_BuildPreview = FBuildPreview(); + + USS_LOG("Exited build mode"); + } + } + + void FBuildingManager::SetBuildMaterial(EBuildingMaterial Material) + { + m_CurrentMaterial = Material; + + if (m_bIsInBuildMode) + { + m_BuildPreview.Material = Material; + m_BuildPreview.Cost = GetDefaultBuildCost(Material); + } + + USS_LOG("Set build material: %d", static_cast(Material)); + } + + void FBuildingManager::CycleMaterial() + { + switch (m_CurrentMaterial) + { + case EBuildingMaterial::Wood: + SetBuildMaterial(EBuildingMaterial::Stone); + break; + case EBuildingMaterial::Stone: + SetBuildMaterial(EBuildingMaterial::Metal); + break; + case EBuildingMaterial::Metal: + default: + SetBuildMaterial(EBuildingMaterial::Wood); + break; + } + } + + void FBuildingManager::UpdateBuildPreview(float LocationX, float LocationY, float LocationZ, float Rotation) + { + if (!m_bIsInBuildMode) + return; + + m_BuildPreview.LocationX = LocationX; + m_BuildPreview.LocationY = LocationY; + m_BuildPreview.LocationZ = LocationZ; + m_BuildPreview.Rotation = Rotation; + + // Validate placement + m_BuildPreview.bIsOverlapping = CheckOverlap(LocationX, LocationY, LocationZ); + m_BuildPreview.bIsFloating = !CheckSupport(LocationX, LocationY, LocationZ, m_BuildPreview.Type); + + m_BuildPreview.bIsValidPlacement = !m_BuildPreview.bIsOverlapping && !m_BuildPreview.bIsFloating; + + // Check resources + if (m_pInventoryManager) + { + int32 Wood = m_pInventoryManager->GetWoodCount(); + int32 Stone = m_pInventoryManager->GetStoneCount(); + int32 Metal = m_pInventoryManager->GetMetalCount(); + + m_BuildPreview.bCanAfford = m_BuildPreview.Cost.CanAfford(Wood, Stone, Metal); + } + } + + EResult FBuildingManager::ConfirmBuild() + { + if (!m_bIsInBuildMode) + { + return EResult::InvalidState; + } + + if (!m_BuildPreview.bIsValidPlacement) + { + USS_WARN("Cannot build: Invalid placement"); + return EResult::InvalidPlacement; + } + + if (!m_BuildPreview.bCanAfford) + { + USS_WARN("Cannot build: Insufficient resources"); + return EResult::InsufficientResources; + } + + if (IsAtBuildLimit()) + { + USS_WARN("Cannot build: At build limit"); + return EResult::BuildLimitReached; + } + + // Consume resources + if (m_pInventoryManager) + { + const FBuildingCost& Cost = m_BuildPreview.Cost; + + if (Cost.WoodCost > 0) + m_pInventoryManager->ConsumeResources(EResourceType::Wood, Cost.WoodCost); + if (Cost.StoneCost > 0) + m_pInventoryManager->ConsumeResources(EResourceType::Stone, Cost.StoneCost); + if (Cost.MetalCost > 0) + m_pInventoryManager->ConsumeResources(EResourceType::Metal, Cost.MetalCost); + } + + // Create building + FBuildingPiece NewBuilding; + NewBuilding.BuildingId = GenerateBuildingId(); + NewBuilding.Type = m_BuildPreview.Type; + NewBuilding.Material = m_BuildPreview.Material; + NewBuilding.Tier = EBuildingTier::Tier1; + NewBuilding.State = EBuildingState::Building; + NewBuilding.Stats = GetDefaultBuildStats(m_BuildPreview.Material); + NewBuilding.Cost = m_BuildPreview.Cost; + NewBuilding.bIsPlayerBuilt = true; + + // Apply build speed multiplier + NewBuilding.Stats.BuildTime /= m_BuildSpeedMultiplier; + + // Grid position (TODO: implement proper grid snapping) + NewBuilding.GridX = static_cast(m_BuildPreview.LocationX / 512.0f); + NewBuilding.GridY = static_cast(m_BuildPreview.LocationY / 512.0f); + NewBuilding.GridZ = static_cast(m_BuildPreview.LocationZ / 512.0f); + NewBuilding.Rotation = m_BuildPreview.Rotation; + + // Store building + m_Buildings[NewBuilding.BuildingId] = NewBuilding; + + // Register in grid lookup + uint64 GridKey = (static_cast(NewBuilding.GridX) << 40) | + (static_cast(NewBuilding.GridY) << 20) | + static_cast(NewBuilding.GridZ); + m_GridToBuildingId[GridKey] = NewBuilding.BuildingId; + + // Notify + FBuildingChangeEvent Event; + Event.Type = FBuildingChangeEvent::EChangeType::Built; + Event.BuildingId = NewBuilding.BuildingId; + Event.NewHealth = NewBuilding.Stats.CurrentHealth; + NotifyChange(Event); + + USS_LOG("Built %d at grid (%d, %d, %d) - ID: %s", + static_cast(NewBuilding.Type), + NewBuilding.GridX, NewBuilding.GridY, NewBuilding.GridZ, + NewBuilding.BuildingId.c_str()); + + return EResult::Success; + } + + void FBuildingManager::CancelBuild() + { + m_BuildPreview = FBuildPreview(); + m_BuildPreview.Type = m_CurrentBuildType; + m_BuildPreview.Material = m_CurrentMaterial; + } + + const FBuildingPiece* FBuildingManager::GetBuilding(const std::string& BuildingId) const + { + auto It = m_Buildings.find(BuildingId); + return (It != m_Buildings.end()) ? &It->second : nullptr; + } + + const FBuildingPiece* FBuildingManager::GetBuildingAtGrid(int32 X, int32 Y, int32 Z) const + { + uint64 GridKey = (static_cast(X) << 40) | + (static_cast(Y) << 20) | + static_cast(Z); + + auto It = m_GridToBuildingId.find(GridKey); + if (It != m_GridToBuildingId.end()) + { + return GetBuilding(It->second); + } + return nullptr; + } + + std::vector FBuildingManager::GetPlayerBuildings(const std::string& PlayerId) const + { + std::vector Result; + + for (const auto& Pair : m_Buildings) + { + if (Pair.second.OwnerId == PlayerId || Pair.second.bIsPlayerBuilt) + { + Result.push_back(&Pair.second); + } + } + + return Result; + } + + int32 FBuildingManager::GetPlayerBuildingCount(const std::string& PlayerId) const + { + int32 Count = 0; + + for (const auto& Pair : m_Buildings) + { + if (Pair.second.OwnerId == PlayerId || Pair.second.bIsPlayerBuilt) + { + ++Count; + } + } + + return Count; + } + + EResult FBuildingManager::DamageBuilding(const std::string& BuildingId, float Damage, void* DamageCauser) + { + auto It = m_Buildings.find(BuildingId); + if (It == m_Buildings.end()) + { + return EResult::BuildingNotFound; + } + + FBuildingPiece& Building = It->second; + float OldHealth = Building.Stats.CurrentHealth; + + // Apply damage resistance + float ActualDamage = Damage * (1.0f - Building.Stats.DamageResistance); + Building.Stats.CurrentHealth -= ActualDamage; + + FBuildingChangeEvent Event; + Event.BuildingId = BuildingId; + Event.OldHealth = OldHealth; + Event.NewHealth = Building.Stats.CurrentHealth; + Event.Damage = ActualDamage; + Event.DamageCauser = DamageCauser; + + if (Building.Stats.CurrentHealth <= 0.0f) + { + Building.Stats.CurrentHealth = 0.0f; + Building.State = EBuildingState::Destroying; + + Event.Type = FBuildingChangeEvent::EChangeType::Destroyed; + + // Remove from grid + uint64 GridKey = (static_cast(Building.GridX) << 40) | + (static_cast(Building.GridY) << 20) | + static_cast(Building.GridZ); + m_GridToBuildingId.erase(GridKey); + + // Remove traps attached to this building + std::vector TrapsToRemove; + for (const auto& TrapPair : m_Traps) + { + if (TrapPair.second.AttachedBuildingId == BuildingId) + { + TrapsToRemove.push_back(TrapPair.first); + } + } + for (const auto& TrapId : TrapsToRemove) + { + m_Traps.erase(TrapId); + } + + m_Buildings.erase(It); + + USS_LOG("Building destroyed: %s", BuildingId.c_str()); + } + else + { + Building.State = EBuildingState::Damaged; + Event.Type = FBuildingChangeEvent::EChangeType::Damaged; + } + + NotifyChange(Event); + + return EResult::Success; + } + + EResult FBuildingManager::RepairBuilding(const std::string& BuildingId, float Amount) + { + auto It = m_Buildings.find(BuildingId); + if (It == m_Buildings.end()) + { + return EResult::BuildingNotFound; + } + + FBuildingPiece& Building = It->second; + + if (Building.IsFullHealth()) + { + return EResult::Success; // Already full + } + + float OldHealth = Building.Stats.CurrentHealth; + Building.Stats.CurrentHealth += Amount; + + if (Building.Stats.CurrentHealth >= Building.Stats.MaxHealth) + { + Building.Stats.CurrentHealth = Building.Stats.MaxHealth; + Building.State = EBuildingState::Built; + } + else + { + Building.State = EBuildingState::Repairing; + } + + FBuildingChangeEvent Event; + Event.Type = FBuildingChangeEvent::EChangeType::Repaired; + Event.BuildingId = BuildingId; + Event.OldHealth = OldHealth; + Event.NewHealth = Building.Stats.CurrentHealth; + NotifyChange(Event); + + return EResult::Success; + } + + EResult FBuildingManager::UpgradeBuilding(const std::string& BuildingId) + { + auto It = m_Buildings.find(BuildingId); + if (It == m_Buildings.end()) + { + return EResult::BuildingNotFound; + } + + FBuildingPiece& Building = It->second; + + if (Building.Tier >= EBuildingTier::Tier3) + { + USS_WARN("Building already at max tier"); + return EResult::InvalidState; + } + + // Check upgrade cost (STW uses different resources for upgrades) + // TODO: Consume upgrade materials + + // Upgrade tier + Building.Tier = static_cast(static_cast(Building.Tier) + 1); + Building.State = EBuildingState::Upgrading; + + // Increase stats + Building.Stats.MaxHealth *= 1.5f; + Building.Stats.CurrentHealth = Building.Stats.MaxHealth; + Building.Stats.DamageResistance += 0.05f; + + FBuildingChangeEvent Event; + Event.Type = FBuildingChangeEvent::EChangeType::Upgraded; + Event.BuildingId = BuildingId; + Event.NewHealth = Building.Stats.CurrentHealth; + NotifyChange(Event); + + USS_LOG("Upgraded building %s to tier %d", BuildingId.c_str(), static_cast(Building.Tier)); + + Building.State = EBuildingState::Built; + + return EResult::Success; + } + + EResult FBuildingManager::DemolishBuilding(const std::string& BuildingId) + { + auto It = m_Buildings.find(BuildingId); + if (It == m_Buildings.end()) + { + return EResult::BuildingNotFound; + } + + const FBuildingPiece& Building = It->second; + + // Refund partial resources (50%) + if (m_pInventoryManager) + { + FInventoryItem Refund; + Refund.Category = EItemCategory::Resource; + + if (Building.Cost.WoodCost > 0) + { + Refund.TemplateId = "Resource:Wood"; + Refund.Count = Building.Cost.WoodCost / 2; + m_pInventoryManager->AddItem(Refund); + } + if (Building.Cost.StoneCost > 0) + { + Refund.TemplateId = "Resource:Stone"; + Refund.Count = Building.Cost.StoneCost / 2; + m_pInventoryManager->AddItem(Refund); + } + if (Building.Cost.MetalCost > 0) + { + Refund.TemplateId = "Resource:Metal"; + Refund.Count = Building.Cost.MetalCost / 2; + m_pInventoryManager->AddItem(Refund); + } + } + + // Remove from grid + uint64 GridKey = (static_cast(Building.GridX) << 40) | + (static_cast(Building.GridY) << 20) | + static_cast(Building.GridZ); + m_GridToBuildingId.erase(GridKey); + + // Remove traps + std::vector TrapsToRemove; + for (const auto& TrapPair : m_Traps) + { + if (TrapPair.second.AttachedBuildingId == BuildingId) + { + TrapsToRemove.push_back(TrapPair.first); + } + } + for (const auto& TrapId : TrapsToRemove) + { + m_Traps.erase(TrapId); + } + + m_Buildings.erase(It); + + FBuildingChangeEvent Event; + Event.Type = FBuildingChangeEvent::EChangeType::Destroyed; + Event.BuildingId = BuildingId; + NotifyChange(Event); + + USS_LOG("Demolished building: %s", BuildingId.c_str()); + + return EResult::Success; + } + + EResult FBuildingManager::EditBuilding(const std::string& BuildingId, EBuildingType NewType) + { + auto It = m_Buildings.find(BuildingId); + if (It == m_Buildings.end()) + { + return EResult::BuildingNotFound; + } + + // Can only edit walls/floors/etc + FBuildingPiece& Building = It->second; + + if (Building.Type == EBuildingType::Trap || + Building.Type == EBuildingType::DefenseBuild || + Building.Type == EBuildingType::StructuralBuild) + { + return EResult::InvalidParameter; + } + + Building.Type = NewType; + + USS_LOG("Edited building %s to type %d", BuildingId.c_str(), static_cast(NewType)); + + return EResult::Success; + } + + EResult FBuildingManager::EnterTrapPlacementMode(ETrapType TrapType, const std::string& TrapItemId) + { + if (TrapType == ETrapType::None) + { + return EResult::InvalidParameter; + } + + m_bIsPlacingTrap = true; + m_CurrentTrapType = TrapType; + m_CurrentTrapItemId = TrapItemId; + + USS_LOG("Entered trap placement mode: Type=%d", static_cast(TrapType)); + + return EResult::Success; + } + + void FBuildingManager::ExitTrapPlacementMode() + { + if (m_bIsPlacingTrap) + { + m_bIsPlacingTrap = false; + m_CurrentTrapType = ETrapType::None; + m_CurrentTrapItemId.clear(); + + USS_LOG("Exited trap placement mode"); + } + } + + EResult FBuildingManager::ConfirmTrapPlacement(const std::string& AttachedBuildingId) + { + if (!m_bIsPlacingTrap) + { + return EResult::InvalidState; + } + + // Verify building exists + const FBuildingPiece* Building = GetBuilding(AttachedBuildingId); + if (!Building) + { + return EResult::BuildingNotFound; + } + + // Create trap + FTrapInstance NewTrap; + NewTrap.TrapId = GenerateTrapId(); + NewTrap.Type = m_CurrentTrapType; + NewTrap.State = EBuildingState::Built; + NewTrap.AttachedBuildingId = AttachedBuildingId; + NewTrap.GridX = Building->GridX; + NewTrap.GridY = Building->GridY; + NewTrap.GridZ = Building->GridZ; + NewTrap.bIsArmed = true; + + // Set default trap stats + NewTrap.Stats.Damage = 50.0f * m_TrapDamageMultiplier; + NewTrap.Stats.ReloadTime = 2.0f; + NewTrap.Stats.MaxDurability = 30; + NewTrap.Stats.CurrentDurability = 30; + + // Consume trap item from inventory + if (m_pInventoryManager && !m_CurrentTrapItemId.empty()) + { + m_pInventoryManager->RemoveItem(m_CurrentTrapItemId, 1); + } + + m_Traps[NewTrap.TrapId] = NewTrap; + + FBuildingChangeEvent Event; + Event.Type = FBuildingChangeEvent::EChangeType::TrapPlaced; + Event.BuildingId = NewTrap.TrapId; + NotifyChange(Event); + + USS_LOG("Placed trap %s on building %s", NewTrap.TrapId.c_str(), AttachedBuildingId.c_str()); + + return EResult::Success; + } + + const FTrapInstance* FBuildingManager::GetTrap(const std::string& TrapId) const + { + auto It = m_Traps.find(TrapId); + return (It != m_Traps.end()) ? &It->second : nullptr; + } + + std::vector FBuildingManager::GetTrapsOnBuilding(const std::string& BuildingId) const + { + std::vector Result; + + for (const auto& Pair : m_Traps) + { + if (Pair.second.AttachedBuildingId == BuildingId) + { + Result.push_back(&Pair.second); + } + } + + return Result; + } + + EResult FBuildingManager::TriggerTrap(const std::string& TrapId) + { + auto It = m_Traps.find(TrapId); + if (It == m_Traps.end()) + { + return EResult::TrapNotFound; + } + + FTrapInstance& Trap = It->second; + + if (!Trap.IsReady()) + { + return EResult::TrapNotReady; + } + + ProcessTrapTrigger(Trap); + + return EResult::Success; + } + + EResult FBuildingManager::ReloadTrap(const std::string& TrapId) + { + auto It = m_Traps.find(TrapId); + if (It == m_Traps.end()) + { + return EResult::TrapNotFound; + } + + FTrapInstance& Trap = It->second; + Trap.CooldownRemaining = 0.0f; + Trap.bIsArmed = true; + + USS_LOG("Reloaded trap: %s", TrapId.c_str()); + + return EResult::Success; + } + + bool FBuildingManager::IsAtBuildLimit() const + { + return static_cast(m_Buildings.size()) >= m_BuildLimit; + } + + void FBuildingManager::ApplyConstructorPerks(float BuildSpeed, float TrapDamage, int32 ExtraBuildLimit) + { + m_BuildSpeedMultiplier = BuildSpeed; + m_TrapDamageMultiplier = TrapDamage; + m_BuildLimit += ExtraBuildLimit; + + USS_LOG("Applied constructor perks: BuildSpeed=%.2f, TrapDamage=%.2f, ExtraLimit=%d", + BuildSpeed, TrapDamage, ExtraBuildLimit); + } + + void FBuildingManager::RegisterEventCallback(FBuildingEventCallback Callback) + { + if (Callback) + { + m_EventCallbacks.push_back(std::move(Callback)); + } + } + + void FBuildingManager::OnProcessEvent(void* Object, void* Function, void* Params) + { + // Handle building-related ProcessEvents + } + + std::string FBuildingManager::GenerateBuildingId() const + { + char Buffer[64]; + snprintf(Buffer, sizeof(Buffer), "bld_%u", ++m_BuildingIdCounter); + return Buffer; + } + + std::string FBuildingManager::GenerateTrapId() const + { + char Buffer[64]; + snprintf(Buffer, sizeof(Buffer), "trap_%u", ++m_TrapIdCounter); + return Buffer; + } + + bool FBuildingManager::ValidatePlacement(const FBuildPreview& Preview) const + { + return Preview.bIsValidPlacement && Preview.bCanAfford; + } + + bool FBuildingManager::CheckOverlap(float X, float Y, float Z) const + { + int32 GridX = static_cast(X / 512.0f); + int32 GridY = static_cast(Y / 512.0f); + int32 GridZ = static_cast(Z / 512.0f); + + return GetBuildingAtGrid(GridX, GridY, GridZ) != nullptr; + } + + bool FBuildingManager::CheckSupport(float X, float Y, float Z, EBuildingType Type) const + { + // Floors can be placed on ground or supported by walls + // Walls need floor below or adjacent wall + // Ramps need floor or ground + + // Simplified: Always allow for now + // TODO: Would check adjacent buildings and terrain + + return true; + } + + void FBuildingManager::NotifyChange(const FBuildingChangeEvent& Event) + { + for (const auto& Callback : m_EventCallbacks) + { + if (Callback) + { + Callback(Event); + } + } + } + + void FBuildingManager::UpdateTraps(float DeltaTime) + { + for (auto& Pair : m_Traps) + { + FTrapInstance& Trap = Pair.second; + + // Update cooldown + if (Trap.CooldownRemaining > 0.0f) + { + Trap.CooldownRemaining -= DeltaTime; + if (Trap.CooldownRemaining < 0.0f) + { + Trap.CooldownRemaining = 0.0f; + } + } + + // TODO: Check for enemies in range and trigger + } + } + + void FBuildingManager::ProcessTrapTrigger(FTrapInstance& Trap) + { + Trap.bIsTriggered = true; + Trap.Stats.CurrentDurability -= Trap.Stats.UsesPerActivation; + Trap.CooldownRemaining = Trap.Stats.ReloadTime; + + FBuildingChangeEvent Event; + Event.Type = FBuildingChangeEvent::EChangeType::TrapTriggered; + Event.BuildingId = Trap.TrapId; + NotifyChange(Event); + + USS_LOG("Trap triggered: %s (durability: %d/%d)", + Trap.TrapId.c_str(), + Trap.Stats.CurrentDurability, + Trap.Stats.MaxDurability); + + if (Trap.Stats.CurrentDurability <= 0) + { + Trap.bIsArmed = false; + Trap.State = EBuildingState::Destroying; + + FBuildingChangeEvent DestroyEvent; + DestroyEvent.Type = FBuildingChangeEvent::EChangeType::TrapDestroyed; + DestroyEvent.BuildingId = Trap.TrapId; + NotifyChange(DestroyEvent); + + USS_LOG("Trap exhausted: %s", Trap.TrapId.c_str()); + } + + Trap.bIsTriggered = false; + } + +} \ No newline at end of file diff --git a/STW/Building/BuildingManager.h b/STW/Building/BuildingManager.h new file mode 100644 index 0000000..d9b07b6 --- /dev/null +++ b/STW/Building/BuildingManager.h @@ -0,0 +1,322 @@ +/** + * UniversalSlashingSimulator - Building Manager + * + * Manages STW building system: construction, traps, upgrades, and repairs. + */ + +#pragma once + +#include "BuildingTypes.h" +#include "../../Engine/UObject/UObjectWrapper.h" +#include +#include +#include + +namespace USS +{ + // Forward declarations + class FInventoryManager; + + /** + * Callback for building events + */ + using FBuildingEventCallback = std::function; + + /** + * Building Manager + * + * Handles all building operations including: + * - Building placement and preview + * - Material selection and switching + * - Trap placement and management + * - Building upgrades (STW tiers) + * - Repair and demolition + * - Build limit tracking + */ + class FBuildingManager + { + public: + FBuildingManager(); + ~FBuildingManager(); + + // Lifecycle + EResult Initialize(FInventoryManager* InventoryManager); + void Shutdown(); + void Update(); + + // =========================================== + // Building Operations + // =========================================== + + /** + * Enter build mode for a specific type + */ + EResult EnterBuildMode(EBuildingType Type); + + /** + * Exit build mode + */ + void ExitBuildMode(); + + /** + * Check if currently in build mode + */ + bool IsInBuildMode() const { return m_bIsInBuildMode; } + + /** + * Get current build type being previewed + */ + EBuildingType GetCurrentBuildType() const { return m_CurrentBuildType; } + + /** + * Set building material + */ + void SetBuildMaterial(EBuildingMaterial Material); + + /** + * Get current building material + */ + EBuildingMaterial GetBuildMaterial() const { return m_CurrentMaterial; } + + /** + * Cycle to next material + */ + void CycleMaterial(); + + /** + * Update build preview position + */ + void UpdateBuildPreview(float LocationX, float LocationY, float LocationZ, float Rotation); + + /** + * Get current build preview + */ + const FBuildPreview& GetBuildPreview() const { return m_BuildPreview; } + + /** + * Confirm placement at current preview location + */ + EResult ConfirmBuild(); + + /** + * Cancel current build operation + */ + void CancelBuild(); + + // =========================================== + // Building Access + // =========================================== + + /** + * Get building by ID + */ + const FBuildingPiece* GetBuilding(const std::string& BuildingId) const; + + /** + * Get building at grid position + */ + const FBuildingPiece* GetBuildingAtGrid(int32 X, int32 Y, int32 Z) const; + + /** + * Get all buildings owned by player + */ + std::vector GetPlayerBuildings(const std::string& PlayerId) const; + + /** + * Get total building count + */ + int32 GetBuildingCount() const { return static_cast(m_Buildings.size()); } + + /** + * Get player's building count + */ + int32 GetPlayerBuildingCount(const std::string& PlayerId) const; + + // =========================================== + // Building Modifications + // =========================================== + + /** + * Damage a building + */ + EResult DamageBuilding(const std::string& BuildingId, float Damage, void* DamageCauser); + + /** + * Repair a building + */ + EResult RepairBuilding(const std::string& BuildingId, float Amount); + + /** + * Upgrade building to next tier (STW) + */ + EResult UpgradeBuilding(const std::string& BuildingId); + + /** + * Demolish a building (refund partial materials) + */ + EResult DemolishBuilding(const std::string& BuildingId); + + /** + * Edit building piece (change type/shape) + */ + EResult EditBuilding(const std::string& BuildingId, EBuildingType NewType); + + // =========================================== + // Trap Operations + // =========================================== + + /** + * Enter trap placement mode + */ + EResult EnterTrapPlacementMode(ETrapType TrapType, const std::string& TrapItemId); + + /** + * Exit trap placement mode + */ + void ExitTrapPlacementMode(); + + /** + * Check if in trap placement mode + */ + bool IsInTrapPlacementMode() const { return m_bIsPlacingTrap; } + + /** + * Confirm trap placement + */ + EResult ConfirmTrapPlacement(const std::string& AttachedBuildingId); + + /** + * Get trap by ID + */ + const FTrapInstance* GetTrap(const std::string& TrapId) const; + + /** + * Get traps attached to building + */ + std::vector GetTrapsOnBuilding(const std::string& BuildingId) const; + + /** + * Trigger trap manually (for testing) + */ + EResult TriggerTrap(const std::string& TrapId); + + /** + * Reload/reset trap + */ + EResult ReloadTrap(const std::string& TrapId); + + // =========================================== + // STW-Specific Features + // =========================================== + + /** + * Get current build limit (based on constructor bonus, etc.) + */ + int32 GetBuildLimit() const { return m_BuildLimit; } + + /** + * Set build limit + */ + void SetBuildLimit(int32 Limit) { m_BuildLimit = Limit; } + + /** + * Check if at build limit + */ + bool IsAtBuildLimit() const; + + /** + * Get building speed multiplier (constructor bonus) + */ + float GetBuildSpeedMultiplier() const { return m_BuildSpeedMultiplier; } + + /** + * Set building speed multiplier + */ + void SetBuildSpeedMultiplier(float Multiplier) { m_BuildSpeedMultiplier = Multiplier; } + + /** + * Get trap damage multiplier (constructor bonus) + */ + float GetTrapDamageMultiplier() const { return m_TrapDamageMultiplier; } + + /** + * Set trap damage multiplier + */ + void SetTrapDamageMultiplier(float Multiplier) { m_TrapDamageMultiplier = Multiplier; } + + /** + * Apply constructor perks + */ + void ApplyConstructorPerks(float BuildSpeed, float TrapDamage, int32 ExtraBuildLimit); + + // =========================================== + // Events + // =========================================== + + /** + * Register callback for building events + */ + void RegisterEventCallback(FBuildingEventCallback Callback); + + /** + * Handle ProcessEvent for building-related functions + */ + void OnProcessEvent(void* Object, void* Function, void* Params); + + private: + // Internal helpers + std::string GenerateBuildingId() const; + std::string GenerateTrapId() const; + + bool ValidatePlacement(const FBuildPreview& Preview) const; + bool CheckOverlap(float X, float Y, float Z) const; + bool CheckSupport(float X, float Y, float Z, EBuildingType Type) const; + + void NotifyChange(const FBuildingChangeEvent& Event); + + void UpdateTraps(float DeltaTime); + void ProcessTrapTrigger(FTrapInstance& Trap); + + // State + bool m_bIsInBuildMode = false; + bool m_bIsPlacingTrap = false; + + EBuildingType m_CurrentBuildType = EBuildingType::None; + EBuildingMaterial m_CurrentMaterial = EBuildingMaterial::Wood; + ETrapType m_CurrentTrapType = ETrapType::None; + std::string m_CurrentTrapItemId; + + FBuildPreview m_BuildPreview; + + // Building storage + std::unordered_map m_Buildings; + std::unordered_map m_Traps; + + // Grid lookup (for fast position-based queries) + std::unordered_map m_GridToBuildingId; + + // ID counters + mutable uint32 m_BuildingIdCounter = 0; + mutable uint32 m_TrapIdCounter = 0; + + // STW bonuses + int32 m_BuildLimit = 1000; + float m_BuildSpeedMultiplier = 1.0f; + float m_TrapDamageMultiplier = 1.0f; + + // References + FInventoryManager* m_pInventoryManager = nullptr; + + UObjectWrapper m_BuildingManagerActor; + UObjectWrapper m_TrapManagerActor; + + // Event callbacks + std::vector m_EventCallbacks; + }; + + /** + * Global building manager accessor + */ + FBuildingManager& GetLocalBuildingManager(); + +} \ No newline at end of file diff --git a/STW/Building/BuildingTypes.h b/STW/Building/BuildingTypes.h new file mode 100644 index 0000000..f68f249 --- /dev/null +++ b/STW/Building/BuildingTypes.h @@ -0,0 +1,382 @@ +/** + * UniversalSlashingSimulator - Building System Types + * + * Defines STW building structures, traps, and construction types. + */ + +#pragma once + +#include "../../Core/Common.h" +#include +#include + +namespace USS +{ + /** + * Building piece types + */ + enum class EBuildingType : uint8 + { + None = 0, + Wall, + Floor, + Ramp, + Roof, + Stair, + + // STW-specific + Trap, + StructuralBuild, // Mission objectives like radar towers + DefenseBuild, // Atlas, amplifiers, etc. + }; + + /** + * Building material tiers + */ + enum class EBuildingMaterial : uint8 + { + Wood = 0, + Stone, + Metal, + + // Special materials (STW) + Tier2, // Upgraded wood + Tier3, // Upgraded stone/brick + Tier4, // Upgraded metal + }; + + /** + * Building upgrade level (STW progression) + */ + enum class EBuildingTier : uint8 + { + Tier1 = 1, + Tier2 = 2, + Tier3 = 3, + }; + + /** + * Trap types in STW + */ + enum class ETrapType : uint8 + { + None = 0, + + // Floor traps + FloorSpikes, + FloorFreeze, + FloorLauncher, + FloorElectric, + FloorRetractSpikes, + FloorBroadside, + + // Wall traps + WallDarts, + WallDynamo, + WallLights, + WallLauncher, + WallSpikes, + + // Ceiling traps + CeilingZapper, + CeilingGas, + CeilingDropTrap, + CeilingElectric, + CeilingTire, + + // Defender posts + DefenderPost, + }; + + /** + * Trap targeting behavior + */ + enum class ETrapTargeting : uint8 + { + None = 0, + Proximity, // Triggers when enemy is near + Path, // Targets pathing enemies + Random, // Random targeting + Strongest, // Targets highest HP + Closest, // Targets closest enemy + }; + + /** + * Building state + */ + enum class EBuildingState : uint8 + { + None = 0, + Previewing, // Ghost preview before placement + Placing, // In placement mode + Building, // Construction in progress + Built, // Fully constructed + Damaged, // Has taken damage + Upgrading, // Being upgraded + Repairing, // Being repaired + Destroying, // Being demolished + }; + + /** + * Resource cost for building + */ + struct FBuildingCost + { + int32 WoodCost = 0; + int32 StoneCost = 0; + int32 MetalCost = 0; + + // STW ingredients + int32 NutsAndBolts = 0; + int32 PlanksCount = 0; + int32 RoughOre = 0; + + bool CanAfford(int32 Wood, int32 Stone, int32 Metal) const + { + return Wood >= WoodCost && Stone >= StoneCost && Metal >= MetalCost; + } + }; + + /** + * Building piece stats + */ + struct FBuildingStats + { + float MaxHealth = 100.0f; + float CurrentHealth = 100.0f; + float BuildTime = 3.0f; // Seconds to build + float RepairRate = 10.0f; // Health per second when repairing + float DamageResistance = 0.0f; // Percentage damage reduction + + // Material-specific modifiers + float FireResistance = 0.0f; + float PhysicalResistance = 0.0f; + float EnergyResistance = 0.0f; + }; + + /** + * Trap stats + */ + struct FTrapStats + { + float Damage = 0.0f; + float ReloadTime = 0.0f; // Seconds between activations + float Range = 0.0f; // Trigger range + float KnockbackForce = 0.0f; + float SlowPercent = 0.0f; // Movement slow on hit + float StunDuration = 0.0f; + + int32 MaxDurability = 0; + int32 CurrentDurability = 0; + int32 UsesPerActivation = 1; // How many uses per trigger + + ETrapTargeting Targeting = ETrapTargeting::Proximity; + + // Status effects + bool bAppliesAffliction = false; + bool bAppliesSnare = false; + bool bAppliesStun = false; + bool bAppliesFreeze = false; + }; + + /** + * Building piece data + */ + struct FBuildingPiece + { + std::string BuildingId; + EBuildingType Type = EBuildingType::None; + EBuildingMaterial Material = EBuildingMaterial::Wood; + EBuildingTier Tier = EBuildingTier::Tier1; + EBuildingState State = EBuildingState::None; + + FBuildingStats Stats; + FBuildingCost Cost; + + // Grid position + int32 GridX = 0; + int32 GridY = 0; + int32 GridZ = 0; + float Rotation = 0.0f; + + // Owner info + std::string OwnerId; + bool bIsPlayerBuilt = false; + + // Native actor reference + void* BuildingActor = nullptr; + + float GetHealthPercent() const + { + return (Stats.MaxHealth > 0.0f) ? (Stats.CurrentHealth / Stats.MaxHealth) : 0.0f; + } + + bool IsFullHealth() const + { + return Stats.CurrentHealth >= Stats.MaxHealth; + } + + bool IsDamaged() const + { + return Stats.CurrentHealth < Stats.MaxHealth; + } + }; + + /** + * Trap instance data + */ + struct FTrapInstance + { + std::string TrapId; + ETrapType Type = ETrapType::None; + EBuildingState State = EBuildingState::None; + + FTrapStats Stats; + FBuildingCost Cost; + + // Attached building + std::string AttachedBuildingId; + + // Placement + int32 GridX = 0; + int32 GridY = 0; + int32 GridZ = 0; + float Rotation = 0.0f; + + // State + float CooldownRemaining = 0.0f; + int32 TotalKills = 0; + bool bIsArmed = true; + bool bIsTriggered = false; + + // Owner + std::string OwnerId; + + // Native reference + void* TrapActor = nullptr; + + bool IsReady() const + { + return bIsArmed && CooldownRemaining <= 0.0f && Stats.CurrentDurability > 0; + } + + float GetDurabilityPercent() const + { + return (Stats.MaxDurability > 0) ? + (static_cast(Stats.CurrentDurability) / Stats.MaxDurability) : 0.0f; + } + }; + + /** + * Build preview ghost data + */ + struct FBuildPreview + { + EBuildingType Type = EBuildingType::None; + EBuildingMaterial Material = EBuildingMaterial::Wood; + + // Preview position + float LocationX = 0.0f; + float LocationY = 0.0f; + float LocationZ = 0.0f; + float Rotation = 0.0f; + + // Validity + bool bIsValidPlacement = false; + bool bCanAfford = false; + bool bIsOverlapping = false; + bool bIsFloating = false; + + FBuildingCost Cost; + }; + + /** + * Building change event data + */ + struct FBuildingChangeEvent + { + enum class EChangeType + { + Built, + Destroyed, + Damaged, + Repaired, + Upgraded, + TrapPlaced, + TrapTriggered, + TrapDestroyed, + }; + + EChangeType Type; + std::string BuildingId; + std::string PlayerId; + + float OldHealth = 0.0f; + float NewHealth = 0.0f; + float Damage = 0.0f; + + void* DamageCauser = nullptr; + }; + + /** + * Default building costs by material + */ + inline FBuildingCost GetDefaultBuildCost(EBuildingMaterial Material) + { + FBuildingCost Cost; + + switch (Material) + { + case EBuildingMaterial::Wood: + Cost.WoodCost = 10; + break; + case EBuildingMaterial::Stone: + Cost.StoneCost = 10; + break; + case EBuildingMaterial::Metal: + Cost.MetalCost = 10; + break; + default: + Cost.WoodCost = 10; + break; + } + + return Cost; + } + + /** + * Default building stats by material + */ + inline FBuildingStats GetDefaultBuildStats(EBuildingMaterial Material) + { + FBuildingStats Stats; + + switch (Material) + { + case EBuildingMaterial::Wood: + Stats.MaxHealth = 150.0f; + Stats.BuildTime = 3.0f; + Stats.FireResistance = -0.25f; // Weak to fire + break; + case EBuildingMaterial::Stone: + Stats.MaxHealth = 300.0f; + Stats.BuildTime = 4.0f; + Stats.PhysicalResistance = 0.1f; + break; + case EBuildingMaterial::Metal: + Stats.MaxHealth = 500.0f; + Stats.BuildTime = 5.0f; + Stats.PhysicalResistance = 0.15f; + Stats.EnergyResistance = -0.25f; // Weak to energy + break; + default: + Stats.MaxHealth = 150.0f; + Stats.BuildTime = 3.0f; + break; + } + + Stats.CurrentHealth = Stats.MaxHealth; + return Stats; + } + +} \ No newline at end of file diff --git a/STW/GameMode/STWGameMode.cpp b/STW/GameMode/STWGameMode.cpp new file mode 100644 index 0000000..d5ab224 --- /dev/null +++ b/STW/GameMode/STWGameMode.cpp @@ -0,0 +1,374 @@ +/** + * UniversalSlashingSimulator - STW Game Mode Implementation + * + * Based on farmstead_plate.cpp from PolarisV2-STW. + */ + +#include "STWGameMode.h" +#include "../../Core/Logging/Log.h" +#include "../../Core/Hooks/HookTypes.h" +#include "../../Engine/EngineCore.h" +#include "../Missions/MissionManager.h" +#include "../Inventory/InventoryManager.h" +#include "../Building/BuildingManager.h" +#include "../Player/STWPlayerController.h" +#include + +namespace USS +{ + FSTWGameMode::FSTWGameMode() + : m_State(ESTWGameState::None) + , m_bWorldReady(false) + , m_bPlayersLoaded(false) + { + } + + FSTWGameMode::~FSTWGameMode() + { + Shutdown(); + } + + FSTWGameMode& FSTWGameMode::Get() + { + static FSTWGameMode Instance; + return Instance; + } + + EResult FSTWGameMode::Initialize(const FSTWGameConfig& Config) + { + if (m_State != ESTWGameState::None) + return EResult::AlreadyInitialized; + + USS_LOG("Initializing STW GameMode..."); + USS_LOG(" Zone: %s", Config.ZoneName.c_str()); + USS_LOG(" Mission: %s", Config.MissionBlueprint.c_str()); + + m_Config = Config; + SetState(ESTWGameState::Initializing); + + // Create managers + m_pMissionManager = std::make_unique(); + m_pInventoryManager = std::make_unique(); + m_pBuildingManager = std::make_unique(); + + // Register for ProcessEvent callbacks (@timmie implements hooks) + // When hooks are implemented, register a callback that forwards to OnProcessEvent: + // Hook::GetProcessEventDispatcher().RegisterPre([this](void* Obj, void* Func, void* Params) -> bool { + // OnProcessEvent(Obj, Func, Params); + // return true; + // }); + + // Initialize managers + if (m_pMissionManager->Initialize() != EResult::Success) + { + USS_WARN("Mission manager initialization incomplete"); + } + + if (m_pInventoryManager->Initialize(nullptr) != EResult::Success) + { + USS_WARN("Inventory manager initialization incomplete"); + } + + if (m_pBuildingManager->Initialize(m_pInventoryManager.get()) != EResult::Success) + { + USS_WARN("Building manager initialization incomplete"); + } + + SetState(ESTWGameState::WaitingForWorld); + + USS_LOG("STW GameMode initialized, waiting for world..."); + return EResult::Success; + } + + void FSTWGameMode::Shutdown() + { + if (m_State == ESTWGameState::None || m_State == ESTWGameState::Shutdown) + return; + + USS_LOG("Shutting down STW GameMode..."); + + SetState(ESTWGameState::Shutdown); + + m_pLocalPlayer.reset(); + m_pBuildingManager.reset(); + m_pInventoryManager.reset(); + m_pMissionManager.reset(); + + m_World = UObjectWrapper(); + m_GameState = UObjectWrapper(); + m_GameMode = UObjectWrapper(); + + m_bWorldReady = false; + m_bPlayersLoaded = false; + + m_State = ESTWGameState::None; + + USS_LOG("STW GameMode shutdown complete"); + } + + void FSTWGameMode::Update() + { + // Called each tick - update managers + if (m_pMissionManager) + m_pMissionManager->Update(); + + if (m_pBuildingManager) + m_pBuildingManager->Update(); + } + + void FSTWGameMode::SetState(ESTWGameState NewState) + { + if (m_State == NewState) + return; + + ESTWGameState OldState = m_State; + m_State = NewState; + + USS_LOG("GameMode state: %s -> %s", + GetGameStateName(OldState), + GetGameStateName(NewState)); + + // Notify callbacks + for (const auto& Callback : m_StateChangeCallbacks) + { + Callback(OldState, NewState); + } + } + + void FSTWGameMode::SetLocalPlayer(std::unique_ptr Player) + { + m_pLocalPlayer = std::move(Player); + } + + void FSTWGameMode::RegisterStateChangeCallback(StateChangeCallback Callback) + { + m_StateChangeCallbacks.push_back(std::move(Callback)); + } + + // @timmie: replace with normal VFT / hooking system when available + void FSTWGameMode::OnProcessEvent(void* Object, void* Function, void* Params) + { + if (!Function) + return; + + // Get function name + UFunctionWrapper FuncWrapper(Function); + std::string FuncName = FuncWrapper.GetName(); + + // Handle specific events based on current state + if (FuncName.find("ReadyToStartMatch") != std::string::npos) + { + OnReadyToStartMatch(); + } + else if (FuncName.find("ServerHandleMissionEvent_ToggledEditMode") != std::string::npos) + { + OnToggleEditMode(Params); + } + else if (FuncName.find("ServerHandleMissionEvent_StartLeavingZone") != std::string::npos) + { + OnStartLeavingZone(Params); + } + else if (FuncName.find("ServerCraftSchematic") != std::string::npos) + { + OnCraftSchematic(Params); + } + else if (FuncName.find("Tick") != std::string::npos) + { + Update(); + } + + // Forward to sub-managers + if (m_pMissionManager) + m_pMissionManager->OnProcessEvent(Object, Function, Params); + + if (m_pBuildingManager && m_pLocalPlayer) + m_pBuildingManager->OnProcessEvent(Object, Function, Params); + } + + void FSTWGameMode::OnReadyToStartMatch() + { + if (m_State != ESTWGameState::WaitingForWorld) + return; + + USS_LOG("ReadyToStartMatch received"); + + InitializeWorld(); + OnWorldReady(); + + // Transition to waiting for players + SetState(ESTWGameState::WaitingForPlayers); + + // For single-player, immediately proceed + OnAllPlayersLoaded(); + } + + void FSTWGameMode::OnWorldReady() + { + USS_LOG("World is ready"); + m_bWorldReady = true; + + // Cache world references + m_World = GetEngineCore().FindObjectByName("PersistentLevel"); + + // Load husk assets into memory + LoadHuskAssets(); + } + + void FSTWGameMode::OnAllPlayersLoaded() + { + if (m_bPlayersLoaded) + return; + + USS_LOG("All players loaded"); + m_bPlayersLoaded = true; + + SetState(ESTWGameState::LoadingMission); + + // Spawn local player + SpawnLocalPlayer(); + + // Setup inventory + SetupInventory(); + + // Initialize mission + InitializeMission(); + + SetState(ESTWGameState::MissionActive); + } + + void FSTWGameMode::OnMissionEvent(const char* EventName, void* Params) + { + USS_LOG("Mission event: %s", EventName); + + if (m_pMissionManager) + { + m_pMissionManager->OnMissionEvent(EventName, Params); + } + } + + void FSTWGameMode::OnToggleEditMode(void* Params) + { + USS_LOG("Edit mode toggled"); + + // Toggle between build mode and normal mode + if (m_pBuildingManager) + { + if (m_pBuildingManager->IsInBuildMode()) + { + m_pBuildingManager->ExitBuildMode(); + } + else + { + m_pBuildingManager->EnterBuildMode(EBuildingType::Wall); + } + } + } + + void FSTWGameMode::OnStartLeavingZone(void* Params) + { + USS_LOG("Starting to leave zone"); + + SetState(ESTWGameState::LeavingZone); + + // TODO: Handle zone exit + } + + void FSTWGameMode::OnCraftSchematic(void* Params) + { + USS_LOG("Craft schematic requested"); + + // TODO: Parse schematic ID from Params and call CraftItem + // if (m_pInventoryManager) + // { + // m_pInventoryManager->CraftItem(schematicId, 1); + // } + } + + void FSTWGameMode::InitializeWorld() + { + USS_LOG("Initializing world..."); + + // TODO: Additional world initialization + // - Patch gameplay abilities + // - Setup replication + } + + void FSTWGameMode::InitializeMission() + { + USS_LOG("Initializing mission..."); + + if (!m_pMissionManager) + return; + + // Create mission based on config + FMissionConfig MissionConfig; + MissionConfig.Type = m_Config.MissionType; + MissionConfig.DifficultyLevel = m_Config.DifficultyLevel; + MissionConfig.BlueprintPath = m_Config.MissionBlueprint; + + m_pMissionManager->StartMission(MissionConfig); + } + + void FSTWGameMode::SpawnLocalPlayer() + { + USS_LOG("Spawning local player..."); + + // Find local player controller from engine + void* LocalController = GetEngineCore().FindLocalPlayerController(); + + if (!LocalController) + { + USS_WARN("Local player controller not found yet"); + return; + } + + // Create player controller wrapper + m_pLocalPlayer = std::make_unique(LocalController); + + if (!m_pLocalPlayer->IsValid()) + { + USS_ERROR("Failed to wrap local player controller"); + m_pLocalPlayer.reset(); + return; + } + + USS_LOG("Local player spawned successfully"); + } + + void FSTWGameMode::SetupInventory() + { + USS_LOG("Setting up inventory..."); + + if (!m_pInventoryManager || !m_pLocalPlayer) + return; + + // Re-initialize inventory with the player controller + m_pInventoryManager->Initialize(m_pLocalPlayer->GetNative()); + } + + void FSTWGameMode::LoadHuskAssets() + { + USS_LOG("Loading husk assets into memory..."); + + // Based on athena_plate.cpp husk loading + const char* HuskAssets[] = { + "/Game/Characters/Enemies/Husk/Blueprints/HuskPawn.HuskPawn_C", + "/Game/Characters/Enemies/Husk/Blueprints/HuskPawn_Fire.HuskPawn_Fire_C", + "/Game/Characters/Enemies/Husk/Blueprints/HuskPawn_Ice.HuskPawn_Ice_C", + "/Game/Characters/Enemies/Husk/Blueprints/HuskPawn_Lightning.HuskPawn_Lightning_C", + "/Game/Characters/Enemies/Husk/Blueprints/HuskPawn_Beehive.HuskPawn_Beehive_C", + "/Game/Characters/Enemies/Husk/Blueprints/HuskPawn_Bombshell.HuskPawn_Bombshell_C", + "/Game/Characters/Enemies/Husk/Blueprints/HuskPawn_Bombshell_Poison.HuskPawn_Bombshell_Poison_C", + "/Game/Characters/Enemies/Husk/Blueprints/HuskPawn_Dwarf.HuskPawn_Dwarf_C", + "/Game/Characters/Enemies/Husk/Blueprints/HuskPawn_Dwarf_Fire.HuskPawn_Dwarf_Fire_C", + "/Game/Characters/Enemies/Husk/Blueprints/HuskPawn_Dwarf_Ice.HuskPawn_Dwarf_Ice_C", + "/Game/Characters/Enemies/Husk/Blueprints/HuskPawn_Dwarf_Lightning.HuskPawn_Dwarf_Lightning_C", + }; + + // TODO: Implement FindOrLoadObject via engine core + // For each asset, call engine's StaticLoadObject + + USS_LOG("Loaded %zu husk asset types", sizeof(HuskAssets) / sizeof(HuskAssets[0])); + } + +} \ No newline at end of file diff --git a/STW/GameMode/STWGameMode.h b/STW/GameMode/STWGameMode.h new file mode 100644 index 0000000..fed43a6 --- /dev/null +++ b/STW/GameMode/STWGameMode.h @@ -0,0 +1,192 @@ +/** + * UniversalSlashingSimulator - STW Game Mode + * + * Core STW game mode controller. Manages the game lifecycle, + * coordinates systems, and handles ProcessEvent routing. + * + * Based on FarmsteadPlate from PolarisV2-STW. + * + * Lifecycle: + * 1. Initialize() - Called after engine core ready + * 2. OnWorldReady() - Called when world is loaded + * 3. OnPlayersLoaded() - Called when all players joined + * 4. OnMissionStart() - Mission gameplay begins + * 5. OnMissionEnd() - Mission complete/failed + * 6. Shutdown() - Cleanup + */ + +#pragma once + +#include "../../Core/Common.h" +#include "../../Engine/UObject/UObjectWrapper.h" +#include "../Missions/MissionTypes.h" +#include + +namespace USS +{ + // Forward declarations + class FMissionManager; + class FInventoryManager; + class FBuildingManager; + class FSTWPlayerController; + + // Game mode state + enum class ESTWGameState : uint8 + { + None, + Initializing, + WaitingForWorld, + WaitingForPlayers, + LoadingMission, + MissionActive, + MissionComplete, + MissionFailed, + LeavingZone, + Shutdown + }; + + // Game mode configuration + struct FSTWGameConfig + { + // Zone configuration + std::string ZoneName; // e.g., "Zone_Onboarding_FarmsteadFort" + std::string MissionBlueprint; // e.g., "Mission_FarmsteadFort_C" + std::string MapName; // Map to load + + // Player settings + int32 MaxPlayers; + bool bAllowBots; + + // Mission settings + EMissionType MissionType; + int32 DifficultyLevel; + int32 DefaultDifficulty; // Default difficulty for the zone + + // Feature toggles + bool bEnableMissions; + bool bEnableInventory; + bool bEnableBuilding; + + FSTWGameConfig() + : MaxPlayers(4) + , bAllowBots(true) + , MissionType(EMissionType::FarmsteadDefense) + , DifficultyLevel(1) + , DefaultDifficulty(1) + , bEnableMissions(true) + , bEnableInventory(true) + , bEnableBuilding(true) + { + } + }; + + // STW Game Mode + class FSTWGameMode + { + public: + USS_NON_COPYABLE(FSTWGameMode) + USS_NON_MOVABLE(FSTWGameMode) + + static FSTWGameMode& Get(); + + // Lifecycle + EResult Initialize(const FSTWGameConfig& Config); + void Shutdown(); + void Update(); // Called each tick + + // State + ESTWGameState GetState() const { return m_State; } + bool IsInitialized() const { return m_State != ESTWGameState::None; } + bool IsMissionActive() const { return m_State == ESTWGameState::MissionActive; } + + // Configuration + const FSTWGameConfig& GetConfig() const { return m_Config; } + + // Manager access + FMissionManager* GetMissionManager() const { return m_pMissionManager.get(); } + FInventoryManager* GetInventoryManager() const { return m_pInventoryManager.get(); } + FBuildingManager* GetBuildingManager() const { return m_pBuildingManager.get(); } + + // Player management + FSTWPlayerController* GetLocalPlayer() const { return m_pLocalPlayer.get(); } + void SetLocalPlayer(std::unique_ptr Player); + + // Event handlers (called from ProcessEvent hook) + void OnProcessEvent(void* Object, void* Function, void* Params); + + // Callbacks for external systems + using StateChangeCallback = std::function; + void RegisterStateChangeCallback(StateChangeCallback Callback); + + private: + FSTWGameMode(); + ~FSTWGameMode(); + + // State transitions + void SetState(ESTWGameState NewState); + + // Event handlers + void OnReadyToStartMatch(); + void OnWorldReady(); + void OnAllPlayersLoaded(); + void OnMissionEvent(const char* EventName, void* Params); + void OnToggleEditMode(void* Params); + void OnStartLeavingZone(void* Params); + void OnCraftSchematic(void* Params); + + // Initialization helpers + void InitializeWorld(); + void InitializeMission(); + void SpawnLocalPlayer(); + void SetupInventory(); + void LoadHuskAssets(); + + // State + ESTWGameState m_State; + FSTWGameConfig m_Config; + bool m_bWorldReady; + bool m_bPlayersLoaded; + + // Managers + std::unique_ptr m_pMissionManager; + std::unique_ptr m_pInventoryManager; + std::unique_ptr m_pBuildingManager; + + // Player + std::unique_ptr m_pLocalPlayer; + + // Callbacks + std::vector m_StateChangeCallbacks; + + // Engine references (cached) + UObjectWrapper m_World; + UObjectWrapper m_GameState; + UObjectWrapper m_GameMode; + }; + + // Convenience function + inline FSTWGameMode& GetSTWGameMode() + { + return FSTWGameMode::Get(); + } + + // State name helper + inline const char* GetGameStateName(ESTWGameState State) + { + switch (State) + { + case ESTWGameState::None: return "None"; + case ESTWGameState::Initializing: return "Initializing"; + case ESTWGameState::WaitingForWorld: return "WaitingForWorld"; + case ESTWGameState::WaitingForPlayers: return "WaitingForPlayers"; + case ESTWGameState::LoadingMission: return "LoadingMission"; + case ESTWGameState::MissionActive: return "MissionActive"; + case ESTWGameState::MissionComplete: return "MissionComplete"; + case ESTWGameState::MissionFailed: return "MissionFailed"; + case ESTWGameState::LeavingZone: return "LeavingZone"; + case ESTWGameState::Shutdown: return "Shutdown"; + default: return "Unknown"; + } + } + +} \ No newline at end of file diff --git a/STW/Inventory/InventoryManager.cpp b/STW/Inventory/InventoryManager.cpp new file mode 100644 index 0000000..a435a9e --- /dev/null +++ b/STW/Inventory/InventoryManager.cpp @@ -0,0 +1,639 @@ +/** + * UniversalSlashingSimulator - Inventory Manager Implementation + */ + +#include "InventoryManager.h" +#include "../../Core/Logging/Log.h" +#include "../../Engine/EngineCore.h" + +namespace USS +{ + static FInventoryManager g_LocalInventoryManager; + + FInventoryManager& GetLocalInventoryManager() + { + return g_LocalInventoryManager; + } + + FInventoryManager::FInventoryManager() + : m_MaxSlots(200) + , m_MaxStackSize(999) + , m_ItemIdCounter(0) + { + } + + FInventoryManager::~FInventoryManager() + { + Shutdown(); + } + + EResult FInventoryManager::Initialize(void* PlayerController) + { + USS_LOG("Initializing Inventory Manager..."); + + m_PlayerController = UObjectWrapper(PlayerController); + + // Initialize quickbars + for (int32 i = 0; i < 2; ++i) + { + m_Quickbars[i].QuickbarIndex = i; + m_Quickbars[i].CurrentSlot = 0; + m_Quickbars[i].Slots.resize(i == 0 ? 6 : 4); // 6 weapon slots, 4 build slots + + for (size_t j = 0; j < m_Quickbars[i].Slots.size(); ++j) + { + m_Quickbars[i].Slots[j].SlotIndex = static_cast(j); + m_Quickbars[i].Slots[j].bIsEmpty = true; + } + } + + // Find inventory components + // TODO: Traverse to find UFortInventory on controller + + USS_LOG("Inventory Manager initialized"); + return EResult::Success; + } + + void FInventoryManager::Shutdown() + { + m_Items.clear(); + m_SlotToItem.clear(); + m_EventCallbacks.clear(); + + m_PlayerController = UObjectWrapper(); + m_InventoryComponent = UObjectWrapper(); + m_QuickbarComponent = UObjectWrapper(); + } + + void FInventoryManager::Update() + { + // Sync inventory state from engine if needed + } + + const FInventoryItem* FInventoryManager::GetItem(const std::string& ItemId) const + { + auto It = m_Items.find(ItemId); + return (It != m_Items.end()) ? &It->second : nullptr; + } + + const FInventoryItem* FInventoryManager::GetItemBySlot(int32 SlotIndex) const + { + auto It = m_SlotToItem.find(SlotIndex); + if (It != m_SlotToItem.end()) + { + return GetItem(It->second); + } + return nullptr; + } + + std::vector FInventoryManager::GetItemsByCategory(EItemCategory Category) const + { + std::vector Result; + + for (const auto& Pair : m_Items) + { + if (Pair.second.Category == Category) + { + Result.push_back(&Pair.second); + } + } + + return Result; + } + + int32 FInventoryManager::GetItemCount(const std::string& TemplateId) const + { + int32 Total = 0; + + for (const auto& Pair : m_Items) + { + if (Pair.second.TemplateId == TemplateId) + { + Total += Pair.second.Count; + } + } + + return Total; + } + + bool FInventoryManager::HasItem(const std::string& TemplateId, int32 MinCount) const + { + return GetItemCount(TemplateId) >= MinCount; + } + + EResult FInventoryManager::AddItem(const FInventoryItem& Item) + { + // Check if we can stack with existing + for (auto& Pair : m_Items) + { + if (Pair.second.TemplateId == Item.TemplateId && + Pair.second.Count < Pair.second.MaxStackSize) + { + int32 CanAdd = Pair.second.MaxStackSize - Pair.second.Count; + int32 ToAdd = (Item.Count < CanAdd) ? Item.Count : CanAdd; + + Pair.second.Count += ToAdd; + + FInventoryChangeEvent Event; + Event.Type = FInventoryChangeEvent::EChangeType::Modified; + Event.ItemId = Pair.first; + Event.OldCount = Pair.second.Count - ToAdd; + Event.NewCount = Pair.second.Count; + NotifyChange(Event); + + USS_LOG("Stacked %d %s (total: %d)", ToAdd, Item.ItemName.c_str(), Pair.second.Count); + + // All added by stacking + if (ToAdd >= Item.Count) + return EResult::Success; + + // Need to create new stack for remainder + FInventoryItem Remainder = Item; + Remainder.Count = Item.Count - ToAdd; + return AddItem(Remainder); + } + } + + // Need new slot + int32 FreeSlot = FindFreeSlot(); + if (FreeSlot < 0) + { + USS_WARN("Inventory full, cannot add item: %s", Item.ItemName.c_str()); + return EResult::InventoryFull; + } + + // Create new item + FInventoryItem NewItem = Item; + NewItem.ItemId = GenerateItemId(); + NewItem.SlotIndex = FreeSlot; + + m_Items[NewItem.ItemId] = NewItem; + m_SlotToItem[FreeSlot] = NewItem.ItemId; + + FInventoryChangeEvent Event; + Event.Type = FInventoryChangeEvent::EChangeType::Added; + Event.ItemId = NewItem.ItemId; + Event.OldCount = 0; + Event.NewCount = NewItem.Count; + Event.SlotIndex = FreeSlot; + NotifyChange(Event); + + USS_LOG("Added item: %s x%d (slot %d)", NewItem.ItemName.c_str(), NewItem.Count, FreeSlot); + + return EResult::Success; + } + + EResult FInventoryManager::RemoveItem(const std::string& ItemId, int32 Count) + { + auto It = m_Items.find(ItemId); + if (It == m_Items.end()) + { + return EResult::ItemNotFound; + } + + FInventoryItem& Item = It->second; + int32 OldCount = Item.Count; + + if (Count >= Item.Count) + { + // Remove entirely + int32 Slot = Item.SlotIndex; + m_SlotToItem.erase(Slot); + m_Items.erase(It); + + FInventoryChangeEvent Event; + Event.Type = FInventoryChangeEvent::EChangeType::Removed; + Event.ItemId = ItemId; + Event.OldCount = OldCount; + Event.NewCount = 0; + Event.SlotIndex = Slot; + NotifyChange(Event); + + USS_LOG("Removed item: %s", ItemId.c_str()); + } + else + { + // Reduce count + Item.Count -= Count; + + FInventoryChangeEvent Event; + Event.Type = FInventoryChangeEvent::EChangeType::Modified; + Event.ItemId = ItemId; + Event.OldCount = OldCount; + Event.NewCount = Item.Count; + NotifyChange(Event); + + USS_LOG("Reduced item %s: %d -> %d", ItemId.c_str(), OldCount, Item.Count); + } + + return EResult::Success; + } + + EResult FInventoryManager::MoveItem(const std::string& ItemId, int32 NewSlot) + { + auto It = m_Items.find(ItemId); + if (It == m_Items.end()) + return EResult::ItemNotFound; + + if (NewSlot < 0 || NewSlot >= m_MaxSlots) + return EResult::InvalidParameter; + + FInventoryItem& Item = It->second; + int32 OldSlot = Item.SlotIndex; + + // Check if target slot is occupied + auto TargetIt = m_SlotToItem.find(NewSlot); + if (TargetIt != m_SlotToItem.end()) + { + // Swap items + const std::string& TargetItemId = TargetIt->second; + auto TargetItemIt = m_Items.find(TargetItemId); + + if (TargetItemIt != m_Items.end()) + { + TargetItemIt->second.SlotIndex = OldSlot; + m_SlotToItem[OldSlot] = TargetItemId; + } + } + else + { + m_SlotToItem.erase(OldSlot); + } + + Item.SlotIndex = NewSlot; + m_SlotToItem[NewSlot] = ItemId; + + USS_LOG("Moved item %s: slot %d -> %d", ItemId.c_str(), OldSlot, NewSlot); + + return EResult::Success; + } + + EResult FInventoryManager::StackItems(const std::string& SourceId, const std::string& TargetId) + { + auto SourceIt = m_Items.find(SourceId); + auto TargetIt = m_Items.find(TargetId); + + if (SourceIt == m_Items.end() || TargetIt == m_Items.end()) + return EResult::ItemNotFound; + + FInventoryItem& Source = SourceIt->second; + FInventoryItem& Target = TargetIt->second; + + if (Source.TemplateId != Target.TemplateId) + return EResult::InvalidParameter; + + int32 CanAdd = Target.MaxStackSize - Target.Count; + int32 ToAdd = (Source.Count < CanAdd) ? Source.Count : CanAdd; + + Target.Count += ToAdd; + Source.Count -= ToAdd; + + if (Source.Count <= 0) + { + m_SlotToItem.erase(Source.SlotIndex); + m_Items.erase(SourceIt); + } + + return EResult::Success; + } + + int32 FInventoryManager::GetWoodCount() const + { + const FInventoryItem* Item = FindResourceItem(EResourceType::Wood); + return Item ? Item->Count : 0; + } + + int32 FInventoryManager::GetStoneCount() const + { + const FInventoryItem* Item = FindResourceItem(EResourceType::Stone); + return Item ? Item->Count : 0; + } + + int32 FInventoryManager::GetMetalCount() const + { + const FInventoryItem* Item = FindResourceItem(EResourceType::Metal); + return Item ? Item->Count : 0; + } + + EResult FInventoryManager::ConsumeResources(EResourceType Type, int32 Amount) + { + // Find resource item + for (auto& Pair : m_Items) + { + if (Pair.second.Category == EItemCategory::Resource) + { + // TODO: Check actual resource type matches + if (Pair.second.Count >= Amount) + { + return RemoveItem(Pair.first, Amount); + } + } + } + + return EResult::InsufficientResources; + } + + const FQuickbar* FInventoryManager::GetQuickbar(int32 Index) const + { + if (Index >= 0 && Index < 2) + { + return &m_Quickbars[Index]; + } + return nullptr; + } + + int32 FInventoryManager::GetCurrentQuickbarSlot(int32 QuickbarIndex) const + { + if (QuickbarIndex >= 0 && QuickbarIndex < 2) + { + return m_Quickbars[QuickbarIndex].CurrentSlot; + } + return -1; + } + + EResult FInventoryManager::SetQuickbarSlot(int32 QuickbarIndex, int32 SlotIndex, const std::string& ItemId) + { + if (QuickbarIndex < 0 || QuickbarIndex >= 2) + return EResult::InvalidParameter; + + FQuickbar& Quickbar = m_Quickbars[QuickbarIndex]; + + if (SlotIndex < 0 || SlotIndex >= static_cast(Quickbar.Slots.size())) + return EResult::InvalidParameter; + + Quickbar.Slots[SlotIndex].ItemId = ItemId; + Quickbar.Slots[SlotIndex].bIsEmpty = ItemId.empty(); + + return EResult::Success; + } + + EResult FInventoryManager::ClearQuickbarSlot(int32 QuickbarIndex, int32 SlotIndex) + { + return SetQuickbarSlot(QuickbarIndex, SlotIndex, ""); + } + + EResult FInventoryManager::SelectQuickbarSlot(int32 QuickbarIndex, int32 SlotIndex) + { + if (QuickbarIndex < 0 || QuickbarIndex >= 2) + return EResult::InvalidParameter; + + FQuickbar& Quickbar = m_Quickbars[QuickbarIndex]; + + if (SlotIndex < 0 || SlotIndex >= static_cast(Quickbar.Slots.size())) + return EResult::InvalidParameter; + + Quickbar.CurrentSlot = SlotIndex; + + USS_LOG("Selected quickbar %d slot %d", QuickbarIndex, SlotIndex); + + return EResult::Success; + } + + const FInventoryItem* FInventoryManager::GetEquippedWeapon() const + { + return GetItem(m_EquippedWeaponId); + } + + const FInventoryItem* FInventoryManager::GetEquippedPickaxe() const + { + return GetItem(m_EquippedPickaxeId); + } + + EResult FInventoryManager::EquipItem(const std::string& ItemId) + { + auto It = m_Items.find(ItemId); + if (It == m_Items.end()) + return EResult::ItemNotFound; + + FInventoryItem& Item = It->second; + + if (Item.Category == EItemCategory::Weapon || Item.Category == EItemCategory::Melee) + { + m_EquippedWeaponId = ItemId; + Item.bIsEquipped = true; + + FInventoryChangeEvent Event; + Event.Type = FInventoryChangeEvent::EChangeType::Equipped; + Event.ItemId = ItemId; + NotifyChange(Event); + + USS_LOG("Equipped weapon: %s", Item.ItemName.c_str()); + } + + return EResult::Success; + } + + EResult FInventoryManager::UnequipItem(const std::string& ItemId) + { + auto It = m_Items.find(ItemId); + if (It == m_Items.end()) + return EResult::ItemNotFound; + + FInventoryItem& Item = It->second; + Item.bIsEquipped = false; + + if (m_EquippedWeaponId == ItemId) + { + m_EquippedWeaponId.clear(); + } + + FInventoryChangeEvent Event; + Event.Type = FInventoryChangeEvent::EChangeType::Unequipped; + Event.ItemId = ItemId; + NotifyChange(Event); + + return EResult::Success; + } + + EResult FInventoryManager::SwapWeaponSlots(int32 SlotA, int32 SlotB) + { + // Swap in primary quickbar + if (SlotA < 0 || SlotB < 0) + return EResult::InvalidParameter; + + FQuickbar& Primary = m_Quickbars[0]; + + if (SlotA >= static_cast(Primary.Slots.size()) || + SlotB >= static_cast(Primary.Slots.size())) + return EResult::InvalidParameter; + + std::swap(Primary.Slots[SlotA], Primary.Slots[SlotB]); + Primary.Slots[SlotA].SlotIndex = SlotA; + Primary.Slots[SlotB].SlotIndex = SlotB; + + return EResult::Success; + } + + float FInventoryManager::GetItemDurability(const std::string& ItemId) const + { + const FInventoryItem* Item = GetItem(ItemId); + return Item ? Item->Durability : 0.0f; + } + + EResult FInventoryManager::UseItemDurability(const std::string& ItemId, float Amount) + { + auto It = m_Items.find(ItemId); + if (It == m_Items.end()) + return EResult::ItemNotFound; + + FInventoryItem& Item = It->second; + Item.Durability -= Amount; + + if (Item.Durability < 0.0f) + Item.Durability = 0.0f; + + if (Item.Durability <= 0.0f) + { + USS_LOG("Item broken: %s", Item.ItemName.c_str()); + } + + return EResult::Success; + } + + EResult FInventoryManager::RepairItem(const std::string& ItemId) + { + auto It = m_Items.find(ItemId); + if (It == m_Items.end()) + return EResult::ItemNotFound; + + FInventoryItem& Item = It->second; + Item.Durability = Item.MaxDurability; + + USS_LOG("Repaired item: %s", Item.ItemName.c_str()); + + return EResult::Success; + } + + bool FInventoryManager::IsItemBroken(const std::string& ItemId) const + { + const FInventoryItem* Item = GetItem(ItemId); + return Item ? (Item->Durability <= 0.0f) : true; + } + + bool FInventoryManager::CanCraftItem(const std::string& SchematicId) const + { + // TODO: Look up recipe and check if we have materials + return false; + } + + EResult FInventoryManager::CraftItem(const std::string& SchematicId, int32 Count) + { + if (!CanCraftItem(SchematicId)) + { + return EResult::InsufficientResources; + } + + // TODO: Consume materials and create item + USS_LOG("Crafted item from schematic: %s x%d", SchematicId.c_str(), Count); + + return EResult::Success; + } + + std::vector FInventoryManager::GetAvailableRecipes() const + { + // TODO: Return recipes player can craft + return {}; + } + + int32 FInventoryManager::GetAmmoCount(const std::string& AmmoType) const + { + return GetItemCount(AmmoType); + } + + EResult FInventoryManager::ConsumeAmmo(const std::string& AmmoType, int32 Amount) + { + for (auto& Pair : m_Items) + { + if (Pair.second.Category == EItemCategory::Ammo && + Pair.second.TemplateId == AmmoType) + { + return RemoveItem(Pair.first, Amount); + } + } + + return EResult::ItemNotFound; + } + + EResult FInventoryManager::ReloadWeapon(const std::string& WeaponId) + { + auto It = m_Items.find(WeaponId); + if (It == m_Items.end()) + return EResult::ItemNotFound; + + FInventoryItem& Weapon = It->second; + + // TODO: Determine ammo type for weapon and consume from inventory + Weapon.AmmoCount = Weapon.MaxAmmo; + + USS_LOG("Reloaded weapon: %s", Weapon.ItemName.c_str()); + + return EResult::Success; + } + + void FInventoryManager::RegisterEventCallback(FInventoryEventCallback Callback) + { + if (Callback) + { + m_EventCallbacks.push_back(std::move(Callback)); + } + } + + void FInventoryManager::OnProcessEvent(void* Object, void* Function, void* Params) + { + // Handle inventory-related ProcessEvents + } + + void FInventoryManager::SyncFromEngine() + { + // TODO: Read inventory state from engine UFortInventory + } + + void FInventoryManager::SyncToEngine() + { + // TODO: Write inventory changes to engine UFortInventory + } + + void FInventoryManager::NotifyChange(const FInventoryChangeEvent& Event) + { + for (const auto& Callback : m_EventCallbacks) + { + if (Callback) + { + Callback(Event); + } + } + } + + int32 FInventoryManager::FindFreeSlot() const + { + for (int32 i = 0; i < m_MaxSlots; ++i) + { + if (m_SlotToItem.find(i) == m_SlotToItem.end()) + { + return i; + } + } + return -1; + } + + std::string FInventoryManager::GenerateItemId() const + { + char Buffer[64]; + snprintf(Buffer, sizeof(Buffer), "item_%u", ++m_ItemIdCounter); + return Buffer; + } + + const FInventoryItem* FInventoryManager::FindResourceItem(EResourceType Type) const + { + // TODO: would check actual resource type + for (const auto& Pair : m_Items) + { + if (Pair.second.Category == EItemCategory::Resource) + { + return &Pair.second; + } + } + return nullptr; + } + +} \ No newline at end of file diff --git a/STW/Inventory/InventoryManager.h b/STW/Inventory/InventoryManager.h new file mode 100644 index 0000000..9ed3818 --- /dev/null +++ b/STW/Inventory/InventoryManager.h @@ -0,0 +1,137 @@ +/** + * UniversalSlashingSimulator - Inventory Manager + * + * Manages player inventory, quickbars, and item operations. + * Handles STW-specific inventory logic like durability, crafting, etc. + */ + +#pragma once + +#include "../../Core/Common.h" +#include "../../Engine/UObject/UObjectWrapper.h" +#include "InventoryTypes.h" +#include +#include +#include + +namespace USS +{ + // Inventory event callback + using FInventoryEventCallback = std::function; + + class FInventoryManager + { + public: + FInventoryManager(); + ~FInventoryManager(); + + USS_NON_COPYABLE(FInventoryManager) + USS_NON_MOVABLE(FInventoryManager) + + // Lifecycle + EResult Initialize(void* PlayerController); + void Shutdown(); + void Update(); + + // Item queries + const FInventoryItem* GetItem(const std::string& ItemId) const; + const FInventoryItem* GetItemBySlot(int32 SlotIndex) const; + std::vector GetItemsByCategory(EItemCategory Category) const; + int32 GetItemCount(const std::string& TemplateId) const; + bool HasItem(const std::string& TemplateId, int32 MinCount = 1) const; + + // Item operations + EResult AddItem(const FInventoryItem& Item); + EResult RemoveItem(const std::string& ItemId, int32 Count = 1); + EResult MoveItem(const std::string& ItemId, int32 NewSlot); + EResult StackItems(const std::string& SourceId, const std::string& TargetId); + + // Resource shortcuts + int32 GetWoodCount() const; + int32 GetStoneCount() const; + int32 GetMetalCount() const; + EResult ConsumeResources(EResourceType Type, int32 Amount); + + // Quickbar + const FQuickbar* GetQuickbar(int32 Index) const; + int32 GetCurrentQuickbarSlot(int32 QuickbarIndex) const; + EResult SetQuickbarSlot(int32 QuickbarIndex, int32 SlotIndex, const std::string& ItemId); + EResult ClearQuickbarSlot(int32 QuickbarIndex, int32 SlotIndex); + EResult SelectQuickbarSlot(int32 QuickbarIndex, int32 SlotIndex); + + // Equipment + const FInventoryItem* GetEquippedWeapon() const; + const FInventoryItem* GetEquippedPickaxe() const; + EResult EquipItem(const std::string& ItemId); + EResult UnequipItem(const std::string& ItemId); + EResult SwapWeaponSlots(int32 SlotA, int32 SlotB); + + // Durability (STW-specific) + float GetItemDurability(const std::string& ItemId) const; + EResult UseItemDurability(const std::string& ItemId, float Amount); + EResult RepairItem(const std::string& ItemId); + bool IsItemBroken(const std::string& ItemId) const; + + // Crafting (STW-specific) + bool CanCraftItem(const std::string& SchematicId) const; + EResult CraftItem(const std::string& SchematicId, int32 Count = 1); + std::vector GetAvailableRecipes() const; + + // Ammo + int32 GetAmmoCount(const std::string& AmmoType) const; + EResult ConsumeAmmo(const std::string& AmmoType, int32 Amount); + EResult ReloadWeapon(const std::string& WeaponId); + + // Inventory limits + int32 GetMaxInventorySlots() const { return m_MaxSlots; } + int32 GetUsedSlots() const { return static_cast(m_Items.size()); } + int32 GetFreeSlots() const { return m_MaxSlots - GetUsedSlots(); } + bool HasFreeSlot() const { return GetFreeSlots() > 0; } + + // Events + void RegisterEventCallback(FInventoryEventCallback Callback); + void OnProcessEvent(void* Object, void* Function, void* Params); + + // Sync with engine + void SyncFromEngine(); + void SyncToEngine(); + + private: + void NotifyChange(const FInventoryChangeEvent& Event); + int32 FindFreeSlot() const; + std::string GenerateItemId() const; + const FInventoryItem* FindResourceItem(EResourceType Type) const; + + // Items by ID + std::unordered_map m_Items; + + // Slot mapping + std::unordered_map m_SlotToItem; + + // Quickbars (0 = primary/weapons, 1 = secondary/build) + FQuickbar m_Quickbars[2]; + + // Currently equipped + std::string m_EquippedWeaponId; + std::string m_EquippedPickaxeId; + + // Limits + int32 m_MaxSlots; + int32 m_MaxStackSize; + + // Engine references + UObjectWrapper m_PlayerController; + UObjectWrapper m_InventoryComponent; // UFortInventory* + UObjectWrapper m_QuickbarComponent; // UFortQuickBars* + + // Callbacks + std::vector m_EventCallbacks; + + // ID counter for generated IDs + mutable uint32 m_ItemIdCounter; + }; + + // Global inventory access (for current local player) + FInventoryManager& GetLocalInventoryManager(); + +} \ No newline at end of file diff --git a/STW/Inventory/InventoryTypes.h b/STW/Inventory/InventoryTypes.h new file mode 100644 index 0000000..6199356 --- /dev/null +++ b/STW/Inventory/InventoryTypes.h @@ -0,0 +1,273 @@ +/** + * UniversalSlashingSimulator - Inventory Types + * + * Type definitions for STW inventory system. + * Handles items, weapons, resources, schematics, etc. + */ + +#pragma once + +#include "../../Core/Common.h" +#include +#include + +// Forward declare types from BuildingTypes.h to avoid circular dependency +// Full definitions are in BuildingTypes.h - include that for ETrapType and FTrapStats + +namespace USS +{ + // Item rarity (STW uses different names than BR) + enum class EItemRarity : uint8 + { + Common, // Gray + Uncommon, // Green + Rare, // Blue + Epic, // Purple + Legendary, // Orange + Mythic // Gold (only for certain heroes/schematics) + }; + + // Item category + enum class EItemCategory : uint8 + { + None, + Weapon, // Ranged weapons + Melee, // Melee weapons + Trap, // Traps (floor, wall, ceiling) + Resource, // Wood, Stone, Metal + Crafting, // Crafting materials + Ammo, // Ammunition + Consumable, // Healing items, etc. + Gadget, // Adrenaline Rush, Turrets, etc. + Hero, // Hero characters + Schematic, // Weapon/trap schematics + Survivor, // Survivor squad members + Defender, // Defenders + LootDrop // Loot llamas, etc. + }; + + // Weapon type + enum class EWeaponType : uint8 + { + AssaultRifle, + Shotgun, + SMG, + Pistol, + Sniper, + ExplosiveLauncher, + Bow, + Sword, + Axe, + Hammer, + Spear, + Scythe, + Club, + Hardware + }; + + // NOTE: ETrapType is defined in BuildingTypes.h + // Include "../Building/BuildingTypes.h" if we need trap type definitions + + // Resource type + enum class EResourceType : uint8 + { + Wood, + Stone, + Metal, + + // Crafting tiers + Copper, // Tier 1 + Silver, // Tier 2 + Malachite, // Tier 3 + Obsidian, // Tier 4 + Shadowshard, // Tier 4 (alt) + Brightcore, // Tier 5 + Sunbeam, // Tier 5 (alt) + + // Other crafting + Twine, + Rough, // Rough Ore + Mineral, // Mineral Powder + Mechanical, // Mechanical Parts + Duct, // Duct Tape + Bacon, // Batteries, etc. + Herb, + Flower, + Resin + }; + + // Item instance (runtime item in inventory) + struct FInventoryItem + { + std::string ItemId; // Unique ID + std::string TemplateId; // Item definition template + std::string ItemName; // Display name + + EItemCategory Category; + EItemRarity Rarity; + + int32 Count; // Stack count + int32 MaxStackSize; + int32 Level; // Item level (power level) + + // Weapon-specific + float Durability; // Current durability + float MaxDurability; + int32 AmmoCount; // Current ammo in clip + int32 MaxAmmo; + + // Schematic-specific + int32 SchematicLevel; // Evolution level + int32 SchematicTier; // Material tier + + // Slot info + int32 SlotIndex; + bool bIsEquipped; + bool bIsFavorite; + + FInventoryItem() + : Category(EItemCategory::None) + , Rarity(EItemRarity::Common) + , Count(1) + , MaxStackSize(1) + , Level(1) + , Durability(100.0f) + , MaxDurability(100.0f) + , AmmoCount(0) + , MaxAmmo(0) + , SchematicLevel(1) + , SchematicTier(1) + , SlotIndex(-1) + , bIsEquipped(false) + , bIsFavorite(false) + {} + }; + + // Quickbar slot (weapon/ability slots) + struct FQuickbarSlot + { + int32 SlotIndex; + std::string ItemId; // Linked inventory item + bool bIsEmpty; + bool bIsEnabled; + + FQuickbarSlot() + : SlotIndex(-1) + , bIsEmpty(true) + , bIsEnabled(true) + {} + }; + + // Quickbar (primary = weapons, secondary = build, etc.) + struct FQuickbar + { + int32 QuickbarIndex; + int32 CurrentSlot; + std::vector Slots; + + FQuickbar() + : QuickbarIndex(-1) + , CurrentSlot(0) + {} + }; + + // Weapon stats (from schematic) + struct FWeaponStats + { + float Damage; + float FireRate; + float ReloadTime; + float MagazineSize; + float Range; + float CritChance; + float CritDamage; + float Impact; // Stagger/knockback + float DurabilityPerUse; + + // Element + std::string ElementType; // Fire, Water, Nature, Physical, Energy + float ElementDamagePercent; + + FWeaponStats() + : Damage(0.0f) + , FireRate(1.0f) + , ReloadTime(1.0f) + , MagazineSize(30) + , Range(1000.0f) + , CritChance(0.05f) + , CritDamage(0.5f) + , Impact(0.0f) + , DurabilityPerUse(0.01f) + , ElementDamagePercent(0.0f) + {} + }; + + // NOTE: FTrapStats is defined in BuildingTypes.h + // Include "../Building/BuildingTypes.h" if we need trap stats definitions + + // Crafting recipe + struct FCraftingRecipe + { + std::string ResultTemplateId; + int32 ResultCount; + + struct FIngredient + { + std::string TemplateId; + int32 Count; + }; + + std::vector Ingredients; + + FCraftingRecipe() + : ResultCount(1) + {} + }; + + // Loot drop definition + struct FLootDrop + { + std::string LootId; + std::string LootTableId; + float DropChance; + int32 MinCount; + int32 MaxCount; + EItemRarity MinRarity; + EItemRarity MaxRarity; + + FLootDrop() + : DropChance(1.0f) + , MinCount(1) + , MaxCount(1) + , MinRarity(EItemRarity::Common) + , MaxRarity(EItemRarity::Legendary) + {} + }; + + // Inventory change event + struct FInventoryChangeEvent + { + enum class EChangeType : uint8 + { + Added, + Removed, + Modified, + Equipped, + Unequipped + }; + + EChangeType Type; + std::string ItemId; + int32 OldCount; + int32 NewCount; + int32 SlotIndex; + + FInventoryChangeEvent() + : Type(EChangeType::Modified) + , OldCount(0) + , NewCount(0) + , SlotIndex(-1) + {} + }; + +} \ No newline at end of file diff --git a/STW/Missions/MissionManager.cpp b/STW/Missions/MissionManager.cpp new file mode 100644 index 0000000..f41364e --- /dev/null +++ b/STW/Missions/MissionManager.cpp @@ -0,0 +1,425 @@ +/** + * UniversalSlashingSimulator - Mission Manager Implementation + */ + +#include "MissionManager.h" +#include "MissionObjective.h" +#include "../../Core/Logging/Log.h" +#include "../../Engine/EngineCore.h" + +namespace USS +{ + FMissionManager::FMissionManager() + : m_State(EMissionState::None) + , m_Score(0) + { + m_WaveInfo = {}; + } + + FMissionManager::~FMissionManager() + { + Shutdown(); + } + + EResult FMissionManager::Initialize() + { + USS_LOG("Initializing Mission Manager..."); + + m_State = EMissionState::None; + m_Objectives.clear(); + m_EventCallbacks.clear(); + m_Score = 0; + + // Cache engine references + // TODO: Find AFortMissionManager in world + m_MissionManagerActor = GetEngineCore().FindObjectByName("FortMissionManager"); + + USS_LOG("Mission Manager initialized"); + return EResult::Success; + } + + void FMissionManager::Shutdown() + { + if (m_State != EMissionState::None) + { + AbortMission(); + } + + m_Objectives.clear(); + m_EventCallbacks.clear(); + m_MissionActor = UObjectWrapper(); + m_MissionManagerActor = UObjectWrapper(); + m_AIDirector = UObjectWrapper(); + } + + void FMissionManager::Update() + { + if (!IsActive()) + return; + + // Update all objectives + for (auto& Objective : m_Objectives) + { + if (Objective) + { + Objective->Update(); + } + } + + // Check for mission completion + CheckObjectivesComplete(); + } + + EResult FMissionManager::StartMission(const FMissionConfig& Config) + { + if (m_State != EMissionState::None && m_State != EMissionState::Complete && + m_State != EMissionState::Failed) + { + USS_WARN("Cannot start mission - already active"); + return EResult::InvalidState; + } + + USS_LOG("Starting mission: %s (Type: %d, Difficulty: %d)", + Config.MissionName.c_str(), + static_cast(Config.Type), + Config.Difficulty); + + m_Config = Config; + m_Score = 0; + m_Objectives.clear(); + + // Initialize wave info + m_WaveInfo.CurrentWave = 0; + m_WaveInfo.MaxWaves = Config.MaxWaves > 0 ? Config.MaxWaves : 1; + m_WaveInfo.EnemiesRemaining = 0; + m_WaveInfo.EnemiesSpawned = 0; + m_WaveInfo.bIsActive = false; + + // Create default objectives based on mission type + CreateDefaultObjectives(); + + SetState(EMissionState::Loading); + + // TODO: Would spawn mission actors, initialize AI director, etc. + // For now, transition directly to active + SetState(EMissionState::Active); + + USS_LOG("Mission started with %d objectives", static_cast(m_Objectives.size())); + + return EResult::Success; + } + + void FMissionManager::EndMission(bool bSuccess) + { + if (!IsActive() && m_State != EMissionState::Loading) + { + USS_WARN("Cannot end mission - not active"); + return; + } + + USS_LOG("Ending mission: %s", bSuccess ? "SUCCESS" : "FAILURE"); + + // End any active wave + if (m_WaveInfo.bIsActive) + { + EndWave(); + } + + // Calculate result + CalculateResult(); + m_Result.bSuccess = bSuccess; + + SetState(bSuccess ? EMissionState::Complete : EMissionState::Failed); + + // Notify callbacks + OnMissionEvent(bSuccess ? "MissionComplete" : "MissionFailed", &m_Result); + } + + void FMissionManager::AbortMission() + { + USS_LOG("Aborting mission"); + + if (m_WaveInfo.bIsActive) + { + EndWave(); + } + + m_Result = FMissionResult(); + m_Result.bSuccess = false; + + SetState(EMissionState::None); + + OnMissionEvent("MissionAborted", nullptr); + } + + void FMissionManager::AddObjective(std::unique_ptr Objective) + { + if (Objective) + { + USS_LOG("Adding objective: %s", Objective->GetDisplayText().c_str()); + m_Objectives.push_back(std::move(Objective)); + } + } + + FMissionObjective* FMissionManager::GetObjective(int32 Index) const + { + if (Index >= 0 && Index < static_cast(m_Objectives.size())) + { + return m_Objectives[Index].get(); + } + return nullptr; + } + + void FMissionManager::UpdateObjectiveProgress(int32 Index, int32 Progress) + { + FMissionObjective* Objective = GetObjective(Index); + if (Objective) + { + Objective->SetProgress(Progress); + } + } + + void FMissionManager::StartWave(int32 WaveNumber) + { + if (m_State != EMissionState::Active && m_State != EMissionState::DefensePhase) + { + USS_WARN("Cannot start wave - mission not active"); + return; + } + + USS_LOG("Starting wave %d/%d", WaveNumber, m_WaveInfo.MaxWaves); + + m_WaveInfo.CurrentWave = WaveNumber; + m_WaveInfo.bIsActive = true; + m_WaveInfo.EnemiesSpawned = 0; + m_WaveInfo.EnemiesRemaining = 0; + m_WaveInfo.WaveStartTime = 0.0f; // Would get from engine + + SetState(EMissionState::DefensePhase); + + OnMissionEvent("WaveStarted", &m_WaveInfo); + } + + void FMissionManager::EndWave() + { + if (!m_WaveInfo.bIsActive) + return; + + USS_LOG("Ending wave %d", m_WaveInfo.CurrentWave); + + m_WaveInfo.bIsActive = false; + + OnMissionEvent("WaveEnded", &m_WaveInfo); + + // Check if all waves complete + if (m_WaveInfo.CurrentWave >= m_WaveInfo.MaxWaves) + { + USS_LOG("All waves complete"); + OnMissionEvent("AllWavesComplete", nullptr); + } + else + { + // Return to active state between waves + SetState(EMissionState::Active); + } + } + + void FMissionManager::AddScore(int32 Points) + { + m_Score += Points; + USS_LOG("Score: %d (+%d)", m_Score, Points); + } + + void FMissionManager::OnProcessEvent(void* Object, void* Function, void* Params) + { + // Route relevant ProcessEvent calls to mission system + // TODO: Would check function name and dispatch accordingly + // Prefferrably we would avoid PE alltogether and hook into specific game events + + // Example event routing: + // "ServerHandleEnemyKilled" -> OnMissionEvent("EnemyKilled", Params) + // "ServerHandleSurvivorRescued" -> OnMissionEvent("SurvivorRescued", Params) + } + + void FMissionManager::OnMissionEvent(const char* EventName, void* Params) + { + // Dispatch to objectives + for (auto& Objective : m_Objectives) + { + if (Objective) + { + Objective->OnEvent(EventName, Params); + } + } + + // Dispatch to registered callbacks + for (const auto& Callback : m_EventCallbacks) + { + if (Callback) + { + Callback(EventName, Params); + } + } + } + + void FMissionManager::RegisterEventCallback(FMissionEventCallback Callback) + { + if (Callback) + { + m_EventCallbacks.push_back(std::move(Callback)); + } + } + + FMissionResult FMissionManager::GetResult() const + { + return m_Result; + } + + void FMissionManager::SetState(EMissionState NewState) + { + if (m_State != NewState) + { + EMissionState OldState = m_State; + m_State = NewState; + + USS_LOG("Mission state: %d -> %d", static_cast(OldState), static_cast(NewState)); + } + } + + void FMissionManager::CreateDefaultObjectives() + { + // Create objectives based on mission type + switch (m_Config.Type) + { + case EMissionType::FarmsteadDefense: + { + FObjectiveDefinition DefendDef; + DefendDef.Type = EObjectiveType::Defend; + DefendDef.DisplayText = "Defend the Atlas"; + DefendDef.TimeLimit = 480.0f; // 8 minutes + DefendDef.bIsPrimary = true; + AddObjective(CreateObjective(DefendDef)); + break; + } + + case EMissionType::SurvivorsRescue: + { + FObjectiveDefinition RescueDef; + RescueDef.Type = EObjectiveType::Rescue; + RescueDef.DisplayText = "Rescue Survivors"; + RescueDef.TargetCount = 6; + RescueDef.bIsPrimary = true; + AddObjective(CreateObjective(RescueDef)); + break; + } + + case EMissionType::EncampmentDestruction: + { + FObjectiveDefinition DestroyDef; + DestroyDef.Type = EObjectiveType::Kill; + DestroyDef.DisplayText = "Destroy Encampments"; + DestroyDef.TargetCount = 5; + DestroyDef.bIsPrimary = true; + AddObjective(CreateObjective(DestroyDef)); + break; + } + + case EMissionType::StormShieldDefense: + { + FObjectiveDefinition DefendDef; + DefendDef.Type = EObjectiveType::Defend; + DefendDef.DisplayText = "Defend the Storm Shield"; + DefendDef.bIsPrimary = true; + AddObjective(CreateObjective(DefendDef)); + break; + } + + case EMissionType::RadarGridConstruction: + { + FObjectiveDefinition BuildDef; + BuildDef.Type = EObjectiveType::Build; + BuildDef.DisplayText = "Build Radar Towers"; + BuildDef.TargetCount = 5; + BuildDef.bIsPrimary = true; + AddObjective(CreateObjective(BuildDef)); + break; + } + + default: + { + // Generic kill objective + FObjectiveDefinition KillDef; + KillDef.Type = EObjectiveType::Kill; + KillDef.DisplayText = "Eliminate Enemies"; + KillDef.TargetCount = 50; + KillDef.bIsPrimary = true; + AddObjective(CreateObjective(KillDef)); + break; + } + } + } + + void FMissionManager::CheckObjectivesComplete() + { + bool bAllPrimaryComplete = true; + bool bAnyPrimaryFailed = false; + + for (const auto& Objective : m_Objectives) + { + if (Objective && Objective->GetDefinition().bIsPrimary) + { + if (Objective->IsFailed()) + { + bAnyPrimaryFailed = true; + break; + } + + if (!Objective->IsComplete()) + { + bAllPrimaryComplete = false; + } + } + } + + if (bAnyPrimaryFailed) + { + EndMission(false); + } + else if (bAllPrimaryComplete) + { + EndMission(true); + } + } + + void FMissionManager::CalculateResult() + { + m_Result.FinalScore = m_Score; + m_Result.WavesCompleted = m_WaveInfo.CurrentWave; + m_Result.TotalWaves = m_WaveInfo.MaxWaves; + m_Result.bSuccess = true; + + // Count objective completions + m_Result.ObjectivesCompleted = 0; + m_Result.TotalObjectives = static_cast(m_Objectives.size()); + + for (const auto& Objective : m_Objectives) + { + if (Objective && Objective->IsComplete()) + { + m_Result.ObjectivesCompleted++; + } + else if (Objective && Objective->IsFailed() && Objective->GetDefinition().bIsPrimary) + { + m_Result.bSuccess = false; + } + } + + USS_LOG("Mission result: Score=%d, Objectives=%d/%d, Waves=%d/%d", + m_Result.FinalScore, + m_Result.ObjectivesCompleted, + m_Result.TotalObjectives, + m_Result.WavesCompleted, + m_Result.TotalWaves); + } + +} \ No newline at end of file diff --git a/STW/Missions/MissionManager.h b/STW/Missions/MissionManager.h new file mode 100644 index 0000000..01ff878 --- /dev/null +++ b/STW/Missions/MissionManager.h @@ -0,0 +1,99 @@ +/** + * UniversalSlashingSimulator - Mission Manager + * + * Manages mission lifecycle, objectives, waves, and scoring. + * Based on AFortMission and AFortMissionManager from STW. + */ + +#pragma once + +#include "../../Core/Common.h" +#include "../../Engine/UObject/UObjectWrapper.h" +#include "MissionTypes.h" +#include "MissionObjective.h" +#include +#include +#include + +namespace USS +{ + // Mission event callback + using FMissionEventCallback = std::function; + + class FMissionManager + { + public: + FMissionManager(); + ~FMissionManager(); + + USS_NON_COPYABLE(FMissionManager) + USS_NON_MOVABLE(FMissionManager) + + // Lifecycle + EResult Initialize(); + void Shutdown(); + void Update(); + + // Mission control + EResult StartMission(const FMissionConfig& Config); + void EndMission(bool bSuccess); + void AbortMission(); + + // State + EMissionState GetState() const { return m_State; } + const FMissionConfig& GetConfig() const { return m_Config; } + bool IsActive() const { return m_State == EMissionState::Active || m_State == EMissionState::DefensePhase; } + + // Objectives + void AddObjective(std::unique_ptr Objective); + FMissionObjective* GetObjective(int32 Index) const; + int32 GetObjectiveCount() const { return static_cast(m_Objectives.size()); } + void UpdateObjectiveProgress(int32 Index, int32 Progress); + + // Waves + void StartWave(int32 WaveNumber); + void EndWave(); + const FWaveInfo& GetWaveInfo() const { return m_WaveInfo; } + + // Scoring + int32 GetScore() const { return m_Score; } + void AddScore(int32 Points); + + // Events + void OnProcessEvent(void* Object, void* Function, void* Params); + void OnMissionEvent(const char* EventName, void* Params); + + // Callbacks + void RegisterEventCallback(FMissionEventCallback Callback); + + // Result + FMissionResult GetResult() const; + + private: + void SetState(EMissionState NewState); + void CreateDefaultObjectives(); + void CheckObjectivesComplete(); + void CalculateResult(); + + // State + EMissionState m_State; + FMissionConfig m_Config; + FWaveInfo m_WaveInfo; + int32 m_Score; + + // Objectives + std::vector> m_Objectives; + + // Engine references (cached) + UObjectWrapper m_MissionActor; // AFortMission* + UObjectWrapper m_MissionManagerActor; // AFortMissionManager* + UObjectWrapper m_AIDirector; // AFortAIDirector* + + // Callbacks + std::vector m_EventCallbacks; + + // Result + FMissionResult m_Result; + }; + +} \ No newline at end of file diff --git a/STW/Missions/MissionObjective.cpp b/STW/Missions/MissionObjective.cpp new file mode 100644 index 0000000..87d65a2 --- /dev/null +++ b/STW/Missions/MissionObjective.cpp @@ -0,0 +1,321 @@ +/** + * UniversalSlashingSimulator - Mission Objective Implementation + */ + +#include "MissionObjective.h" +#include "../../Core/Logging/Log.h" + +namespace USS +{ + // ======================================================================== + // FMissionObjective (Base) + // ======================================================================== + + FMissionObjective::FMissionObjective(const FObjectiveDefinition& Definition) + : m_Definition(Definition) + , m_State(EObjectiveState::Inactive) + , m_CurrentProgress(0) + { + } + + void FMissionObjective::Update() + { + // Base implementation does nothing + // Derived classes override for time-based updates + } + + void FMissionObjective::OnEvent(const char* EventName, void* Params) + { + // Base implementation does nothing + // Derived classes handle specific events + } + + float FMissionObjective::GetProgressPercent() const + { + if (m_Definition.TargetCount <= 0) + return m_State == EObjectiveState::Completed ? 1.0f : 0.0f; + + return static_cast(m_CurrentProgress) / static_cast(m_Definition.TargetCount); + } + + void FMissionObjective::AddProgress(int32 Amount) + { + SetProgress(m_CurrentProgress + Amount); + } + + void FMissionObjective::SetProgress(int32 Progress) + { + int32 OldProgress = m_CurrentProgress; + m_CurrentProgress = Progress; + + if (m_CurrentProgress < 0) + m_CurrentProgress = 0; + + if (m_CurrentProgress != OldProgress) + { + OnProgressChanged(); + + // Auto-complete if target reached + if (m_Definition.TargetCount > 0 && m_CurrentProgress >= m_Definition.TargetCount) + { + Complete(); + } + } + } + + void FMissionObjective::Complete() + { + if (m_State != EObjectiveState::Completed && m_State != EObjectiveState::Failed) + { + SetState(EObjectiveState::Completed); + USS_LOG("Objective completed: %s", m_Definition.DisplayText.c_str()); + } + } + + void FMissionObjective::Fail() + { + if (m_State != EObjectiveState::Completed && m_State != EObjectiveState::Failed) + { + SetState(EObjectiveState::Failed); + USS_LOG("Objective failed: %s", m_Definition.DisplayText.c_str()); + } + } + + void FMissionObjective::OnProgressChanged() + { + USS_LOG("Objective progress: %s - %d/%d", + m_Definition.DisplayText.c_str(), + m_CurrentProgress, + m_Definition.TargetCount); + } + + void FMissionObjective::OnStateChanged(EObjectiveState OldState) + { + // Base implementation does nothing + } + + void FMissionObjective::SetState(EObjectiveState NewState) + { + if (m_State != NewState) + { + EObjectiveState OldState = m_State; + m_State = NewState; + OnStateChanged(OldState); + } + } + + // ======================================================================== + // FKillObjective + // ======================================================================== + + FKillObjective::FKillObjective(const FObjectiveDefinition& Definition) + : FMissionObjective(Definition) + { + // Could parse target class from definition metadata + } + + void FKillObjective::OnEvent(const char* EventName, void* Params) + { + if (m_State != EObjectiveState::Active) + return; + + // Handle enemy killed event + // TODO: Check if EventName matches kill event, + // validate killed actor class, increment progress + if (strcmp(EventName, "EnemyKilled") == 0) + { + // TODO: Validate target class if specified + AddProgress(1); + } + } + + // ======================================================================== + // FCollectObjective + // ======================================================================== + + FCollectObjective::FCollectObjective(const FObjectiveDefinition& Definition) + : FMissionObjective(Definition) + { + } + + void FCollectObjective::OnEvent(const char* EventName, void* Params) + { + if (m_State != EObjectiveState::Active) + return; + + if (strcmp(EventName, "ItemCollected") == 0) + { + // TODO: Validate item class + AddProgress(1); + } + } + + // ======================================================================== + // FDefendObjective + // ======================================================================== + + FDefendObjective::FDefendObjective(const FObjectiveDefinition& Definition) + : FMissionObjective(Definition) + , m_DefendTimeRemaining(Definition.TimeLimit) + , m_CurrentHealth(100.0f) + , m_MaxHealth(100.0f) + , m_bDefenseActive(false) + { + } + + void FDefendObjective::Update() + { + if (m_State != EObjectiveState::Active || !m_bDefenseActive) + return; + + // TODO: Get delta time from engine + float DeltaTime = 1.0f / 30.0f; // Placeholder 30fps + + m_DefendTimeRemaining -= DeltaTime; + + if (m_DefendTimeRemaining <= 0.0f) + { + m_DefendTimeRemaining = 0.0f; + Complete(); + } + } + + void FDefendObjective::OnEvent(const char* EventName, void* Params) + { + if (m_State != EObjectiveState::Active) + return; + + if (strcmp(EventName, "DefenseStarted") == 0) + { + m_bDefenseActive = true; + USS_LOG("Defense phase started"); + } + else if (strcmp(EventName, "ObjectDamaged") == 0) + { + // TODO: Extract damage amount from Params + float Damage = 10.0f; // Placeholder + m_CurrentHealth -= Damage; + + if (m_CurrentHealth <= 0.0f) + { + m_CurrentHealth = 0.0f; + Fail(); + } + } + } + + float FDefendObjective::GetHealthPercent() const + { + if (m_MaxHealth <= 0.0f) + return 1.0f; + return m_CurrentHealth / m_MaxHealth; + } + + // ======================================================================== + // FExploreObjective + // ======================================================================== + + FExploreObjective::FExploreObjective(const FObjectiveDefinition& Definition) + : FMissionObjective(Definition) + , m_bLocationReached(false) + { + } + + void FExploreObjective::OnEvent(const char* EventName, void* Params) + { + if (m_State != EObjectiveState::Active) + return; + + if (strcmp(EventName, "LocationReached") == 0) + { + // TODO: Validate location matches objective + m_bLocationReached = true; + Complete(); + } + } + + // ======================================================================== + // FBuildObjective + // ======================================================================== + + FBuildObjective::FBuildObjective(const FObjectiveDefinition& Definition) + : FMissionObjective(Definition) + { + } + + void FBuildObjective::OnEvent(const char* EventName, void* Params) + { + if (m_State != EObjectiveState::Active) + return; + + if (strcmp(EventName, "BuildingPlaced") == 0) + { + // TODO: Validate building class if specified + AddProgress(1); + } + } + + // ======================================================================== + // FRescueObjective + // ======================================================================== + + FRescueObjective::FRescueObjective(const FObjectiveDefinition& Definition) + : FMissionObjective(Definition) + , m_SurvivorsLost(0) + , m_MaxLosses(0) // Could be set from definition + { + } + + void FRescueObjective::OnEvent(const char* EventName, void* Params) + { + if (m_State != EObjectiveState::Active) + return; + + if (strcmp(EventName, "SurvivorRescued") == 0) + { + AddProgress(1); + } + else if (strcmp(EventName, "SurvivorLost") == 0) + { + m_SurvivorsLost++; + + if (m_MaxLosses > 0 && m_SurvivorsLost >= m_MaxLosses) + { + Fail(); + } + } + } + + // ======================================================================== + // Factory + // ======================================================================== + + std::unique_ptr CreateObjective(const FObjectiveDefinition& Definition) + { + switch (Definition.Type) + { + case EObjectiveType::Kill: + return std::make_unique(Definition); + + case EObjectiveType::Collect: + return std::make_unique(Definition); + + case EObjectiveType::Defend: + return std::make_unique(Definition); + + case EObjectiveType::Explore: + return std::make_unique(Definition); + + case EObjectiveType::Build: + return std::make_unique(Definition); + + case EObjectiveType::Rescue: + return std::make_unique(Definition); + + default: + USS_WARN("Unknown objective type: %d", static_cast(Definition.Type)); + return std::make_unique(Definition); + } + } + +} \ No newline at end of file diff --git a/STW/Missions/MissionObjective.h b/STW/Missions/MissionObjective.h new file mode 100644 index 0000000..5c1902d --- /dev/null +++ b/STW/Missions/MissionObjective.h @@ -0,0 +1,150 @@ +/** + * UniversalSlashingSimulator - Mission Objective + * + * Base class for mission objectives. Derived classes handle + * specific objective types (kill, collect, defend, etc.). + */ + +#pragma once + +#include "../../Core/Common.h" +#include "../../Engine/UObject/UObjectWrapper.h" +#include "MissionTypes.h" +#include + +namespace USS +{ + // Base mission objective class + class FMissionObjective + { + public: + FMissionObjective(const FObjectiveDefinition& Definition); + virtual ~FMissionObjective() = default; + + USS_NON_COPYABLE(FMissionObjective) + + // Core interface + virtual void Update(); + virtual void OnEvent(const char* EventName, void* Params); + + // State + EObjectiveState GetState() const { return m_State; } + bool IsComplete() const { return m_State == EObjectiveState::Completed; } + bool IsFailed() const { return m_State == EObjectiveState::Failed; } + + // Progress + int32 GetCurrentProgress() const { return m_CurrentProgress; } + int32 GetTargetProgress() const { return m_Definition.TargetCount; } + float GetProgressPercent() const; + + // Definition + const FObjectiveDefinition& GetDefinition() const { return m_Definition; } + EObjectiveType GetType() const { return m_Definition.Type; } + const std::string& GetDisplayText() const { return m_Definition.DisplayText; } + + // Progress updates + void AddProgress(int32 Amount); + void SetProgress(int32 Progress); + void Complete(); + void Fail(); + + protected: + virtual void OnProgressChanged(); + virtual void OnStateChanged(EObjectiveState OldState); + + void SetState(EObjectiveState NewState); + + FObjectiveDefinition m_Definition; + EObjectiveState m_State; + int32 m_CurrentProgress; + + // Cached engine references + UObjectWrapper m_ObjectiveActor; // AFortObjective* + }; + + // Kill enemies objective + class FKillObjective : public FMissionObjective + { + public: + FKillObjective(const FObjectiveDefinition& Definition); + + void OnEvent(const char* EventName, void* Params) override; + + private: + std::string m_TargetActorClass; // Specific enemy class to kill, empty = any + }; + + // Collect items objective + class FCollectObjective : public FMissionObjective + { + public: + FCollectObjective(const FObjectiveDefinition& Definition); + + void OnEvent(const char* EventName, void* Params) override; + + private: + std::string m_ItemClass; // Item class to collect + }; + + // Defend location/object objective + class FDefendObjective : public FMissionObjective + { + public: + FDefendObjective(const FObjectiveDefinition& Definition); + + void Update() override; + void OnEvent(const char* EventName, void* Params) override; + + // Defend-specific + float GetDefendTimeRemaining() const { return m_DefendTimeRemaining; } + float GetDefendTimerTotal() const { return m_Definition.TimeLimit; } + float GetHealthPercent() const; + + private: + float m_DefendTimeRemaining; + float m_CurrentHealth; + float m_MaxHealth; + bool m_bDefenseActive; + }; + + // Explore/reach location objective + class FExploreObjective : public FMissionObjective + { + public: + FExploreObjective(const FObjectiveDefinition& Definition); + + void OnEvent(const char* EventName, void* Params) override; + + private: + bool m_bLocationReached; + }; + + // Build structures objective + class FBuildObjective : public FMissionObjective + { + public: + FBuildObjective(const FObjectiveDefinition& Definition); + + void OnEvent(const char* EventName, void* Params) override; + + private: + std::string m_BuildingClass; // Specific building type, empty = any + }; + + // Rescue survivors objective + class FRescueObjective : public FMissionObjective + { + public: + FRescueObjective(const FObjectiveDefinition& Definition); + + void OnEvent(const char* EventName, void* Params) override; + + private: + int32 m_SurvivorsLost; + int32 m_MaxLosses; + }; + + // Factory function to create objective by type + std::unique_ptr CreateObjective(const FObjectiveDefinition& Definition); + +} \ No newline at end of file diff --git a/STW/Missions/MissionTypes.h b/STW/Missions/MissionTypes.h new file mode 100644 index 0000000..3d8ff42 --- /dev/null +++ b/STW/Missions/MissionTypes.h @@ -0,0 +1,316 @@ +/** + * UniversalSlashingSimulator - Mission Types + * + * Types and enums for the STW mission system. + */ + +#pragma once + +#include "../../Core/Common.h" +#include +#include + +namespace USS +{ + // Mission types from STW + enum class EMissionType : uint8 + { + Unknown = 0, + + // Core mission types + FarmsteadDefense, // Defend the base (Homebase) + SurvivorsRescue, // Rescue survivors + EncampmentDestroy, // Destroy encampments + EncampmentDestruction, // Alias for EncampmentDestroy + RadarBuild, // Build radar towers + RadarGridConstruction, // Alias for RadarBuild + DataRetrieval, // Retrieve data + + // Storm Shield + StormShieldDefense, // SSD missions + + // Special types + EliminateAndCollect, // Kill and collect + DeliverTheBomb, // DTB mission + RepairTheShelter, // RTS mission + EvacuateTheShelter, // Evac mission + RideTheLightning, // RTL mission + CategoryStorm, // 4-atlas + RetrieveTheData, // RTD mission + LaunchTheBalloon, // LTB mission + + // Horde modes + ChallengeTheHorde, + FrostniteEndurance, + + // Wargames + Wargames, + + Max + }; + + // Mission state + enum class EMissionState : uint8 + { + None, + Loading, + Setup, // Players setting up defenses + Active, // Mission in progress + DefensePhase, // Defending objective + Intermission, // Between waves + Completed, + Complete = Completed, // Alias + Failed, + Abandoned + }; + + // Objective state + enum class EObjectiveState : uint8 + { + Inactive, + Active, + Completed, + Failed, + Optional + }; + + // Objective type + enum class EObjectiveType : uint8 + { + Unknown = 0, + DefendLocation, // Defend a specific point + Defend = DefendLocation, // Alias + KillEnemies, // Kill X enemies + Kill = KillEnemies, // Alias + CollectItems, // Collect X items + Collect = CollectItems, // Alias + BuildStructures, // Build X structures + Build = BuildStructures, // Alias + RescueSurvivors, // Rescue X survivors + Rescue = RescueSurvivors, // Alias + DestroyObjects, // Destroy X objects + EscortPayload, // Escort/deliver payload + ActivateDevice, // Activate a device + SurviveWaves, // Survive X waves + Timer, // Time-based objective + Explore, // Exploration objective + }; + + // Difficulty tier + enum class EDifficultyTier : uint8 + { + VeryLow = 0, + Low, + Medium, + High, + VeryHigh, + Extreme, + Max + }; + + // Mission alert type + enum class EMissionAlert : uint8 + { + None = 0, + Storm, + MiniBeoss, + ElementalModifier, + MutantStorm, + GroupMission, + }; + + // Mission configuration + struct FMissionConfig + { + EMissionType Type; + std::string BlueprintPath; // e.g., "Mission_FarmsteadFort_C" + std::string ZoneName; + int32 DifficultyLevel; // 1-140+ + EDifficultyTier DifficultyTier; + EMissionAlert AlertType; + + // Time limits (seconds, 0 = no limit) + int32 SetupTimeLimit; + int32 MissionTimeLimit; + + // Wave settings + int32 WaveCount; + int32 MaxWaves; // Alias for WaveCount + int32 EnemiesPerWave; + + FMissionConfig() + : Type(EMissionType::Unknown) + , DifficultyLevel(1) + , DifficultyTier(EDifficultyTier::Low) + , AlertType(EMissionAlert::None) + , SetupTimeLimit(0) + , MissionTimeLimit(0) + , WaveCount(1) + , MaxWaves(1) + , EnemiesPerWave(20) + {} + }; + + // Objective definition + struct FObjectiveDefinition + { + EObjectiveType Type; + std::string Name; + std::string DisplayText; // Display name for UI + std::string Description; + + int32 TargetCount; // How many to complete + int32 CurrentCount; // Current progress + float TimeLimit; // Time limit in seconds (0 = no limit) + + bool bIsRequired; // Required for mission success + bool bIsPrimary; // Primary objective + bool bIsBonus; // Bonus objective + + EObjectiveState State; + + FObjectiveDefinition() + : Type(EObjectiveType::Unknown) + , TargetCount(1) + , CurrentCount(0) + , TimeLimit(0.0f) + , bIsRequired(true) + , bIsPrimary(true) + , bIsBonus(false) + , State(EObjectiveState::Inactive) + {} + + float GetProgress() const + { + if (TargetCount <= 0) return 0.0f; + return static_cast(CurrentCount) / static_cast(TargetCount); + } + + bool IsComplete() const + { + return CurrentCount >= TargetCount; + } + }; + + // Wave info + struct FWaveInfo + { + int32 WaveNumber; + int32 CurrentWave; // Alias for WaveNumber + int32 TotalWaves; + int32 MaxWaves; // Alias for TotalWaves + int32 EnemiesRemaining; + int32 EnemiesTotal; + int32 EnemiesSpawned; // Total enemies spawned so far + float TimeRemaining; // Seconds + double WaveStartTime; // When this wave started + bool bIsDefenseWave; + bool bIsActive; // Is wave currently active + + FWaveInfo() + : WaveNumber(0) + , CurrentWave(0) + , TotalWaves(1) + , MaxWaves(1) + , EnemiesRemaining(0) + , EnemiesTotal(0) + , EnemiesSpawned(0) + , TimeRemaining(0.0f) + , WaveStartTime(0.0) + , bIsDefenseWave(false) + , bIsActive(false) + {} + }; + + // Mission rewards + struct FMissionReward + { + std::string ItemId; + int32 Quantity; + int32 Rarity; // 0=common, 1=uncommon, etc. + bool bIsBonusReward; + + FMissionReward() + : Quantity(1) + , Rarity(0) + , bIsBonusReward(false) + {} + }; + + // Mission result + struct FMissionResult + { + EMissionState FinalState; + bool bSuccess; // Did mission succeed + float CompletionPercentage; + int32 ScoreEarned; + int32 FinalScore; // Alias for ScoreEarned + int32 XPEarned; + + // Wave completion stats + int32 WavesCompleted; + int32 TotalWaves; + + // Objective stats + int32 ObjectivesCompleted; + int32 TotalObjectives; + + std::vector Rewards; + + FMissionResult() + : FinalState(EMissionState::None) + , bSuccess(false) + , CompletionPercentage(0.0f) + , ScoreEarned(0) + , FinalScore(0) + , XPEarned(0) + , WavesCompleted(0) + , TotalWaves(0) + , ObjectivesCompleted(0) + , TotalObjectives(0) + {} + }; + + // Helper functions + inline const char* GetMissionTypeName(EMissionType Type) + { + switch (Type) + { + case EMissionType::FarmsteadDefense: return "Defend the Base"; + case EMissionType::SurvivorsRescue: return "Rescue the Survivors"; + case EMissionType::EncampmentDestroy: return "Destroy the Encampments"; + case EMissionType::RadarBuild: return "Build the Radar"; + case EMissionType::DataRetrieval: return "Retrieve the Data"; + case EMissionType::StormShieldDefense: return "Storm Shield Defense"; + case EMissionType::DeliverTheBomb: return "Deliver the Bomb"; + case EMissionType::RepairTheShelter: return "Repair the Shelter"; + case EMissionType::EvacuateTheShelter: return "Evacuate the Shelter"; + case EMissionType::RideTheLightning: return "Ride the Lightning"; + case EMissionType::CategoryStorm: return "Category Storm"; + case EMissionType::RetrieveTheData: return "Retrieve the Data"; + case EMissionType::LaunchTheBalloon: return "Launch the Balloon"; + case EMissionType::ChallengeTheHorde: return "Challenge the Horde"; + case EMissionType::FrostniteEndurance: return "Frostnite"; + case EMissionType::Wargames: return "Wargames"; + default: return "Unknown"; + } + } + + inline const char* GetMissionStateName(EMissionState State) + { + switch (State) + { + case EMissionState::None: return "None"; + case EMissionState::Loading: return "Loading"; + case EMissionState::Setup: return "Setup"; + case EMissionState::Active: return "Active"; + case EMissionState::DefensePhase: return "Defense"; + case EMissionState::Intermission: return "Intermission"; + case EMissionState::Completed: return "Completed"; + case EMissionState::Failed: return "Failed"; + case EMissionState::Abandoned: return "Abandoned"; + default: return "Unknown"; + } + } + +} \ No newline at end of file diff --git a/STW/Player/STWPlayerController.cpp b/STW/Player/STWPlayerController.cpp new file mode 100644 index 0000000..42280ee --- /dev/null +++ b/STW/Player/STWPlayerController.cpp @@ -0,0 +1,397 @@ +/** + * UniversalSlashingSimulator - STW Player Controller Implementation + */ + +#include "STWPlayerController.h" +#include "STWPlayerPawn.h" +#include "../../Core/Logging/Log.h" +#include "../../Engine/EngineCore.h" + +namespace USS +{ + // ======================================================================== + // FSTWPlayerController + // ======================================================================== + + FSTWPlayerController::FSTWPlayerController() + : m_ReadyState(EPlayerReadyState::NotReady) + , m_TeamIndex(0) + , m_SquadSlot(-1) + , m_bInBuildMode(false) + , m_bInfoDirty(true) + { + } + + FSTWPlayerController::FSTWPlayerController(void* InController) + : m_Controller(InController) + , m_ReadyState(EPlayerReadyState::NotReady) + , m_TeamIndex(0) + , m_SquadSlot(-1) + , m_bInBuildMode(false) + , m_bInfoDirty(true) + { + if (IsValid()) + { + UpdateFromNative(); + } + } + + FSTWPlayerController::~FSTWPlayerController() + { + m_pPawn.reset(); + } + + bool FSTWPlayerController::IsValid() const + { + return m_Controller.IsValid(); + } + + FSTWPlayerInfo FSTWPlayerController::GetPlayerInfo() const + { + if (m_bInfoDirty && IsValid()) + { + // Read from native controller + // TODO: Read PlayerState properties + + m_CachedInfo.bIsValid = true; + m_CachedInfo.TeamIndex = m_TeamIndex; + m_CachedInfo.SquadSlot = m_SquadSlot; + m_CachedInfo.ReadyState = m_ReadyState; + + // TODO: Read from APlayerState + // m_CachedInfo.PlayerName = ... + // m_CachedInfo.PlayerId = ... + + m_bInfoDirty = false; + } + + return m_CachedInfo; + } + + std::string FSTWPlayerController::GetPlayerName() const + { + if (!IsValid()) + return ""; + + // TODO: Read from PlayerState->GetPlayerName() + return m_CachedInfo.PlayerName; + } + + std::string FSTWPlayerController::GetPlayerId() const + { + if (!IsValid()) + return ""; + + // TODO: Read from PlayerState->GetUniqueId() + return m_CachedInfo.PlayerId; + } + + FSTWPlayerPawn* FSTWPlayerController::GetPawn() const + { + return m_pPawn.get(); + } + + void FSTWPlayerController::SetPawn(void* InPawn) + { + if (InPawn) + { + m_pPawn = std::make_unique(InPawn); + USS_LOG("Pawn set for player: %s", GetPlayerName().c_str()); + } + else + { + m_pPawn.reset(); + } + } + + bool FSTWPlayerController::HasPawn() const + { + return m_pPawn && m_pPawn->IsValid(); + } + + void FSTWPlayerController::SetReadyState(EPlayerReadyState State) + { + if (m_ReadyState != State) + { + m_ReadyState = State; + m_bInfoDirty = true; + + USS_LOG("Player %s ready state: %d", GetPlayerName().c_str(), static_cast(State)); + } + } + + void FSTWPlayerController::SetTeamInfo(int32 TeamIndex, int32 SquadSlot) + { + m_TeamIndex = TeamIndex; + m_SquadSlot = SquadSlot; + m_bInfoDirty = true; + } + + void FSTWPlayerController::ActivateAbility(int32 AbilityIndex) + { + if (!HasPawn()) + return; + + // TODO: Call ability activation on pawn's AbilitySystemComponent + USS_LOG("Activating ability %d for player %s", AbilityIndex, GetPlayerName().c_str()); + } + + void FSTWPlayerController::DeactivateAbility(int32 AbilityIndex) + { + if (!HasPawn()) + return; + + USS_LOG("Deactivating ability %d", AbilityIndex); + } + + bool FSTWPlayerController::IsAbilityReady(int32 AbilityIndex) const + { + if (!HasPawn()) + return false; + + // TODO: Check ability cooldown + return true; + } + + void FSTWPlayerController::UseGadget(int32 GadgetSlot) + { + if (!HasPawn()) + return; + + USS_LOG("Using gadget slot %d", GadgetSlot); + // TODO: Trigger gadget use + } + + bool FSTWPlayerController::IsGadgetReady(int32 GadgetSlot) const + { + // TODO: Check gadget cooldown + return true; + } + + void FSTWPlayerController::EnterBuildMode() + { + if (!m_bInBuildMode) + { + m_bInBuildMode = true; + USS_LOG("Entering build mode"); + // TODO: Set building mode state on controller + } + } + + void FSTWPlayerController::ExitBuildMode() + { + if (m_bInBuildMode) + { + m_bInBuildMode = false; + USS_LOG("Exiting build mode"); + } + } + + void FSTWPlayerController::SendChatMessage(const char* Message) + { + if (!IsValid() || !Message) + return; + + USS_LOG("Chat from %s: %s", GetPlayerName().c_str(), Message); + // TODO: Broadcast to other players + } + + void FSTWPlayerController::ShowNotification(const char* Message, float Duration) + { + if (!IsValid() || !Message) + return; + + // TODO: Call client RPC to show notification + USS_LOG("Notification to %s: %s", GetPlayerName().c_str(), Message); + } + + void FSTWPlayerController::ServerAcknowledgePossession(void* Pawn) + { + SetPawn(Pawn); + } + + void FSTWPlayerController::ServerSetReadyState(EPlayerReadyState State) + { + SetReadyState(State); + } + + void FSTWPlayerController::ServerRequestRespawn() + { + if (!IsValid()) + return; + + USS_LOG("Respawn requested for %s", GetPlayerName().c_str()); + // TODO: Handle respawn logic + } + + void FSTWPlayerController::OnProcessEvent(void* Function, void* Params) + { + // Handle controller-specific events + // // prefferrably avoid PE where possible + // TODO: Check function name and dispatch + } + + void FSTWPlayerController::UpdateFromNative() + { + if (!IsValid()) + return; + + // Read current state from native controller + // TODO: Read team, ready state, etc. from engine + m_bInfoDirty = true; + } + + // ======================================================================== + // FPlayerControllerManager + // ======================================================================== + + FPlayerControllerManager& FPlayerControllerManager::Get() + { + static FPlayerControllerManager Instance; + return Instance; + } + + EResult FPlayerControllerManager::Initialize() + { + USS_LOG("Initializing Player Controller Manager..."); + m_Players.clear(); + return EResult::Success; + } + + void FPlayerControllerManager::Shutdown() + { + USS_LOG("Shutting down Player Controller Manager"); + m_Players.clear(); + } + + void FPlayerControllerManager::Update() + { + // Update all player controllers + for (auto& Pair : m_Players) + { + if (Pair.second && Pair.second->HasPawn()) + { + Pair.second->GetPawn()->Update(); + } + } + } + + FSTWPlayerController* FPlayerControllerManager::RegisterPlayer(void* Controller) + { + if (!Controller) + return nullptr; + + auto It = m_Players.find(Controller); + if (It != m_Players.end()) + { + return It->second.get(); + } + + auto NewController = std::make_unique(Controller); + FSTWPlayerController* Result = NewController.get(); + m_Players[Controller] = std::move(NewController); + + USS_LOG("Registered player controller: %p", Controller); + + return Result; + } + + void FPlayerControllerManager::UnregisterPlayer(void* Controller) + { + auto It = m_Players.find(Controller); + if (It != m_Players.end()) + { + USS_LOG("Unregistered player controller: %p", Controller); + m_Players.erase(It); + } + } + + FSTWPlayerController* FPlayerControllerManager::GetPlayer(void* Controller) const + { + auto It = m_Players.find(Controller); + return (It != m_Players.end()) ? It->second.get() : nullptr; + } + + FSTWPlayerController* FPlayerControllerManager::GetPlayerById(const char* PlayerId) const + { + if (!PlayerId) + return nullptr; + + for (const auto& Pair : m_Players) + { + if (Pair.second && Pair.second->GetPlayerId() == PlayerId) + { + return Pair.second.get(); + } + } + + return nullptr; + } + + FSTWPlayerController* FPlayerControllerManager::GetPlayerByIndex(int32 Index) const + { + if (Index < 0 || Index >= static_cast(m_Players.size())) + return nullptr; + + auto It = m_Players.begin(); + std::advance(It, Index); + return It->second.get(); + } + + std::vector FPlayerControllerManager::GetPlayersOnTeam(int32 TeamIndex) const + { + std::vector Result; + + for (const auto& Pair : m_Players) + { + if (Pair.second && Pair.second->GetTeamIndex() == TeamIndex) + { + Result.push_back(Pair.second.get()); + } + } + + return Result; + } + + int32 FPlayerControllerManager::GetReadyPlayerCount() const + { + int32 Count = 0; + + for (const auto& Pair : m_Players) + { + if (Pair.second && Pair.second->IsReady()) + { + Count++; + } + } + + return Count; + } + + bool FPlayerControllerManager::AreAllPlayersReady() const + { + if (m_Players.empty()) + return false; + + for (const auto& Pair : m_Players) + { + if (Pair.second && !Pair.second->IsReady() && !Pair.second->IsInGame()) + { + return false; + } + } + + return true; + } + + void FPlayerControllerManager::OnPlayerJoined(void* Controller) + { + RegisterPlayer(Controller); + } + + void FPlayerControllerManager::OnPlayerLeft(void* Controller) + { + UnregisterPlayer(Controller); + } + +} \ No newline at end of file diff --git a/STW/Player/STWPlayerController.h b/STW/Player/STWPlayerController.h new file mode 100644 index 0000000..9b1a78f --- /dev/null +++ b/STW/Player/STWPlayerController.h @@ -0,0 +1,190 @@ +/** + * UniversalSlashingSimulator - STW Player Controller + * + * Wrapper for AFortPlayerController with STW-specific functionality. + * Handles player input, abilities, and communication with server. + */ + +#pragma once + +#include "../../Core/Common.h" +#include "../../Engine/UObject/UObjectWrapper.h" +#include +#include + +namespace USS +{ + // Forward declarations + class FSTWPlayerPawn; + class FInventoryManager; + + // Player state for STW + enum class EPlayerReadyState : uint8 + { + NotReady, + Ready, + InGame, + Spectating + }; + + // Player info structure + struct FSTWPlayerInfo + { + std::string PlayerName; + std::string PlayerId; // EpicID/AccountID + int32 TeamIndex; + int32 SquadSlot; + int32 PowerLevel; + int32 CommanderLevel; + EPlayerReadyState ReadyState; + bool bIsPartyLeader; + bool bIsValid; + + FSTWPlayerInfo() + : TeamIndex(0) + , SquadSlot(-1) + , PowerLevel(1) + , CommanderLevel(1) + , ReadyState(EPlayerReadyState::NotReady) + , bIsPartyLeader(false) + , bIsValid(false) + {} + }; + + // STW Player Controller wrapper + class FSTWPlayerController + { + public: + FSTWPlayerController(); + explicit FSTWPlayerController(void* InController); + ~FSTWPlayerController(); + + USS_NON_COPYABLE(FSTWPlayerController) + + // Validity + bool IsValid() const; + void* GetNative() const { return m_Controller.GetRaw(); } + + // Player info + FSTWPlayerInfo GetPlayerInfo() const; + std::string GetPlayerName() const; + std::string GetPlayerId() const; + + // Pawn management + FSTWPlayerPawn* GetPawn() const; + void SetPawn(void* InPawn); + bool HasPawn() const; + + // State + EPlayerReadyState GetReadyState() const { return m_ReadyState; } + void SetReadyState(EPlayerReadyState State); + bool IsReady() const { return m_ReadyState == EPlayerReadyState::Ready; } + bool IsInGame() const { return m_ReadyState == EPlayerReadyState::InGame; } + + // Team + int32 GetTeamIndex() const { return m_TeamIndex; } + int32 GetSquadSlot() const { return m_SquadSlot; } + void SetTeamInfo(int32 TeamIndex, int32 SquadSlot); + + // Abilities (STW heroes have abilities) + void ActivateAbility(int32 AbilityIndex); + void DeactivateAbility(int32 AbilityIndex); + bool IsAbilityReady(int32 AbilityIndex) const; + + // Gadgets + void UseGadget(int32 GadgetSlot); + bool IsGadgetReady(int32 GadgetSlot) const; + + // Building mode + void EnterBuildMode(); + void ExitBuildMode(); + bool IsInBuildMode() const { return m_bInBuildMode; } + + // Communication + void SendChatMessage(const char* Message); + void ShowNotification(const char* Message, float Duration = 3.0f); + + // Server RPCs (stubs - actual implementation calls engine functions) + void ServerAcknowledgePossession(void* Pawn); + void ServerSetReadyState(EPlayerReadyState State); + void ServerRequestRespawn(); + + // Event handling + void OnProcessEvent(void* Function, void* Params); + + private: + void UpdateFromNative(); + + UObjectWrapper m_Controller; // AFortPlayerController* + std::unique_ptr m_pPawn; + + // Cached state + EPlayerReadyState m_ReadyState; + int32 m_TeamIndex; + int32 m_SquadSlot; + bool m_bInBuildMode; + + // Cached player info + mutable FSTWPlayerInfo m_CachedInfo; + mutable bool m_bInfoDirty; + }; + + // Player controller manager - tracks all connected players + class FPlayerControllerManager + { + public: + static FPlayerControllerManager& Get(); + + USS_NON_COPYABLE(FPlayerControllerManager) + USS_NON_MOVABLE(FPlayerControllerManager) + + // Lifecycle + EResult Initialize(); + void Shutdown(); + void Update(); + + // Player management + FSTWPlayerController* RegisterPlayer(void* Controller); + void UnregisterPlayer(void* Controller); + FSTWPlayerController* GetPlayer(void* Controller) const; + FSTWPlayerController* GetPlayerById(const char* PlayerId) const; + + // Iteration + int32 GetPlayerCount() const { return static_cast(m_Players.size()); } + FSTWPlayerController* GetPlayerByIndex(int32 Index) const; + + template + void ForEachPlayer(Callback&& Func) const + { + for (const auto& Pair : m_Players) + { + if (Pair.second) + { + if (!Func(Pair.second.get())) + break; + } + } + } + + // Team queries + std::vector GetPlayersOnTeam(int32 TeamIndex) const; + int32 GetReadyPlayerCount() const; + bool AreAllPlayersReady() const; + + // Events + void OnPlayerJoined(void* Controller); + void OnPlayerLeft(void* Controller); + + private: + FPlayerControllerManager() = default; + ~FPlayerControllerManager() = default; + + std::unordered_map> m_Players; + }; + + inline FPlayerControllerManager& GetPlayerControllerManager() + { + return FPlayerControllerManager::Get(); + } + +} \ No newline at end of file diff --git a/STW/Player/STWPlayerPawn.cpp b/STW/Player/STWPlayerPawn.cpp new file mode 100644 index 0000000..4577f2f --- /dev/null +++ b/STW/Player/STWPlayerPawn.cpp @@ -0,0 +1,400 @@ +/** + * UniversalSlashingSimulator - STW Player Pawn Implementation + */ + +#include "STWPlayerPawn.h" +#include "../../Core/Logging/Log.h" + +namespace USS +{ + FSTWPlayerPawn::FSTWPlayerPawn() + : m_State(EPawnState::None) + , m_CurrentHealth(100.0f) + , m_CurrentShield(0.0f) + , m_HeroClass(EHeroClass::Soldier) + , m_PowerLevel(1) + , m_bIsSprinting(false) + , m_bIsJumping(false) + , m_bIsCrouching(false) + , m_bIsAiming(false) + , m_bIsFiring(false) + , m_DBNOTimer(0.0f) + , m_DBNOMaxTime(20.0f) + , m_LocationX(0.0f) + , m_LocationY(0.0f) + , m_LocationZ(0.0f) + , m_RotationPitch(0.0f) + , m_RotationYaw(0.0f) + , m_RotationRoll(0.0f) + { + } + + FSTWPlayerPawn::FSTWPlayerPawn(void* InPawn) + : FSTWPlayerPawn() + { + m_Pawn = UObjectWrapper(InPawn); + + if (IsValid()) + { + m_State = EPawnState::Alive; + UpdateFromNative(); + } + } + + FSTWPlayerPawn::~FSTWPlayerPawn() + { + } + + bool FSTWPlayerPawn::IsValid() const + { + return m_Pawn.IsValid(); + } + + void FSTWPlayerPawn::Update() + { + if (!IsValid()) + return; + + // Update cached values from engine + UpdateFromNative(); + + // Update ability cooldowns + float DeltaTime = 1.0f / 30.0f; // Placeholder + UpdateAbilityCooldowns(DeltaTime); + + // Update DBNO timer + if (m_State == EPawnState::DBNO) + { + m_DBNOTimer -= DeltaTime; + if (m_DBNOTimer <= 0.0f) + { + Die(); + } + } + } + + float FSTWPlayerPawn::GetHealthPercent() const + { + if (m_Stats.MaxHealth <= 0.0f) + return 0.0f; + return m_CurrentHealth / m_Stats.MaxHealth; + } + + float FSTWPlayerPawn::GetShieldPercent() const + { + if (m_Stats.MaxShield <= 0.0f) + return 0.0f; + return m_CurrentShield / m_Stats.MaxShield; + } + + void FSTWPlayerPawn::SetHealth(float Health) + { + m_CurrentHealth = Health; + + if (m_CurrentHealth < 0.0f) + m_CurrentHealth = 0.0f; + if (m_CurrentHealth > m_Stats.MaxHealth) + m_CurrentHealth = m_Stats.MaxHealth; + + UpdateState(); + } + + void FSTWPlayerPawn::SetShield(float Shield) + { + m_CurrentShield = Shield; + + if (m_CurrentShield < 0.0f) + m_CurrentShield = 0.0f; + if (m_CurrentShield > m_Stats.MaxShield) + m_CurrentShield = m_Stats.MaxShield; + } + + void FSTWPlayerPawn::ApplyDamage(float Damage, void* DamageCauser) + { + if (m_State != EPawnState::Alive) + return; + + float RemainingDamage = Damage; + + // Shield absorbs first + if (m_CurrentShield > 0.0f) + { + float ShieldDamage = (RemainingDamage < m_CurrentShield) ? RemainingDamage : m_CurrentShield; + m_CurrentShield -= ShieldDamage; + RemainingDamage -= ShieldDamage; + } + + // Then health + if (RemainingDamage > 0.0f) + { + m_CurrentHealth -= RemainingDamage; + } + + OnDamageReceived(Damage, DamageCauser); + UpdateState(); + } + + void FSTWPlayerPawn::Heal(float Amount) + { + if (m_State != EPawnState::Alive && m_State != EPawnState::DBNO) + return; + + m_CurrentHealth += Amount; + if (m_CurrentHealth > m_Stats.MaxHealth) + m_CurrentHealth = m_Stats.MaxHealth; + + USS_LOG("Healed for %.1f, health: %.1f", Amount, m_CurrentHealth); + } + + void FSTWPlayerPawn::AddShield(float Amount) + { + m_CurrentShield += Amount; + if (m_CurrentShield > m_Stats.MaxShield) + m_CurrentShield = m_Stats.MaxShield; + + USS_LOG("Added shield %.1f, total: %.1f", Amount, m_CurrentShield); + } + + void FSTWPlayerPawn::SetHeroClass(EHeroClass HeroClass) + { + m_HeroClass = HeroClass; + + // Apply default stats based on hero class + switch (HeroClass) + { + case EHeroClass::Soldier: + m_Stats.WeaponDamageMultiplier = 1.1f; + m_Stats.AbilityDamageMultiplier = 1.0f; + break; + + case EHeroClass::Constructor: + m_Stats.BuildingSpeedMultiplier = 1.2f; + m_Stats.TrapDamageMultiplier = 1.1f; + m_Stats.MaxHealth = 120.0f; + break; + + case EHeroClass::Ninja: + m_Stats.MoveSpeed = 660.0f; + m_Stats.SprintSpeed = 935.0f; + m_Stats.AbilityDamageMultiplier = 1.1f; + m_Stats.MaxHealth = 80.0f; + break; + + case EHeroClass::Outlander: + m_Stats.HarvestingEfficiency = 1.24f; + m_Stats.MoveSpeed = 620.0f; + break; + } + } + + void FSTWPlayerPawn::SetPowerLevel(int32 Level) + { + m_PowerLevel = Level; + // TODO: Scale stats based on power level + } + + void FSTWPlayerPawn::ApplyHeroStats(const FHeroStats& Stats) + { + m_Stats = Stats; + } + + const FAbilityInfo* FSTWPlayerPawn::GetAbility(int32 Index) const + { + if (Index >= 0 && Index < 3) + { + return &m_Abilities[Index]; + } + return nullptr; + } + + bool FSTWPlayerPawn::CanActivateAbility(int32 Index) const + { + if (Index < 0 || Index >= 3) + return false; + + const FAbilityInfo& Ability = m_Abilities[Index]; + return !Ability.bIsActive && !Ability.bIsOnCooldown && IsAlive(); + } + + void FSTWPlayerPawn::ActivateAbility(int32 Index) + { + if (!CanActivateAbility(Index)) + return; + + FAbilityInfo& Ability = m_Abilities[Index]; + Ability.bIsActive = true; + + USS_LOG("Activated ability %d: %s", Index, Ability.AbilityName.c_str()); + + // TODO: Actually trigger ability on engine side + } + + void FSTWPlayerPawn::DeactivateAbility(int32 Index) + { + if (Index < 0 || Index >= 3) + return; + + FAbilityInfo& Ability = m_Abilities[Index]; + + if (Ability.bIsActive) + { + Ability.bIsActive = false; + Ability.bIsOnCooldown = true; + Ability.CurrentCooldown = Ability.Cooldown; + + USS_LOG("Deactivated ability %d, cooldown: %.1fs", Index, Ability.Cooldown); + } + } + + void FSTWPlayerPawn::UpdateAbilityCooldowns(float DeltaTime) + { + for (int32 i = 0; i < 3; ++i) + { + FAbilityInfo& Ability = m_Abilities[i]; + + if (Ability.bIsOnCooldown) + { + Ability.CurrentCooldown -= DeltaTime; + + if (Ability.CurrentCooldown <= 0.0f) + { + Ability.CurrentCooldown = 0.0f; + Ability.bIsOnCooldown = false; + } + } + } + } + + void FSTWPlayerPawn::GetLocation(float& OutX, float& OutY, float& OutZ) const + { + OutX = m_LocationX; + OutY = m_LocationY; + OutZ = m_LocationZ; + } + + void FSTWPlayerPawn::GetRotation(float& OutPitch, float& OutYaw, float& OutRoll) const + { + OutPitch = m_RotationPitch; + OutYaw = m_RotationYaw; + OutRoll = m_RotationRoll; + } + + void FSTWPlayerPawn::SetLocation(float X, float Y, float Z) + { + m_LocationX = X; + m_LocationY = Y; + m_LocationZ = Z; + + // TODO: Actually set location on engine pawn + } + + void FSTWPlayerPawn::SetRotation(float Pitch, float Yaw, float Roll) + { + m_RotationPitch = Pitch; + m_RotationYaw = Yaw; + m_RotationRoll = Roll; + + // TODO: Actually set rotation on engine pawn + } + + void* FSTWPlayerPawn::GetCurrentWeapon() const + { + // TODO: Read from pawn's CurrentWeapon + return nullptr; + } + + void FSTWPlayerPawn::EquipWeapon(int32 Slot) + { + USS_LOG("Equipping weapon slot %d", Slot); + // TODO: Trigger weapon equip + } + + void FSTWPlayerPawn::EnterDBNO() + { + if (m_State != EPawnState::Alive) + return; + + m_State = EPawnState::DBNO; + m_DBNOTimer = m_DBNOMaxTime; + + USS_LOG("Player entered DBNO state"); + } + + void FSTWPlayerPawn::ReviveFromDBNO(void* Reviver) + { + if (m_State != EPawnState::DBNO) + return; + + m_State = EPawnState::Alive; + m_CurrentHealth = m_Stats.MaxHealth * 0.3f; // Revive with 30% health + + OnRevived(Reviver); + USS_LOG("Player revived from DBNO"); + } + + void FSTWPlayerPawn::Die() + { + if (m_State == EPawnState::Dead) + return; + + EPawnState OldState = m_State; + m_State = EPawnState::Dead; + m_CurrentHealth = 0.0f; + + OnDeath(nullptr); + USS_LOG("Player died (was in state: %d)", static_cast(OldState)); + } + + void FSTWPlayerPawn::OnDamageReceived(float Damage, void* DamageCauser) + { + USS_LOG("Damage received: %.1f, health: %.1f, shield: %.1f", + Damage, m_CurrentHealth, m_CurrentShield); + } + + void FSTWPlayerPawn::OnDeath(void* Killer) + { + USS_LOG("Player death event"); + } + + void FSTWPlayerPawn::OnRevived(void* Reviver) + { + USS_LOG("Player revived event"); + } + + void FSTWPlayerPawn::OnProcessEvent(void* Function, void* Params) + { + // Handle pawn-specific events + } + + void FSTWPlayerPawn::UpdateFromNative() + { + if (!IsValid()) + return; + + // TODO: Read actual values from engine pawn + // m_CurrentHealth = Read health from pawn + // m_CurrentShield = Read shield from pawn + // Read location/rotation + // Read movement state + } + + void FSTWPlayerPawn::UpdateState() + { + if (m_State == EPawnState::Dead) + return; + + if (m_CurrentHealth <= 0.0f) + { + if (m_State == EPawnState::Alive) + { + // In STW, players go DBNO first + EnterDBNO(); + } + else if (m_State == EPawnState::DBNO) + { + Die(); + } + } + } + +} \ No newline at end of file diff --git a/STW/Player/STWPlayerPawn.h b/STW/Player/STWPlayerPawn.h new file mode 100644 index 0000000..b6b2b99 --- /dev/null +++ b/STW/Player/STWPlayerPawn.h @@ -0,0 +1,209 @@ +/** + * UniversalSlashingSimulator - STW Player Pawn + * + * Wrapper for AFortPlayerPawn with STW-specific functionality. + * Handles hero abilities, stats, and player-specific game logic. + */ + +#pragma once + +#include "../../Core/Common.h" +#include "../../Engine/UObject/UObjectWrapper.h" +#include + +namespace USS +{ + // Hero class types + enum class EHeroClass : uint8 + { + Soldier, + Constructor, + Ninja, + Outlander + }; + + // Pawn state + enum class EPawnState : uint8 + { + None, + Alive, + DBNO, // Down But Not Out + Dead, + Spectating + }; + + // Hero stats structure + struct FHeroStats + { + float MaxHealth; + float MaxShield; + float HealthRegenRate; + float ShieldRegenRate; + float MoveSpeed; + float SprintSpeed; + float JumpHeight; + float AbilityDamageMultiplier; + float WeaponDamageMultiplier; + float BuildingSpeedMultiplier; + float HarvestingEfficiency; + float TrapDamageMultiplier; + + FHeroStats() + : MaxHealth(100.0f) + , MaxShield(100.0f) + , HealthRegenRate(0.0f) + , ShieldRegenRate(5.0f) + , MoveSpeed(600.0f) + , SprintSpeed(850.0f) + , JumpHeight(400.0f) + , AbilityDamageMultiplier(1.0f) + , WeaponDamageMultiplier(1.0f) + , BuildingSpeedMultiplier(1.0f) + , HarvestingEfficiency(1.0f) + , TrapDamageMultiplier(1.0f) + {} + }; + + // Hero ability info + struct FAbilityInfo + { + std::string AbilityName; + std::string AbilityClass; // UClass name + float Cooldown; + float CurrentCooldown; + int32 AbilityIndex; + bool bIsActive; + bool bIsOnCooldown; + + FAbilityInfo() + : Cooldown(0.0f) + , CurrentCooldown(0.0f) + , AbilityIndex(-1) + , bIsActive(false) + , bIsOnCooldown(false) + {} + }; + + // STW Player Pawn wrapper + class FSTWPlayerPawn + { + public: + FSTWPlayerPawn(); + explicit FSTWPlayerPawn(void* InPawn); + ~FSTWPlayerPawn(); + + USS_NON_COPYABLE(FSTWPlayerPawn) + + // Validity + bool IsValid() const; + void* GetNative() const { return m_Pawn.GetRaw(); } + + // Update + void Update(); + + // State + EPawnState GetState() const { return m_State; } + bool IsAlive() const { return m_State == EPawnState::Alive; } + bool IsDBNO() const { return m_State == EPawnState::DBNO; } + bool IsDead() const { return m_State == EPawnState::Dead; } + + // Health/Shield + float GetHealth() const { return m_CurrentHealth; } + float GetMaxHealth() const { return m_Stats.MaxHealth; } + float GetHealthPercent() const; + float GetShield() const { return m_CurrentShield; } + float GetMaxShield() const { return m_Stats.MaxShield; } + float GetShieldPercent() const; + + void SetHealth(float Health); + void SetShield(float Shield); + void ApplyDamage(float Damage, void* DamageCauser = nullptr); + void Heal(float Amount); + void AddShield(float Amount); + + // Hero info + EHeroClass GetHeroClass() const { return m_HeroClass; } + const std::string& GetHeroName() const { return m_HeroName; } + const FHeroStats& GetStats() const { return m_Stats; } + int32 GetPowerLevel() const { return m_PowerLevel; } + + void SetHeroClass(EHeroClass HeroClass); + void SetPowerLevel(int32 Level); + void ApplyHeroStats(const FHeroStats& Stats); + + // Abilities + int32 GetAbilityCount() const { return 3; } // STW heroes have 3 abilities + const FAbilityInfo* GetAbility(int32 Index) const; + bool CanActivateAbility(int32 Index) const; + void ActivateAbility(int32 Index); + void DeactivateAbility(int32 Index); + void UpdateAbilityCooldowns(float DeltaTime); + + // Position/Movement + void GetLocation(float& OutX, float& OutY, float& OutZ) const; + void GetRotation(float& OutPitch, float& OutYaw, float& OutRoll) const; + void SetLocation(float X, float Y, float Z); + void SetRotation(float Pitch, float Yaw, float Roll); + + bool IsSprinting() const { return m_bIsSprinting; } + bool IsJumping() const { return m_bIsJumping; } + bool IsCrouching() const { return m_bIsCrouching; } + + // Combat + void* GetCurrentWeapon() const; + void EquipWeapon(int32 Slot); + bool IsAiming() const { return m_bIsAiming; } + bool IsFiring() const { return m_bIsFiring; } + + // DBNO (Down But Not Out) system + float GetDBNOTimer() const { return m_DBNOTimer; } + float GetDBNOMaxTime() const { return m_DBNOMaxTime; } + void EnterDBNO(); + void ReviveFromDBNO(void* Reviver = nullptr); + void Die(); + + // Events + void OnDamageReceived(float Damage, void* DamageCauser); + void OnDeath(void* Killer); + void OnRevived(void* Reviver); + void OnProcessEvent(void* Function, void* Params); + + private: + void UpdateFromNative(); + void UpdateState(); + + UObjectWrapper m_Pawn; // AFortPlayerPawn* + + // State + EPawnState m_State; + float m_CurrentHealth; + float m_CurrentShield; + + // Hero info + EHeroClass m_HeroClass; + std::string m_HeroName; + FHeroStats m_Stats; + int32 m_PowerLevel; + + // Abilities + FAbilityInfo m_Abilities[3]; + + // Movement state + bool m_bIsSprinting; + bool m_bIsJumping; + bool m_bIsCrouching; + + // Combat state + bool m_bIsAiming; + bool m_bIsFiring; + + // DBNO + float m_DBNOTimer; + float m_DBNOMaxTime; + + // Cached position (updated each frame) + float m_LocationX, m_LocationY, m_LocationZ; + float m_RotationPitch, m_RotationYaw, m_RotationRoll; + }; + +} \ No newline at end of file diff --git a/UniversalSlashingSimulator.sln b/UniversalSlashingSimulator.sln new file mode 100644 index 0000000..3a99417 --- /dev/null +++ b/UniversalSlashingSimulator.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.10.0.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UniversalSlashingSimulator", "UniversalSlashingSimulator.vcxproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|x64 + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|x64 + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|x64 + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {12345678-9ABC-DEF0-1234-567890ABCDEF} + EndGlobalSection +EndGlobal diff --git a/UniversalSlashingSimulator.vcxproj b/UniversalSlashingSimulator.vcxproj new file mode 100644 index 0000000..3f92ffe --- /dev/null +++ b/UniversalSlashingSimulator.vcxproj @@ -0,0 +1,168 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} + UniversalSlashingSimulator + 10.0 + UniversalSlashingSimulator + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)obj\$(Configuration)\ + USS + .dll + + + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)obj\$(Configuration)\ + USS + .dll + + + + + Level4 + true + WIN32_LEAN_AND_MEAN;NOMINMAX;_CRT_SECURE_NO_WARNINGS;USS_DEBUG;_DEBUG;_WINDOWS;_USRDLL;USS_EXPORTS;%(PreprocessorDefinitions) + true + stdcpp17 + $(ProjectDir);$(ProjectDir)external\minhook\include;$(ProjectDir)external\memcury\include;%(AdditionalIncludeDirectories) + true + MultiThreadedDebugDLL + ProgramDatabase + Disabled + + + Windows + true + psapi.lib;%(AdditionalDependencies) + $(ProjectDir)external\minhook\lib;%(AdditionalLibraryDirectories) + + + + + + Level4 + true + true + true + WIN32_LEAN_AND_MEAN;NOMINMAX;_CRT_SECURE_NO_WARNINGS;NDEBUG;_WINDOWS;_USRDLL;USS_EXPORTS;%(PreprocessorDefinitions) + true + stdcpp17 + $(ProjectDir);$(ProjectDir)external\minhook\include;$(ProjectDir)external\memcury\include;%(AdditionalIncludeDirectories) + true + MultiThreadedDLL + MaxSpeed + AnySuitable + true + + + Windows + true + true + true + psapi.lib;%(AdditionalDependencies) + $(ProjectDir)external\minhook\lib;%(AdditionalLibraryDirectories) + UseLinkTimeCodeGeneration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UniversalSlashingSimulator.vcxproj.filters b/UniversalSlashingSimulator.vcxproj.filters new file mode 100644 index 0000000..cc95aba --- /dev/null +++ b/UniversalSlashingSimulator.vcxproj.filters @@ -0,0 +1,212 @@ + + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + + + {A3C2E5D1-9B8F-4C7E-B6A5-D4F3E2C1B0A9} + + + {B4D3C2E1-0A9F-5D8E-C7B6-E5F4D3C2B1A0} + + + {C5E4D3F2-1B0A-6E9F-D8C7-F6E5D4C3B2A1} + + + {D6F5E4G3-2C1B-7F0A-E9D8-G7F6E5D4C3B2} + + + {E7G6F5H4-3D2C-8G1B-F0E9-H8G7F6E5D4C3} + + + {F8H7G6I5-4E3D-9H2C-G1F0-I9H8G7F6E5D4} + + + {A9I8H7J6-5F4E-0I3D-H2G1-J0I9H8G7F6E5} + + + {B0J9I8K7-6G5F-1J4E-I3H2-K1J0I9H8G7F6} + + + {C1K0J9L8-7H6G-2K5F-J4I3-L2K1J0I9H8G7} + + + {D2L1K0M9-8I7H-3L6G-K5J4-M3L2K1J0I9H8} + + + {E3M2L1N0-9J8I-4M7H-L6K5-N4M3L2K1J0I9} + + + {F4N3M2O1-0K9J-5N8I-M7L6-O5N4M3L2K1J0} + + + {G5O4N3P2-1L0K-6O9J-N8M7-P6O5N4M3L2K1} + + + {H6P5O4Q3-2M1L-7P0K-O9N8-Q7P6O5N4M3L2} + + + {I7Q6P5R4-3N2M-8Q1L-P0O9-R8Q7P6O5N4M3} + + + + + + + + Core\Logging + + + Core\Memory + + + Core\Versioning + + + + + Engine\CoreTypes + + + Engine\CoreTypes + + + Engine\CoreTypes + + + Engine\UObject + + + Engine\Reflection + + + Engine\Replication + + + Engine\Events + + + Engine + + + + + STW\GameMode + + + STW\Missions + + + STW\Missions + + + STW\Player + + + STW\Player + + + STW\Inventory + + + STW\Building + + + + + Entry + + + + + + + + Core + + + Core\Logging + + + Core\Memory + + + Core\Memory + + + Core\Versioning + + + Core\Versioning + + + Core\Hooks + + + + + Engine\CoreTypes + + + Engine\CoreTypes + + + Engine\CoreTypes + + + Engine\UObject + + + Engine\Reflection + + + Engine\Replication + + + Engine\Events + + + Engine + + + + + STW\GameMode + + + STW\Missions + + + STW\Missions + + + STW\Missions + + + STW\Player + + + STW\Player + + + STW\Inventory + + + STW\Inventory + + + STW\Building + + + STW\Building + + + diff --git a/UniversalSlashingSimulator.vcxproj.user b/UniversalSlashingSimulator.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/UniversalSlashingSimulator.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/external/memcury/memcury.h b/external/memcury/memcury.h new file mode 100644 index 0000000..347fd7b --- /dev/null +++ b/external/memcury/memcury.h @@ -0,0 +1,1217 @@ +#pragma once + +/* + Memcury is a single-header file library for memory manipulation in C++. + + Containers: + -PE::Address: A pointer container. + -PE::Section: Portable executable section container for internal usage. + + Modules: + -Scanner: + -Constructors: + -Default: Takes a pointer to start the scanning from. + -FindPattern: Finds a pattern in memory. + -FindStringRef: Finds a string reference in memory, supports all types of strings. + -Functions: + -SetTargetModule: Sets the target module for the scanner. + -ScanFor: Scans for a byte(s) near the current address. + -FindFunctionBoundary: Finds the boundary of a function near the current address. + -RelativeOffset: Gets the relative offset of the current address. + -AbsoluteOffset: Gets the absolute offset of the current address. + -GetAs: Gets the current address as a type. + -Get: Gets the current address as an int64. + + -TrampolineHook: + -Constructors: + -Default: Takes a pointer pointer to the target function and a pointer to the hook function. + -Functions: + -Commit: Commits the hook. + -Revert: Reverts the hook. + -Toggle: Toggles the hook on\off. + + -VEHHook: + -Functions: + -Init: Initializes the VEH Hook system. + -AddHook: Adds a hook to the VEH Hook system. + -RemoveHook: Removes a hook from the VEH Hook system. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma comment(lib, "Dbghelp.lib") + +#undef min +#undef max + +#define MemcuryAssert(cond) \ + if (!(cond)) \ + { \ + MessageBoxA(nullptr, #cond, __FUNCTION__, MB_ICONERROR | MB_OK); \ + Memcury::Safety::FreezeCurrentThread(); \ + } + +#define MemcuryAssertM(cond, msg) \ + if (!(cond)) \ + { \ + MessageBoxA(nullptr, msg, __FUNCTION__, MB_ICONERROR | MB_OK); \ + Memcury::Safety::FreezeCurrentThread(); \ + } + +#define MemcuryThrow(msg) \ + MessageBoxA(nullptr, msg, __FUNCTION__, MB_ICONERROR | MB_OK); \ + Memcury::Safety::FreezeCurrentThread(); + +namespace Memcury +{ + extern "C" IMAGE_DOS_HEADER __ImageBase; + + inline auto GetCurrentModule() -> HMODULE + { + return reinterpret_cast(&__ImageBase); + } + + namespace Util + { + template + constexpr static auto IsInRange(T value, T min, T max) -> bool + { + return value >= min && value < max; + } + + constexpr auto StrHash(const char* str, int h = 0) -> unsigned int + { + return !str[h] ? 5381 : (StrHash(str, h + 1) * 33) ^ str[h]; + } + + inline auto IsSamePage(void* A, void* B) -> bool + { + MEMORY_BASIC_INFORMATION InfoA; + if (!VirtualQuery(A, &InfoA, sizeof(InfoA))) + { + return true; + } + + MEMORY_BASIC_INFORMATION InfoB; + if (!VirtualQuery(B, &InfoB, sizeof(InfoB))) + { + return true; + } + + return InfoA.BaseAddress == InfoB.BaseAddress; + } + + inline auto GetModuleStartAndEnd() -> std::pair + { + auto HModule = GetCurrentModule(); + auto NTHeaders = reinterpret_cast((uintptr_t)HModule + reinterpret_cast((uintptr_t)HModule)->e_lfanew); + + uintptr_t dllStart = (uintptr_t)HModule; + uintptr_t dllEnd = (uintptr_t)HModule + NTHeaders->OptionalHeader.SizeOfImage; + + return { dllStart, dllEnd }; + } + + inline auto CopyToClipboard(std::string str) + { + auto mem = GlobalAlloc(GMEM_FIXED, str.size() + 1); + memcpy(mem, str.c_str(), str.size() + 1); + + OpenClipboard(nullptr); + EmptyClipboard(); + SetClipboardData(CF_TEXT, mem); + CloseClipboard(); + + GlobalFree(mem); + } + } + + namespace Safety + { + enum class ExceptionMode + { + None, + CatchDllExceptionsOnly, + CatchAllExceptions + }; + + static auto FreezeCurrentThread() -> void + { + SuspendThread(GetCurrentThread()); + } + + static auto PrintStack(CONTEXT* ctx) -> void + { + STACKFRAME64 stack; + memset(&stack, 0, sizeof(STACKFRAME64)); + + auto process = GetCurrentProcess(); + auto thread = GetCurrentThread(); + + SymInitialize(process, NULL, TRUE); + + bool result; + DWORD64 displacement = 0; + + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)] { 0 }; + char name[256] { 0 }; + char module[256] { 0 }; + + PSYMBOL_INFO symbolInfo = (PSYMBOL_INFO)buffer; + + for (ULONG frame = 0;; frame++) + { + result = StackWalk64( + IMAGE_FILE_MACHINE_AMD64, + process, + thread, + &stack, + ctx, + NULL, + SymFunctionTableAccess64, + SymGetModuleBase64, + NULL); + + if (!result) + break; + + symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFO); + symbolInfo->MaxNameLen = MAX_SYM_NAME; + SymFromAddr(process, (ULONG64)stack.AddrPC.Offset, &displacement, symbolInfo); + + HMODULE hModule = NULL; + lstrcpyA(module, ""); + GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (const wchar_t*)(stack.AddrPC.Offset), &hModule); + + if (hModule != NULL) + GetModuleFileNameA(hModule, module, 256); + + printf("[%lu] Name: %s - Address: %p - Module: %s\n", frame, symbolInfo->Name, (void*)symbolInfo->Address, module); + } + } + + template + auto MemcuryGlobalHandler(EXCEPTION_POINTERS* ExceptionInfo) -> long + { + auto [dllStart, dllEnd] = Util::GetModuleStartAndEnd(); + + if constexpr (mode == ExceptionMode::CatchDllExceptionsOnly) + { + if (!Util::IsInRange(ExceptionInfo->ContextRecord->Rip, dllStart, dllEnd)) + { + return EXCEPTION_CONTINUE_SEARCH; + } + } + + auto message = std::format("Memcury caught an exception at [{:x}]\nPress Yes if you want the address to be copied to your clipboard", ExceptionInfo->ContextRecord->Rip); + if (MessageBoxA(nullptr, message.c_str(), "Error", MB_ICONERROR | MB_YESNO) == IDYES) + { + std::string clip = std::format("{:x}", ExceptionInfo->ContextRecord->Rip); + Util::CopyToClipboard(clip); + } + + PrintStack(ExceptionInfo->ContextRecord); + + FreezeCurrentThread(); + + return EXCEPTION_EXECUTE_HANDLER; + } + + template + static auto SetExceptionMode() -> void + { + SetUnhandledExceptionFilter(MemcuryGlobalHandler); + } + } + + namespace Globals + { + constexpr const bool bLogging = true; + + inline const char* moduleName = nullptr; + } + + namespace ASM + { + //@todo: this whole namespace needs a rework, should somehow make this more modern and less ugly. + enum MNEMONIC : uint8_t + { + JMP_REL8 = 0xEB, + JMP_REL32 = 0xE9, + JMP_EAX = 0xE0, + CALL = 0xE8, + LEA = 0x8D, + CDQ = 0x99, + CMOVL = 0x4C, + CMOVS = 0x48, + CMOVNS = 0x49, + NOP = 0x90, + INT3 = 0xCC, + RETN_REL8 = 0xC2, + RETN = 0xC3, + NONE = 0x00 + }; + + constexpr int SIZE_OF_JMP_RELATIVE_INSTRUCTION = 5; + constexpr int SIZE_OF_JMP_ABSLOUTE_INSTRUCTION = 13; + + constexpr auto MnemonicToString(MNEMONIC e) -> const char* + { + switch (e) + { + case JMP_REL8: + return "JMP_REL8"; + case JMP_REL32: + return "JMP_REL32"; + case JMP_EAX: + return "JMP_EAX"; + case CALL: + return "CALL"; + case LEA: + return "LEA"; + case CDQ: + return "CDQ"; + case CMOVL: + return "CMOVL"; + case CMOVS: + return "CMOVS"; + case CMOVNS: + return "CMOVNS"; + case NOP: + return "NOP"; + case INT3: + return "INT3"; + case RETN_REL8: + return "RETN_REL8"; + case RETN: + return "RETN"; + case NONE: + return "NONE"; + default: + return "UNKNOWN"; + } + } + + constexpr auto Mnemonic(const char* s) -> MNEMONIC + { + switch (Util::StrHash(s)) + { + case Util::StrHash("JMP_REL8"): + return JMP_REL8; + case Util::StrHash("JMP_REL32"): + return JMP_REL32; + case Util::StrHash("JMP_EAX"): + return JMP_EAX; + case Util::StrHash("CALL"): + return CALL; + case Util::StrHash("LEA"): + return LEA; + case Util::StrHash("CDQ"): + return CDQ; + case Util::StrHash("CMOVL"): + return CMOVL; + case Util::StrHash("CMOVS"): + return CMOVS; + case Util::StrHash("CMOVNS"): + return CMOVNS; + case Util::StrHash("NOP"): + return NOP; + case Util::StrHash("INT3"): + return INT3; + case Util::StrHash("RETN_REL8"): + return RETN_REL8; + case Util::StrHash("RETN"): + return RETN; + default: + return NONE; + } + } + + inline auto byteIsA(uint8_t byte, MNEMONIC opcode) -> bool + { + return byte == opcode; + } + + inline auto byteIsAscii(uint8_t byte) -> bool + { + static constexpr bool isAscii[0x100] = { + false, false, false, false, false, false, false, false, false, true, true, false, false, true, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false + }; + + return isAscii[byte]; + } + + inline bool isJump(uint8_t byte) + { + return byte >= 0x70 && byte <= 0x7F; + } + + static auto pattern2bytes(const char* pattern) -> std::vector + { + auto bytes = std::vector {}; + const auto start = const_cast(pattern); + const auto end = const_cast(pattern) + strlen(pattern); + + for (auto current = start; current < end; ++current) + { + if (*current == '?') + { + ++current; + if (*current == '?') + ++current; + bytes.push_back(-1); + } + else + { + bytes.push_back(strtoul(current, ¤t, 16)); + } + } + return bytes; + } + } + + namespace PE + { + inline auto SetCurrentModule(const char* moduleName) -> void + { + Globals::moduleName = moduleName; + } + + inline auto GetModuleBase() -> uintptr_t + { + return reinterpret_cast(GetModuleHandleA(Globals::moduleName)); + } + + inline auto GetDOSHeader() -> PIMAGE_DOS_HEADER + { + return reinterpret_cast(GetModuleBase()); + } + + inline auto GetNTHeaders() -> PIMAGE_NT_HEADERS + { + return reinterpret_cast(GetModuleBase() + GetDOSHeader()->e_lfanew); + } + + class Address + { + uintptr_t _address; + + public: + Address() + { + _address = 0; + } + + Address(uintptr_t address) + : _address(address) + { + } + + Address(void* address) + : _address(reinterpret_cast(address)) + { + } + + auto operator=(uintptr_t address) -> Address + { + _address = address; + return *this; + } + + auto operator=(void* address) -> Address + { + _address = reinterpret_cast(address); + return *this; + } + + auto operator+(uintptr_t offset) -> Address + { + return Address(_address + offset); + } + + bool operator>(uintptr_t offset) + { + return _address > offset; + } + + bool operator>(Address address) + { + return _address > address._address; + } + + bool operator<(uintptr_t offset) + { + return _address < offset; + } + + bool operator<(Address address) + { + return _address < address._address; + } + + bool operator>=(uintptr_t offset) + { + return _address >= offset; + } + + bool operator>=(Address address) + { + return _address >= address._address; + } + + bool operator<=(uintptr_t offset) + { + return _address <= offset; + } + + bool operator<=(Address address) + { + return _address <= address._address; + } + + bool operator==(uintptr_t offset) + { + return _address == offset; + } + + bool operator==(Address address) + { + return _address == address._address; + } + + bool operator!=(uintptr_t offset) + { + return _address != offset; + } + + bool operator!=(Address address) + { + return _address != address._address; + } + + auto RelativeOffset(uint32_t offset) -> Address + { + _address = ((_address + offset + 4) + *(int32_t*)(_address + offset)); + return *this; + } + + auto AbsoluteOffset(uint32_t offset) -> Address + { + _address = _address + offset; + return *this; + } + + auto Jump() -> Address + { + if (ASM::isJump(*reinterpret_cast(_address))) + { + UINT8 toSkip = *reinterpret_cast(_address + 1); + _address = _address + 2 + toSkip; + } + + return *this; + } + + auto Get() -> uintptr_t + { + return _address; + } + + template + auto GetAs() -> T + { + return reinterpret_cast(_address); + } + + auto IsValid() -> bool + { + return _address != 0; + } + }; + + class Section + { + public: + std::string sectionName; + IMAGE_SECTION_HEADER rawSection; + + static auto GetAllSections() -> std::vector
+ { + std::vector
sections; + + auto sectionsSize = GetNTHeaders()->FileHeader.NumberOfSections; + auto section = IMAGE_FIRST_SECTION(GetNTHeaders()); + + for (WORD i = 0; i < sectionsSize; i++, section++) + { + auto secName = std::string((char*)section->Name); + + sections.push_back({ secName, *section }); + } + + return sections; + } + + static auto GetSection(std::string sectionName) -> Section + { + for (auto& section : GetAllSections()) + { + if (section.sectionName == sectionName) + { + return section; + } + } + + MemcuryThrow("Section not found"); + return Section {}; + } + + auto GetSectionSize() -> uint32_t + { + return rawSection.Misc.VirtualSize; + } + + auto GetSectionStart() -> Address + { + return Address(GetModuleBase() + rawSection.VirtualAddress); + } + + auto GetSectionEnd() -> Address + { + return Address(GetSectionStart() + GetSectionSize()); + } + + auto isInSection(Address address) -> bool + { + return address >= GetSectionStart() && address < GetSectionEnd(); + } + }; + } + + class Scanner + { + PE::Address _address; + + public: + Scanner(PE::Address address) + : _address(address) + { + } + + static auto SetTargetModule(const char* moduleName) -> void + { + PE::SetCurrentModule(moduleName); + } + + static auto FindPatternEx(HANDLE handle, const char* pattern, const char* mask, uint64_t begin, uint64_t end) -> Scanner + { + auto scan = [](const char* pattern, const char* mask, char* begin, unsigned int size) -> char* + { + size_t patternLen = strlen(mask); + for (unsigned int i = 0; i < size - patternLen; i++) + { + bool found = true; + for (unsigned int j = 0; j < patternLen; j++) + { + if (mask[j] != '?' && pattern[j] != *(begin + i + j)) + { + found = false; + break; + } + } + + if (found) + return (begin + i); + } + return nullptr; + }; + + uint64_t match = NULL; + SIZE_T bytesRead; + char* buffer = nullptr; + MEMORY_BASIC_INFORMATION mbi = { 0 }; + + uint64_t curr = begin; + + for (uint64_t curr = begin; curr < end; curr += mbi.RegionSize) + { + if (!VirtualQueryEx(handle, (void*)curr, &mbi, sizeof(mbi))) + continue; + + if (mbi.State != MEM_COMMIT || mbi.Protect == PAGE_NOACCESS) + continue; + + buffer = new char[mbi.RegionSize]; + + if (ReadProcessMemory(handle, mbi.BaseAddress, buffer, mbi.RegionSize, &bytesRead)) + { + char* internalAddr = scan(pattern, mask, buffer, (unsigned int)bytesRead); + + if (internalAddr != nullptr) + { + match = curr + (uint64_t)(internalAddr - buffer); + break; + } + } + } + delete[] buffer; + + MemcuryAssertM(match != 0, "FindPatternEx return nullptr"); + + return Scanner(match); + } + + static auto FindPatternEx(HANDLE handle, const char* sig) -> Scanner + { + char pattern[100]; + char mask[100]; + + char lastChar = ' '; + unsigned int j = 0; + + for (unsigned int i = 0; i < strlen(sig); i++) + { + if ((sig[i] == '?' || sig[i] == '*') && (lastChar != '?' && lastChar != '*')) + { + pattern[j] = mask[j] = '?'; + j++; + } + + else if (isspace(lastChar)) + { + pattern[j] = lastChar = (char)strtol(&sig[i], 0, 16); + mask[j] = 'x'; + j++; + } + lastChar = sig[i]; + } + pattern[j] = mask[j] = '\0'; + + auto module = (uint64_t)GetModuleHandle(nullptr); + + return FindPatternEx(handle, pattern, mask, module, module + Memcury::PE::GetNTHeaders()->OptionalHeader.SizeOfImage); + } + + static auto FindPattern(const char* signature) -> Scanner + { + PE::Address add { nullptr }; + + const auto sizeOfImage = PE::GetNTHeaders()->OptionalHeader.SizeOfImage; + auto patternBytes = ASM::pattern2bytes(signature); + const auto scanBytes = reinterpret_cast(PE::GetModuleBase()); + + const auto s = patternBytes.size(); + const auto d = patternBytes.data(); + + for (auto i = 0ul; i < sizeOfImage - s; ++i) + { + bool found = true; + for (auto j = 0ul; j < s; ++j) + { + if (scanBytes[i + j] != d[j] && d[j] != -1) + { + found = false; + break; + } + } + + if (found) + { + add = reinterpret_cast(&scanBytes[i]); + break; + } + } + + MemcuryAssertM(add != 0, "FindPattern return nullptr"); + + return Scanner(add); + } + + // Supports wide and normal strings both std and pointers + template + static auto FindStringRef(T string, bool find_first = false) -> Scanner + { + PE::Address add { nullptr }; + + constexpr auto bIsWide = std::is_same::value; + constexpr auto bIsChar = std::is_same::value; + + constexpr auto bIsPtr = bIsWide || bIsChar; + + auto textSection = PE::Section::GetSection(".text"); + auto rdataSection = PE::Section::GetSection(".rdata"); + + const auto scanBytes = reinterpret_cast(textSection.GetSectionStart().Get()); + + // scan only text section + for (DWORD i = 0x0; i < textSection.GetSectionSize(); i++) + { + if ((scanBytes[i] == ASM::CMOVL || scanBytes[i] == ASM::CMOVS) && scanBytes[i + 1] == ASM::LEA) + { + auto stringAdd = PE::Address(&scanBytes[i]).RelativeOffset(3); + + // Check if the string is in the .rdata section + if (rdataSection.isInSection(stringAdd)) + { + auto strBytes = stringAdd.GetAs(); + + // Check if the first char is printable + if (ASM::byteIsAscii(strBytes[0])) + { + if constexpr (!bIsPtr) + { + typedef T::value_type char_type; + + auto lea = stringAdd.GetAs(); + + T leaT(lea); + + if (leaT == string) + { + add = PE::Address(&scanBytes[i]); + if (find_first) + break; + } + } + else + { + auto lea = stringAdd.GetAs(); + + if constexpr (bIsWide) + { + if (wcscmp(string, lea) == 0) + { + add = PE::Address(&scanBytes[i]); + if (find_first) + break; + } + } + else + { + if (strcmp(string, lea) == 0) + { + add = PE::Address(&scanBytes[i]); + if (find_first) + break; + } + } + } + } + } + } + } + + MemcuryAssertM(add != 0, "FindStringRef return nullptr"); + + return Scanner(add); + } + + auto Jump() -> Scanner + { + _address.Jump(); + return *this; + } + + auto ScanFor(std::vector opcodesToFind, bool forward = true, int toSkip = 0) -> Scanner + { + const auto scanBytes = _address.GetAs(); + + for (auto i = (forward ? 1 : -1); forward ? (i < 2048) : (i > -2048); forward ? i++ : i--) + { + bool found = true; + + for (int k = 0; k < opcodesToFind.size() && found; k++) + { + if (opcodesToFind[k] == -1) + continue; + found = opcodesToFind[k] == scanBytes[i + k]; + } + + if (found) + { + _address = &scanBytes[i]; + if (toSkip != 0) + { + return ScanFor(opcodesToFind, forward, toSkip - 1); + } + + break; + } + } + + return *this; + } + + auto FindFunctionBoundary(bool forward = false) -> Scanner + { + const auto scanBytes = _address.GetAs(); + + for (auto i = (forward ? 1 : -1); forward ? (i < 2048) : (i > -2048); forward ? i++ : i--) + { + auto& byte = scanBytes[i]; + auto& next = forward ? scanBytes[i + 1] : scanBytes[i - 1]; + + if ( // ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::JMP_REL8) || + // ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::JMP_REL32) || + // ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::JMP_EAX) || + // ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::RETN_REL8) || + (ASM::byteIsA(byte, ASM::MNEMONIC::RETN) && ASM::byteIsA(next, ASM::MNEMONIC::INT3)) + || (ASM::byteIsA(byte, ASM::MNEMONIC::INT3) && ASM::byteIsA(next, ASM::MNEMONIC::INT3))) + { + _address = (uintptr_t)&scanBytes[i + 1]; + break; + } + } + + return *this; + } + + auto RelativeOffset(uint32_t offset) -> Scanner + { + _address.RelativeOffset(offset); + + return *this; + } + + auto AbsoluteOffset(uint32_t offset) -> Scanner + { + _address.AbsoluteOffset(offset); + + return *this; + } + + template + auto GetAs() -> T + { + return _address.GetAs(); + } + + auto Get() -> uintptr_t + { + return _address.Get(); + } + + auto IsValid() -> bool + { + return _address.IsValid(); + } + }; + + /* Bad don't use it tbh... */ + class TrampolineHook + { + void** originalFunctionPtr; + PE::Address originalFunction; + PE::Address hookFunction; + PE::Address allocatedPage; + std::vector restore; + + void PointToCodeIfNot(PE::Address& ptr) + { + auto bytes = ptr.GetAs(); + + if (ASM::byteIsA(bytes[0], ASM::MNEMONIC::JMP_REL32)) + { + ptr = bytes + 5 + *(int32_t*)&bytes[1]; + } + } + + void* AllocatePageNearAddress(void* targetAddr) + { + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + const uint64_t PAGE_SIZE = sysInfo.dwPageSize; + + uint64_t startAddr = (uint64_t(targetAddr) & ~(PAGE_SIZE - 1)); // round down to nearest page boundary + uint64_t minAddr = std::min(startAddr - 0x7FFFFF00, (uint64_t)sysInfo.lpMinimumApplicationAddress); + uint64_t maxAddr = std::max(startAddr + 0x7FFFFF00, (uint64_t)sysInfo.lpMaximumApplicationAddress); + + uint64_t startPage = (startAddr - (startAddr % PAGE_SIZE)); + + for (uint64_t pageOffset = 1; pageOffset; pageOffset++) + { + uint64_t byteOffset = pageOffset * PAGE_SIZE; + uint64_t highAddr = startPage + byteOffset; + uint64_t lowAddr = (startPage > byteOffset) ? startPage - byteOffset : 0; + + bool needsExit = highAddr > maxAddr && lowAddr < minAddr; + + if (highAddr < maxAddr) + { + void* outAddr = VirtualAlloc((void*)highAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (outAddr) + return outAddr; + } + + if (lowAddr > minAddr) + { + void* outAddr = VirtualAlloc((void*)lowAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (outAddr != nullptr) + return outAddr; + } + + if (needsExit) + { + break; + } + } + + return nullptr; + } + + void WriteAbsoluteJump(void* jumpLocation, void* destination) + { + uint8_t absJumpInstructions[] = { + ASM::Mnemonic("CMOVNS"), 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, addr + 0x41, 0xFF, 0xE2 // jmp r10 + }; + + auto destination64 = (uint64_t)destination; + memcpy(&absJumpInstructions[2], &destination64, sizeof(destination64)); + memcpy(jumpLocation, absJumpInstructions, sizeof(absJumpInstructions)); + } + + uintptr_t PrepareRestore() + { + /* + This is not a correct way to do it at all, since not all functions sub from the stack + This needs so much more tests, but it works for now. + */ + + Scanner scanner(originalFunction); + scanner.ScanFor({ 0x48, 0x83, 0xEC }); // sub rsp + + auto restoreSize = scanner.Get() - originalFunction.Get(); + + MemcuryAssert(restoreSize > 0 && restoreSize < 0x100); + + restore.reserve(restoreSize); + for (auto i = 0; i < restoreSize; i++) + { + restore.push_back(originalFunction.GetAs()[i]); + } + + return restoreSize; + } + + void WriteRestore() + { + auto restorePtr = allocatedPage + ASM::SIZE_OF_JMP_ABSLOUTE_INSTRUCTION + 2; + + memcpy(restorePtr.GetAs(), restore.data(), restore.size()); + + *originalFunctionPtr = restorePtr.GetAs(); + + // Write a jump back to where the execution should resume + restorePtr.AbsoluteOffset((uint32_t)restore.size()); + + auto contuineExecution = originalFunction + restore.size(); + + WriteAbsoluteJump(restorePtr.GetAs(), contuineExecution.GetAs()); + } + + auto PrepareJMPInstruction(uint64_t dst) + { + uint8_t bytes[5] = { ASM::Mnemonic("JMP_REL32"), 0x0, 0x0, 0x0, 0x0 }; + + const uint64_t relAddr = dst - (originalFunction.Get() + ASM::SIZE_OF_JMP_RELATIVE_INSTRUCTION); + memcpy(bytes + 1, &relAddr, 4); + + return std::move(bytes); + } + + bool IsHooked() + { + return originalFunction.GetAs()[0] == ASM::Mnemonic("JMP_REL32"); + } + + public: + TrampolineHook(void** originalFunction, void* hookFunction) + { + this->originalFunctionPtr = originalFunction; + + this->originalFunction = *originalFunction; + this->hookFunction = hookFunction; + + PointToCodeIfNot(this->originalFunction); + PointToCodeIfNot(this->hookFunction); + }; + + bool Commit() + { + auto fnStart = originalFunction.GetAs(); + + auto restoreSize = PrepareRestore(); + + if (!allocatedPage.IsValid()) + { + allocatedPage = AllocatePageNearAddress(fnStart); + } + + memset(allocatedPage.GetAs(), ASM::MNEMONIC::INT3, 0x1000); + + WriteAbsoluteJump(allocatedPage.GetAs(), hookFunction.GetAs()); + + DWORD oldProtect; + VirtualProtect(fnStart, 1024, PAGE_EXECUTE_READWRITE, &oldProtect); + + auto jmpInstruction = PrepareJMPInstruction(allocatedPage.Get()); + + WriteRestore(); + + memset(fnStart, ASM::MNEMONIC::INT3, restoreSize); + memcpy(fnStart, jmpInstruction, ASM::SIZE_OF_JMP_RELATIVE_INSTRUCTION); + + return true; + } + + bool Revert() + { + auto fnStart = originalFunction.GetAs(); + + DWORD oldProtect; + VirtualProtect(fnStart, 1024, PAGE_EXECUTE_READWRITE, &oldProtect); + + memcpy(fnStart, restore.data(), restore.size()); + + *originalFunctionPtr = originalFunction.GetAs(); + + // VirtualFree(allocatedPage.GetAs(), 0x1000, MEM_RELEASE); + + return true; + } + + auto Toggle() + { + if (IsHooked()) + Revert(); + else + Commit(); + + return IsHooked(); + } + }; + + namespace VEHHook + { + struct HOOK_INFO + { + void* Original; + void* Detour; + + HOOK_INFO(void* Original, void* Detour) + : Original(Original) + , Detour(Detour) + { + } + }; + + inline std::vector Hooks; + inline std::vector HookProtections; + inline HANDLE ExceptionHandler; + + inline long Handler(EXCEPTION_POINTERS* Exception) + { + if (Exception->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) + { + auto Itr = std::find_if(Hooks.begin(), Hooks.end(), [Rip = Exception->ContextRecord->Rip](const HOOK_INFO& Hook) + { return Hook.Original == (void*)Rip; }); + if (Itr != Hooks.end()) + { + Exception->ContextRecord->Rip = (uintptr_t)Itr->Detour; + } + + Exception->ContextRecord->EFlags |= 0x100; // SINGLE_STEP_FLAG + + return EXCEPTION_CONTINUE_EXECUTION; + } + else if (Exception->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) + { + // TODO: find a way to only vp the function that about to get executed + for (auto& Hook : Hooks) + { + DWORD dwOldProtect; + VirtualProtect(Hook.Original, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &dwOldProtect); + } + + return EXCEPTION_CONTINUE_EXECUTION; + } + + return EXCEPTION_CONTINUE_SEARCH; + } + + inline bool Init() + { + if (ExceptionHandler == nullptr) + { + ExceptionHandler = AddVectoredExceptionHandler(true, (PVECTORED_EXCEPTION_HANDLER)Handler); + } + return ExceptionHandler != nullptr; + } + + inline bool AddHook(void* Target, void* Detour) + { + if (ExceptionHandler == nullptr) + { + return false; + } + + if (Util::IsSamePage(Target, Detour)) + { + return false; + } + + if (!VirtualProtect(Target, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &HookProtections.emplace_back())) + { + HookProtections.pop_back(); + return false; + } + + Hooks.emplace_back(Target, Detour); + return true; + } + + inline bool RemoveHook(void* Original) + { + auto Itr = std::find_if(Hooks.begin(), Hooks.end(), [Original](const HOOK_INFO& Hook) + { return Hook.Original == Original; }); + + if (Itr == Hooks.end()) + { + return false; + } + + const auto ProtItr = HookProtections.begin() + std::distance(Hooks.begin(), Itr); + Hooks.erase(Itr); + + DWORD dwOldProtect; + bool Ret = VirtualProtect(Original, 1, *ProtItr, &dwOldProtect); + HookProtections.erase(ProtItr); + + return false; + } + } +} \ No newline at end of file diff --git a/external/minhook/include/MinHook.h b/external/minhook/include/MinHook.h new file mode 100644 index 0000000..492d83f --- /dev/null +++ b/external/minhook/include/MinHook.h @@ -0,0 +1,185 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__) + #error MinHook supports only x86 and x64 systems. +#endif + +#include + +// MinHook Error Codes. +typedef enum MH_STATUS +{ + // Unknown error. Should not be returned. + MH_UNKNOWN = -1, + + // Successful. + MH_OK = 0, + + // MinHook is already initialized. + MH_ERROR_ALREADY_INITIALIZED, + + // MinHook is not initialized yet, or already uninitialized. + MH_ERROR_NOT_INITIALIZED, + + // The hook for the specified target function is already created. + MH_ERROR_ALREADY_CREATED, + + // The hook for the specified target function is not created yet. + MH_ERROR_NOT_CREATED, + + // The hook for the specified target function is already enabled. + MH_ERROR_ENABLED, + + // The hook for the specified target function is not enabled yet, or already + // disabled. + MH_ERROR_DISABLED, + + // The specified pointer is invalid. It points the address of non-allocated + // and/or non-executable region. + MH_ERROR_NOT_EXECUTABLE, + + // The specified target function cannot be hooked. + MH_ERROR_UNSUPPORTED_FUNCTION, + + // Failed to allocate memory. + MH_ERROR_MEMORY_ALLOC, + + // Failed to change the memory protection. + MH_ERROR_MEMORY_PROTECT, + + // The specified module is not loaded. + MH_ERROR_MODULE_NOT_FOUND, + + // The specified function is not found. + MH_ERROR_FUNCTION_NOT_FOUND +} +MH_STATUS; + +// Can be passed as a parameter to MH_EnableHook, MH_DisableHook, +// MH_QueueEnableHook or MH_QueueDisableHook. +#define MH_ALL_HOOKS NULL + +#ifdef __cplusplus +extern "C" { +#endif + + // Initialize the MinHook library. You must call this function EXACTLY ONCE + // at the beginning of your program. + MH_STATUS WINAPI MH_Initialize(VOID); + + // Uninitialize the MinHook library. You must call this function EXACTLY + // ONCE at the end of your program. + MH_STATUS WINAPI MH_Uninitialize(VOID); + + // Creates a hook for the specified target function, in disabled state. + // Parameters: + // pTarget [in] A pointer to the target function, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal); + + // Creates a hook for the specified API function, in disabled state. + // Parameters: + // pszModule [in] A pointer to the loaded module name which contains the + // target function. + // pszProcName [in] A pointer to the target function name, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHookApi( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal); + + // Creates a hook for the specified API function, in disabled state. + // Parameters: + // pszModule [in] A pointer to the loaded module name which contains the + // target function. + // pszProcName [in] A pointer to the target function name, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + // ppTarget [out] A pointer to the target function, which will be used + // with other functions. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHookApiEx( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget); + + // Removes an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget); + + // Enables an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // enabled in one go. + MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget); + + // Disables an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // disabled in one go. + MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget); + + // Queues to enable an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // queued to be enabled. + MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget); + + // Queues to disable an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // queued to be disabled. + MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget); + + // Applies all queued changes in one go. + MH_STATUS WINAPI MH_ApplyQueued(VOID); + + // Translates the MH_STATUS to its name as a string. + const char * WINAPI MH_StatusToString(MH_STATUS status); + +#ifdef __cplusplus +} +#endif diff --git a/external/minhook/lib/libMinHook.x64.lib b/external/minhook/lib/libMinHook.x64.lib new file mode 100644 index 0000000000000000000000000000000000000000..e211ad6897c374d24570c4076a1989186a706f94 GIT binary patch literal 548944 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
olsl_T|V1S^hX}NA;UtX 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 literal 0 HcmV?d00001 diff --git a/external/minhook/lib/libMinHook.x86.lib b/external/minhook/lib/libMinHook.x86.lib new file mode 100644 index 0000000000000000000000000000000000000000..b15b79b8c700226a9779c7e117b6af4d7be84b51 GIT binary patch literal 528266 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 z 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 z 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�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 z≀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