mirror of
https://github.com/ApfelTeeSaft/UniversalSlashingSimulator.git
synced 2026-08-26 19:33:39 +00:00
first commit
This commit is contained in:
+233
@@ -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 "")
|
||||
+111
@@ -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 <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
|
||||
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__)
|
||||
|
||||
}
|
||||
@@ -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 <MinHook.h>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
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<std::mutex> 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<std::mutex> 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<typename T>
|
||||
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<void*>(Target);
|
||||
void* pDetour = reinterpret_cast<void*>(Detour);
|
||||
void** ppOriginal = reinterpret_cast<void**>(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<typename T>
|
||||
inline EResult Create(uintptr Target, T Detour, T* OutOriginal)
|
||||
{
|
||||
if (!Detail::g_bInitialized)
|
||||
return EResult::NotInitialized;
|
||||
|
||||
void* pTarget = reinterpret_cast<void*>(Target);
|
||||
void* pDetour = reinterpret_cast<void*>(Detour);
|
||||
void** ppOriginal = reinterpret_cast<void**>(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<void*>(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<void*>(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<void*>(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<void*>(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<void*>(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<bool(void* Object, void* Function, void* Params)>;
|
||||
using PostCallback = std::function<void(void* Object, void* Function, void* Params)>;
|
||||
|
||||
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<std::mutex> Lock(m_Mutex);
|
||||
m_PreCallbacks.push_back(std::move(Callback));
|
||||
}
|
||||
|
||||
// Register a post-ProcessEvent callback
|
||||
void RegisterPost(PostCallback Callback)
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<PreCallback> Callbacks;
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<PostCallback> Callbacks;
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<std::mutex> Lock(m_Mutex);
|
||||
m_PreCallbacks.clear();
|
||||
m_PostCallbacks.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
FSimpleProcessEventDispatcher() : m_pOriginal(nullptr) {}
|
||||
|
||||
std::mutex m_Mutex;
|
||||
std::vector<PreCallback> m_PreCallbacks;
|
||||
std::vector<PostCallback> m_PostCallbacks;
|
||||
ProcessEventFn m_pOriginal;
|
||||
};
|
||||
|
||||
inline FSimpleProcessEventDispatcher& GetSimpleProcessEventDispatcher()
|
||||
{
|
||||
return FSimpleProcessEventDispatcher::Get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* UniversalSlashingSimulator - Logging System Implementation
|
||||
*/
|
||||
|
||||
#include "Log.h"
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <mutex>
|
||||
#include <fstream>
|
||||
#include <cstdarg>
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* UniversalSlashingSimulator - Memory Utilities Implementation
|
||||
*/
|
||||
|
||||
#include "Memory.h"
|
||||
#include "../Logging/Log.h"
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
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<uintptr>(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<uint8>(*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<const uint8*>(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<char> Pattern;
|
||||
std::vector<char> 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<char>(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<int32>(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<void*>(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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <Psapi.h>
|
||||
|
||||
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<typename T>
|
||||
static bool Read(uintptr Address, T& OutValue);
|
||||
|
||||
template<typename T>
|
||||
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<typename T>
|
||||
bool Memory::Read(uintptr Address, T& OutValue)
|
||||
{
|
||||
if (!IsValidAddress(Address))
|
||||
return false;
|
||||
|
||||
__try
|
||||
{
|
||||
OutValue = *reinterpret_cast<T*>(Address);
|
||||
return true;
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool Memory::Write(uintptr Address, const T& Value)
|
||||
{
|
||||
DWORD OldProtect;
|
||||
if (!VirtualProtect(reinterpret_cast<void*>(Address), sizeof(T), PAGE_EXECUTE_READWRITE, &OldProtect))
|
||||
return false;
|
||||
|
||||
__try
|
||||
{
|
||||
*reinterpret_cast<T*>(Address) = Value;
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
VirtualProtect(reinterpret_cast<void*>(Address), sizeof(T), OldProtect, &OldProtect);
|
||||
return false;
|
||||
}
|
||||
|
||||
VirtualProtect(reinterpret_cast<void*>(Address), sizeof(T), OldProtect, &OldProtect);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
// TODO: @timmie replace with ur offset finder or whatever lol
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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 <cstring>
|
||||
|
||||
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<char>(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<char>(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<uint32>(Mapping.FortniteVersion);
|
||||
m_VersionInfo.FortniteSeasonMinor = static_cast<uint32>((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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<uintptr>(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<uintptr>(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<uintptr>(m_ChunksPtr + (ChunkIndex * sizeof(uintptr)), ChunkPtr))
|
||||
return false;
|
||||
|
||||
if (ChunkPtr == 0)
|
||||
return false;
|
||||
|
||||
uintptr EntryPtr = 0;
|
||||
if (!Memory::Read<uintptr>(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<int32>(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<wchar_t>(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<char>(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<uint32>(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<uintptr>(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<uint16>(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<wchar_t>(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<char>(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<INamePool> CreateNamePool()
|
||||
{
|
||||
const auto& Version = GetVersionResolver().GetVersionInfo();
|
||||
|
||||
if (Version.bUseFNamePool)
|
||||
{
|
||||
USS_LOG("Creating FNamePoolImpl for UE %s",
|
||||
Version.GetEngineVersionString().c_str());
|
||||
return std::make_unique<FNamePoolImpl>();
|
||||
}
|
||||
else
|
||||
{
|
||||
USS_LOG("Creating FGNamesArray for UE %s",
|
||||
Version.GetEngineVersionString().c_str());
|
||||
return std::make_unique<FGNamesArray>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<char>(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<INamePool> CreateNamePool();
|
||||
|
||||
}
|
||||
@@ -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<uintptr>(TUObjectArrayAddr + 0x00, m_ObjectsPtr))
|
||||
{
|
||||
USS_ERROR("Failed to read Objects pointer from FUObjectArray");
|
||||
return EResult::Failed;
|
||||
}
|
||||
|
||||
if (!Memory::Read<int32>(TUObjectArrayAddr + 0x08, m_MaxElements))
|
||||
{
|
||||
USS_ERROR("Failed to read MaxElements from FUObjectArray");
|
||||
return EResult::Failed;
|
||||
}
|
||||
|
||||
if (!Memory::Read<int32>(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<void*>(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<void*>(ItemAddr + 0x00, OutItem.Object))
|
||||
return false;
|
||||
if (!Memory::Read<int32>(ItemAddr + 0x08, OutItem.Flags))
|
||||
return false;
|
||||
if (!Memory::Read<int32>(ItemAddr + 0x0C, OutItem.ClusterIndex))
|
||||
return false;
|
||||
if (!Memory::Read<int32>(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<uintptr>(TUObjectArrayAddr + 0x00, m_ChunksPtr))
|
||||
{
|
||||
USS_ERROR("Failed to read Chunks pointer from FChunkedObjectArray");
|
||||
return EResult::Failed;
|
||||
}
|
||||
|
||||
if (!Memory::Read<int32>(TUObjectArrayAddr + 0x10, m_MaxElements))
|
||||
{
|
||||
USS_ERROR("Failed to read MaxElements from FChunkedObjectArray");
|
||||
return EResult::Failed;
|
||||
}
|
||||
|
||||
if (!Memory::Read<int32>(TUObjectArrayAddr + 0x14, m_NumElements))
|
||||
{
|
||||
USS_ERROR("Failed to read NumElements from FChunkedObjectArray");
|
||||
return EResult::Failed;
|
||||
}
|
||||
|
||||
if (!Memory::Read<int32>(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<uintptr>(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<void*>(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<uintptr>(ChunkPtrAddr, ChunkPtr))
|
||||
return false;
|
||||
|
||||
if (ChunkPtr == 0)
|
||||
return false;
|
||||
|
||||
// Get item from chunk
|
||||
uintptr ItemAddr = ChunkPtr + (WithinChunkIndex * m_ItemSize);
|
||||
|
||||
if (!Memory::Read<void*>(ItemAddr + 0x00, OutItem.Object))
|
||||
return false;
|
||||
if (!Memory::Read<int32>(ItemAddr + 0x08, OutItem.Flags))
|
||||
return false;
|
||||
if (!Memory::Read<int32>(ItemAddr + 0x0C, OutItem.ClusterIndex))
|
||||
return false;
|
||||
if (!Memory::Read<int32>(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<IObjectArray> CreateObjectArray()
|
||||
{
|
||||
const auto& Version = GetVersionResolver().GetVersionInfo();
|
||||
|
||||
if (Version.bUseChunkedObjects)
|
||||
{
|
||||
USS_LOG("Creating FChunkedObjectArray for UE %s",
|
||||
Version.GetEngineVersionString().c_str());
|
||||
return std::make_unique<FChunkedObjectArray>();
|
||||
}
|
||||
else
|
||||
{
|
||||
USS_LOG("Creating FFixedObjectArray for UE %s",
|
||||
Version.GetEngineVersionString().c_str());
|
||||
return std::make_unique<FFixedObjectArray>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<int32>(EInternalObjectFlags::Unreachable)) != 0;
|
||||
}
|
||||
|
||||
bool IsPendingKill() const
|
||||
{
|
||||
return (Flags & static_cast<int32>(EInternalObjectFlags::PendingKill)) != 0;
|
||||
}
|
||||
|
||||
bool IsRootSet() const
|
||||
{
|
||||
return (Flags & static_cast<int32>(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<IObjectArray> CreateObjectArray();
|
||||
|
||||
}
|
||||
@@ -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 <cstring>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<void*>(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<void*>(reinterpret_cast<uintptr>(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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <string>
|
||||
|
||||
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<typename Callback>
|
||||
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<IObjectArray> m_pObjectArray;
|
||||
std::unique_ptr<INamePool> m_pNamePool;
|
||||
|
||||
uintptr m_GObjectsAddress;
|
||||
uintptr m_GNamesAddress;
|
||||
uintptr m_GWorldAddress;
|
||||
};
|
||||
|
||||
// Convenience function
|
||||
inline FEngineCore& GetEngineCore()
|
||||
{
|
||||
return FEngineCore::Get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <algorithm>
|
||||
|
||||
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<FPropertyInfo> 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<uintptr>(Parameters) + PropInfo.Offset;
|
||||
|
||||
switch (PropInfo.Type)
|
||||
{
|
||||
case EPropertyType::BoolProperty:
|
||||
Memory::Read<bool>(ParamAddr, OutParam.BoolValue);
|
||||
break;
|
||||
|
||||
case EPropertyType::ByteProperty:
|
||||
case EPropertyType::Int8Property:
|
||||
{
|
||||
int8 Value = 0;
|
||||
Memory::Read<int8>(ParamAddr, Value);
|
||||
OutParam.IntValue = Value;
|
||||
break;
|
||||
}
|
||||
|
||||
case EPropertyType::Int16Property:
|
||||
case EPropertyType::UInt16Property:
|
||||
{
|
||||
int16 Value = 0;
|
||||
Memory::Read<int16>(ParamAddr, Value);
|
||||
OutParam.IntValue = Value;
|
||||
break;
|
||||
}
|
||||
|
||||
case EPropertyType::IntProperty:
|
||||
case EPropertyType::UInt32Property:
|
||||
Memory::Read<int32>(ParamAddr, OutParam.IntValue);
|
||||
break;
|
||||
|
||||
case EPropertyType::Int64Property:
|
||||
case EPropertyType::UInt64Property:
|
||||
Memory::Read<int64>(ParamAddr, OutParam.Int64Value);
|
||||
break;
|
||||
|
||||
case EPropertyType::FloatProperty:
|
||||
Memory::Read<float>(ParamAddr, OutParam.FloatValue);
|
||||
break;
|
||||
|
||||
case EPropertyType::DoubleProperty:
|
||||
Memory::Read<double>(ParamAddr, OutParam.DoubleValue);
|
||||
break;
|
||||
|
||||
case EPropertyType::ObjectProperty:
|
||||
case EPropertyType::ClassProperty:
|
||||
case EPropertyType::InterfaceProperty:
|
||||
case EPropertyType::WeakObjectProperty:
|
||||
case EPropertyType::LazyObjectProperty:
|
||||
case EPropertyType::SoftObjectProperty:
|
||||
Memory::Read<void*>(ParamAddr, OutParam.PointerValue);
|
||||
break;
|
||||
|
||||
case EPropertyType::NameProperty:
|
||||
{
|
||||
// FName is typically int32 ComparisonIndex + int32 Number
|
||||
int32 NameIndex = 0;
|
||||
Memory::Read<int32>(ParamAddr, NameIndex);
|
||||
OutParam.StringValue = GetEngineCore().GetNameFromIndex(NameIndex);
|
||||
break;
|
||||
}
|
||||
|
||||
case EPropertyType::StrProperty:
|
||||
{
|
||||
// FString is TArray<TCHAR>
|
||||
// Try to read as FString
|
||||
uintptr DataPtr = 0;
|
||||
int32 Len = 0;
|
||||
Memory::Read<uintptr>(ParamAddr, DataPtr);
|
||||
Memory::Read<int32>(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<wchar_t>(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<char>(Ch);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EPropertyType::StructProperty:
|
||||
// Store pointer to struct data
|
||||
OutParam.PointerValue = reinterpret_cast<void*>(ParamAddr);
|
||||
break;
|
||||
|
||||
case EPropertyType::ArrayProperty:
|
||||
// Store pointer to array
|
||||
OutParam.PointerValue = reinterpret_cast<void*>(ParamAddr);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Store raw pointer for unknown types
|
||||
OutParam.PointerValue = reinterpret_cast<void*>(ParamAddr);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FProcessEventDispatcher::WriteParameterValue(void* Parameters, const FPropertyInfo& PropInfo, const FParsedParameter& Param)
|
||||
{
|
||||
if (!Parameters)
|
||||
return false;
|
||||
|
||||
uintptr ParamAddr = reinterpret_cast<uintptr>(Parameters) + PropInfo.Offset;
|
||||
|
||||
switch (PropInfo.Type)
|
||||
{
|
||||
case EPropertyType::BoolProperty:
|
||||
return Memory::Write<bool>(ParamAddr, Param.BoolValue);
|
||||
|
||||
case EPropertyType::IntProperty:
|
||||
return Memory::Write<int32>(ParamAddr, Param.IntValue);
|
||||
|
||||
case EPropertyType::Int64Property:
|
||||
return Memory::Write<int64>(ParamAddr, Param.Int64Value);
|
||||
|
||||
case EPropertyType::FloatProperty:
|
||||
return Memory::Write<float>(ParamAddr, Param.FloatValue);
|
||||
|
||||
case EPropertyType::DoubleProperty:
|
||||
return Memory::Write<double>(ParamAddr, Param.DoubleValue);
|
||||
|
||||
case EPropertyType::ObjectProperty:
|
||||
return Memory::Write<void*>(ParamAddr, Param.PointerValue);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool FProcessEventDispatcher::GetFunctionInfo(void* Function, std::vector<FPropertyInfo>& 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<FPropertyInfo> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <unordered_map>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <any>
|
||||
|
||||
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<FParsedParameter> 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<typename T>
|
||||
bool GetParamValue(const char* Name, T& OutValue) const;
|
||||
|
||||
/**
|
||||
* Set output parameter value
|
||||
*/
|
||||
template<typename T>
|
||||
bool SetOutParamValue(const char* Name, const T& Value);
|
||||
};
|
||||
|
||||
/**
|
||||
* ProcessEvent handler callback type
|
||||
*/
|
||||
using FProcessEventHandler = std::function<bool(FProcessEventContext&)>;
|
||||
|
||||
/**
|
||||
* 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<int32>(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<FPropertyInfo>& OutParams);
|
||||
|
||||
/**
|
||||
* Sort handlers by priority
|
||||
*/
|
||||
void SortHandlers();
|
||||
|
||||
// State
|
||||
bool m_bInitialized;
|
||||
int32 m_NextHandlerId;
|
||||
|
||||
// Registered handlers (sorted by priority)
|
||||
std::vector<FRegisteredHandler> m_Handlers;
|
||||
bool m_bHandlersDirty;
|
||||
|
||||
// Function parameter cache (UFunction* -> params)
|
||||
std::unordered_map<void*, std::vector<FPropertyInfo>> m_FunctionCache;
|
||||
|
||||
// Statistics
|
||||
uint64 m_TotalEventsProcessed;
|
||||
uint64 m_TotalEventsHandled;
|
||||
uint64 m_TotalEventsBlocked;
|
||||
};
|
||||
|
||||
/**
|
||||
* Global dispatcher accessor
|
||||
*/
|
||||
FProcessEventDispatcher& GetProcessEventDispatcher();
|
||||
|
||||
//=========================================================================
|
||||
// Template Implementations
|
||||
//=========================================================================
|
||||
|
||||
template<typename T>
|
||||
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<T, bool>)
|
||||
{
|
||||
if (Param->Type == EPropertyType::BoolProperty)
|
||||
{
|
||||
OutValue = Param->BoolValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int32>)
|
||||
{
|
||||
if (Param->Type == EPropertyType::IntProperty ||
|
||||
Param->Type == EPropertyType::ByteProperty)
|
||||
{
|
||||
OutValue = Param->IntValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int64>)
|
||||
{
|
||||
if (Param->Type == EPropertyType::Int64Property)
|
||||
{
|
||||
OutValue = Param->Int64Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, float>)
|
||||
{
|
||||
if (Param->Type == EPropertyType::FloatProperty)
|
||||
{
|
||||
OutValue = Param->FloatValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, double>)
|
||||
{
|
||||
if (Param->Type == EPropertyType::DoubleProperty)
|
||||
{
|
||||
OutValue = Param->DoubleValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::string>)
|
||||
{
|
||||
if (Param->Type == EPropertyType::StrProperty ||
|
||||
Param->Type == EPropertyType::NameProperty)
|
||||
{
|
||||
OutValue = Param->StringValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_pointer_v<T>)
|
||||
{
|
||||
if (Param->Type == EPropertyType::ObjectProperty ||
|
||||
Param->Type == EPropertyType::ClassProperty)
|
||||
{
|
||||
OutValue = static_cast<T>(Param->PointerValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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<uint8*>(Parameters) + Param.Offset;
|
||||
|
||||
if constexpr (std::is_same_v<T, bool>)
|
||||
{
|
||||
*static_cast<bool*>(ParamPtr) = Value;
|
||||
Param.BoolValue = Value;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int32>)
|
||||
{
|
||||
*static_cast<int32*>(ParamPtr) = Value;
|
||||
Param.IntValue = Value;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, float>)
|
||||
{
|
||||
*static_cast<float*>(ParamPtr) = Value;
|
||||
Param.FloatValue = Value;
|
||||
}
|
||||
// Add more types as needed
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<void*>(reinterpret_cast<uintptr>(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<uintptr>(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<int32>(PropAddr + m_UProperty_ArrayDimOffset, OutInfo.ArrayDim);
|
||||
Memory::Read<int32>(PropAddr + m_UProperty_ElementSizeOffset, OutInfo.ElementSize);
|
||||
Memory::Read<uint64>(PropAddr + m_UProperty_PropertyFlagsOffset, OutInfo.PropertyFlags);
|
||||
Memory::Read<int32>(PropAddr + m_UProperty_OffsetOffset, OutInfo.Offset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void* FUPropertyIterator::GetPropertyLink(void* Struct)
|
||||
{
|
||||
if (!Struct)
|
||||
return nullptr;
|
||||
|
||||
void* PropertyLink = nullptr;
|
||||
Memory::Read<void*>(reinterpret_cast<uintptr>(Struct) + m_PropertyLinkOffset, PropertyLink);
|
||||
return PropertyLink;
|
||||
}
|
||||
|
||||
void* FUPropertyIterator::GetNextProperty(void* Property)
|
||||
{
|
||||
if (!Property)
|
||||
return nullptr;
|
||||
|
||||
void* Next = nullptr;
|
||||
Memory::Read<void*>(reinterpret_cast<uintptr>(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<void*>(reinterpret_cast<uintptr>(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<uintptr>(Field);
|
||||
|
||||
OutInfo.PropertyPtr = Field;
|
||||
|
||||
// Get name from FField (stored as FName)
|
||||
// FName is at m_FField_NameOffset
|
||||
int32 NameIndex = 0;
|
||||
if (Memory::Read<int32>(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<int32>(FieldAddr + m_FProperty_ArrayDimOffset, OutInfo.ArrayDim);
|
||||
Memory::Read<int32>(FieldAddr + m_FProperty_ElementSizeOffset, OutInfo.ElementSize);
|
||||
Memory::Read<uint64>(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<int32>(FieldAddr + m_FProperty_OffsetOffset, RawOffset);
|
||||
OutInfo.Offset = RawOffset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void* FFFieldPropertyIterator::GetChildProperties(void* Struct)
|
||||
{
|
||||
if (!Struct)
|
||||
return nullptr;
|
||||
|
||||
void* ChildProperties = nullptr;
|
||||
Memory::Read<void*>(reinterpret_cast<uintptr>(Struct) + m_ChildPropertiesOffset, ChildProperties);
|
||||
return ChildProperties;
|
||||
}
|
||||
|
||||
void* FFFieldPropertyIterator::GetNextField(void* Field)
|
||||
{
|
||||
if (!Field)
|
||||
return nullptr;
|
||||
|
||||
void* Next = nullptr;
|
||||
Memory::Read<void*>(reinterpret_cast<uintptr>(Field) + m_FField_NextOffset, Next);
|
||||
return Next;
|
||||
}
|
||||
|
||||
std::string FFFieldPropertyIterator::GetFieldClassName(void* Field)
|
||||
{
|
||||
if (!Field)
|
||||
return "";
|
||||
|
||||
uintptr FieldAddr = reinterpret_cast<uintptr>(Field);
|
||||
|
||||
// Get FFieldClass* from Field
|
||||
void* FieldClass = nullptr;
|
||||
if (!Memory::Read<void*>(FieldAddr + m_FField_ClassOffset, FieldClass))
|
||||
return "";
|
||||
|
||||
if (!FieldClass)
|
||||
return "";
|
||||
|
||||
// FFieldClass has Name as FName at offset 0
|
||||
int32 NameIndex = 0;
|
||||
if (Memory::Read<int32>(reinterpret_cast<uintptr>(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<IPropertyIterator> g_PropertyIterator;
|
||||
|
||||
std::unique_ptr<IPropertyIterator> CreatePropertyIterator()
|
||||
{
|
||||
const auto& Version = GetVersionResolver().GetVersionInfo();
|
||||
|
||||
if (Version.bUseFField)
|
||||
{
|
||||
USS_LOG("Creating FFFieldPropertyIterator for UE %s",
|
||||
Version.GetEngineVersionString().c_str());
|
||||
return std::make_unique<FFFieldPropertyIterator>();
|
||||
}
|
||||
else
|
||||
{
|
||||
USS_LOG("Creating FUPropertyIterator for UE %s",
|
||||
Version.GetEngineVersionString().c_str());
|
||||
return std::make_unique<FUPropertyIterator>();
|
||||
}
|
||||
}
|
||||
|
||||
IPropertyIterator& GetPropertyIterator()
|
||||
{
|
||||
if (!g_PropertyIterator)
|
||||
{
|
||||
g_PropertyIterator = CreatePropertyIterator();
|
||||
g_PropertyIterator->Initialize();
|
||||
}
|
||||
return *g_PropertyIterator;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <string>
|
||||
#include <functional>
|
||||
|
||||
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<bool(const FPropertyInfo&)>;
|
||||
|
||||
/**
|
||||
* 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<IPropertyIterator> CreatePropertyIterator();
|
||||
|
||||
/**
|
||||
* Global property iterator accessor
|
||||
*/
|
||||
IPropertyIterator& GetPropertyIterator();
|
||||
|
||||
}
|
||||
@@ -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<uintptr>(FastArrayPtr);
|
||||
|
||||
// Read TArray structure at ItemsOffset
|
||||
// TArray layout: Data*, Num, Max
|
||||
uintptr TArrayAddr = BaseAddr + ItemsOffset;
|
||||
|
||||
if (!Memory::Read<uintptr>(TArrayAddr + 0x00, m_ItemsDataPtr))
|
||||
{
|
||||
USS_ERROR("Failed to read Items.Data from FastArraySerializer");
|
||||
return EResult::Failed;
|
||||
}
|
||||
|
||||
if (!Memory::Read<int32>(TArrayAddr + 0x08, m_ItemsNum))
|
||||
{
|
||||
USS_ERROR("Failed to read Items.Num from FastArraySerializer");
|
||||
return EResult::Failed;
|
||||
}
|
||||
|
||||
if (!Memory::Read<int32>(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<uintptr>(m_FastArrayPtr) + m_ItemsOffset;
|
||||
Memory::Read<int32>(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<uintptr>(m_FastArrayPtr) + m_ItemsOffset;
|
||||
Memory::Read<uintptr>(TArrayAddr + 0x00, DataPtr);
|
||||
|
||||
if (DataPtr == 0)
|
||||
return nullptr;
|
||||
|
||||
return reinterpret_cast<void*>(DataPtr + (Index * m_ItemSize));
|
||||
}
|
||||
|
||||
int32 FLegacyFastArraySerializer::GetItemReplicationID(int32 Index) const
|
||||
{
|
||||
void* Item = GetItem(Index);
|
||||
if (!Item)
|
||||
return -1;
|
||||
|
||||
int32 ReplicationID = -1;
|
||||
Memory::Read<int32>(reinterpret_cast<uintptr>(Item) + ReplicationIDOffset, ReplicationID);
|
||||
return ReplicationID;
|
||||
}
|
||||
|
||||
int32 FLegacyFastArraySerializer::GetItemReplicationKey(int32 Index) const
|
||||
{
|
||||
void* Item = GetItem(Index);
|
||||
if (!Item)
|
||||
return -1;
|
||||
|
||||
int32 ReplicationKey = -1;
|
||||
Memory::Read<int32>(reinterpret_cast<uintptr>(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<int32>(reinterpret_cast<uintptr>(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<uintptr>(m_FastArrayPtr);
|
||||
Memory::Read<int32>(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<uintptr>(FastArrayPtr);
|
||||
|
||||
// Read TArray structure
|
||||
uintptr TArrayAddr = BaseAddr + ItemsOffset;
|
||||
|
||||
if (!Memory::Read<uintptr>(TArrayAddr + 0x00, m_ItemsDataPtr))
|
||||
{
|
||||
USS_ERROR("Failed to read Items.Data from FastArraySerializer");
|
||||
return EResult::Failed;
|
||||
}
|
||||
|
||||
if (!Memory::Read<int32>(TArrayAddr + 0x08, m_ItemsNum))
|
||||
{
|
||||
USS_ERROR("Failed to read Items.Num from FastArraySerializer");
|
||||
return EResult::Failed;
|
||||
}
|
||||
|
||||
if (!Memory::Read<int32>(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<int32>(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<uintptr>(m_FastArrayPtr) + m_ItemsOffset;
|
||||
Memory::Read<int32>(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<uintptr>(m_FastArrayPtr) + m_ItemsOffset;
|
||||
Memory::Read<uintptr>(TArrayAddr + 0x00, DataPtr);
|
||||
|
||||
if (DataPtr == 0)
|
||||
return nullptr;
|
||||
|
||||
return reinterpret_cast<void*>(DataPtr + (Index * m_ItemSize));
|
||||
}
|
||||
|
||||
int32 FNewFastArraySerializer::GetItemReplicationID(int32 Index) const
|
||||
{
|
||||
void* Item = GetItem(Index);
|
||||
if (!Item)
|
||||
return -1;
|
||||
|
||||
int32 ReplicationID = -1;
|
||||
Memory::Read<int32>(reinterpret_cast<uintptr>(Item) + ReplicationIDOffset, ReplicationID);
|
||||
return ReplicationID;
|
||||
}
|
||||
|
||||
int32 FNewFastArraySerializer::GetItemReplicationKey(int32 Index) const
|
||||
{
|
||||
void* Item = GetItem(Index);
|
||||
if (!Item)
|
||||
return -1;
|
||||
|
||||
int32 ReplicationKey = -1;
|
||||
Memory::Read<int32>(reinterpret_cast<uintptr>(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<int32>(reinterpret_cast<uintptr>(Item) + MostRecentArrayReplicationKeyOffset, ItemArrayKey);
|
||||
|
||||
return ItemArrayKey != GetArrayReplicationKey();
|
||||
}
|
||||
|
||||
void FNewFastArraySerializer::MarkItemDirty(int32 Index)
|
||||
{
|
||||
void* Item = GetItem(Index);
|
||||
if (!Item)
|
||||
return;
|
||||
|
||||
uintptr ItemAddr = reinterpret_cast<uintptr>(Item);
|
||||
|
||||
// Increment ReplicationKey
|
||||
int32 CurrentKey = GetItemReplicationKey(Index);
|
||||
Memory::Write<int32>(ItemAddr + ReplicationKeyOffset, CurrentKey + 1);
|
||||
|
||||
// Update MostRecentArrayReplicationKey to current
|
||||
int32 ArrayKey = GetArrayReplicationKey();
|
||||
Memory::Write<int32>(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<uintptr>(m_FastArrayPtr);
|
||||
Memory::Read<int32>(BaseAddr + m_ArrayReplicationKeyOffset, ArrayKey);
|
||||
return ArrayKey;
|
||||
}
|
||||
|
||||
int32 FNewFastArraySerializer::GetIDCounter() const
|
||||
{
|
||||
if (!m_bInitialized)
|
||||
return 0;
|
||||
|
||||
int32 IDCounter = 0;
|
||||
uintptr BaseAddr = reinterpret_cast<uintptr>(m_FastArrayPtr);
|
||||
Memory::Read<int32>(BaseAddr + m_IDCounterOffset, IDCounter);
|
||||
return IDCounter;
|
||||
}
|
||||
|
||||
void FNewFastArraySerializer::IncrementArrayReplicationKey()
|
||||
{
|
||||
if (!m_bInitialized)
|
||||
return;
|
||||
|
||||
int32 CurrentKey = GetArrayReplicationKey();
|
||||
uintptr BaseAddr = reinterpret_cast<uintptr>(m_FastArrayPtr);
|
||||
Memory::Write<int32>(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<IFastArraySerializer> CreateFastArraySerializer()
|
||||
{
|
||||
const auto& Version = GetVersionResolver().GetVersionInfo();
|
||||
|
||||
if (Version.bUseNewFastArraySerializer)
|
||||
{
|
||||
USS_LOG("Creating NewFastArraySerializer (post-8.30 format)");
|
||||
return std::make_unique<FNewFastArraySerializer>();
|
||||
}
|
||||
else
|
||||
{
|
||||
USS_LOG("Creating LegacyFastArraySerializer (pre-8.30 format)");
|
||||
return std::make_unique<FLegacyFastArraySerializer>();
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// 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<FFastArrayChange>& 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<int32>(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<int32>(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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <vector>
|
||||
#include <functional>
|
||||
|
||||
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<void(const FFastArrayChange&)>;
|
||||
|
||||
/**
|
||||
* 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<ItemType> Items; // 0x00 - Standard TArray
|
||||
* TMap<int32, int32> 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<FFastArrayChangeCallback> m_Callbacks;
|
||||
bool m_bInitialized;
|
||||
};
|
||||
|
||||
/**
|
||||
* Post-8.30 Fast Array Serializer
|
||||
*
|
||||
* Layout (restructured):
|
||||
* struct FFastArraySerializer {
|
||||
* TArray<ItemType> 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<FFastArrayChangeCallback> m_Callbacks;
|
||||
bool m_bInitialized;
|
||||
};
|
||||
|
||||
/**
|
||||
* Factory function to create appropriate serializer based on version
|
||||
*/
|
||||
std::unique_ptr<IFastArraySerializer> 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<FFastArrayChange>& OutChanges);
|
||||
|
||||
/**
|
||||
* Reset tracking state
|
||||
*/
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
IFastArraySerializer* m_Serializer;
|
||||
int32 m_LastArrayReplicationKey;
|
||||
std::vector<int32> m_LastItemKeys; // ReplicationKey per item
|
||||
std::vector<int32> m_LastItemIDs; // ReplicationID per item
|
||||
int32 m_LastItemCount;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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 <sstream>
|
||||
|
||||
namespace USS
|
||||
{
|
||||
// Static name pool instance (lazy initialized)
|
||||
static std::unique_ptr<INamePool> 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 "<uninitialized>";
|
||||
|
||||
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<uintptr>(m_pObject));
|
||||
}
|
||||
|
||||
int32 UObjectWrapper::GetObjectFlags() const
|
||||
{
|
||||
if (!IsValid())
|
||||
return 0;
|
||||
|
||||
const auto& Offsets = GetOffsetResolver().GetOffsets();
|
||||
int32 Flags = 0;
|
||||
|
||||
Memory::Read<int32>(
|
||||
reinterpret_cast<uintptr>(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<int32>(
|
||||
reinterpret_cast<uintptr>(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<void*>(
|
||||
reinterpret_cast<uintptr>(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<uintptr>(m_pObject) + Offsets.UObject.Name;
|
||||
|
||||
int32 ComparisonIndex = 0;
|
||||
int32 Number = 0;
|
||||
|
||||
Memory::Read<int32>(NameAddr + 0, ComparisonIndex);
|
||||
Memory::Read<int32>(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<void*>(
|
||||
reinterpret_cast<uintptr>(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<std::string> 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<void*>(
|
||||
reinterpret_cast<uintptr>(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<void*>(
|
||||
reinterpret_cast<uintptr>(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<void*>(
|
||||
reinterpret_cast<uintptr>(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<int32>(
|
||||
reinterpret_cast<uintptr>(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<int32>(
|
||||
reinterpret_cast<uintptr>(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<void*>(
|
||||
reinterpret_cast<uintptr>(m_pObject) + Offsets.UStruct.ChildProperties,
|
||||
FirstProperty
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pre-4.25: Use Children (UField*), filter to properties
|
||||
Memory::Read<void*>(
|
||||
reinterpret_cast<uintptr>(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<void*>(
|
||||
reinterpret_cast<uintptr>(m_pCurrent) + Offsets.FField.Next,
|
||||
NextPtr
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Memory::Read<void*>(
|
||||
reinterpret_cast<uintptr>(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<uintptr>(m_pCurrent) + Offsets.FField.NamePrivate;
|
||||
Memory::Read<int32>(NameAddr + 0, ComparisonIndex);
|
||||
Memory::Read<int32>(NameAddr + 4, Number);
|
||||
}
|
||||
else
|
||||
{
|
||||
// UProperty is a UObject, name at standard offset
|
||||
uintptr NameAddr = reinterpret_cast<uintptr>(m_pCurrent) + Offsets.UObject.Name;
|
||||
Memory::Read<int32>(NameAddr + 0, ComparisonIndex);
|
||||
Memory::Read<int32>(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<int32>(
|
||||
reinterpret_cast<uintptr>(m_pCurrent) + Offsets.FProperty.Offset,
|
||||
Offset
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Memory::Read<int32>(
|
||||
reinterpret_cast<uintptr>(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<int32>(
|
||||
reinterpret_cast<uintptr>(m_pCurrent) + Offsets.FProperty.ElementSize,
|
||||
Size
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Memory::Read<int32>(
|
||||
reinterpret_cast<uintptr>(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<uint32>(
|
||||
reinterpret_cast<uintptr>(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<uint8>(
|
||||
reinterpret_cast<uintptr>(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<uint16>(
|
||||
reinterpret_cast<uintptr>(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<void*>(
|
||||
reinterpret_cast<uintptr>(m_pObject) + Offsets.UFunction.Func,
|
||||
Func
|
||||
);
|
||||
|
||||
return Func;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<typename T>
|
||||
T* GetAs() const { return static_cast<T*>(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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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=<MissionBlueprint> Mission blueprint to load
|
||||
* -USS_Map=<MapName> Map to load
|
||||
* -USS_Difficulty=<1-140> Difficulty level
|
||||
* -USS_MaxPlayers=<1-4> Maximum players
|
||||
* -USS_Zone=<ZoneName> 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 <string>
|
||||
#include <sstream>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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<int>(Type), static_cast<int>(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<int>(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<int32>(m_BuildPreview.LocationX / 512.0f);
|
||||
NewBuilding.GridY = static_cast<int32>(m_BuildPreview.LocationY / 512.0f);
|
||||
NewBuilding.GridZ = static_cast<int32>(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<uint64>(NewBuilding.GridX) << 40) |
|
||||
(static_cast<uint64>(NewBuilding.GridY) << 20) |
|
||||
static_cast<uint64>(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<int>(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<uint64>(X) << 40) |
|
||||
(static_cast<uint64>(Y) << 20) |
|
||||
static_cast<uint64>(Z);
|
||||
|
||||
auto It = m_GridToBuildingId.find(GridKey);
|
||||
if (It != m_GridToBuildingId.end())
|
||||
{
|
||||
return GetBuilding(It->second);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<const FBuildingPiece*> FBuildingManager::GetPlayerBuildings(const std::string& PlayerId) const
|
||||
{
|
||||
std::vector<const FBuildingPiece*> 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<uint64>(Building.GridX) << 40) |
|
||||
(static_cast<uint64>(Building.GridY) << 20) |
|
||||
static_cast<uint64>(Building.GridZ);
|
||||
m_GridToBuildingId.erase(GridKey);
|
||||
|
||||
// Remove traps attached to this building
|
||||
std::vector<std::string> 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<EBuildingTier>(static_cast<int>(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<int>(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<uint64>(Building.GridX) << 40) |
|
||||
(static_cast<uint64>(Building.GridY) << 20) |
|
||||
static_cast<uint64>(Building.GridZ);
|
||||
m_GridToBuildingId.erase(GridKey);
|
||||
|
||||
// Remove traps
|
||||
std::vector<std::string> 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<int>(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<int>(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<const FTrapInstance*> FBuildingManager::GetTrapsOnBuilding(const std::string& BuildingId) const
|
||||
{
|
||||
std::vector<const FTrapInstance*> 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<int32>(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<int32>(X / 512.0f);
|
||||
int32 GridY = static_cast<int32>(Y / 512.0f);
|
||||
int32 GridZ = static_cast<int32>(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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <functional>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
|
||||
namespace USS
|
||||
{
|
||||
// Forward declarations
|
||||
class FInventoryManager;
|
||||
|
||||
/**
|
||||
* Callback for building events
|
||||
*/
|
||||
using FBuildingEventCallback = std::function<void(const FBuildingChangeEvent&)>;
|
||||
|
||||
/**
|
||||
* 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<const FBuildingPiece*> GetPlayerBuildings(const std::string& PlayerId) const;
|
||||
|
||||
/**
|
||||
* Get total building count
|
||||
*/
|
||||
int32 GetBuildingCount() const { return static_cast<int32>(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<const FTrapInstance*> 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<std::string, FBuildingPiece> m_Buildings;
|
||||
std::unordered_map<std::string, FTrapInstance> m_Traps;
|
||||
|
||||
// Grid lookup (for fast position-based queries)
|
||||
std::unordered_map<uint64, std::string> 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<FBuildingEventCallback> m_EventCallbacks;
|
||||
};
|
||||
|
||||
/**
|
||||
* Global building manager accessor
|
||||
*/
|
||||
FBuildingManager& GetLocalBuildingManager();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
/**
|
||||
* UniversalSlashingSimulator - Building System Types
|
||||
*
|
||||
* Defines STW building structures, traps, and construction types.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../Core/Common.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<float>(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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <cstring>
|
||||
|
||||
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<FMissionManager>();
|
||||
m_pInventoryManager = std::make_unique<FInventoryManager>();
|
||||
m_pBuildingManager = std::make_unique<FBuildingManager>();
|
||||
|
||||
// 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<FSTWPlayerController> 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<FSTWPlayerController>(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]));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <functional>
|
||||
|
||||
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<FSTWPlayerController> Player);
|
||||
|
||||
// Event handlers (called from ProcessEvent hook)
|
||||
void OnProcessEvent(void* Object, void* Function, void* Params);
|
||||
|
||||
// Callbacks for external systems
|
||||
using StateChangeCallback = std::function<void(ESTWGameState OldState, ESTWGameState NewState)>;
|
||||
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<FMissionManager> m_pMissionManager;
|
||||
std::unique_ptr<FInventoryManager> m_pInventoryManager;
|
||||
std::unique_ptr<FBuildingManager> m_pBuildingManager;
|
||||
|
||||
// Player
|
||||
std::unique_ptr<FSTWPlayerController> m_pLocalPlayer;
|
||||
|
||||
// Callbacks
|
||||
std::vector<StateChangeCallback> 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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<int32>(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<const FInventoryItem*> FInventoryManager::GetItemsByCategory(EItemCategory Category) const
|
||||
{
|
||||
std::vector<const FInventoryItem*> 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<int32>(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<int32>(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<int32>(Primary.Slots.size()) ||
|
||||
SlotB >= static_cast<int32>(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<FCraftingRecipe> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <unordered_map>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace USS
|
||||
{
|
||||
// Inventory event callback
|
||||
using FInventoryEventCallback = std::function<void(const FInventoryChangeEvent&)>;
|
||||
|
||||
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<const FInventoryItem*> 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<FCraftingRecipe> 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<int32>(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<std::string, FInventoryItem> m_Items;
|
||||
|
||||
// Slot mapping
|
||||
std::unordered_map<int32, std::string> 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<FInventoryEventCallback> m_EventCallbacks;
|
||||
|
||||
// ID counter for generated IDs
|
||||
mutable uint32 m_ItemIdCounter;
|
||||
};
|
||||
|
||||
// Global inventory access (for current local player)
|
||||
FInventoryManager& GetLocalInventoryManager();
|
||||
|
||||
}
|
||||
@@ -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 <string>
|
||||
#include <vector>
|
||||
|
||||
// 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<FQuickbarSlot> 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<FIngredient> 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)
|
||||
{}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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<int>(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<int>(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<FMissionObjective> 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<int32>(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<int>(OldState), static_cast<int>(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<int32>(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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
namespace USS
|
||||
{
|
||||
// Mission event callback
|
||||
using FMissionEventCallback = std::function<void(const char* EventName, void* Params)>;
|
||||
|
||||
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<FMissionObjective> Objective);
|
||||
FMissionObjective* GetObjective(int32 Index) const;
|
||||
int32 GetObjectiveCount() const { return static_cast<int32>(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<std::unique_ptr<FMissionObjective>> m_Objectives;
|
||||
|
||||
// Engine references (cached)
|
||||
UObjectWrapper m_MissionActor; // AFortMission*
|
||||
UObjectWrapper m_MissionManagerActor; // AFortMissionManager*
|
||||
UObjectWrapper m_AIDirector; // AFortAIDirector*
|
||||
|
||||
// Callbacks
|
||||
std::vector<FMissionEventCallback> m_EventCallbacks;
|
||||
|
||||
// Result
|
||||
FMissionResult m_Result;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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<float>(m_CurrentProgress) / static_cast<float>(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<FMissionObjective> CreateObjective(const FObjectiveDefinition& Definition)
|
||||
{
|
||||
switch (Definition.Type)
|
||||
{
|
||||
case EObjectiveType::Kill:
|
||||
return std::make_unique<FKillObjective>(Definition);
|
||||
|
||||
case EObjectiveType::Collect:
|
||||
return std::make_unique<FCollectObjective>(Definition);
|
||||
|
||||
case EObjectiveType::Defend:
|
||||
return std::make_unique<FDefendObjective>(Definition);
|
||||
|
||||
case EObjectiveType::Explore:
|
||||
return std::make_unique<FExploreObjective>(Definition);
|
||||
|
||||
case EObjectiveType::Build:
|
||||
return std::make_unique<FBuildObjective>(Definition);
|
||||
|
||||
case EObjectiveType::Rescue:
|
||||
return std::make_unique<FRescueObjective>(Definition);
|
||||
|
||||
default:
|
||||
USS_WARN("Unknown objective type: %d", static_cast<int>(Definition.Type));
|
||||
return std::make_unique<FMissionObjective>(Definition);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <string>
|
||||
|
||||
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<FMissionObjective> CreateObjective(const FObjectiveDefinition& Definition);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* UniversalSlashingSimulator - Mission Types
|
||||
*
|
||||
* Types and enums for the STW mission system.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../Core/Common.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<float>(CurrentCount) / static_cast<float>(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<FMissionReward> 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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<FSTWPlayerPawn>(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<int>(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<FSTWPlayerController>(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<int32>(m_Players.size()))
|
||||
return nullptr;
|
||||
|
||||
auto It = m_Players.begin();
|
||||
std::advance(It, Index);
|
||||
return It->second.get();
|
||||
}
|
||||
|
||||
std::vector<FSTWPlayerController*> FPlayerControllerManager::GetPlayersOnTeam(int32 TeamIndex) const
|
||||
{
|
||||
std::vector<FSTWPlayerController*> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <string>
|
||||
#include <vector>
|
||||
|
||||
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<FSTWPlayerPawn> 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<int32>(m_Players.size()); }
|
||||
FSTWPlayerController* GetPlayerByIndex(int32 Index) const;
|
||||
|
||||
template<typename Callback>
|
||||
void ForEachPlayer(Callback&& Func) const
|
||||
{
|
||||
for (const auto& Pair : m_Players)
|
||||
{
|
||||
if (Pair.second)
|
||||
{
|
||||
if (!Func(Pair.second.get()))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Team queries
|
||||
std::vector<FSTWPlayerController*> 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<void*, std::unique_ptr<FSTWPlayerController>> m_Players;
|
||||
};
|
||||
|
||||
inline FPlayerControllerManager& GetPlayerControllerManager()
|
||||
{
|
||||
return FPlayerControllerManager::Get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<int>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <string>
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,168 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid>
|
||||
<RootNamespace>UniversalSlashingSimulator</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>UniversalSlashingSimulator</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<!-- Output Directories -->
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<OutDir>$(SolutionDir)bin\$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)obj\$(Configuration)\</IntDir>
|
||||
<TargetName>USS</TargetName>
|
||||
<TargetExt>.dll</TargetExt>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<OutDir>$(SolutionDir)bin\$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)obj\$(Configuration)\</IntDir>
|
||||
<TargetName>USS</TargetName>
|
||||
<TargetExt>.dll</TargetExt>
|
||||
</PropertyGroup>
|
||||
<!-- Debug Configuration -->
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;NOMINMAX;_CRT_SECURE_NO_WARNINGS;USS_DEBUG;_DEBUG;_WINDOWS;_USRDLL;USS_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)external\minhook\include;$(ProjectDir)external\memcury\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<Optimization>Disabled</Optimization>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>psapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(ProjectDir)external\minhook\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<!-- Release Configuration -->
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;NOMINMAX;_CRT_SECURE_NO_WARNINGS;NDEBUG;_WINDOWS;_USRDLL;USS_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)external\minhook\include;$(ProjectDir)external\memcury\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>psapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(ProjectDir)external\minhook\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<!-- Source Files -->
|
||||
<ItemGroup>
|
||||
<!-- Core -->
|
||||
<ClCompile Include="Core\Logging\Log.cpp" />
|
||||
<ClCompile Include="Core\Memory\Memory.cpp" />
|
||||
<ClCompile Include="Core\Versioning\VersionResolver.cpp" />
|
||||
<!-- Engine -->
|
||||
<ClCompile Include="Engine\CoreTypes\ObjectArray.cpp" />
|
||||
<ClCompile Include="Engine\CoreTypes\NamePool.cpp" />
|
||||
<ClCompile Include="Engine\CoreTypes\OffsetResolver.cpp" />
|
||||
<ClCompile Include="Engine\UObject\UObjectWrapper.cpp" />
|
||||
<ClCompile Include="Engine\Reflection\PropertyIterator.cpp" />
|
||||
<ClCompile Include="Engine\Replication\FastArraySerializer.cpp" />
|
||||
<ClCompile Include="Engine\Events\ProcessEventDispatcher.cpp" />
|
||||
<ClCompile Include="Engine\EngineCore.cpp" />
|
||||
<!-- STW -->
|
||||
<ClCompile Include="STW\GameMode\STWGameMode.cpp" />
|
||||
<ClCompile Include="STW\Missions\MissionManager.cpp" />
|
||||
<ClCompile Include="STW\Missions\MissionObjective.cpp" />
|
||||
<ClCompile Include="STW\Player\STWPlayerController.cpp" />
|
||||
<ClCompile Include="STW\Player\STWPlayerPawn.cpp" />
|
||||
<ClCompile Include="STW\Inventory\InventoryManager.cpp" />
|
||||
<ClCompile Include="STW\Building\BuildingManager.cpp" />
|
||||
<!-- Entry -->
|
||||
<ClCompile Include="Entry\DllMain.cpp" />
|
||||
</ItemGroup>
|
||||
<!-- Header Files -->
|
||||
<ItemGroup>
|
||||
<!-- Core -->
|
||||
<ClInclude Include="Core\Common.h" />
|
||||
<ClInclude Include="Core\Logging\Log.h" />
|
||||
<ClInclude Include="Core\Memory\Memory.h" />
|
||||
<ClInclude Include="Core\Memory\PatternScanner.h" />
|
||||
<ClInclude Include="Core\Versioning\VersionInfo.h" />
|
||||
<ClInclude Include="Core\Versioning\VersionResolver.h" />
|
||||
<ClInclude Include="Core\Hooks\HookTypes.h" />
|
||||
<!-- Engine -->
|
||||
<ClInclude Include="Engine\CoreTypes\ObjectArray.h" />
|
||||
<ClInclude Include="Engine\CoreTypes\NamePool.h" />
|
||||
<ClInclude Include="Engine\CoreTypes\OffsetResolver.h" />
|
||||
<ClInclude Include="Engine\UObject\UObjectWrapper.h" />
|
||||
<ClInclude Include="Engine\Reflection\PropertyIterator.h" />
|
||||
<ClInclude Include="Engine\Replication\FastArraySerializer.h" />
|
||||
<ClInclude Include="Engine\Events\ProcessEventDispatcher.h" />
|
||||
<ClInclude Include="Engine\EngineCore.h" />
|
||||
<!-- STW -->
|
||||
<ClInclude Include="STW\GameMode\STWGameMode.h" />
|
||||
<ClInclude Include="STW\Missions\MissionManager.h" />
|
||||
<ClInclude Include="STW\Missions\MissionObjective.h" />
|
||||
<ClInclude Include="STW\Missions\MissionTypes.h" />
|
||||
<ClInclude Include="STW\Player\STWPlayerController.h" />
|
||||
<ClInclude Include="STW\Player\STWPlayerPawn.h" />
|
||||
<ClInclude Include="STW\Inventory\InventoryManager.h" />
|
||||
<ClInclude Include="STW\Inventory\InventoryTypes.h" />
|
||||
<ClInclude Include="STW\Building\BuildingManager.h" />
|
||||
<ClInclude Include="STW\Building\BuildingTypes.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,212 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!-- Filter Definitions -->
|
||||
<ItemGroup>
|
||||
<Filter Include="Core">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Core\Logging">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Core\Memory">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Core\Versioning">
|
||||
<UniqueIdentifier>{A3C2E5D1-9B8F-4C7E-B6A5-D4F3E2C1B0A9}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Core\Hooks">
|
||||
<UniqueIdentifier>{B4D3C2E1-0A9F-5D8E-C7B6-E5F4D3C2B1A0}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Engine">
|
||||
<UniqueIdentifier>{C5E4D3F2-1B0A-6E9F-D8C7-F6E5D4C3B2A1}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Engine\CoreTypes">
|
||||
<UniqueIdentifier>{D6F5E4G3-2C1B-7F0A-E9D8-G7F6E5D4C3B2}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Engine\UObject">
|
||||
<UniqueIdentifier>{E7G6F5H4-3D2C-8G1B-F0E9-H8G7F6E5D4C3}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Engine\Reflection">
|
||||
<UniqueIdentifier>{F8H7G6I5-4E3D-9H2C-G1F0-I9H8G7F6E5D4}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Engine\Replication">
|
||||
<UniqueIdentifier>{A9I8H7J6-5F4E-0I3D-H2G1-J0I9H8G7F6E5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Engine\Events">
|
||||
<UniqueIdentifier>{B0J9I8K7-6G5F-1J4E-I3H2-K1J0I9H8G7F6}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="STW">
|
||||
<UniqueIdentifier>{C1K0J9L8-7H6G-2K5F-J4I3-L2K1J0I9H8G7}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="STW\GameMode">
|
||||
<UniqueIdentifier>{D2L1K0M9-8I7H-3L6G-K5J4-M3L2K1J0I9H8}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="STW\Missions">
|
||||
<UniqueIdentifier>{E3M2L1N0-9J8I-4M7H-L6K5-N4M3L2K1J0I9}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="STW\Player">
|
||||
<UniqueIdentifier>{F4N3M2O1-0K9J-5N8I-M7L6-O5N4M3L2K1J0}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="STW\Inventory">
|
||||
<UniqueIdentifier>{G5O4N3P2-1L0K-6O9J-N8M7-P6O5N4M3L2K1}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="STW\Building">
|
||||
<UniqueIdentifier>{H6P5O4Q3-2M1L-7P0K-O9N8-Q7P6O5N4M3L2}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Entry">
|
||||
<UniqueIdentifier>{I7Q6P5R4-3N2M-8Q1L-P0O9-R8Q7P6O5N4M3}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Source Files -->
|
||||
<ItemGroup>
|
||||
<!-- Core -->
|
||||
<ClCompile Include="Core\Logging\Log.cpp">
|
||||
<Filter>Core\Logging</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Core\Memory\Memory.cpp">
|
||||
<Filter>Core\Memory</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Core\Versioning\VersionResolver.cpp">
|
||||
<Filter>Core\Versioning</Filter>
|
||||
</ClCompile>
|
||||
|
||||
<!-- Engine -->
|
||||
<ClCompile Include="Engine\CoreTypes\ObjectArray.cpp">
|
||||
<Filter>Engine\CoreTypes</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Engine\CoreTypes\NamePool.cpp">
|
||||
<Filter>Engine\CoreTypes</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Engine\CoreTypes\OffsetResolver.cpp">
|
||||
<Filter>Engine\CoreTypes</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Engine\UObject\UObjectWrapper.cpp">
|
||||
<Filter>Engine\UObject</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Engine\Reflection\PropertyIterator.cpp">
|
||||
<Filter>Engine\Reflection</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Engine\Replication\FastArraySerializer.cpp">
|
||||
<Filter>Engine\Replication</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Engine\Events\ProcessEventDispatcher.cpp">
|
||||
<Filter>Engine\Events</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Engine\EngineCore.cpp">
|
||||
<Filter>Engine</Filter>
|
||||
</ClCompile>
|
||||
|
||||
<!-- STW -->
|
||||
<ClCompile Include="STW\GameMode\STWGameMode.cpp">
|
||||
<Filter>STW\GameMode</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="STW\Missions\MissionManager.cpp">
|
||||
<Filter>STW\Missions</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="STW\Missions\MissionObjective.cpp">
|
||||
<Filter>STW\Missions</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="STW\Player\STWPlayerController.cpp">
|
||||
<Filter>STW\Player</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="STW\Player\STWPlayerPawn.cpp">
|
||||
<Filter>STW\Player</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="STW\Inventory\InventoryManager.cpp">
|
||||
<Filter>STW\Inventory</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="STW\Building\BuildingManager.cpp">
|
||||
<Filter>STW\Building</Filter>
|
||||
</ClCompile>
|
||||
|
||||
<!-- Entry -->
|
||||
<ClCompile Include="Entry\DllMain.cpp">
|
||||
<Filter>Entry</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Header Files -->
|
||||
<ItemGroup>
|
||||
<!-- Core -->
|
||||
<ClInclude Include="Core\Common.h">
|
||||
<Filter>Core</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Core\Logging\Log.h">
|
||||
<Filter>Core\Logging</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Core\Memory\Memory.h">
|
||||
<Filter>Core\Memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Core\Memory\PatternScanner.h">
|
||||
<Filter>Core\Memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Core\Versioning\VersionInfo.h">
|
||||
<Filter>Core\Versioning</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Core\Versioning\VersionResolver.h">
|
||||
<Filter>Core\Versioning</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Core\Hooks\HookTypes.h">
|
||||
<Filter>Core\Hooks</Filter>
|
||||
</ClInclude>
|
||||
|
||||
<!-- Engine -->
|
||||
<ClInclude Include="Engine\CoreTypes\ObjectArray.h">
|
||||
<Filter>Engine\CoreTypes</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Engine\CoreTypes\NamePool.h">
|
||||
<Filter>Engine\CoreTypes</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Engine\CoreTypes\OffsetResolver.h">
|
||||
<Filter>Engine\CoreTypes</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Engine\UObject\UObjectWrapper.h">
|
||||
<Filter>Engine\UObject</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Engine\Reflection\PropertyIterator.h">
|
||||
<Filter>Engine\Reflection</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Engine\Replication\FastArraySerializer.h">
|
||||
<Filter>Engine\Replication</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Engine\Events\ProcessEventDispatcher.h">
|
||||
<Filter>Engine\Events</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Engine\EngineCore.h">
|
||||
<Filter>Engine</Filter>
|
||||
</ClInclude>
|
||||
|
||||
<!-- STW -->
|
||||
<ClInclude Include="STW\GameMode\STWGameMode.h">
|
||||
<Filter>STW\GameMode</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="STW\Missions\MissionManager.h">
|
||||
<Filter>STW\Missions</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="STW\Missions\MissionObjective.h">
|
||||
<Filter>STW\Missions</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="STW\Missions\MissionTypes.h">
|
||||
<Filter>STW\Missions</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="STW\Player\STWPlayerController.h">
|
||||
<Filter>STW\Player</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="STW\Player\STWPlayerPawn.h">
|
||||
<Filter>STW\Player</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="STW\Inventory\InventoryManager.h">
|
||||
<Filter>STW\Inventory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="STW\Inventory\InventoryTypes.h">
|
||||
<Filter>STW\Inventory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="STW\Building\BuildingManager.h">
|
||||
<Filter>STW\Building</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="STW\Building\BuildingTypes.h">
|
||||
<Filter>STW\Building</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
||||
Vendored
+1217
File diff suppressed because it is too large
Load Diff
Vendored
+185
@@ -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 <windows.h>
|
||||
|
||||
// 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
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Reference in New Issue
Block a user