Compare commits

...

7 commits

Author SHA1 Message Date
ad04bd9eb8 mas subidas con correcciones 2026-01-12 00:40:15 +01:00
e99cfe5c24 sibir hook.h y gameprt.h modificados 2026-01-12 00:34:02 +01:00
7d805879b4 Merge branch 'main' of https://github.com/Jous99/F4MP 2026-01-12 00:30:39 +01:00
ac8000d63d update main.cc 2026-01-12 00:30:36 +01:00
Jous99
57626d2645
Simplify legal disclaimer in README
Removed redundant legal disclaimer section.
2026-01-12 00:20:02 +01:00
Jous99
ecc13f6d64
Update roadmap link to point to PHP version 2026-01-12 00:19:23 +01:00
Jous99
018b413598
Revise README for F4MP project details and structure
Updated project description and features for clarity and detail.
2026-01-12 00:17:46 +01:00
10 changed files with 614 additions and 58 deletions

View file

@ -3,5 +3,5 @@
<Platform Name="x64" /> <Platform Name="x64" />
<Platform Name="x86" /> <Platform Name="x86" />
</Configurations> </Configurations>
<Project Path="F4MP_Client.vcxproj" /> <Project Path="F4MP_Client.vcxproj" Id="93aa22ac-17c4-420a-9ce3-16430bf1ace3" />
</Solution> </Solution>

View file

@ -113,6 +113,7 @@
<LanguageStandard>stdcpp20</LanguageStandard> <LanguageStandard>stdcpp20</LanguageStandard>
<PrecompiledHeader>Use</PrecompiledHeader> <PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
@ -131,6 +132,7 @@
<LanguageStandard>stdcpp20</LanguageStandard> <LanguageStandard>stdcpp20</LanguageStandard>
<PrecompiledHeader>Use</PrecompiledHeader> <PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>

View file

@ -0,0 +1,126 @@
#ifndef COMMON_EXCEPTIONS_H
#define COMMON_EXCEPTIONS_H
#include <Windows.h>
#include <stdexcept>
namespace Exceptions {
namespace Core {
namespace Exceptions {
class RuntimeException : public std::runtime_error
{
public:
explicit RuntimeException(const std::string &msg) :
std::runtime_error(msg.c_str())
{ }
explicit RuntimeException(const char *msg) :
std::runtime_error(msg)
{ }
};
class DetourException : public RuntimeException
{
public:
explicit DetourException(const std::string &msg) :
RuntimeException(msg.c_str())
{ }
explicit DetourException(const char *msg) :
RuntimeException(msg)
{ }
};
class GenericWinAPIException : public std::runtime_error
{
DWORD last_error_;
public:
explicit GenericWinAPIException(const std::string &msg) :
std::runtime_error(msg.c_str()), last_error_(GetLastError())
{ }
explicit GenericWinAPIException(const char *msg) :
std::runtime_error(msg), last_error_(GetLastError())
{ }
virtual DWORD get_last_error() const
{
return last_error_;
}
};
class ModuleNotFoundException : public GenericWinAPIException
{
public:
explicit ModuleNotFoundException(const std::string &msg) :
GenericWinAPIException(msg)
{ }
explicit ModuleNotFoundException(const char *msg) :
GenericWinAPIException(msg)
{ }
};
class ProcAddressNotFoundException : public GenericWinAPIException
{
public:
explicit ProcAddressNotFoundException(const std::string &msg) :
GenericWinAPIException(msg)
{ }
explicit ProcAddressNotFoundException(const char *msg) :
GenericWinAPIException(msg)
{ }
};
class COMInterfaceException : public RuntimeException
{
HRESULT hresult_;
public:
explicit COMInterfaceException(const std::string &msg, HRESULT result) :
RuntimeException(msg), hresult_(result)
{ }
explicit COMInterfaceException(const char *msg, HRESULT result) :
RuntimeException(msg), hresult_(result)
{ }
HRESULT hresult() const
{
return hresult_;
}
};
class DXAPIException : public COMInterfaceException
{
public:
explicit DXAPIException(const std::string &msg, HRESULT result) :
COMInterfaceException(msg, result)
{ }
explicit DXAPIException(const char *msg, HRESULT result) :
COMInterfaceException(msg, result)
{ }
};
class ARCException : public COMInterfaceException
{
public:
explicit ARCException(const std::string &msg, HRESULT result) :
COMInterfaceException(msg, result)
{ }
explicit ARCException(const char *msg, HRESULT result) :
COMInterfaceException(msg, result)
{ }
};
};
};
};
#endif

