Native 命令:注册机制、参数封送与 game/script/commands_* 分类
脚本能做的一切“游戏事情”——创建 ped、控制摄像机、读写统计、网络会话——都通过 native 命令完成。本页说明 native 如何在 C++ 侧注册(SCR_REGISTER_* 宏 → scrThread::RegisterCommand)、参数与返回值如何在 VM 栈和 C++ 函数之间封送、game/script/commands_*.cpp 各文件的领域划分与规模、game/script_headers/*.sch 如何向脚本声明这些命令,以及发行构建中 native 集合如何被裁剪。
背景与相关页面#
- VM 侧
OP_NATIVE的执行、Validate把哈希解析为函数指针、阻塞语义:script-vm.md(第 6.3、6.4、8、10 节)。 - 调用 native 的“当前脚本”上下文(
GtaThread、handler、GUID 实体注册):script-handlers-and-threads.md。 .sch中NATIVE声明的编译、randomizenatives/scannative工具:script-compiler-and-toolchain.md。- 各领域的具体实现(ped、车辆、网络……)请看对应子系统页面,例如 ../06-peds/ped-architecture.md、../08-vehicles/vehicle-architecture.md、../16-network-online/network-sessions-and-transitions.md、../19-persistence-replay/stats.md。
- 防篡改相关内容只写存在与边界:../23-debug-security/security-overview.md。
相关源码文件#
| 文件 | 作用 |
|---|---|
rage/script/src/script/wrapper.h | Arg<T>/AssignReturnValue/CallFunction 封送模板、GetFunctionSignature、全部 SCR_REGISTER_* 宏 |
rage/script/src/script/thread.h/.cpp | scrThread::Info、RegisterCommand/RegisterUnusedCommand、命令表 scrCommandHash、内置命令 |
rage/script/src/script/signature.h/.cpp | scrSignature(64 位类型签名) |
rage/script/src/script/varargs.h、array.h | VARARGS 与变长数组参数辅助 |
game/script/script.cpp | CTheScripts::RegisterScriptCommands / ScrambleAndRegister:游戏命令的注册入口 |
game/script/commands_*.cpp/.h | 53 个命令文件,每个文件一个 xxx_commands 命名空间与 SetupScriptCommands() |
game/script/script_helper.h | SCRIPT_VERIFY*/SCRIPT_ASSERT* 等带脚本名+PC 的校验宏 |
game/script/script.h | CTheScripts::GetEntityToModifyFromGUID 等句柄解析接口 |
game/script_headers/*.sch | 脚本侧的 NATIVE 声明、句柄类型、共享枚举(68 个 .sch) |
game/debug/DebugNodes.cpp | BANK 构建额外注册的调试 native |
1. native 标识:从“名字哈希”到“64 位随机数”#
每个 native 由一个 u64 标识。脚本侧的声明形如 NATIVE PROC SET_CLOCK_TIME(INT h, INT m, INT s) = "0x…"(值省略) src/dev_ng/game/script_headers/commands_clock.sch:47;C++ 侧在注册宏的第二个参数写入同一个值 src/dev_ng/game/script/commands_clock.cpp:215。编译器要求每个 NATIVE 声明都带 = "0x0123456789abcdef" 形式的 64 位随机数(选项缺失时警告“use uuid64”),否则退回到名字的符号哈希 src/dev_ng/rage/script/tools/scriptcompiler/scr.y:1213-1221、src/dev_ng/rage/script/tools/scriptcompiler/scr.y:677-713。
历史上 native 标识是 scrComputeHash(名字):SCRHASH(str, code) 宏现在直接返回 code src/dev_ng/rage/script/src/script/wrapper.h:30,断言构建下 scrValidateHash 只对 32 位值校验名字哈希、对高位非零的值直接放行 src/dev_ng/rage/script/src/script/hash.cpp:53-62。RAGE 早期的 bankscript.cpp、scr_grcore 仍是 32 位名字哈希 src/dev_ng/rage/script/src/scr_grcore/grcore.cpp:109-122。RegisterUnusedCommand 对 <2 的哈希加 2,以兼容两种来源 src/dev_ng/rage/script/src/script/thread.cpp:2579-2583。
这一设计的结果:脚本二进制里不含 native 名字,只有随机标识(加上加载期的进一步变换,见 script-vm.md 第 13 节)。随机值由 uuid64.exe 手工生成或由 randomizenatives 批量生成并同步到 .sch 与 C++(见 script-compiler-and-toolchain.md)。本页不列出任何具体值。
2. 注册宏族#
所有宏都定义在 wrapper.h 末尾 src/dev_ng/rage/script/src/script/wrapper.h:1280-1348。核心形态(SCR_REGISTER_SECURE):为每个命令生成一个局部结构 scrWrapped_##name,其静态成员 Call(scrThread::Info&) 调用 scrWrapper::CallFunction(fn, s) 完成参数解包与返回值写回;然后调用 scrThread::RegisterCommand(hashcode, scrWrapped_##name::Call [, #name, __FILE__, scrSignature(GetFunctionSignature(fn))]) src/dev_ng/rage/script/src/script/wrapper.h:1291-1301。Call 标为 ATTR_COLD,并在剖析构建中打 PROFILER_EVENT(#name"::Native"),BANK/Profile 下可做 RAGE_TRACK src/dev_ng/rage/script/src/script/wrapper.h:13-26。
| 宏 | 行为 | game/ 中出现次数 |
|---|---|---|
SCR_REGISTER_SECURE(name, hash, fn) | 标准注册;额外调用 ApiCheckPlugin_AddNative((u64)fn)(安全插件钩子) | 6045 |
SCR_REGISTER_UNUSED(name, hash, fn) | 非 FINAL:经 RegisterUnusedCommand 注册;FINAL:展开为空 src/dev_ng/rage/script/src/script/wrapper.h:1332-1343 | 2181 |
SCR_REGISTER_SECURE_EXPORT | SCR_REGISTER_SECURE 的别名 src/dev_ng/rage/script/src/script/wrapper.h:1328 | 301 |
DLC_SCR_REGISTER_SECURE[_BLOCKING/_EXPORT] | 同 SECURE,但注释写明“永远不希望被裁剪”(见第 8 节为什么前缀能防止裁剪) src/dev_ng/rage/script/src/script/wrapper.h:1345-1348 | 68 |
SCR_REGISTER_SECURE_HONEYPOT | PC:包装函数在调用前额外记录返回地址(防篡改用途);其他平台等同 SECURE src/dev_ng/rage/script/src/script/wrapper.h:1303-1315 | 7 |
SCR_REGISTER_SECURE_UNHASHED | 同 SECURE 但不调用 ApiCheckPlugin_AddNative src/dev_ng/rage/script/src/script/wrapper.h:1281-1289 | 4 |
SCR_REGISTER_SECURE_BLOCKING | 使用 CallFunctionBlocking(只在线程仍 RUNNING 时写回返回值) src/dev_ng/rage/script/src/script/wrapper.h:1318-1326 | 0 |
出现次数由在 src/dev_ng/game 下对宏名做全文计数得出(包含 #if 0 内与 DebugNodes.cpp、commands_html.cpp 中的出现),仅表示量级。注意 CallFunctionBlocking 依赖的 AssignReturnValueIfRunning 被 #if 0 包住 src/dev_ng/rage/script/src/script/wrapper.h:91-98,非 void 的阻塞变体实例化时将无法编译——这与游戏代码完全不使用 SCR_REGISTER_SECURE_BLOCKING 一致(推断其已废弃)。真正需要阻塞的命令(WAIT、START_NEW_SCRIPT)是用 RegisterCommand 直接注册的原生 void(Info&) 处理函数 src/dev_ng/rage/script/src/script/thread.cpp:4412-4418。
RegisterCommand 把处理函数放入 s_CommandHash(哈希重复即断言),并在 SCRIPT_DEBUGGING 下登记名字、源文件名(用于 -nativetrace)与签名(s_SignatureHash,键为 scrComputeHash(name),冲突断言) src/dev_ng/rage/script/src/script/thread.cpp:2607-2631。
3. 注册流程(GTAV)#
证据:CTheScripts::Init 依次 scrProgram::InitClass()、scrThread::InitClass()、……、scrThread::RegisterBuiltinCommands()、RegisterScriptCommands()、scrThread::ParseNativeTraceCommandLine() src/dev_ng/game/script/script.cpp:698-728;RegisterScriptCommands 走 #if 1 分支调用 ScrambleAndRegister(#else 分支保留了顺序调用的旧写法) src/dev_ng/game/script/script.cpp:359-417;ScrambleAndRegister 压入 51 个 SetupScriptCommands、用 fwRandom 打乱 1000 次后逐个执行,末尾调用 ApiCheckPlugin_StopNativeHash() 与 RegistrationComplete(true) src/dev_ng/game/script/script.cpp:281-357。RegisterBuiltinCommands 开头的 ApiCheckPlugin_StartNativeHash() 与这里的 Stop 构成一对(安全插件接口,声明在 game/security/plugins/apicheckplugin.h src/dev_ng/game/security/plugins/apicheckplugin.h:13-15)。打乱注册顺序的用途属于防篡改范畴(推断:使命令表内存布局不可预测),本页不展开。
其他注册点:
debug_commands::SetupScriptCommands在 BANK 下额外调用DebugNodeSys::SetupScriptCommands()src/dev_ng/game/script/commands_debug.cpp:1805、src/dev_ng/game/debug/DebugNodes.cpp:233。game/frontend/html/commands_html.cpp定义了html_commands::SetupScriptCommandssrc/dev_ng/game/frontend/html/commands_html.cpp:195,但不在打乱列表中,也未在game/中检索到引用——这表明它是未接入的遗留代码。- RAGE 自带的
Register_BankScript、Register_grcore在game/中没有调用点。
4. 参数与返回值封送#
4.1 scrThread::Info#
native 处理函数的统一签名是 void (*scrCmd)(scrThread::Info&) src/dev_ng/rage/script/src/script/thread.h:807。Info 只有三个核心字段:ResultPtr(返回值写入处,无返回值时为 NULL)、ParamCount(参数槽数)、Params(指向 VM 栈上第一个参数),外加最多 4 个 Vector3 引用参数的拷入/拷出缓冲 src/dev_ng/rage/script/src/script/thread.h:226-250。VM 构造 Info 时 ResultPtr 与 Params 指向同一位置——返回值覆盖第一个参数槽,调用后 VM 弹出 paramCount - returnSize 个槽 src/dev_ng/rage/script/src/script/thread.cpp:4884-4891。(本代码库中不存在 scrNativeCallContext 这一类型;在 rage/script 与 game/ 中检索无结果。)
4.2 Arg<T> 与 AssignReturnValue#
CallFunction 为 0–18 个参数、void/非 void 返回分别特化:按声明顺序用 Arg<Tn>::Value(info, N) 逐个取参(N 为槽游标),断言构建下 AssertParamMatch 检查消耗的槽数等于 ParamCount,最后 AssignReturnValue(info, fn(...)) src/dev_ng/rage/script/src/script/wrapper.h:594-678。
| C++ 形参类型 | 解包方式 | 证据 |
|---|---|---|
int / float / bool | 取一个槽的 Int/Float/Int!=0 | src/dev_ng/rage/script/src/script/wrapper.h:108-121 |
const char* | 槽中字符串指针(scrDecodeString) | src/dev_ng/rage/script/src/script/wrapper.h:123-126 |
const scrVector& | 直接把三个连续槽重解释为 scrVector(64 位下靠 x,xPad,… 布局对齐) | src/dev_ng/rage/script/src/script/wrapper.h:144-150 |
Vector3(按值) | 从三个槽拷出 | src/dev_ng/rage/script/src/script/wrapper.h:152-172 |
int& / float& | 槽中是脚本变量地址,解引用得到其 Int/Float | src/dev_ng/rage/script/src/script/wrapper.h:174-182 |
bool& | 刻意不支持(C++ bool 与脚本 BOOL 尺寸不同) | src/dev_ng/rage/script/src/script/wrapper.h:185-189 |
Vector3& / const Vector3& | Info::GetVector3 把脚本向量拷到 Buffer[],调用后 CopyReferencedParametersOut 写回 | src/dev_ng/rage/script/src/script/wrapper.h:196-208、src/dev_ng/rage/script/src/script/thread.cpp:5308-5334 |
自定义指针类型 T* | SCR_DEFINE_NEW_POINTER_PARAM_AND_RET(T) 生成 Arg<T*>、返回值重载与 ArgType=OBJECT | src/dev_ng/rage/script/src/script/wrapper.h:570-588 |
| 自定义值类型 | SCR_DEFINE_NEW_PARAM_TYPE(T, conv) | src/dev_ng/rage/script/src/script/wrapper.h:557-568 |
| 线程 TLS 指针 | SCR_DEFINE_TLS_TYPE_VALUE(T, slot):形参 T* 从当前线程 TLS(slot) 取,不消耗槽 | src/dev_ng/rage/script/src/script/wrapper.h:537-555 |
返回值:float/int/u32/bool 写 ResultPtr 一个槽;scrVector 在 64 位下写三个槽;const char* 写字符串指针 src/dev_ng/rage/script/src/script/wrapper.h:56-89。返回 const char* 的 native 必须保证指针在脚本使用期间有效(通常指向静态/持久缓冲)——这是由实现方式推出的约束,代码中没有生命周期检查。
4.3 结构体、数组、文本标签、VARARGS#
- 结构体按引用传递:脚本
STRUCT&实参是地址;C++ 用SCR_DEFINE_NEW_POINTER_PARAM_AND_RET声明的类型接收,其成员必须逐个声明为scrValue(每成员一个 8 字节槽),例如scrScMembershipInfosrc/dev_ng/game/network/NetworkTypes.h:593-600。尺寸靠CompileTimeAssert保证,例如 gamer handle 缓冲src/dev_ng/game/script/commands_socialclub.cpp:191。 - 变长数组:形参写
scrArrayBaseAddr&(实为int&,指向数组的计数槽),用scrArray::GetCount/GetElements<T>访问src/dev_ng/rage/script/src/script/array.h:8-60;实例CommandGetNearbyVehiclessrc/dev_ng/game/script/commands_ped.cpp:12746-12753。 - 文本标签:C++ 侧使用
scrTextLabel15/63等按sizeof(scrValue)计尺寸的char[],大小须与.sch声明的TEXT_LABEL_n一致(script.dtx也强调 TEXT_LABEL 变体在传参时尺寸必须完全匹配src/dev_ng/rage/script/src/script/script.dtx:24-30),例如断言sizeof(scrTextLabel63) >= RL_MAX_DISPLAY_NAME_BUF_SIZEsrc/dev_ng/game/script/commands_network.cpp:6354。 - VARARGS:原生
void(Info&)处理函数用SCRIPT_VA_BEGIN/TYPE/NEXT遍历;第一个槽是每参数 2 位的类型位图(VA_INT/FLOAT/STRINGPTR/VECTOR)src/dev_ng/rage/script/src/script/varargs.h:1-30;内置PRINTLN等即用此方式。
4.4 类型签名 scrSignature#
GetFunctionSignature(fn) 在编译期把 C++ 函数类型映射为 64 位签名:位 0–1 为状态(STATE_VALID),位 2–4 返回类型,之后每参数 3 位、最多 18 个参数 src/dev_ng/rage/script/src/script/signature.h:71-131、src/dev_ng/rage/script/src/script/wrapper.h:297-323。类型映射由 ArgType<T> 给出:int/u32→INT、bool→BOOL、float→FLOAT、const char*→STRING、向量类→VECTOR、int&/float&→OBJECT src/dev_ng/rage/script/src/script/wrapper.h:210-290。签名只在 SCRIPT_DEBUGGING 下保存,用于 native 追踪时格式化参数、PrettyPrint 出脚本风格原型 src/dev_ng/rage/script/src/script/signature.cpp:26-40;它不参与运行时类型检查——发行构建里参数类型正确性完全依赖 .sch 声明与 C++ 实现手工一致。
5. native 实现中的惯用约定#
- 实体句柄 = 脚本 GUID:脚本里的
PED_INDEX/VEHICLE_INDEX等是fwScriptGuid池中的索引,fwScriptGuid::CreateGuid为fwExtensibleBase附加 GUID 扩展并返回GetPool()->GetIndex()src/dev_ng/rage/framework/src/fwscript/scriptguid.cpp:29-81。命令应通过CTheScripts::GetEntityToModifyFromGUID<T>(修改)或GetEntityToQueryFromGUID<T>(只读,放宽 clone 检查)解析,它们在断言构建下按GUID_ASSERT_FLAG_ENTITY_EXISTS/DEAD_CHECK/NOT_CLONE做检查src/dev_ng/game/script/script.h:198-210、src/dev_ng/game/script/script.h:352-391。FromGuid<T>会验证类 ID(RTTI)匹配src/dev_ng/rage/framework/src/fwscript/scriptguid.h:52-67。 - 校验宏:
SCRIPT_VERIFY/SCRIPT_ASSERT自动在消息前加CTheScripts::GetCurrentScriptNameAndProgramCounter();网络相关还有SCRIPT_VERIFY_NETWORK_REGISTERED/ACTIVE、SCRIPT_VERIFY_PLAYER_INDEX等src/dev_ng/game/script/script_helper.h:18-56。典型 native 先断言参数范围,再检查运行环境(例如SET_CLOCK_TIME拒绝在网络游戏中调用)src/dev_ng/game/script/commands_clock.cpp:18-35。 - 拥有关系:创建实体/资源的命令应把对象登记到当前脚本的 handler(
CTheScripts::RegisterEntity、RegisterScriptResource),脚本结束时统一清理(见 script-handlers-and-threads.md)。 - 命名:C++ 实现函数一般命名为
CommandXxx,放在xxx_commands命名空间中,文件末尾的SetupScriptCommands()集中注册src/dev_ng/game/script/commands_clock.cpp:14-35、src/dev_ng/game/script/commands_clock.cpp:211-230。
6. game/script/commands_*.cpp 分类#
计数说明:“注册行”= 以 SCR_REGISTER_*/DLC_SCR_REGISTER_* 开头的行数(rg -c 统计,包括少量位于条件编译块中的行),括号内为其中 SCR_REGISTER_UNUSED 行数(FINAL 中不注册);“.sch”= game/script_headers 中同名头文件的 NATIVE FUNC/PROC 声明数。全部 commands_*.cpp 合计 8569 行注册(其中 UNUSED 2147),全部 .sch 合计 8595 个 NATIVE FUNC/PROC 声明(含 rage_builtins.sch 的 52 个)。“入口”列链接到各文件 SetupScriptCommands 定义。
| 文件 / 命名空间 | 领域 | 注册行(UNUSED) | .sch | 代表命令 | 入口 |
|---|---|---|---|---|---|
commands_apps / apps_commands | 手机 App 数据存取 | 18 (1) | 19 | APP_DATA_VALID、APP_GET_INT | commands_apps.cpp:322 |
commands_audio / audio_commands | 音频:语音、铃声、音乐、场景、电台 | 388 (113) | 367 | PLAY_PED_RINGTONE | commands_audio.cpp:3910 |
commands_brains / brain_commands | 脚本大脑注册(ped/物体/世界点) | 15 (4) | 15 | REGISTER_OBJECT_SCRIPT_BRAIN | commands_brains.cpp:222 |
commands_camera / camera_commands | 脚本摄像机、渲染切换、镜头效果 | 281 (38) | 281 | CREATE_CAM、RENDER_SCRIPT_CAMS | commands_camera.cpp:3690 |
commands_clock / clock_commands | 游戏时钟与日期 | 19 (3) | 19 | SET_CLOCK_TIME | commands_clock.cpp:211 |
commands_cutscene / cutscene_commands | 过场请求/播放/实体注册 | 72 (19) | 72 | REQUEST_CUTSCENE | commands_cutscene.cpp:1225 |
commands_datafile / datafile_commands | UGC/云数据文件读写(JSON 式对象树) | 81 (24) | 81 | DATAFILE_IS_VALID_REQUEST_ID | commands_datafile.cpp:2434 |
commands_debug / debug_commands | 调试绘制/输出(全部 UNUSED)+ BANK 的 DebugNodes | 108 (108) | 115 | — | commands_debug.cpp:1657 |
commands_decorator / decorator_commands | 实体装饰器(fwDecorator) | 18 (6) | 18 | DECOR_SET_BOOL | commands_decorator.cpp:249 |
commands_dlc / dlc_commands | DLC 存在性/内容检查 | 26 (14) | 26 | IS_DLC_PRESENT | commands_dlc.cpp:304 |
commands_entity / entity_commands | 通用实体(位置、可见性、碰撞、动画、附着) | 200 (16) | 203 | DOES_ENTITY_EXIST | commands_entity.cpp:6313 |
commands_event / event_commands | 决策器、shocking event | 20 (7) | 20 | ADD_SHOCKING_EVENT_AT_POSITION | commands_event.cpp:471 |
commands_extrametadata / extrametadata_commands | DLC 扩展元数据(纹身/服装店条目) | 62 (13) | 59 | GET_TATTOO_SHOP_DLC_ITEM_DATA | commands_extrametadata.cpp:779 |
commands_fire / fire_commands | 火焰、爆炸 | 18 (0) | 18 | START_SCRIPT_FIRE | commands_fire.cpp:385 |
commands_gang / gang_commands | 帮派(残留) | 3 (3) | 0 | — | commands_gang.cpp:32 |
commands_graphics / graphics_commands | 绘制/调试图元、粒子、贴花、后处理、渲染目标 | 526 (133) | 525 | DRAW_DEBUG_LINE | commands_graphics.cpp:7021 |
commands_hud / hud_commands | HUD、文本命令、雷达 blip、小地图 | 719 (210) | 716 | BEGIN_TEXT_COMMAND_BUSYSPINNER_ON | commands_hud.cpp:7884 |
commands_interiors / interior_commands | 室内实例/房间/实体集 | 64 (19) | 64 | GET_INTERIOR_HEADING | commands_interiors.cpp:1116 |
commands_inventory / inventory_commands | 空实现 | 0 | 0 | — | commands_inventory.cpp:14 |
commands_itemsets / itemsets_commands | 脚本 item set | 10 (1) | 10 | CREATE_ITEMSET | commands_itemsets.cpp:126 |
commands_landingpage / landingpage_commands | Gen9 落地页(均为 UNUSED) | 4 (4) | 4 | — | commands_landingpage.cpp:61 |
commands_law / law_commands | 执法(残留,均 UNUSED) | 4 (4) | 4 | — | commands_law.cpp:35 |
commands_lobby / lobby_commands | 自动进入多人/大厅 | 11 (3) | 11 | LOBBY_AUTO_MULTIPLAYER_FREEMODE | commands_lobby.cpp:92 |
commands_localization / localization_commands | 系统语言/日期格式 | 6 (3) | 6 | GET_CURRENT_LANGUAGE | commands_localization.cpp:63 |
commands_misc / misc_commands | 杂项:栈/线程信息、随机数、天气、区域、游戏状态等 | 420 (90) | 463 | GET_ALLOCATED_STACK_SIZE | commands_misc.cpp:5639 |
commands_money / money_commands | 联网现金/交易记账 | 327 (9) | 329 | NETWORK_INITIALIZE_CASH | commands_money.cpp:9710 |
commands_netshopping / netshopping_commands | 游戏服务器商品目录与交易 | 64 (24) | 64 | NET_GAMESERVER_CATALOG_ITEM_IS_VALID | commands_netshopping.cpp:2565 |
commands_network / network_commands | 网络:会话、网络脚本/广播数据、玩家、网络实体、UGC、语音 | 1211 (376) | 1216 | NETWORK_IS_SIGNED_IN、NETWORK_SET_THIS_SCRIPT_IS_NETWORK_SCRIPT | commands_network.cpp:17095 |
commands_ny / gta_commands | 手机等 GTA 专属(文件名沿用 GTA IV 的 “ny”;声明在 commands_misc.sch) | 47 (22) | — | CREATE_MOBILE_PHONE | commands_ny.cpp:406 |
commands_object / object_commands | 物体、门、拾取物 | 208 (45) | 212 | CREATE_OBJECT | commands_object.cpp:5564 |
commands_pad / pad_commands | 输入/控制映射 | 86 (38) | 86 | IS_CONTROL_PRESSED | commands_pad.cpp:1686 |
commands_path / path_commands | 路网节点/道路开关 | 78 (19) | 77 | SET_ROADS_IN_AREA | commands_path.cpp:1547 |
commands_ped / ped_commands | ped 创建/属性/外观/行为配置 | 800 (191) | 794 | CREATE_PED | commands_ped.cpp:13608 |
commands_physics / physics_commands | 绳索、物理约束 | 68 (20) | 65 | ADD_ROPE | commands_physics.cpp:1556 |
commands_player / player_commands | 玩家控制、通缉、能力 | 323 (75) | 320 | GET_PLAYER_PED | commands_player.cpp:4871 |
commands_recording / recording_commands | 回放录制事件 | 21 (4) | 21 | REPLAY_START_EVENT | commands_recording.cpp:305 |
commands_replay / replay_commands | 回放编辑器协作 | 8 (2) | 8 | REGISTER_EFFECT_FOR_REPLAY_EDITOR | commands_replay.cpp:92 |
commands_savemigration / savemigration_commands | 存档迁移 | 11 (4) | 11 | SAVEMIGRATION_MP_REQUEST_ACCOUNTS | commands_savemigration.cpp:249 |
commands_script / script_commands | 脚本流式加载、线程管理、事件读取、后台脚本 | 45 (8) | 42 | REQUEST_SCRIPT、GET_EVENT_DATA | commands_script.cpp:800 |
commands_security / security_commands | 防篡改相关(只记录存在) | 4 (1) | 4 | — | commands_security.cpp:86 |
commands_shapetest / shapetest_commands | 异步/同步形状测试(射线、包围盒) | 11 (0) | 11 | START_SHAPE_TEST_LOS_PROBE | commands_shapetest.cpp:375 |
commands_socialclub / socialclub_commands | Social Club 收件箱/许可/成员资格 | 110 (27) | 110 | SC_INBOX_GET_TOTAL_NUM_MESSAGES | commands_socialclub.cpp:1070 |
commands_stats / stats_commands | 统计读写、排行榜、成就 | 401 (90) | 408 | STAT_LOAD、STAT_SAVE | commands_stats.cpp:20612 |
commands_streaming / streaming_commands | 模型/资产请求、加载场景、流式体积 | 136 (16) | 136 | LOAD_SCENE | commands_streaming.cpp:2200 |
commands_task / task_commands | AI 任务下达、序列、waypoint 录制 | 400 (95) | 404 | TASK_STAND_STILL | commands_task.cpp:9003 |
commands_vehicle / vehicle_commands | 车辆创建/属性/改装/录制 | 899 (164) | 892 | CREATE_VEHICLE | commands_vehicle.cpp:15344 |
commands_volume / volume_commands | 脚本体积(均 UNUSED) | 15 (15) | 15 | — | commands_volume.cpp:270 |
commands_water / water_commands | 水面高度/探测 | 14 (2) | 14 | GET_WATER_HEIGHT | commands_water.cpp:232 |
commands_weapon / weapon_commands | 武器、组件、弹药 | 149 (33) | 148 | GET_WEAPONTYPE_MODEL | commands_weapon.cpp:3229 |
commands_xml / xml_commands | XML 读取(均 UNUSED) | 21 (21) | 21 | — | commands_xml.cpp:499 |
commands_zone / zone_commands | 地图区域 | 19 (10) | 19 | GET_ZONE_AT_COORDS | commands_zone.cpp:235 |
commands_waypoint / waypoint_commands | 无 SetupScriptCommands;为 commands_task.cpp 提供 waypoint 辅助实现 | — | — | — | — |
commands_gamestream | 空文件(0 行) | — | — | — | — |
(表中“入口”链接路径均位于 src/dev_ng/game/script/ 下,相对链接为 ../../GTAV%20Source/src/dev_ng/game/script/<文件>#L<行>。领域描述来自命名空间名、代表命令及 .sch 归属;未逐一阅读每个文件的全部实现。)
观察:
- 规模集中在 network(1211)、vehicle(899)、ped(800)、hud(719)、graphics(526)、misc(420)、stats(401)、task(400)、audio(388)几个文件,它们合计超过总数的 60%。
.sch声明数与 C++ 注册行大致一一对应,少量差异来自条件编译、DEBUGONLY声明以及跨文件声明(如gta_commands的命令声明在commands_misc.sch)。- 若干文件全部为 UNUSED(debug、landingpage、law、volume、xml、gang),即这些命令在 FINAL 可执行文件中根本不存在;FINAL 脚本若调用它们会在加载时被
Validate标为缺失(见 script-vm.md 6.4)。
7. game/script_headers:脚本侧声明#
script_headers 是一个 projgen 工程(Project script_headers),列出 63 个 .sch src/dev_ng/game/script_headers/makefile.txt:1-67。目录内共 68 个 .sch,分三类:
| 类别 | 文件 | 内容 |
|---|---|---|
| 命令声明 | commands_*.sch(与 C++ 文件同名,另有 commands_iis.sch 只存在于脚本树副本) | ENUM、STRUCT 与 NATIVE FUNC/PROC … = "0x…" |
| 内建与类型 | rage_builtins.sch(TIMERA/WAIT/PRINT* 等 52 个 native,PRINT* 带 DEBUGONLY) src/dev_ng/game/script_headers/rage_builtins.sch:6-34;types.sch(57 个 NATIVE 句柄类型,支持单继承,如 PED_INDEX : ENTITY_INDEX : ITEM_INDEX) src/dev_ng/game/script_headers/types.sch:1-12、src/dev_ng/game/script_headers/types.sch:54-59 | 编译器内建类型之外的句柄类型 |
| 共享枚举/常量 | event_enums、hud_colours、ped_bonetags、weapon_enums、pickup_enums、material_enums、nm_strings、achievements_enum、leaderboards_enum、presence_enum、gamemodes_enum、script_hud_enums、eulers、generic、brains、stack_sizes | 与 C++ 枚举对应的数值 |
语法要点(由编译器语法规则决定):NATIVE [DEBUGONLY] FUNC <type> NAME(<formals>) = "0x…" / NATIVE [DEBUGONLY] PROC NAME(...) = "0x…" 生成 scrSymCommand(DEBUGONLY 的调用在 -final 编译中被丢弃 src/dev_ng/rage/script/tools/scriptcompiler/node.cpp:2291-2293);NATIVE NAME / NATIVE NAME : PARENT 声明不透明句柄类型,赋值/比较只允许同类型(NULL 兼容所有句柄) src/dev_ng/rage/script/tools/scriptcompiler/scr.y:721-748、src/dev_ng/rage/script/src/script/script.dtx:166-189。
C++ 与 .sch 的对应完全靠约定:
- 标识值:
.sch的= "0x…"必须等于 C++ 宏的第二参数,不一致则脚本加载时Validate报 “NATIVE definition … is missing”。 - 参数布局:每个
.sch形参占的槽数必须等于 C++ 形参经Arg<T>消耗的槽数(VECTOR为 3 槽),只有断言构建的AssertParamMatch能在运行时发现不匹配src/dev_ng/rage/script/src/script/wrapper.h:594-606。 - 枚举:没有自动生成机制,依赖注释提醒,如 “Must match camControlHelperMetadataViewMode::eViewModeContext”
src/dev_ng/game/script_headers/commands_camera.sch:38、“Needs to be kept in sync with fwDecorator::DECORATOR_TYPES”src/dev_ng/game/script_headers/commands_decorator.sch:3、“Keep in sync with … ExtraMetadataDefs.psc”src/dev_ng/game/script_headers/commands_extrametadata.sch:30。 - 栈尺寸:
stack_sizes.sch用CONST_INT给出脚本启动子线程时使用的栈尺寸(如DEFAULT_STACK_SIZE (1424)、MULTIPLAYER_FREEMODE_STACK_SIZE (70500),注释中写有该尺寸的最大并发数)src/dev_ng/game/script_headers/stack_sizes.sch:2-45,它们必须与gameconfig.xml中配置的栈池尺寸完全相等,否则AllocateStack找不到匹配栈(见 script-vm.md 5.2)。
脚本树中的副本:脚本工程的 include 路径使用 script/dev_ng/core/common/native(GTA5_SP.ssproj 的 IncludeDirectories 含 core\common\native script/dev_ng/GTA5_SP.ssproj:20)。对比本快照:game/script_headers 的 66 个 .sch 中 62 个与 core/common/native 下同名文件字节相同,4 个不同(commands_debug、commands_stats、generic、script_hud_enums),stack_sizes.sch、weapon_enums.sch 在脚本树副本中缺失,脚本树多出 commands_iis.sch(通过文件哈希比对得出)。run_randomizenatives.from.scripts.bat 同时 p4 edit 两处目录 script/dev_ng/run_randomizenatives.from.scripts.bat:3-9,说明两处被当作同一份声明维护;具体同步机制(Perforce 分支映射还是脚本拷贝)在源码中未找到证据。
8. 发行构建的 native 裁剪#
SCR_REGISTER_SECURE 与 SCR_REGISTER_UNUSED 的区分不是手写的,而是由 scannative 工具根据发行版脚本实际引用的 native 自动维护:
run_scannative.bat收集含SCR_REGISTER_的 C++ 文件列表(game/script/*.cpp、DebugNodes.cpp、rage/script/src/script/thread.cpp)写入gta5_files.txt,然后对singleplayer\sco\RELEASE\PS4下的.sco运行 scannativescript/dev_ng/run_scannative.bat:1-10。- scannative 加载每个
.sco、收集其 native 表中的全部标识;然后逐个 C++ 文件扫描SCR_REGISTER_SECURE(/SCR_REGISTER_UNUSED((要求前一字符不是_),按“是否被发行脚本引用”把两者互相改写,并p4 edit写回src/dev_ng/rage/script/tools/scannative/scannative.cpp:60-154。 - FINAL 构建中
SCR_REGISTER_UNUSED展开为空,未被发行脚本引用的命令连同其包装函数都不会进入可执行文件(链接器可剥离其实现)。
因为第 2 步要求宏名前一个字符不是 _,DLC_SCR_REGISTER_SECURE 不会被改写——这正是 wrapper.h 注释“for marking things we do NOT ever want dead-stripped”的实现方式(DLC 脚本不在扫描集合中,但其 native 必须常驻)。非 FINAL 下可用 -testunusednatives 让 RegisterUnusedCommand 直接返回,模拟 FINAL 的命令集合 src/dev_ng/rage/script/src/script/thread.cpp:2574-2578。
9. native 调用路径上的安全与校验(高层)#
| 层次 | 机制 | 说明 |
|---|---|---|
| 加载期 | scrThread::Validate | 未注册标识被替换为 InvalidNative(执行即 Quitf),见 script-vm.md |
| 调用期(断言构建) | AssertParamMatch、GUID 断言标志、SCRIPT_VERIFY_* | 参数槽数、实体存在/死亡检查/clone 检查、网络脚本状态 |
| 网络 | CGameScriptHandlerMgr::IsScriptValidForNetworking 白名单 | 只有登记过的脚本名才能 NETWORK_SET_THIS_SCRIPT_IS_NETWORK_SCRIPT src/dev_ng/game/script/Handlers/GameScriptMgr.cpp:784-802(名单内容不在此列出) |
| 防篡改(仅存在性) | sysObfuscated 命令表、随机注册顺序、ApiCheckPlugin_* 钩子、PC HONEYPOT 包装、commands_security.cpp 中的脚本变量登记类命令 | 归 ../23-debug-security/security-overview.md |
10. 新增一个 native 的步骤(由上述机制归纳)#
- 在对应
commands_xxx.cpp的命名空间中实现CommandXxx(...),参数类型限于第 4 节支持的集合;需要句柄时用GetEntityToModifyFromGUID/QueryFromGUID。 - 在同文件
SetupScriptCommands()中添加SCR_REGISTER_SECURE(NAME, <64位值>, CommandXxx)(或DLC_前缀以避免被 scannative 降级)。 - 在
game/script_headers/commands_xxx.sch(及脚本树的core/common/native副本)添加对应NATIVE声明,值与 C++ 一致(可由uuid64生成或留空后运行randomizenatives同步)。 - 若引入新枚举,手工保持
.sch与 C++ 数值一致;若引入新的栈尺寸,同步stack_sizes.sch与gameconfig.xml。
约束与边界行为#
- 每个 native 参数槽 ≤ 63、返回槽 ≤ 3(指令编码限制,见 script-vm.md);
CallFunction模板最多支持 18 个 C++ 形参(签名也限 18)src/dev_ng/rage/script/src/script/wrapper.h:1251-1275、src/dev_ng/rage/script/src/script/signature.h:86。 - 重复标识在注册时断言(
scrCommandHash::Insert);FINAL 下断言不生效时重复注册的后果未验证。 - native 在主更新线程、脚本线程上下文中执行(
scrThread::GetActiveThread()非空),可以访问CTheScripts::GetCurrentGtaScriptThread();在其他线程调用 native 实现函数需要自行保证上下文。
未决问题 / 证据不足#
SCR_REGISTER_SECURE_EXPORT与SECURE完全等价,命名上的“EXPORT”语义(是否被某工具识别)未找到证据。game/script_headers与script/dev_ng/core/common/native的同步流程未在源码中找到;本快照中二者已有差异。- 各命令文件的“领域”描述基于命名空间、代表命令与头文件归属,未逐一审读全部实现。