CD Code Digest Hub /GTA V · v2

Native 命令:注册机制、参数封送与 game/script/commands_* 分类

59 分钟阅读 14 个章节 1 张图

脚本能做的一切“游戏事情”——创建 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.hArg<T>/AssignReturnValue/CallFunction 封送模板、GetFunctionSignature、全部 SCR_REGISTER_* 宏
rage/script/src/script/thread.h/.cppscrThread::Info、RegisterCommand/RegisterUnusedCommand、命令表 scrCommandHash、内置命令
rage/script/src/script/signature.h/.cppscrSignature(64 位类型签名)
rage/script/src/script/varargs.h、array.hVARARGS 与变长数组参数辅助
game/script/script.cppCTheScripts::RegisterScriptCommands / ScrambleAndRegister:游戏命令的注册入口
game/script/commands_*.cpp/.h53 个命令文件,每个文件一个 xxx_commands 命名空间与 SetupScriptCommands()
game/script/script_helper.hSCRIPT_VERIFY*/SCRIPT_ASSERT* 等带脚本名+PC 的校验宏
game/script/script.hCTheScripts::GetEntityToModifyFromGUID 等句柄解析接口
game/script_headers/*.sch脚本侧的 NATIVE 声明、句柄类型、共享枚举(68 个 .sch)
game/debug/DebugNodes.cppBANK 构建额外注册的调试 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-13432181
SCR_REGISTER_SECURE_EXPORTSCR_REGISTER_SECURE 的别名 src/dev_ng/rage/script/src/script/wrapper.h:1328301
DLC_SCR_REGISTER_SECURE[_BLOCKING/_EXPORT]同 SECURE,但注释写明“永远不希望被裁剪”(见第 8 节为什么前缀能防止裁剪) src/dev_ng/rage/script/src/script/wrapper.h:1345-134868
SCR_REGISTER_SECURE_HONEYPOTPC:包装函数在调用前额外记录返回地址(防篡改用途);其他平台等同 SECURE src/dev_ng/rage/script/src/script/wrapper.h:1303-13157
SCR_REGISTER_SECURE_UNHASHED同 SECURE 但不调用 ApiCheckPlugin_AddNative src/dev_ng/rage/script/src/script/wrapper.h:1281-12894
SCR_REGISTER_SECURE_BLOCKING使用 CallFunctionBlocking(只在线程仍 RUNNING 时写回返回值) src/dev_ng/rage/script/src/script/wrapper.h:1318-13260

出现次数由在 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::SetupScriptCommands src/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!=0src/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/Floatsrc/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=OBJECTsrc/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 字节槽),例如 scrScMembershipInfo src/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;实例 CommandGetNearbyVehicles src/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_SIZE src/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)19APP_DATA_VALID、APP_GET_INTcommands_apps.cpp:322
commands_audio / audio_commands音频:语音、铃声、音乐、场景、电台388 (113)367PLAY_PED_RINGTONEcommands_audio.cpp:3910
commands_brains / brain_commands脚本大脑注册(ped/物体/世界点)15 (4)15REGISTER_OBJECT_SCRIPT_BRAINcommands_brains.cpp:222
commands_camera / camera_commands脚本摄像机、渲染切换、镜头效果281 (38)281CREATE_CAM、RENDER_SCRIPT_CAMScommands_camera.cpp:3690
commands_clock / clock_commands游戏时钟与日期19 (3)19SET_CLOCK_TIMEcommands_clock.cpp:211
commands_cutscene / cutscene_commands过场请求/播放/实体注册72 (19)72REQUEST_CUTSCENEcommands_cutscene.cpp:1225
commands_datafile / datafile_commandsUGC/云数据文件读写(JSON 式对象树)81 (24)81DATAFILE_IS_VALID_REQUEST_IDcommands_datafile.cpp:2434
commands_debug / debug_commands调试绘制/输出(全部 UNUSED)+ BANK 的 DebugNodes108 (108)115—commands_debug.cpp:1657
commands_decorator / decorator_commands实体装饰器(fwDecorator)18 (6)18DECOR_SET_BOOLcommands_decorator.cpp:249
commands_dlc / dlc_commandsDLC 存在性/内容检查26 (14)26IS_DLC_PRESENTcommands_dlc.cpp:304
commands_entity / entity_commands通用实体(位置、可见性、碰撞、动画、附着)200 (16)203DOES_ENTITY_EXISTcommands_entity.cpp:6313
commands_event / event_commands决策器、shocking event20 (7)20ADD_SHOCKING_EVENT_AT_POSITIONcommands_event.cpp:471
commands_extrametadata / extrametadata_commandsDLC 扩展元数据(纹身/服装店条目)62 (13)59GET_TATTOO_SHOP_DLC_ITEM_DATAcommands_extrametadata.cpp:779
commands_fire / fire_commands火焰、爆炸18 (0)18START_SCRIPT_FIREcommands_fire.cpp:385
commands_gang / gang_commands帮派(残留)3 (3)0—commands_gang.cpp:32
commands_graphics / graphics_commands绘制/调试图元、粒子、贴花、后处理、渲染目标526 (133)525DRAW_DEBUG_LINEcommands_graphics.cpp:7021
commands_hud / hud_commandsHUD、文本命令、雷达 blip、小地图719 (210)716BEGIN_TEXT_COMMAND_BUSYSPINNER_ONcommands_hud.cpp:7884
commands_interiors / interior_commands室内实例/房间/实体集64 (19)64GET_INTERIOR_HEADINGcommands_interiors.cpp:1116
commands_inventory / inventory_commands空实现00—commands_inventory.cpp:14
commands_itemsets / itemsets_commands脚本 item set10 (1)10CREATE_ITEMSETcommands_itemsets.cpp:126
commands_landingpage / landingpage_commandsGen9 落地页(均为 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)11LOBBY_AUTO_MULTIPLAYER_FREEMODEcommands_lobby.cpp:92
commands_localization / localization_commands系统语言/日期格式6 (3)6GET_CURRENT_LANGUAGEcommands_localization.cpp:63
commands_misc / misc_commands杂项:栈/线程信息、随机数、天气、区域、游戏状态等420 (90)463GET_ALLOCATED_STACK_SIZEcommands_misc.cpp:5639
commands_money / money_commands联网现金/交易记账327 (9)329NETWORK_INITIALIZE_CASHcommands_money.cpp:9710
commands_netshopping / netshopping_commands游戏服务器商品目录与交易64 (24)64NET_GAMESERVER_CATALOG_ITEM_IS_VALIDcommands_netshopping.cpp:2565
commands_network / network_commands网络:会话、网络脚本/广播数据、玩家、网络实体、UGC、语音1211 (376)1216NETWORK_IS_SIGNED_IN、NETWORK_SET_THIS_SCRIPT_IS_NETWORK_SCRIPTcommands_network.cpp:17095
commands_ny / gta_commands手机等 GTA 专属(文件名沿用 GTA IV 的 “ny”;声明在 commands_misc.sch)47 (22)—CREATE_MOBILE_PHONEcommands_ny.cpp:406
commands_object / object_commands物体、门、拾取物208 (45)212CREATE_OBJECTcommands_object.cpp:5564
commands_pad / pad_commands输入/控制映射86 (38)86IS_CONTROL_PRESSEDcommands_pad.cpp:1686
commands_path / path_commands路网节点/道路开关78 (19)77SET_ROADS_IN_AREAcommands_path.cpp:1547
commands_ped / ped_commandsped 创建/属性/外观/行为配置800 (191)794CREATE_PEDcommands_ped.cpp:13608
commands_physics / physics_commands绳索、物理约束68 (20)65ADD_ROPEcommands_physics.cpp:1556
commands_player / player_commands玩家控制、通缉、能力323 (75)320GET_PLAYER_PEDcommands_player.cpp:4871
commands_recording / recording_commands回放录制事件21 (4)21REPLAY_START_EVENTcommands_recording.cpp:305
commands_replay / replay_commands回放编辑器协作8 (2)8REGISTER_EFFECT_FOR_REPLAY_EDITORcommands_replay.cpp:92
commands_savemigration / savemigration_commands存档迁移11 (4)11SAVEMIGRATION_MP_REQUEST_ACCOUNTScommands_savemigration.cpp:249
commands_script / script_commands脚本流式加载、线程管理、事件读取、后台脚本45 (8)42REQUEST_SCRIPT、GET_EVENT_DATAcommands_script.cpp:800
commands_security / security_commands防篡改相关(只记录存在)4 (1)4—commands_security.cpp:86
commands_shapetest / shapetest_commands异步/同步形状测试(射线、包围盒)11 (0)11START_SHAPE_TEST_LOS_PROBEcommands_shapetest.cpp:375
commands_socialclub / socialclub_commandsSocial Club 收件箱/许可/成员资格110 (27)110SC_INBOX_GET_TOTAL_NUM_MESSAGEScommands_socialclub.cpp:1070
commands_stats / stats_commands统计读写、排行榜、成就401 (90)408STAT_LOAD、STAT_SAVEcommands_stats.cpp:20612
commands_streaming / streaming_commands模型/资产请求、加载场景、流式体积136 (16)136LOAD_SCENEcommands_streaming.cpp:2200
commands_task / task_commandsAI 任务下达、序列、waypoint 录制400 (95)404TASK_STAND_STILLcommands_task.cpp:9003
commands_vehicle / vehicle_commands车辆创建/属性/改装/录制899 (164)892CREATE_VEHICLEcommands_vehicle.cpp:15344
commands_volume / volume_commands脚本体积(均 UNUSED)15 (15)15—commands_volume.cpp:270
commands_water / water_commands水面高度/探测14 (2)14GET_WATER_HEIGHTcommands_water.cpp:232
commands_weapon / weapon_commands武器、组件、弹药149 (33)148GET_WEAPONTYPE_MODELcommands_weapon.cpp:3229
commands_xml / xml_commandsXML 读取(均 UNUSED)21 (21)21—commands_xml.cpp:499
commands_zone / zone_commands地图区域19 (10)19GET_ZONE_AT_COORDScommands_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 自动维护:

  1. 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 运行 scannative script/dev_ng/run_scannative.bat:1-10。
  2. scannative 加载每个 .sco、收集其 native 表中的全部标识;然后逐个 C++ 文件扫描 SCR_REGISTER_SECURE(/SCR_REGISTER_UNUSED((要求前一字符不是 _),按“是否被发行脚本引用”把两者互相改写,并 p4 edit 写回 src/dev_ng/rage/script/tools/scannative/scannative.cpp:60-154。
  3. 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 的步骤(由上述机制归纳)#

  1. 在对应 commands_xxx.cpp 的命名空间中实现 CommandXxx(...),参数类型限于第 4 节支持的集合;需要句柄时用 GetEntityToModifyFromGUID/QueryFromGUID。
  2. 在同文件 SetupScriptCommands() 中添加 SCR_REGISTER_SECURE(NAME, <64位值>, CommandXxx)(或 DLC_ 前缀以避免被 scannative 降级)。
  3. 在 game/script_headers/commands_xxx.sch(及脚本树的 core/common/native 副本)添加对应 NATIVE 声明,值与 C++ 一致(可由 uuid64 生成或留空后运行 randomizenatives 同步)。
  4. 若引入新枚举,手工保持 .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 的同步流程未在源码中找到;本快照中二者已有差异。
  • 各命令文件的“领域”描述基于命名空间、代表命令与头文件归属,未逐一审读全部实现。