View file

@ -0,0 +1,81 @@
#ifndef COMMON_GAMEPTR_H
#define COMMON_GAMEPTR_H
#include <Windows.h>
#include <utility>
#include <cstdint>
namespace Memory {
class GamePtr_Manager {
public:
GamePtr_Manager();
static uintptr_t s_baseAddress;
};
template <typename T>
class GamePtr {
public :
GamePtr(uintptr_t offset) : offset(offset + Memory::GamePtr_Manager::s_baseAddress){}
T * GetPtr() const {
return reinterpret_cast <T *>(offset);
}
T * operator->() const
{
return GetPtr();
}
operator T *() const
{
return GetPtr();
}
bool operator!() const {
return !GetPtr();
}
const T * GetConstPtr() const {
return reinterpret_cast <T *>(offset);
}
uintptr_t GetUIntPtr() const
{
return offset;
}
private:
uintptr_t offset;
GamePtr();
GamePtr(GamePtr & rhs);
GamePtr & operator=(GamePtr & rhs);
};
template <typename T>
class GameAddr{
public:
GameAddr(uintptr_t offset) : offset(reinterpret_cast<ConversionType *>(offset + Memory::GamePtr_Manager::s_baseAddress)){}
operator T() {
return reinterpret_cast<T>(offset);
}
uintptr_t GetUIntPtr(){
return reinterpret_cast<uintptr_t >(offset);
}
private:
struct ConversionType {};
ConversionType * offset;
GameAddr();
GameAddr(GameAddr & rhs);
GameAddr & operator=(GameAddr & rhs);
};
}
#endif

View file

@ -0,0 +1,122 @@
#ifndef COMMON_HOOK_H
#define COMMON_HOOK_H
#include <Windows.h>
#include <cstdint>
#include "detours.h" // Asegúrate de tener detours.lib en tu proyecto
#include "Types.h"
#include "Exceptions.h"
namespace Hooks {
enum class CallConvention
{
stdcall_t,
cdecl_t,
fastcall_t // Añadido fastcall por si interceptas funciones internas del engine
};
// Helper para definir la convención de llamada
template <CallConvention cc, typename retn, typename ...args>
struct convention;
template <typename retn, typename ...args>
struct convention<CallConvention::stdcall_t, retn, args...>
{
typedef retn (__stdcall *type)(args ...);
};
template <typename retn, typename ...args>
struct convention<CallConvention::cdecl_t, retn, args...>
{
typedef retn (__cdecl *type)(args ...);
};
// Fallout 4 usa mucho fastcall (__rcall en x64)
template <typename retn, typename ...args>
struct convention<CallConvention::fastcall_t, retn, args...>
{
typedef retn (__fastcall *type)(args ...);
};
template <CallConvention cc, typename retn, typename ...args>
class Hook
{
typedef typename convention<cc, retn, args...>::type type;
uintptr_t orig_; // Usamos uintptr_t para compatibilidad x64
type detour_;
bool is_applied_;
bool has_open_transaction_;
void transaction_begin()
{
if (DetourTransactionBegin() != NO_ERROR)
throw Exceptions::Core::Exceptions::DetourException("A pending transaction already exists");
has_open_transaction_ = true;
}
void transaction_commit()
{
if (DetourTransactionCommit() != NO_ERROR)
throw Exceptions::Core::Exceptions::DetourException("Error committing detour transaction");
has_open_transaction_ = false;
}
static void update_thread(HANDLE hThread)
{
DetourUpdateThread(hThread);
}
public:
Hook() : orig_(0), detour_(0), is_applied_(false), has_open_transaction_(false) {}
~Hook()
{
if (has_open_transaction_) DetourTransactionAbort();
remove();
}
// pFunc ahora acepta uintptr_t para que funcione con GameAddr::GetUIntPtr()
void apply(uintptr_t pFunc, type detour)
{
if (is_applied_) return;
detour_ = detour;
orig_ = pFunc;
transaction_begin();
update_thread(GetCurrentThread());
// Detours requiere un puntero al puntero de la función original
DetourAttach(reinterpret_cast<void **>(&orig_), reinterpret_cast<void *>(detour_));
transaction_commit();
is_applied_ = true;
}
void remove()
{
if (!is_applied_) return;
transaction_begin();
update_thread(GetCurrentThread());
DetourDetach(reinterpret_cast<void **>(&orig_), reinterpret_cast<void *>(detour_));
transaction_commit();
is_applied_ = false;
}
// Esta es la función que llamas desde tu lambda en main.cpp
retn call_orig(args ... p)
{
return reinterpret_cast<type>(orig_)(p...);
}
uintptr_t get_orig() const { return orig_; }
bool is_applied() const { return is_applied_; }
};
}
#endif

