ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

UE4SS 的 UDataTable Lua API 完全指南:在运行时读写与遍历游戏数据表

UE4SS 的 UDataTable Lua API 完全指南:在运行时读写与遍历游戏数据表 游戏开发逆向工程【免费下载链接】RE-UE4SSInjectable LUA scripting system, SDK generator, live property editor and other dumping utilities for UE4/5 games项目地址https://gitcode.com/gh_mirrors/re/RE-UE4SS点击查看免费下载导读UDataTable是 Unreal Engine 中用于存储哈希键值对行名 → 行数据的核心数据结构常见于掉落表、升级经验表、角色基础属性等游戏配置。UE4SS 在 Lua 运行时中为UDataTable提供了完整封装支持运行时读取、写入、删除、清空与遍历整张数据表。阅读本文后你将掌握UDataTable的 Lua 对象模型、全部 10 个方法/元方法的参数与返回值约定、引用式访问的底层原理以及基于行结构体的克隆、批量修改等实战用法。本文以 docs/lua-api/classes/udatatable.md 为主体并结合 LuaUDataTable.cpp 源码进行纵深剖析。对象模型概览在 UE4SS 的 Lua 类型系统中UDataTable的类继承关系为UDataTable → UObject → RemoteObject即 UObject 中定义的所有成员函数如GetFullName()、GetFName()、GetAddress()、GetClass()、IsValid()、type()等在UDataTable对象上同样可用。在源码层面这一关系体现于 LuaUDataTable.hpp 中的class UDataTable : public UObjectBaseUnreal::UDataTable, UDataTableName它基于 UObject 构建并额外注册了数据表专属的元方法与成员函数。对应的 C 侧声明中UDataTable还关联了一个内部工具结构体FDataTableInfo用于缓存数据表的行结构体指针row_struct、其 FNamerow_struct_fname以及单行内存大小row_size所有行级操作都依赖这些信息完成。元方法__len用法#UDataTable返回类型integer返回数据表的总行数。该元方法在源码中对应 LuaUDataTable.cpp 的MetaMethod::Length注册逻辑底层直接读取data_table-GetRowMap().Num()因此#dt等价于行数统计。local total #dataTable print(Total rows:, total)方法详解IsValid()返回类型bool返回数据表是否有效非空指针。该方法继承自 UObject 体系用于在调用行操作前防御性地检查对象是否已被销毁。if dataTable:IsValid() then -- 安全操作 endGetRowStruct()返回类型UScriptStruct|nil返回定义该数据表行结构的 UScriptStruct若未设置则返回 nil。在源码中GetRowStruct()调用data_table-GetRowStruct()若指针为空会显式set_nil()。通过返回的 UScriptStruct 对象你可以检查行包含哪些字段以及字段类型例如配合FindFirstOf或反射 API 查看行结构定义。local rowStruct dataTable:GetRowStruct() if rowStruct then print(Row struct:, rowStruct:GetFullName()) endGetRowMap()返回类型table返回包含全部行的 Lua table以行名为键、行数据为值。需要注意的是返回值的形式取决于行类型是否存在自定义的值推送器custom pusher。在 LuaUDataTable.cpp 的GetRowMap()实现中代码遍历data_table-GetRowMap()类型为TMapFName, unsigned char*以Pair.Key.ToString()作为 Lua 表键并将Pair.Value指向行内存的指针包装成ScriptStructWrapper后通过UScriptStruct::construct压入 Lua——也就是说默认情况下每个值都是一个UScriptStruct 包装对象提供引用式访问如果该行类型注册了专用的 property value pusher则可能直接以普通 Lua table 形式返回。local rowMap dataTable:GetRowMap() for rowName, rowData in pairs(rowMap) do print(rowName, rowData) endFindRow(string RowName)返回类型UScriptStruct|nil按行名查找行找到则返回行数据UScriptStruct未找到返回 nil。关键特性返回的结构体是引用式访问——对其字段的修改会直接作用于数据表本身。示例local row dt:FindRow(Player); row.Health 200会真实修改数据表。底层实现DataTableOperation::FindRow分支使用data_table-FindRowUnchecked(row_name)直接取得行内存指针若为 null 则set_nil()否则构造ScriptStructWrapper{.script_struct row_struct, .start_of_struct row_data, .property nullptr}并压栈。这与 C 中FindRow返回指向真实行内存指针的行为一致因此修改立即生效无需再写回。local row dataTable:FindRow(Player) if row then row.Health 200 -- 直接修改数据表内的 Player 行 endAddRow(string RowName, table|UScriptStruct RowData)向数据表新增一行指定行名与数据若同名行已存在则替换。第二参数接受两种形式Lua table字段需与行结构定义匹配如{Health 100, MaxHealth 100}UScriptStruct数据从该结构体复制适合用于克隆已有行。示例dt:AddRow(NewPlayer, {Health 100, MaxHealth 100})示例dt:AddRow(Clone, dt:FindRow(Original))— 复制一行源码中的DataTableOperation::AddRow分支展示了完整的处理链路校验第一个参数必须是字符串行名否则抛出AddRow expects a string as the first parameter通过lua.is_userdata(1)/lua.is_table(1)区分传入的是 UScriptStruct 还是 Lua table两者都不是则抛出AddRow expects a table or UScriptStruct as the second parameter用FMemory::Malloc(row_size)分配临时内存并InitializeStructUScriptStruct 路径调用CopyScriptStruct(new_row_data, wrapper_data.start_of_struct)从源结构复制字节数据Lua table 路径若该行结构在StaticState::m_property_value_pushers中注册了专用 pusher 则调用 pusher否则回退到convert_lua_table_to_struct(lua, info.row_struct, new_row_data, 1, nullptr)通用转换函数调用data_table-AddRow(row_name, new_row_data, info.row_struct)将数据复制进数据表内部存储由于AddRow已复制数据随后DestroyStructFMemory::Free释放临时分配避免内存泄漏。-- 用 table 新增一行 dataTable:AddRow(NewPlayer, {Health 100, MaxHealth 100}) -- 克隆已有行 local original dataTable:FindRow(Original) dataTable:AddRow(Clone, original)RemoveRow(string RowName)删除指定名称的行若行不存在则不做任何事。对应DataTableOperation::RemoveRow分支内部直接调用data_table-RemoveRow(row_name)无需额外参数校验或返回值。dataTable:RemoveRow(ObsoleteRow)EmptyTable()清空数据表的所有行。注意不会清除 RowStruct 定义清空后表仍然知道自己的行结构可以继续AddRow。dataTable:EmptyTable() -- 表已清空但 GetRowStruct() 依然有效GetRowNames()返回类型table返回一个 1 起始索引的数组包含数据表中所有行名。实现中遍历data_table-GetRowNames()以i 1作为键、行名字符串作为值构建 Lua 表。local names dataTable:GetRowNames() for i 1, #names do print(i, names[i]) endGetAllRows()返回类型table返回一个 1 起始索引的数组每个元素是包含两个字段的 tableName行名stringData行数据table 或 UScriptStructData字段同样以ScriptStructWrapper方式包装行内存因此同样具备引用式访问能力——修改entry.Data.xxx会直接改变数据表内容。for _, entry in ipairs(dataTable:GetAllRows()) do print(Row:, entry.Name) print(Health:, entry.Data.Health) endForEachRow(function Callback)遍历数据表所有行并为每一行调用回调函数。回调参数string rowName、UScriptStruct rowDatarowData提供引用式访问——修改会直接影响数据表。回调可以返回true提前终止遍历。该方法的 C 实现是setup_member_functions中唯一一个没有走prepare_to_handle分发、而是内联实现的方法。其关键逻辑包括构造FDataTableInfo并调用validate_row_struct(lua)若数据表没有 RowStruct 则抛出DataTable has no RowStruct specified每轮迭代通过lua_pushvalue(lua.get_lua_state(), 1)复制回调函数到栈顶依次压入行名字符串与行数据 UScriptStruct 包装再以lua.call_function(2, 1)调用检查返回值若第 2 个栈位置为布尔true则break提前退出否则丢弃栈上的 nil 返回值防止 Lua 栈在下一轮迭代被污染。dataTable:ForEachRow(function(rowName, rowData) print(string.format(Row: %s, Health: %d, rowName, rowData.Health)) -- 直接修改行 rowData.Health rowData.Health 10 -- 返回 true 停止遍历 if rowName TargetRow then return true end end)实战组合示例结合上述 API可以在运行时完成一次完整的查找 → 修改 → 克隆 → 删除数据表维护流程local dt FindFirstOf(DataTable) -- 或通过其他方式获取 UDataTable if not dt or not dt:IsValid() then print(DataTable not found) return end -- 1. 读取行并修改引用式访问直接生效 local player dt:FindRow(Player) if player then player.Health player.Health 50 end -- 2. 新增一行 dt:AddRow(NewPlayer, {Health 100, MaxHealth 100}) -- 3. 克隆一行 dt:AddRow(Clone, dt:FindRow(Player)) -- 4. 遍历并统计 local count 0 dt:ForEachRow(function(name, data) count count 1 print(name, data.Health) end) -- 5. 清理 dt:RemoveRow(Clone) print(Total rows:, #dt)底层原理与注意事项引用式访问从何而来FindRow、GetRowMap、GetAllRows、ForEachRow返回的UScriptStruct本质都是 ScriptStructWrapper包含script_struct、start_of_struct、property三个字段。start_of_struct直接指向数据表内部行内存即TMapFName, unsigned char*的 Value 指针因此通过 UScriptStruct 的__index/__newindex读写字段时操作的就是数据表真实内存无需额外写回步骤。这也解释了为什么FindRow的注释明确强调modifications directly affect the DataTable。行结构校验FDataTableInfo::validate_row_struct会在FindRow、AddRow、GetAllRows、ForEachRow前强制校验row_struct是否存在否则抛出DataTable has no RowStruct specified。__len、GetRowStruct、GetRowNames、RemoveRow、EmptyTable不依赖行结构定义因此不受此限制。空指针保护GetRowStruct、GetRowMap在data_table为空时都会set_nil()返回ForEachRow与prepare_to_handle则在数据表指针为空时直接lua.throw_error(DataTable is null)。在实际 mod 代码中建议先以IsValid()或FindFirstOf的结果判空再执行行操作。设计背景UE4SS 对 DataTable 的完整读写支持并非一蹴而就。官方开发日志 datatables-in-ue4ss.md 记录了早期方案的权衡直接遍历 UE4SS 自研TMap的GetElementsPtr()只适合只读场景因为AddRow/RemoveRow之后指针会失效而利用 KismetUDataTableFunctionLibrary的反射函数又受限于GetDataTableRowFromName这类CustomThunk函数在纯 C 调用时Stack.MostRecentProperty无法填充的问题。最终当前版本的 Lua API 采用直接包装行内存指针ScriptStructWrapper的方案实现了文档中描述的完整读/写/增/删/遍历能力。适用前提本文所述 API 适用于 UE4/UE5 游戏内注入 UE4SS 后的 Lua mod 场景UDataTable对象可通过FindFirstOf(DataTable)、FindAllOf或遍历GetRowMap等方式获取行数据字段名必须与行结构定义一致参考 UScriptStruct 的__index/__newindex约定大小写需与游戏内反射出的属性名匹配部分游戏的行结构可能包含内存对齐/填充差异参见 datatables-in-ue4ss.md 中对 FName 对齐的讨论若遇到异常字段读写可从行结构的内存布局角度排查。方法速查表方法签名返回类型说明__len#dtinteger行数IsValiddt:IsValid()bool对象是否有效继承自 UObjectGetRowStructdt:GetRowStruct()UScriptStruct/nil行结构定义GetRowMapdt:GetRowMap()table行名 → 行数据的映射表FindRowdt:FindRow(name)UScriptStruct/nil按名查行引用式AddRowdt:AddRow(name, data)无新增/替换行RemoveRowdt:RemoveRow(name)无删除行EmptyTabledt:EmptyTable()无清空所有行保留 RowStructGetRowNamesdt:GetRowNames()table行名数组1 起始GetAllRowsdt:GetAllRows()table{Name..., Data...}数组ForEachRowdt:ForEachRow(cb)无遍历回调返回true可提前退出相关资源API 文档原文docs/lua-api/classes/udatatable.md基类文档UObject、UScriptStruct源码实现LuaUDataTable.hpp、LuaUDataTable.cpp、LuaUScriptStruct.hpp设计背景与早期方案讨论datatables-in-ue4ss.md赞分享游戏开发逆向工程【免费下载链接】RE-UE4SSInjectable LUA scripting system, SDK generator, live property editor and other dumping utilities for UE4/5 games项目地址https://gitcode.com/gh_mirrors/re/RE-UE4SS点击查看免费下载相关推荐Tornado 6.3.3 安全更新深度解析Content-Length 与 chunked 编码的严格解析如何阻断 HTTP 请求走私Tornado 6.3.3 安全更新深度解析Content Length 与 chunked 编码的严格解析如何阻断 HTTP 请求走私 导读 Tornado游戏开发逆向工程RE-UE4SS Lua API 实战使用 UE4SS.GetVersion() 获取与检测运行时版本RE UE4SS Lua API 实战使用 UE4SS.GetVersion 获取与检测运行时版本 本文围绕 RE UE4SS 官方 Lua API 文档 d游戏开发逆向工程在 Lua 中使用 FlatBuffers读写序列化数据的完整指南在 Lua 中使用 FlatBuffers读写序列化数据的完整指南 导读 本文以 FlatBuffers 官方文档中 Lua 语言支持章节 docs/sou序列化代码生成上一篇深入解析 rustc-std-workspace-coreRust 标准库依赖 crates.io 生态的桥梁与编译期 shim下一篇Tabby 数据备份完整指南SQLite 数据库、事件日志与后台任务日志的备份与恢复创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表