first commit

This commit is contained in:
ApfelTeeSaft
2026-02-01 11:33:54 +01:00
parent 9d83c843f6
commit 6a3594675b
54 changed files with 15429 additions and 0 deletions
+233
View File
@@ -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
View File
@@ -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__)
}
+376
View File
@@ -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();
}
}
+167
View File
@@ -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);
}
}
+58
View File
@@ -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;
};
}
+171
View File
@@ -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;
}
}
+114
View File
@@ -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;
}
}
+1
View File
@@ -0,0 +1 @@
// TODO: @timmie replace with ur offset finder or whatever lol
+111
View File
@@ -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";
}
}
};
}
+573
View File
@@ -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;
}
}
+82
View File
@@ -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();
}
}
+330
View File
@@ -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>();
}
}
}
+168
View File
@@ -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();
}
+286
View File
@@ -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>();
}
}
}
+150
View File
@@ -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();
}
+319
View File
@@ -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;
}
}
+246
View File
@@ -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();
}
}
+465
View File
@@ -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();
}
}
+138
View File
@@ -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();
}
}
+492
View File
@@ -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;
}
}
+403
View File
@@ -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;
}
}
+620
View File
@@ -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;
}
}
+374
View File
@@ -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();
}
+566
View File
@@ -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));
}
}
}
+329
View File
@@ -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;
};
}
+548
View File
@@ -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;
}
}
+193
View File
@@ -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;
};
}
+336
View File
@@ -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;
}
+3
View File
@@ -0,0 +1,3 @@
# UniversalSlashingSimulator
## WIP Universal STW Gameserver
+830
View File
@@ -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;
}
}
+322
View File
@@ -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();
}
+382
View File
@@ -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;
}
}
+374
View File
@@ -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]));
}
}
+192
View File
@@ -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";
}
}
}
+639
View File
@@ -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;
}
}
+137
View File
@@ -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();
}
+273
View File
@@ -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)
{}
};
}
+425
View File
@@ -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);
}
}
+99
View File
@@ -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;
};
}
+321
View File
@@ -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);
}
}
}
+150
View File
@@ -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);
}
+316
View File
@@ -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";
}
}
}
+397
View File
@@ -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);
}
}
+190
View File
@@ -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();
}
}
+400
View File
@@ -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();
}
}
}
}
+209
View File
@@ -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;
};
}
+24
View File
@@ -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
+168
View File
@@ -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>
+212
View File
@@ -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>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>
+1217
View File
File diff suppressed because it is too large Load Diff
+185
View File
@@ -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
Binary file not shown.
Binary file not shown.