View file

@ -0,0 +1,25 @@
#ifndef COMMON_TYPES_H
#define COMMON_TYPES_H
namespace Types {
// Enteros sin signo (Unsigned)
typedef unsigned char UInt8; // 1 byte
typedef unsigned short UInt16; // 2 bytes
typedef unsigned int UInt32; // 4 bytes (Cambiado long por int para asegurar 32bits)
typedef unsigned long long UInt64; // 8 bytes
// Enteros con signo (Signed)
typedef signed char SInt8;
typedef signed short SInt16;
typedef signed int SInt32;
typedef signed long long SInt64;
// Números de coma flotante
typedef float Float32;
typedef double Float64;
// Alias útiles para punteros de memoria
typedef uintptr_t RawAddr;
}
#endif

View file

@ -1,36 +1,79 @@
#include "f4se/PluginAPI.h" #include "Global.h"
#include <shlobj.h> #include "DirectXHook.h"
#include <common/include/GamePtr.h>
#include <common/include/Utilities.h>
#include <common/include/Types.h>
#include <common/include/Hook.h>
// Información del Mod // --- OFFSETS ACTUALIZADOS PARA FALLOUT 4 NEXT-GEN (v1.10.984) ---
IDebugLog gLog;
PluginHandle g_pluginHandle = kPluginHandle_Invalid;
F4SEMessagingInterface* g_messaging = NULL;
extern "C" { // v1.10.163 era 0x01262EC0 -> Next-Gen es 0x012B9F80 (VPrint)
// Esta función la llama F4SE al arrancar para ver si el mod es compatible static Memory::GameAddr <void> printAddr(0x012B9F80);
bool F4SEPlugin_Query(const F4SEInterface* f4se, PluginInfo* info) {
gLog.OpenRelative(CSIDL_MYDOCUMENTS, "\\My Games\\Fallout4\\F4SE\\F4MP_Project.log");
// Datos del mod static Hooks::Hook<Hooks::CallConvention::cdecl_t, void, const char *, va_list> printHook;
info->infoVersion = PluginInfo::kInfoVersion;
info->name = "F4MP_Project";
info->version = 1;
if (f4se->runtimeVersion != RUNTIME_VERSION_1_10_163) { class ConsoleManager
_MESSAGE("ERROR: Version de juego no compatible."); {
return false; public:
} MEMBER_FN_PREFIX(ConsoleManager);
// Direcciones actualizadas para la clase ConsoleManager
return true; DEFINE_MEMBER_FN(VPrint, void, 0x012B9F80, const char * fmt, va_list args);
} DEFINE_MEMBER_FN(Print, void, 0x012BA010, const char * str);
// Esta función se ejecuta cuando el mod se carga oficialmente
bool F4SEPlugin_Load(const F4SEInterface* f4se) {
_MESSAGE("F4MP: Protocolo de terminal cargado correctamente.");
g_pluginHandle = f4se->GetPluginHandle();
// Aquí es donde en el futuro "engancharemos" los datos
return true;
}
}; };
// Offsets de punteros globales (Data Segment)
// g_console: era 0x058E0AE0 -> Next-Gen es 0x059489A0
static Memory::GamePtr<ConsoleManager *> g_console(0x059489A0);
// g_consoleHandle: era 0x05ADB4A8 -> Next-Gen es 0x05B472C8
static Memory::GameAddr<Types::UInt32*> g_consoleHandle(0x05B472C8);
// ----------------------------------------------------------------
void Console_Print(const char * fmt, ...)
{
ConsoleManager * mgr = *g_console;
if(mgr)
{
va_list args;
va_start(args, fmt);
CALL_MEMBER_FN(mgr, VPrint)(fmt, args);
va_end(args);
}
}
void testPrint(const char * fmt, ...){
// Función de prueba, no requiere cambios de memoria
va_list args;
va_start(args,fmt);
va_end(args);
}
DWORD WINAPI Main(LPVOID lpThreadParameter){
// LOGGING
AllocConsole();
freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);
auto console = spdlog::stdout_color_mt("console");
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("f4mp_logger", "logs/f4mp.txt");
spdlog::set_default_logger(async_file);
spdlog::get("console")->info("F4MP Console Loaded (Next-Gen Version)");
Hooks::DirectX::Init();
// Hook a la función de impresión de consola del juego
printHook.apply(printAddr.GetUIntPtr(), [](const char * fmt, va_list args) -> void {
// Nota: args es un va_list, imprimirlo directamente con std::cout puede dar basura o crash
// En una terminal real usarías vsnprintf para formatear el mensaje
std::cout << "[GAME_CONSOLE] " << fmt << std::endl;
return printHook.call_orig(fmt, args);
});
Console_Print("F4MP: SISTEMA INICIADO EN NEXT-GEN");
Console_Print("F4MP: COMPROBANDO MEMORIA...");
return TRUE;
}
// ... Resto del código (Detach y DllMain) se mantiene igual ...

