当前位置: 首页 > news >正文

26.UE-游戏逆向-绘制骨骼编号

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!

本次游戏没法给

内容参考于:微尘网络安全

上一个内容:25.UE-游戏逆向-获取GetBoneMatrix(根据骨骼索引得到骨骼矩阵)

如下图,GetBoneMatrix函数是在USkeletalMeshComponent类中,然后USkeletalMeshComponent在ACharacter中的0x280位置,ACharacter是骨骼类,所以现在就知道在调用GetBoneMatrix函数时它的this传什么了

第二个参数是OWORD*类型,然后返回值也是,所以这里推断,第二个参数和返回值是同一类型

然后UE4源码中GetBoneMatrix返回了一个FMatrix类型,所以上方的OWORD*应该是FMatrix*类型

然后是索引的值,这就要知道骨骼的数量,数量在之前找骨骼坐标的位置,下图红框的就是骨骼数量0x4B8位置

效果图:绘制出身上所有的骨骼,骨骼的顺序现在是没有顺序的,比如怪物1腰部的序号是30,然后怪物2腰部的序号可能会是40,如果想绘制火柴人需要进行排序

完整代码

// 引入主头文件(包含基础定义、函数声明等)
#include "main.h"// 查找UE游戏窗口(类名固定为"UnrealWindow",用于后续UI绘制和消息处理)
HWND hWnd = FindWindowA("UnrealWindow", NULL);// DirectX 11渲染组件(用于在游戏画面上叠加UI)
static ID3D11Device* g_pd3dDevice = nullptr;               // DX11设备(创建渲染资源)
static ID3D11DeviceContext* g_pd3dDeviceContext = nullptr; // DX11设备上下文(执行渲染命令)
static IDXGISwapChain* g_pSwapChain = nullptr;              // 交换链(将画面显示到窗口)
static ID3D11RenderTargetView* g_mainRenderTargetView = nullptr; // 渲染目标(UI绘制的"画布")
DWORD64* VirtaulTable;  // 交换链虚函数表(用于Hook渲染函数)// 游戏模块基地址和GWorld(游戏世界)地址
DWORD64 add = (DWORD64)GetModuleHandleA("xxx.exe"); // 游戏主程序在内存中的起始位置
DWORD64 GWorld = add + 0x4B48780; // GWorld(游戏世界核心指针)= 基地址 + 偏移(逆向得到)// 3D坐标结构体(存储游戏中3D位置,如角色、骨骼的X/Y/Z)
struct FVector
{float X;  // X轴坐标float Y;  // Y轴坐标float Z;  // Z轴坐标
};// 2D坐标结构体(存储屏幕上的位置,如UI、骨骼标签的位置)
struct FVector2D
{float X;  // 屏幕水平坐标float Y;  // 屏幕垂直坐标
};// 四维向量(继承3D向量,增加W分量,用于矩阵计算)
// 可以理解为"带额外信息的3D坐标",在矩阵中用于存储变换数据
struct FPlane : public FVector
{float W;  // 第4个分量(齐次坐标,用于3D变换计算)
};// 4x4矩阵(由4个四维向量组成)
// 作用:存储骨骼的旋转、缩放、平移信息(3D空间变换的核心工具)
struct FMatrix
{FPlane XPlane;  // 第1行(X轴相关的旋转/缩放)FPlane YPlane;  // 第2行(Y轴相关的旋转/缩放)FPlane ZPlane;  // 第3行(Z轴相关的旋转/缩放)FPlane WPlane;  // 第4行(平移信息:骨骼在世界中的位置)
};// 函数指针:将3D世界坐标转换为屏幕2D坐标(游戏内置功能)
typedef bool (WINAPI* FN_ProjectWorldToScreen)(void* Player, FVector& WorldLocation, FVector2D& ScreenLocation, bool bPlayerViewportRelative);// 函数指针:获取骨骼的矩阵数据(通过骨骼索引获取对应的变换矩阵)
// 参数:Player(骨骼所属的实体组件)、a(输出的矩阵)、BloneIndex(骨骼索引,0=第一个骨骼)
typedef FMatrix*(WINAPI* FN_GetBloneMatrix)(void* Player, FMatrix* a, int BloneIndex);// DirectX交换链的"窗口大小变化"函数指针(处理窗口缩放时的资源调整)
typedef HRESULT(STDMETHODCALLTYPE* ResizeBuffers)(IDXGISwapChain* This, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags);
ResizeBuffers MyResizeBuffers;  // 保存原始函数// DirectX交换链的"显示画面"函数指针(渲染最后一步,负责显示画面)
typedef HRESULT(STDMETHODCALLTYPE* Present)(IDXGISwapChain* This, UINT SyncInterval, UINT Flags);
Present MyPresent;  // 保存原始函数using namespace std;  // 简化代码(不用每次写"std::")// 字符串头部信息(存储字符串的基本属性)
struct FNameEntryHeader
{// 位域:用16位存储多个小信息(节省内存)uint16_t bIsWide : 1;       // 1位:是否为宽字符(1=支持中文,0=仅英文)uint16_t LowercaseProbeHash : 5;  // 5位:小写哈希(快速比较字符串)uint16_t Len : 10;               // 10位:字符串长度(最大1023个字符)
};// 字符串数据结构体(UE中存储字符串的方式)
struct FNameEntry
{FNameEntryHeader Header;  // 头部信息(长度、字符类型等)// 联合体:两种字符存储方式共用内存(按需使用,节省空间)union{char AnsiName[1024];    // 普通字符数组(存英文/数字,1字节/字符)wchar_t WideName[1024]; // 宽字符数组(存中文等,2字节/字符)};
};// GName:游戏的"全局名字池"(相当于一本字典,所有实体的名字都存在这里)
// 地址:游戏基地址 + 偏移0x49C40C0 + 0x10(逆向得到,每个游戏/版本不同)
uint8_t* GName = (uint8_t*)(add + 0x49C40C0 + 0x10);// 读取游戏内存中的"块地址"(GName按"块"存储名字,类似字典分章节)
// 参数:a = 要读取的内存地址
// 返回:读取到的块地址(该章节的起始位置)
uint8_t* MyReadProessMemory(uint8_t* a) {// 1. 找到游戏窗口(通过类名"UnrealWindow")HWND nhwnd = FindWindowA("UnrealWindow", 0);  // 0表示不限制窗口标题// 2. 获取游戏进程ID(每个运行的程序都有唯一ID,类似身份证)DWORD pid = 0;GetWindowThreadProcessId(nhwnd, &pid);  // 通过窗口句柄获取进程ID// 3. 打开游戏进程,获取读取内存的权限(类似拿到访问游戏内存的钥匙)HANDLE nhandle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);  // 申请所有权限// 4. 读取目标地址的8字节数据(64位系统中地址占8字节)uint8_t* buf;  // 存储读取到的地址ReadProcessMemory(nhandle, a, &buf, 8, NULL);  // 从游戏内存中读取数据return (uint8_t*)buf;  // 返回读取到的"块地址"
}// 读取游戏内存中的FNameEntry(包含字符串实际内容)
// 参数:a = FNameEntry在游戏内存中的地址
// 返回:读取到的字符串数据结构体
FNameEntry MyReadProessMemoryFNameEntry(FNameEntry* a) {// 1. 找到游戏窗口并获取进程ID(同上)HWND nhwnd = FindWindowA("UnrealWindow", 0);DWORD pid = 0;GetWindowThreadProcessId(nhwnd, &pid);// 2. 打开游戏进程HANDLE nhandle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);// 3. 读取整个FNameEntry结构体(包含头部和字符数组)FNameEntry buf;  // 存储读取到的结构体ReadProcessMemory(nhandle, a, &buf, sizeof(FNameEntry), NULL);  // 读取结构体大小的数据return buf;  // 返回读取到的字符串数据
}// 根据ID从GName中获取对应的名字(核心函数)
// 参数:id = 名字在GName中的唯一编号(0、1、2...)
// 返回:id对应的字符串(如"Zombie_BP"、"Player"等)
string GetName(int id) {// 1. 计算GName中"块"的地址(GName按"块"存储,每块存65536个名字)// id >> 16:取ID的高16位(作为块索引,确定是哪一块)// 每个块的地址占8字节,所以乘以8定位到块在GName中的位置uint8_t* a = MyReadProessMemory(GName + (id >> 16) * 8);// 2. 计算块内偏移(确定是块中的第几个名字)// id & 65535:取ID的低16位(65535=2^16-1,刚好取低16位)// 乘以2:原代码逻辑(实际应为结构体大小,这里简化)uint8_t* b = (uint8_t*)(2 * (id & 65535));// 3. 计算具体名字的地址并读取数据// a(块地址) + b(块内偏移)= 该ID对应的名字数据地址FNameEntry info = MyReadProessMemoryFNameEntry((FNameEntry*)((DWORD64)a + b));// 4. 从结构体中提取字符串(按头部的长度截取有效字符)return string(info.AnsiName, info.Header.Len);
}// 自定义的Present钩子函数(游戏显示画面时执行,用于绘制骨骼标签)
HRESULT VtPresent(IDXGISwapChain* This, UINT SyncInterval, UINT Flags) {// 初始化ImGui(UI库)新帧(准备绘制UI)ImGui_ImplDX11_NewFrame();ImGui_ImplWin32_NewFrame();ImGui::NewFrame();// 创建ImGui窗口(标题"Hello, world!")ImGui::Begin("Hello, world!");// 1. 获取所有Actor(游戏实体,如角色、怪物)的列表地址// 偏移解析:GWorld -> 0x30(UWorld::CurrentLevel,当前关卡) -> 0x98(ULevel::Actors.Data,实体列表首地址)DWORD64 Aactor = *(DWORD64*)(*(DWORD64*)(*(DWORD64*)GWorld + 0x30) + 0x98);// 2. 获取Actor列表的数量(有多少个实体)// 偏移解析:ULevel::Actors.Num(实体数量),偏移0xA0int Num = *(int*)(*(DWORD64*)(*(DWORD64*)GWorld + 0x30) + 0xa0);// 3. 获取"3D坐标转2D屏幕坐标"函数地址(游戏基地址 + 偏移0x2B51EA0)FN_ProjectWorldToScreen My_ProjectWorldLocationToScreen = (FN_ProjectWorldToScreen)(add + 0x2B51EA0);// 4. 获取"获取骨骼矩阵"函数地址(游戏基地址 + 偏移0x2807A40)FN_GetBloneMatrix My_FN_GetBloneMatrix = (FN_GetBloneMatrix)(add + 0x2807A40);// 5. 获取玩家控制器(用于坐标转换的视角基准)// 偏移解析:GWorld -> 0x180(UWorld::PlayerControllers) -> 0x38(TArray::Data) -> 0x0(第一个玩家) -> 0x30(控制器指针)void* Player = (void*)*(DWORD64*)(*(DWORD64*)(*(DWORD64*)(*(DWORD64*)GWorld + 0x180) + 0x38) + 0x0) + 0x30);// 遍历所有Actor(逐个处理每个实体)for (int i = 0; i < Num; i++){// 过滤无效实体:地址为空或无"根组件"(0x130是AActor::RootComponent,根组件)if ((Aactor + 8 * i) != 0 && *(DWORD64*)(Aactor + 8 * i) != 0 && *(DWORD64*)(*(DWORD64*)(Aactor + 8 * i) + 0x130) != 0) {// 6. 获取当前Actor的名字(用于过滤特定实体,如僵尸)// 偏移解析:AActor::Name(实体名字的ID),偏移0x18string name = GetName(*(DWORD64*)(*(DWORD64*)(Aactor + 8 * i) + 0x18));// 只处理名字包含"Zombie_BP"的实体(如僵尸),其他实体跳过if (name.find("Zombie_BP") == string::npos)continue;// 7. 获取该实体的骨骼数量// 偏移解析:AActor -> 0x280(骨骼网格体组件,USkeletalMeshComponent) -> 0x4B8(骨骼数量)int NumMatrix = *(int*)(*(DWORD64*)(*(DWORD64*)(Aactor + 8 * i) + 0x280) + 0x4B8);// 遍历所有骨骼(逐个处理每个骨骼)for (size_t j = 0; j < NumMatrix; j++){FMatrix myMatrix;  // 存储骨骼的矩阵数据// 8. 获取第j个骨骼的矩阵(通过骨骼索引j)// 参数:骨骼所属的组件(0x280偏移的组件)、输出矩阵、骨骼索引jMy_FN_GetBloneMatrix((void*)(*(DWORD64*)(*(DWORD64*)(Aactor + 8 * i) + 0x280)), &myMatrix, j);// 9. 从矩阵中提取骨骼的3D世界坐标(矩阵的WPlane存储平移信息)FVector MyFVector = { myMatrix.WPlane.X, myMatrix.WPlane.Y, myMatrix.WPlane.Z };// 10. 将骨骼3D坐标转换为屏幕2D坐标FVector2D MyFVector2D = { 0,0 };My_ProjectWorldLocationToScreen(Player, MyFVector, MyFVector2D, 1);  // 1表示相对玩家视口// 11. 在屏幕对应位置绘制骨骼索引(j)char buf[1024];sprintf(buf, "%d", j);  // 将索引转为字符串ImGui::GetForegroundDrawList()->AddText({ MyFVector2D.X,MyFVector2D.Y }, ImColor(255, 255, 0), buf);}}}// 结束ImGui窗口绘制ImGui::End();// 渲染ImGui内容并显示到屏幕ImGui::Render();g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());// 调用原始Present函数,让游戏正常显示画面return MyPresent(This, SyncInterval, Flags);
}// ImGui窗口消息处理(用于UI交互,如鼠标点击)
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
WNDPROC myWNDPROC;  // 保存原始窗口消息处理函数// 自定义窗口消息钩子(让ImGui响应输入)
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))return true;return ::CallWindowProc(myWNDPROC, hWnd, msg, wParam, lParam);
}// 初始化函数(第一次调用Present时执行,设置ImGui和Hook)
HRESULT Init(IDXGISwapChain* This, UINT SyncInterval, UINT Flags) {// 获取DX设备和上下文This->GetDevice(_uuidof(g_pd3dDevice), (void**)&g_pd3dDevice);g_pd3dDevice->GetImmediateContext((ID3D11DeviceContext**)&g_pd3dDeviceContext);// 创建渲染目标视图ID3D11Texture2D* pBackBuffer;This->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &g_mainRenderTargetView);pBackBuffer->Release();// 初始化ImGui(仅一次)static bool is = true;if (is) {is = false;myWNDPROC = (WNDPROC)SetWindowLongPtrA(hWnd, GWLP_WNDPROC, (LONG_PTR)WndProc);ImGui::CreateContext();ImGui::StyleColorsDark();ImGui_ImplWin32_Init(hWnd);}ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);printf("HOOK成功");// 将Present函数替换为自定义的VtPresent(完成Hook)VirtaulTable[8] = (DWORD64)VtPresent;return MyPresent(This, SyncInterval, Flags);
}// 窗口大小变化钩子(处理资源释放)
HRESULT VtResizeBuffers(IDXGISwapChain* This, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) {if (g_pd3dDevice) {g_pd3dDevice->Release();g_pd3dDevice = nullptr;g_mainRenderTargetView->Release();ImGui_ImplDX11_Shutdown();VirtaulTable[8] = (DWORD64)Init;}return MyResizeBuffers(This, BufferCount, Width, Height, NewFormat, SwapChainFlags);
}// 线程入口(初始化DX并Hook渲染函数)
DWORD Go(LPVOID lpThreadParameter) {// 初始化交换链描述DXGI_SWAP_CHAIN_DESC sd;ZeroMemory(&sd, sizeof(sd));sd.BufferCount = 2;sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;sd.OutputWindow = hWnd;sd.SampleDesc.Count = 1;sd.Windowed = TRUE;// 创建DX设备和交换链D3D_FEATURE_LEVEL featureLevel;const D3D_FEATURE_LEVEL featureLevelArray[2] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0 };HRESULT res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);if (res == DXGI_ERROR_UNSUPPORTED)res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, 0, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);if (res != S_OK)return false;// 获取交换链虚函数表并HookVirtaulTable = *(DWORD64**)g_pSwapChain;MyPresent = (Present)VirtaulTable[8];DWORD a;VirtualProtect(VirtaulTable, 1, PAGE_EXECUTE_READWRITE, &a);VirtaulTable[8] = (DWORD64)Init;MyResizeBuffers = (ResizeBuffers)VirtaulTable[13];VirtaulTable[13] = (DWORD64)VtResizeBuffers;return 0;
}