45
Client/src/DirectXHook.h Normal file
View file

@ -0,0 +1,45 @@
#ifndef H_DIRECT3D11
#define H_DIRECT3D11
#include <d3d11.h>
#include <vector>
namespace hDirect3D11 {
class Direct3D11 {
public:
std::vector<uintptr_t> vtable_methods;
Direct3D11() {
ID3D11Device* pDevice = nullptr;
ID3D11DeviceContext* pContext = nullptr;
IDXGISwapChain* pSwapChain = nullptr;
D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0;
DXGI_SWAP_CHAIN_DESC scd = {};
scd.BufferCount = 1;
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
scd.OutputWindow = GetForegroundWindow();
scd.SampleDesc.Count = 1;
scd.Windowed = TRUE;
scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
if (SUCCEEDED(D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, &featureLevel, 1, D3D11_SDK_VERSION, &scd, &pSwapChain, &pDevice, nullptr, &pContext))) {
uintptr_t* vtable = *(uintptr_t**)pSwapChain;
for (int i = 0; i < 150; i++) {
vtable_methods.push_back(vtable[i]);
}
pSwapChain->Release();
pDevice->Release();
pContext->Release();
}
}
uintptr_t* vtable() { return vtable_methods.data(); }
};
}
namespace hDXGI {
enum Index { Present = 8 };
}
#endif

View file

@ -0,0 +1,83 @@
#include "DirectXHook.h"
#include <mutex>
// Definición de variables globales
HWND g_windowHandle = NULL;
ID3D11Device* g_d3d11Device = NULL;
ID3D11DeviceContext* g_d3d11Context = NULL;
IDXGISwapChain* g_d3d11SwapChain = NULL;
ID3D11RenderTargetView* g_mainRenderTargetView = NULL;
bool g_ShowMenu = false;
bool g_Initialized = false;
namespace Hooks {
namespace DirectX {
void Init() {
try {
auto d3d11 = std::make_unique<hDirect3D11::Direct3D11>();
uintptr_t* vtable = d3d11->vtable();
spdlog::info("[F4MP] Hooking DXGI Present...");
swapChainPresent11Hook.apply(vtable[hDXGI::Present], [](IDXGISwapChain* chain, UINT SyncInterval, UINT Flags) -> HRESULT {
static std::once_flag flag;
std::call_once(flag, [&chain]() {
g_d3d11SwapChain = chain;
if (SUCCEEDED(chain->GetDevice(__uuidof(ID3D11Device), (void**)&g_d3d11Device))) {
Pre_Render(chain);
g_Initialized = true;
}
});
Render();
return swapChainPresent11Hook.call_orig(chain, SyncInterval, Flags);
});
}
catch (const std::exception& e) {
spdlog::error("[F4MP] DX11 Hook Error: {}", e.what());
}
}
void Pre_Render(IDXGISwapChain* swapChain) {
swapChain->GetDevice(__uuidof(ID3D11Device), (void**)&g_d3d11Device);
g_d3d11Device->GetImmediateContext(&g_d3d11Context);
DXGI_SWAP_CHAIN_DESC sd;
swapChain->GetDesc(&sd);
g_windowHandle = sd.OutputWindow;
// Hook al WndProc para el ratón
OriginalWndProcHandler = (WNDPROC)SetWindowLongPtr(g_windowHandle, GWLP_WNDPROC, (LONG_PTR)hWndProc);
ImGui::CreateContext();
ImGui_ImplWin32_Init(g_windowHandle);
ImGui_ImplDX11_Init(g_d3d11Device, g_d3d11Context);
ID3D11Texture2D* pBackBuffer;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
g_d3d11Device->CreateRenderTargetView(pBackBuffer, NULL, &g_mainRenderTargetView);
pBackBuffer->Release();
}
void Render() {
if (!g_Initialized) return;
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
if (g_ShowMenu) {
ImGui::Begin("F4MP Next-Gen Console", &g_ShowMenu);
ImGui::Text("F4MP Experimental Multiplayer Mod");
ImGui::Separator();
if (ImGui::Button("Unload Mod")) { /* Lógica de salida */ }
ImGui::End();
}
ImGui::Render();
g_d3d11Context->OMSetRenderTargets(1, &g_mainRenderTargetView, NULL);
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}
}
}

View file