img

http://www.dtcms.com/a/532605.html

相关文章:

  • 可做产品预售的网站九江有限公司
  • 设计模式-状态模式(State)
  • 强化学习推荐系统:不同的探索策略——贪心探索策略(4.1)
  • Git学习-1
  • 如何运营一个行业网站dede建设网站
  • 找公司网站建设3网站有哪些后台
  • 15.如何利用ArcGIS提取出线要素数据所经过的格网
  • 数据结构<C++>——数组
  • vidhub v1.3.13 |聚合主流网盘,自动刮削整理影视资源,有网盘会员的可入,或者使用不限速网盘
  • 专业网站制作地址杭州网站怎么制作
  • 免费素材网站排行榜征求网站建设
  • 汉口网站制作公司营销网站模板下载
  • 将有序数组转换为二叉搜索树解题思路
  • c语言实现栈【由浅入深-数据结构】
  • 教做家常菜的视频网站wordpress 搭建个人博客
  • 【Go】C++ 转 Go 第(五)天:Goroutine 与 Channel | Go 并发编程基础
  • 算法:283. 移动零
  • 设计微信公众号的网站吗举例说明seo
  • 欧米伽男士手表官方网站wordpress下载类型主题
  • Chrome离线版下载版,Chrome离线版安装文件,Chrome离线包
  • 上饶网站建设多少钱分销网站有哪些
  • 阿里云 Qwen 模型的 OpenAI SDK 调用
  • 什么是提示词(Prompt),提示词类型、结构解析
  • MES系列-制造流程数字化的实现
  • 我想在网站上卖食品怎么做百度知道网址
  • 对于使用队列实现栈以及用栈实现队列的题目的解析
  • Spring Boot3零基础教程,事件驱动开发,设计登录成功后增加积分记录信息功能,笔记61
  • 网站开发进度表网络电话免费版
  • 两种Redis序列化对比
  • 精确优化长尾关键词以提升SEO效果的战略分析