@ -1,38 +1,67 @@
# Project F4MP - Fallout 4 Multiplayer Mod # F4MP - Fallout 4 Multiplayer Project 🚀
<p align="center"> <p align="center">
<img src="logo.png" alt="Project F4MP Logo" width="400"> <img src="/logo.png" alt="F4MP Logo" width="300">
<br>
<i>Reconstruyendo la Commonwealth, un paquete de datos a la vez.</i>
</p> </p>
![Game](https://img.shields.io/badge/Game-Fallout%204-brightgreen?style=for-the-badge&logo=bethesda)
![Status](https://img.shields.io/badge/Status-Research_&_Development-orange?style=for-the-badge)
![Platform](https://img.shields.io/badge/Platform-PC-blue?style=for-the-badge)
--- ---
## ☢️ Sobre el Proyecto ## 📝 Descripción del Proyecto
**Project F4MP** es una ambiciosa modificación para **Fallout 4** que introduce capacidades multijugador en el Commonwealth. Explora el yermo, completa misiones y sobrevive a los peligros post-apocalípticos junto a tus amigos.
## ✨ Características Principales **F4MP** es un ambicioso proyecto de código abierto que busca implementar una infraestructura multijugador robusta para **Fallout 4**. A diferencia de otros intentos, F4MP se centra en la creación de un sistema de sincronización basado en un servidor maestro que permita la persistencia de datos, el combate cooperativo y la construcción de asentamientos compartidos.
* **Sincronización en Tiempo Real:** Jugadores, NPCs y clima sincronizados.
* **Mundo Persistente:** Servidores dedicados para una experiencia continua.
* **Compatibilidad:** Diseñado para trabajar con una amplia selección de mods de la comunidad.
* **Estilo Pip-Boy:** Interfaz integrada fiel a la estética de Bethesda.
## 🚀 Instalación Este proyecto es de carácter **educativo y sin ánimo de lucro**, desarrollado por y para la comunidad de entusiastas de la saga.
1. Descarga la última versión desde la sección de [Releases](../../releases).
2. Extrae el contenido en tu carpeta raíz de **Fallout 4**.
3. Ejecuta `F4MP_Launcher.exe`.
4. ¡Conéctate a un servidor y empieza a jugar!
## 🛠️ Requisitos
* Fallout 4 (Versión de Steam/GOG actualizada).
* F4SE (Fallout 4 Script Extender).
* Una conexión a internet estable.
## 🤝 Contribuir
¿Quieres ayudar a reconstruir el mundo? ¡Las contribuciones son bienvenidas!
* Reporta bugs en el apartado de **Issues**.
* Propón nuevas ideas en **Discussions**.
* Revisa nuestro sitio web oficial: [f4mp.joustech.space](https://f4mp.joustech.space/)
--- ---
<p align="center">
Hecho con ❤️ por la comunidad de Project F4MP. ## 🔬 Fase Actual: Investigación y Análisis (R&D)
</p>
Actualmente, el repositorio **no contiene binarios ejecutables**. Nos encontramos en una fase de ingeniería inversa profunda para asegurar que la base del mod sea estable antes de cualquier lanzamiento público.
### Objetivos de Investigación Crítica:
* **Sincronización de Transformaciones:** Mapeo de vectores de posición y rotación de entidades en el Creation Engine.
* **Hooking de Memoria:** Implementación de interceptores para acciones de combate (VATS, disparo, daño recibido).
* **World State Sync:** Análisis de la persistencia de objetos soltados y cambios en el entorno (Cells).
* **Protocolo de Red:** Desarrollo de una capa de transporte híbrida UDP/TCP para minimizar la latencia en el desierto capital.
---
## ⚙️ Arquitectura del Sistema
El ecosistema F4MP se compone de tres pilares tecnológicos:
1. **F4MP Client Core:** Un inyector desarrollado en C++ que actúa como puente entre el motor del juego y nuestra red.
2. **Master Server:** Backend escalable encargado de la validación de usuarios, gestión de instancias y retransmisión de estados.
3. **Terminal de Control Web:** Interfaz de usuario para la gestión de residentes y monitorización del sistema.
---
## 🌐 Seguimiento y Progreso
Para evitar la fragmentación de la información, el progreso detallado de cada fase se publica exclusivamente en nuestra terminal oficial. Allí podrás ver el estado de los módulos de investigación y los hitos alcanzados.
👉 **[CONSULTAR ROADMAP OFICIAL EN LA WEB](https://f4mp.joustech.space/roadmap.php)**
---
## 🤝 Cómo contribuir
Si tienes conocimientos en **ingeniería inversa, C++, Assembly (x64)** o **protocolos de red**, tu ayuda es bienvenida.
1. Haz un **Fork** del proyecto.
2. Crea una rama para tu investigación (`git checkout -b feature/investigacion-x`).
3. Abre un **Pull Request** detallando tus hallazgos en la memoria del juego.
---
## ⚖️ Aviso Legal (Disclaimer)
F4MP es un proyecto independiente y no está afiliado a Bethesda Softworks ni ZeniMax Media. El uso de este software es bajo tu propio riesgo y requiere una copia legal de Fallout 4. Todos los nombres y marcas registradas pertenecen a sus respectivos dueños.