CD Code Digest Hub /GTA V · v2

启动入口与 CApp 应用状态机

75 分钟阅读 15 个章节 3 张图

本页解释 GTAV 进程从操作系统入口到“每帧主循环”的完整路径:RAGE 的 App 风格入口(SET_APP_ENTRY_POINTS)、各平台 main/WinMain 包装、CommonMain 的三段式回调、游戏侧 Main_Prologue/Main_OneLoopIteration/Main_Epilogue,以及驱动整个游戏生命周期的 CApp(fwFsm 派生)五状态机;最后覆盖退出、重启与崩溃上下文(Backtrace/Coredump)路径。

理解这条路径是阅读其余运行时页面的前提:堆在 main() 之前就由静态构造建立(见 memory-and-pools.md),任务/依赖调度器在游戏代码运行前由 RAGE 启动(见 threading-and-render-thread.md),而所有游戏子系统都是在 CApp::InitGame → CGame::Init 中通过 gameSkeleton 装配的(见 game-skeleton-and-main-loop.md)。

背景与相关页面#

相关源码文件#

文件作用
src/dev_ng/game/Core/main.cpp游戏入口:平台堆尺寸宏、Main_Prologue/OneLoopIteration/Epilogue、SET_APP_ENTRY_POINTS、CApp g_myApp、Backtrace 配置
src/dev_ng/rage/base/src/system/main.h被 main.cpp 包含的“入口头”:SET_APP_ENTRY_POINTS 宏、InitGameHeap、全局 operator new/delete、main/WinMain 定义
src/dev_ng/rage/base/src/system/main.cppCommonMain、CommonMain_Prologue/OneLoopIteration/Epilogue、ExceptMain_*、响应文件解析
src/dev_ng/rage/base/src/system/applicationview_durango.winrt.cppXbox One 的 WinRT CoreApplication 入口与游戏线程派生
src/dev_ng/rage/framework/src/fwsys/fsm.h/.cpp、app.hfwFsm 有限状态机与空壳 fwApp
src/dev_ng/game/Core/app.h/.cppCApp 状态机、PC 退出/重启流程、崩溃上下文写入
src/dev_ng/game/system/system.h/.cppCSystem::Init/Shutdown/BeginUpdate/EndUpdate/RenderThreadInit 等底层系统初始化
src/dev_ng/rage/base/src/system/exception.hEXCEPTION_HANDLING、BACKTRACE_ENABLED 等崩溃处理开关与 BacktraceConfig
src/dev_ng/game/Core/dongle*.cpp/.h、memorytest.*、watermark.h、UserEntitlement*game/Core 中的杂项(加密狗、内存测试、水印、Gen9 权益服务)

1. 总览:从进程入口到第一帧#

图中证据:

  • InitGameHeap_t 静态对象在构造时调用 InitGameHeap() 并 BeginLayer():src/dev_ng/rage/base/src/system/main.h:2052-2083
  • SET_APP_ENTRY_POINTS 用静态对象构造函数写入三个函数指针:src/dev_ng/rage/base/src/system/main.h:477-486,游戏侧调用点:src/dev_ng/game/Core/main.cpp:601
  • CApp g_myApp; 全局对象:src/dev_ng/game/Core/main.cpp:362;fwFsm 构造即 Reset(),初始 m_State=0:src/dev_ng/rage/framework/src/fwsys/fsm.cpp:13-27
  • 平台入口:src/dev_ng/rage/base/src/system/main.h:2362-2396
  • CommonMain 主循环 while(carryOn):src/dev_ng/rage/base/src/system/main.cpp:1375-1388
  • 游戏每次迭代调用 g_myApp.RunOneIteration():src/dev_ng/game/Core/main.cpp:565-593

2. main() 之前:静态初始化阶段#

GTAV 的“入口”不是 main(),而是一组依赖初始化顺序的静态对象。game/Core/main.cpp 先定义一批平台宏,然后 #include "system/main.h",后者是一个会产生定义的头文件(定义 main、operator new、InitGameHeap 等),因此它只能被一个翻译单元包含。

静态对象 / 机制作用证据
InitGameHeap_t InitGameHeapInstance在 Win32 上通过 #pragma init_seg(compiler)、在 PS3/Orbis 上通过 init_priority(101) 抢在其他全局构造之前建立整个堆拓扑,并调用 sysMemAllocator::GetCurrent().BeginLayer();析构时 EndLayer("global") 做泄漏检查src/dev_ng/rage/base/src/system/main.h:2052-2083
EARLY_INIT_CALLBACK游戏在 !__NO_OUTPUT 时把它定义为 EarlyInit,后者把 diagGetGameFrame 指向 fwTimer::GetFrameCount,使日志能打印帧号;它在 InitGameHeap() 开头被调用src/dev_ng/game/Core/main.cpp:206-210、src/dev_ng/game/Core/main.cpp:640-653、src/dev_ng/rage/base/src/system/main.h:815-817
SET_APP_ENTRY_POINTS(Main_Prologue, Main_OneLoopIteration, Main_Epilogue)宏展开为一个静态 app_entry_point_setter_t,构造时写入 g_pProjectPrologue / g_pProjectMainOrDoOneLoop / g_pProjectEpiloguesrc/dev_ng/rage/base/src/system/main.h:470-486
g_pProjectMainOrDoOneLoop 默认值 CallMainOnce若项目不设置 App 入口,RAGE 只调用一次 Main();GTAV 的 Main() 只是一个断言“GTA V uses App style entry points”,说明它从不走经典路径src/dev_ng/rage/base/src/system/main.cpp:177-189、src/dev_ng/game/Core/main.cpp:380-385
CApp g_myApp全局应用对象;CApp::sm_Self 直到 RunGame_OnEnter 末尾才被 SetInstance(this)src/dev_ng/game/Core/main.cpp:362、src/dev_ng/game/Core/app.cpp:814、src/dev_ng/game/Core/app.cpp:1253
GameBacktraceConfig s_backtraceTokenSetter仅 BACKTRACE_ENABLED 时存在;在静态初始化中把崩溃上报的 token/提交 URL 交给 RAGE(注释说明“需要在 RAGE 层代码运行前可用”)src/dev_ng/game/Core/main.cpp:767-819

安全说明:main.cpp 中按平台(RSG_SCARLETT/RSG_DURANGO/RSG_PC)以宏 BACKTRACE_TOKEN、BACKTRACE_SUBMISSION_URL 硬编码了崩溃上报服务的 token 和地址,注释中还列出了多组备用 token。本 Wiki 不复制这些值。

全局 operator new 也定义在 system/main.h:它转发到 sysMemAllocator::GetCurrent().LoggedAllocate,在 RAGE_ENABLE_RAGE_NEW 下若调用裸 new 会断言要求改用 rage_new 以便内存追踪 src/dev_ng/rage/base/src/system/main.h:2090-2152。详见 memory-and-pools.md。

3. 平台入口包装#

平台/条件入口行为证据
__WIN32PC && __WINMAINWinMain取 __argc/__argv 直接调用 rage::CommonMainsrc/dev_ng/rage/base/src/system/main.h:2362-2368
RSG_DURANGO[Platform::MTAThread] main(Platform::Array<String^>^)CoreApplication::Run(sysApplicationViewSource);视图的 Run() 在主应用线程初始化键盘后,用 1 MB 栈创建 “[RAGE] Main Game Thread”,自己留在 CoreWindow::Dispatcher->ProcessEvents 循环中src/dev_ng/rage/base/src/system/main.h:2369-2377、src/dev_ng/rage/base/src/system/applicationview_durango.winrt.cpp:244-270
__PPU(PS3,遗留)main → WrapCommonMain在 EXCEPTION_HANDLING 下另起 “[RAGE] Main Thread” 运行 CommonMain 并等待其退出src/dev_ng/rage/base/src/system/main.cpp:591-627
其他(PC 控制台子系统、Orbis 等)main(argc, argv)DISABLE_RFS 时关闭 RFS,调用 CommonMain;PORTCULLIS_ENABLE 时返回前调用 portcullis::Shutdown()src/dev_ng/rage/base/src/system/main.h:2378-2396

Durango 的游戏线程入口有两点特殊:

  1. 由于“当前分配器”是线程局部的(__THREAD sysMemAllocator* sysMemAllocator_sm_Current 等,见 src/dev_ng/rage/base/src/system/memory.h:344-346),新线程必须用 s_pTheAllocator 重新设置 Current/Master/Container src/dev_ng/rage/base/src/system/applicationview_durango.winrt.cpp:205-209。
  2. 游戏线程以 for(;;) 调用 CommonMain_OneLoopIteration_DurangoWrapper(),忽略其返回值;CommonMain_Epilogue_DurangoWrapper() 被注释掉,源码注释自承“currently unreachable” src/dev_ng/rage/base/src/system/applicationview_durango.winrt.cpp:227-240。CommonMain_Prologue_DurangoWrapper 还会把 OS 传入的 argv[0] 改写为 G:\game_durango_<config>.exe src/dev_ng/rage/base/src/system/main.cpp:1472-1480。

PC 与 PS4 上新线程同样继承“创建者的当前分配器”:sysIpcThreadWrapperData::Init 记录 GetCurrent() 并断言其等于 Master,线程包装函数里再 SetCurrent/SetMaster/SetContainer src/dev_ng/rage/base/src/system/ipc.cpp:665-673、src/dev_ng/rage/base/src/system/ipc.cpp:843-855。

4. CommonMain 与三段式回调#

4.1 CommonMain#

  • Orbis:__FINAL 下 argv[0] 强制为 /app0/eboot.bin,否则依据模块名/调试可执行路径修正,然后 sceSystemServiceHideSplashScreen() src/dev_ng/rage/base/src/system/main.cpp:1238-1267。
  • PC 非 final 且挂调试器时设置 _NO_DEBUG_HEAP=1 src/dev_ng/rage/base/src/system/main.cpp:1269-1274。
  • __FINAL_LOGGING 构建会在命令行前拼接一组“烘焙参数”(-rootdir、-commonpack、-platformpack、-update、各网络通道 debug3 日志级别、-output、-logfile=...),合并后最多 MAX_ARGS = 50 个,超出的被忽略并报错 src/dev_ng/rage/base/src/system/main.cpp:1280-1377。
  • 之后 if (CommonMain_Prologue(...)) { while(carryOn) carryOn = CommonMain_OneLoopIteration(); CommonMain_Epilogue(); },返回 g_MainReturnValue src/dev_ng/rage/base/src/system/main.cpp:1375-1388。

4.2 CommonMain_Prologue(RAGE 层启动顺序)#

顺序动作证据
1g_IsStaticInitializationFinished = true;PC 上 WerSetFlags(WER_FAULT_REPORTING_FLAG_NOHEAP)src/dev_ng/rage/base/src/system/main.cpp:674-680
2记录线程 ID、计算主线程栈范围;当前线程加上 THREAD_TYPE_PROCESS_MAIN 与 THREAD_TYPE_UPDATE(“直到被告知其他为止,这就是 update 线程”),记录 g_MainThreadIdsrc/dev_ng/rage/base/src/system/main.cpp:686-699
3ArchInit、fiDeviceTcpIp::InitClass,主机平台读取 rfs.dat 并初始化远程文件系统(RFS)src/dev_ng/rage/base/src/system/main.cpp:701-777
4ResponseFile(@file 响应文件,可递归)与 sysParam::Initsrc/dev_ng/rage/base/src/system/main.cpp:779-786
5RAGE_TRACKING 下按 -memvisualize 建立远程内存追踪器,并为各堆登记范围src/dev_ng/rage/base/src/system/main.cpp:789-981
6PC 非 final:用命名互斥体 Game_PC_SingleInstanceMutex 断言不运行两个实例(-processinstance 可绕过断言)src/dev_ng/rage/base/src/system/main.cpp:998-1008
7打开日志文件(-logsize、-logfile)、sysTmInit(R*TM 目标管理器)src/dev_ng/rage/base/src/system/main.cpp:1010-1024
8sysException::Init(EXCEPTION_HANDLING)、SetupBreakpad(BREAKPAD_ENABLED)、SetupBacktraceDumpAndUpload(Durango)src/dev_ng/rage/base/src/system/main.cpp:1034-1044
9无调试器且未给 -noHangDetectThread 时启动 hang detect 线程src/dev_ng/rage/base/src/system/main.cpp:1047-1056
10diagChannel::InitClass、sysBootManager::InitClass、pgBase::InitClass(g_KnownRefPoolSize)src/dev_ng/rage/base/src/system/main.cpp:1069-1075
11-breakonalloc/-breakonaddr、sysStack::InitClass、atHashStringNamespaceSupport::InitNamespacessrc/dev_ng/rage/base/src/system/main.cpp:1092-1117
12sysTaskManager::InitClass()、sysDependencyScheduler::InitClass()——两套工作线程体系在任何游戏代码之前启动src/dev_ng/rage/base/src/system/main.cpp:1127-1128
13-logmemory 时开始分配日志;最后 return ExceptMain_Prologue()src/dev_ng/rage/base/src/system/main.cpp:1130-1178

g_KnownRefPoolSize 来自游戏定义的 PGKNOWNREFPOOLSIZE:Xenon/PS3 为 0x4000,其余为 0x8800(34816)个 known-reference 槽位 src/dev_ng/game/Core/main.cpp:25、src/dev_ng/rage/base/src/system/main.h:767-775。

ExceptMain_Prologue 在调用项目回调前做全局设置:关闭向量单元非规格化数(Vec::DisableDenormals)、PC 读取数据缓存行大小、-rageversion 时打印版本后直接返回 false、把 sysMemAllowResourceAlloc 设为 true、PC 上 timeBeginPeriod(1) 提高 Sleep/WaitForSingleObject 的定时精度,然后调用 g_pProjectPrologue() src/dev_ng/rage/base/src/system/main.cpp:201-240。

4.3 CommonMain_Epilogue#

顺序为:ExceptMain_Epilogue(项目 Epilogue → PC timeEndPeriod(1) → 关日志)→ 结束内存日志 → sysDependencyScheduler::ShutdownClass → sysTaskManager::ShutdownClass → 删除追踪器 → pgBase::ShutdownClass → sysBootManager::ShutdownClass → RFS/TCP 设备 → 打印主线程栈用量 → atHashStringNamespaceSupport::ShutdownNamespaces;PS3 最后 sys_process_exit(0) src/dev_ng/rage/base/src/system/main.cpp:252-266、src/dev_ng/rage/base/src/system/main.cpp:1186-1230。

注意:PC 的正常退出走 ExitProcess(0)(见第 9 节),Durango 的 Epilogue 不可达,主机平台的 CSystem::WantToExit 也不接用户退出——因此 CommonMain_Epilogue 在零售环境中实际上很少被执行。

5. 游戏侧三个回调#

Main_Prologue#

src/dev_ng/game/Core/main.cpp:387-563

  • 非 final:Durango 读取并校验系统版本(低于 _XDK_VER 则 Quitf);Orbis devkit 读取系统软件版本,低于 RSG_MIN_ORBIS_SYS = 0x01750061 时退出(允许一个特殊版本号)src/dev_ng/game/Core/main.cpp:389-418、src/dev_ng/game/Core/main.cpp:290-296。
  • 输出策略:非 dev 且非 final 构建若未给 -output,或 dev 构建给了 -nooutput(__FINAL_LOGGING 下 final 也适用),则 diagChannel::SetOutput(false)——“GTAV doesn't want output on in release builds” src/dev_ng/game/Core/main.cpp:420-437。
  • SYSTMCMD_ENABLE 且 -rockstartargetmanager:向 R*TM 发送配置 XML;Orbis 上根据构建类型、-memvisualize/-streamingvisualize、是否从 HDD 启动决定是否启用 submitDone 异常 src/dev_ng/game/Core/main.cpp:440-491。
  • PC:未要求输出时隐藏控制台窗口;除非 -nokeyboardhook(非 final 64 位下有调试器时默认不装,-forcekeyboardhook 强制装),安装 WH_KEYBOARD_LL 钩子在窗口有焦点时吞掉 Windows 键;-noaero 在非 D3D11.1 下关闭 DWM 合成 src/dev_ng/game/Core/main.cpp:493-531、src/dev_ng/game/Core/main.cpp:331-357。
  • sysMemAllowResourceAlloc = false(“Rage turns this on by default, we want it off for the game”)src/dev_ng/game/Core/main.cpp:533-534。
  • __ASSERT && (Xenon||PS3) 时自检 IsConstString src/dev_ng/game/Core/main.cpp:542-555;PC 载入错误码语言文件并 CSystemInfo::Initialize() src/dev_ng/game/Core/main.cpp:557-560。

Main_OneLoopIteration#

src/dev_ng/game/Core/main.cpp:565-593:PROFILER_FRAME("MainThread");!__NO_OUTPUT 下 PC 按 Ctrl+Shift+Delete(其他平台 Ctrl+Alt+Delete)触发 __debugbreak() 以便在无目标管理器时获取 coredump;BACKTRACE_ENABLED 时插入 BACKTRACE_TEST_CRASH 测试点;最后 return g_myApp.RunOneIteration()。

Main_Epilogue#

空函数 src/dev_ng/game/Core/main.cpp:596-599。所有清理都在 CApp 的 Shutdown 状态中完成。

6. main.cpp 的平台堆尺寸宏(摘要)#

main.cpp 在包含 system/main.h 之前定义堆尺寸宏(单位 KB),main.h 中的 InitGameHeap() 读取它们建立堆。64 位平台(PC/Durango/Orbis)的关键值:

宏PCDurangoOrbis证据
SIMPLE_HEAP_SIZE(游戏虚拟堆)474 MB(USE_SPARSE_MEMORY 的 PC dev 构建为 620 MB)596 MB596 MBsrc/dev_ng/game/Core/main.cpp:35-45
SIMPLE_PHYSICAL_SIZE(资源堆)256 MB2482 MB2482 MBsrc/dev_ng/game/Core/main.cpp:73-81
SIMPLE_VIRTUAL_SIZE1024 MB00src/dev_ng/game/Core/main.cpp:63-71
DEBUG_HEAP_SIZE220 MB220 MB220 MBsrc/dev_ng/game/Core/main.cpp:191
REPLAY_HEAP_SIZE196 MB196 MB196 MBsrc/dev_ng/game/Core/main.cpp:194-196

完整的堆拓扑、分配器类型与平台差异见 memory-and-pools.md。

7. fwFsm:CApp 的状态机内核#

CApp : fwApp : fwFsm,fwApp 是一个空类 src/dev_ng/rage/framework/src/fwsys/app.h:15-21。fwFsm 的语义决定了 CApp 每次迭代实际执行什么:

要素内容证据
事件OnEnter / OnUpdate / OnExit / OnMessagesrc/dev_ng/rage/framework/src/fwsys/fsm.h:50-57
返回值Unhandled(继续在同一次 Update() 内派发下一个事件)、Continue(本次迭代结束)、Quit(RunOneIteration 返回 false)src/dev_ng/rage/framework/src/fwsys/fsm.h:42-48、src/dev_ng/rage/framework/src/fwsys/fsm.h:74-81
SetState只写 m_NextState,真正切换发生在下次 Update();在上一次切换完成前再次调用会断言src/dev_ng/rage/framework/src/fwsys/fsm.cpp:29-33
Update 派发顺序先累加 fwTimer::GetTimeStep() 到 m_TimeRunning/m_TimeInState;循环直到返回值非 Unhandled:若 m_State != m_NextState → 对旧状态发 OnExit 并置“未进入”标志;否则若有消息 → 发 OnMessage(未处理则交给全局 state -1);否则若未进入 → OnEnter;否则 OnUpdatesrc/dev_ng/rage/framework/src/fwsys/fsm.cpp:41-84
宏fwFsmBegin/fwFsmState/fwFsmOnEnter/OnUpdate/OnExit/OnMsg/fwFsmEnd 展开为 if-else 链;未知状态时断言并返回 Quitsrc/dev_ng/rage/framework/src/fwsys/fsm.h:14-22

关键推论:由于未处理的事件会“落空”到下一个事件,一个没有 OnExit/OnEnter 处理器的状态,会在切换发生的同一次 Update() 内直接执行其 OnUpdate。

8. CApp 状态机#

图中证据:

  • 五个状态枚举:src/dev_ng/game/Core/app.h:23-30;UpdateState 只为 State_RunGame 绑定了 OnEnter,其余只有 OnUpdate:src/dev_ng/game/Core/app.cpp:825-852
  • InitSystem 成功/失败分支:src/dev_ng/game/Core/app.cpp:1034-1042、src/dev_ng/game/Core/app.cpp:1065
  • InitGame → RunGame:src/dev_ng/game/Core/app.cpp:1178
  • RunGame 退出分支(PC 调 CheckExit,其余检查 WantToExit):src/dev_ng/game/Core/app.cpp:1440-1447;PC ExitProcess(0):src/dev_ng/game/Core/app.cpp:1729
  • ShutdownGame → ShutdownSystem → Quit:src/dev_ng/game/Core/app.cpp:1846-1882

8.1 逐次迭代时间线#

结合 fwFsm::Update 的“落空”语义:

RunOneIteration 次序实际执行
第 1 次State_InitSystem 的 OnEnter 未处理 → 落到 OnUpdate → InitSystem(),设置下一状态
第 2 次OnExit(InitSystem) 未处理 → OnEnter(InitGame) 未处理 → InitGame()(包含整个 CGame::Init)
第 3 次OnExit(InitGame) 未处理 → RunGame_OnEnter() 返回 Continue。其中 CGame::UpdateSessionState() 同步跑完引导流程(加载关卡与会话,见 session-state-machine.md)
第 4 次起每次一帧:RunGame()

因此“初始加载”发生在第 3 次迭代、阻塞在一次函数调用里;这期间加载画面由渲染线程上的自定义渲染函数绘制(见 threading-and-render-thread.md)。

另外:app.h 声明了 RunGame_OnExit(),但 UpdateState 没有绑定 fwFsmOnExit,代码库中也没有其定义 src/dev_ng/game/Core/app.h:38。

8.2 InitSystem()#

src/dev_ng/game/Core/app.cpp:917-1067

  1. Orbis:用 16 KB 栈注册 CoredumpHandler(sceCoredumpRegisterCoredumpHandler)src/dev_ng/game/Core/app.cpp:920-925、src/dev_ng/game/Core/app.cpp:177。
  2. PC:断言构建中把 diagLogBugCallback 设为 CDebug::OnLogBug;diagTerminateCallback = CApp::WriteExitFile(致命错误时写退出文件给启动器)src/dev_ng/game/Core/app.cpp:928-935。
  3. 平台商店:__STEAM_BUILD 下验证 steam_api64.dll 证书并 SteamAPI_Init(该宏在 file_config.h 默认为 0 && ...,即默认关闭 src/dev_ng/rage/base/src/file/file_config.h:22-25);EPIC_API_SUPPORTED(PC 非工具构建为 1)时 g_SysEpic.InitClass(...),失败仅 Errorf src/dev_ng/game/Core/app.cpp:937-982。源码在此处硬编码了 Epic 产品/沙箱/客户端凭据,本 Wiki 不复制。
  4. RSG_LAUNCHER_CHECK(RSG_FINAL && RSG_PC):检查父进程是否为官方启动器,不是则 RAGE_PC_FATAL_SILENT(ERR_NO_LAUNCHER) src/dev_ng/game/Core/app.cpp:984-990、src/dev_ng/rage/base/src/file/file_config.h:47-48。这是发行保护的一部分,本页不展开其判定细节。
  5. 计时条:-dumptimebarsonlongmainthread[=ms](默认 200 ms);PF_INIT_TIMEBARS("Main", ...)、启动条 “Startup/System Init” src/dev_ng/game/Core/app.cpp:992-1013。
  6. pm_Init()(GCM 性能监视)、CDebug::SetPrinterFn()、InitNYDrawListDebug()(须早于 drawlist 管理器)、netRemoteLog::InitClass("gta5") src/dev_ng/game/Core/app.cpp:1015-1023。
  7. CSystem::Init(name):非 final 或非 PC 时窗口名为 "Fuzzy",PC final 为 "Grand Theft Auto V";失败 → State_ShutdownGame src/dev_ng/game/Core/app.cpp:1034-1042。详见 8.6。
  8. 加密狗检查(ENABLE_DONGLE,但 dongle.h 中两个分支都定义为 0,实际关闭 src/dev_ng/game/Core/dongle.h:17-21);InitNYDrawList();SetState(State_InitGame) src/dev_ng/game/Core/app.cpp:1053-1066。

8.3 InitGame()#

src/dev_ng/game/Core/app.cpp:1079-1180

  1. PC:CGame::InitCallbacks() 向 GRCDEVICE 注册 26 组设备丢失/重置回调(CSettingsManager、PostFX、GBuffer、CPauseMenu、CScaleformMgr 等;USE_MULTIHEAD_FADE 时再加 1 组,NV_SUPPORT 时另注册立体视觉回调)src/dev_ng/game/Core/game.cpp:3273-3309。
  2. CGame::PreLoadingScreensInit():载入片头视频、着色器、启动 RPF、DLC/内容补丁、文本等“显示加载画面之前必须就绪”的东西(逐项见 game-skeleton-and-main-loop.md)。
  3. 开发构建的内存挤占:devkit 上 -maponly/-maponlyextra/-cheapmode 会 CFileMgr::AllocateDummyResourceMem()(注释“alloc 2x10Mb chunks”)并强制 -nocars/-nopeds/-noambient;-maponlyextra 额外占 14 MB;-stealextramemory=<n> 占用指定字节 src/dev_ng/game/Core/app.cpp:1101-1130。
  4. Bugstar 集成、gTracingStats;CText::Init(INIT_CORE);CLoadingScreens::Init(LOADINGSCREEN_CONTEXT_LEGALMAIN) src/dev_ng/game/Core/app.cpp:1131-1148。
  5. 若流式安装(StreamingInstall)未完成:初始化警告屏与游戏流后阻塞等待安装完成;Durango 安装已完成则 StreamingInstall::Shutdown() src/dev_ng/game/Core/app.cpp:1150-1167。
  6. CGame::Init():注册全部 gameSkeleton 函数并执行 INIT_CORE src/dev_ng/game/Core/app.cpp:1169-1170。

8.4 RunGame_OnEnter()#

src/dev_ng/game/Core/app.cpp:1184-1272

  • RSG_NP:等待奖杯状态已知;若奖杯数据被重装则清除成就缓存并写回档案(Sony TRC 要求)src/dev_ng/game/Core/app.cpp:1190-1201。
  • CGame::UpdateSessionState():首次驱动会话状态机,引导流程中的所有阻塞式检查与首次 InitLevel/InitSession 都在这里同步完成 src/dev_ng/game/Core/app.cpp:1207。
  • CFileMgr::SetupDevicesAfterInit()、创建 PMCPerfControl、非 final 打印初始化耗时并为 16 个内存桶命名 src/dev_ng/game/Core/app.cpp:1220-1242。
  • 对 MEMTYPE_GAME_VIRTUAL/RESOURCE_VIRTUAL/GAME_PHYSICAL/RESOURCE_PHYSICAL 四个分配器做 UpdateMemorySnapshot();g_GameRunning = true(警告屏、加载画面、会话状态机用它判断是否已离开初始加载);PF_FINISH_STARTUPBAR();SetInstance(this) src/dev_ng/game/Core/app.cpp:1244-1253、src/dev_ng/game/Core/app.cpp:809-812。
  • PC:给 grcResourceCache 注册 IsGameRunning 回调、锁定设备重置可用性、按“失焦是否暂停”设置手柄后台输入 src/dev_ng/game/Core/app.cpp:1254-1269。

8.5 RunGame()(每帧)#

src/dev_ng/game/Core/app.cpp:1276-1468。这是主线程一帧的骨架,分阶段详述见 game-skeleton-and-main-loop.md 与 threading-and-render-thread.md:

  1. 帧标记、卡顿检测(暂停菜单/淡出/加载画面期间不计)、-autoMemTrack 第 60 帧请求内存 dump src/dev_ng/game/Core/app.cpp:1278-1315。
  2. “System begin”:s_perfControl->StartFrame()、CReplayMgr::StartFrame()、CSystem::BeginUpdate()(含 fwTimer::Update())src/dev_ng/game/Core/app.cpp:1318-1350。
  3. CGame::Update();截图请求;pm_Display() src/dev_ng/game/Core/app.cpp:1352-1359。
  4. 渲染线程交接:CTaskScheduler::StartBatchingTasks(5) → gRenderThreadInterface.GPU_IdleSection() → gRenderThreadInterface.Synchronise() → CControlMgr::StartUpdateThread() → CGame::Render()(只构建 drawlist)→ CControlMgr::WaitForUpdateThread() src/dev_ng/game/Core/app.cpp:1361-1370。
  5. 遥测;“AudioWait”:非开局自动读档且落地页不活跃时 audNorthAudioEngine::FinishUpdate() src/dev_ng/game/Core/app.cpp:1372-1385。
  6. 回放导出时追赶音频帧、CReplayMgr::PostRender() src/dev_ng/game/Core/app.cpp:1387-1433。
  7. s_perfControl->EndFrame();PC CApp::CheckExit() / 其他平台 WantToExit() → State_ShutdownGame;CSystem::EndUpdate();卡顿检测结束 src/dev_ng/game/Core/app.cpp:1434-1467。

8.6 CSystem::Init:底层系统装配#

src/dev_ng/game/system/system.cpp:440-803。按调用顺序:

步骤动作证据
1InitMemoryBuckets():把纹理/模型→RENDER、phBound→PHYSICS、Scaleform→SCALEFORM、MoVE→ANIMATION 桶src/dev_ng/game/system/system.cpp:281-288、src/dev_ng/game/system/system.cpp:455
2主线程同时标记为 SYS_THREAD_UPDATE 与 SYS_THREAD_RENDER(渲染线程尚未创建),gb_mainThread = true,pgDictionaryBase 链表归主线程src/dev_ng/game/system/system.cpp:457-461
3fiDevice::SetMaxMounts(MAX_ARCHIVES_MOUNTED)(100)、SetMaxExtraMounts(MAX_EXTRA_MOUNTS)src/dev_ng/game/system/system.cpp:463-467、src/dev_ng/rage/framework/src/streaming/streamingdefs.h:134
4INIT_PARSER(必须在 fwConfigManager 之前)、sysUserList、g_SysService、Orbis NP 服务委托src/dev_ng/game/system/system.cpp:469-479
5CStreaming::InitStreamingVisualize、CFileMgr::Initialise、sysAppContent::Init(Durango 设置 Title ID)src/dev_ng/game/system/system.cpp:481-492
6fwConfigManagerImpl<CGameConfig>::InitClass("common:/data/gameconfig.xml")(非 final 可用 -altGameConfig)src/dev_ng/game/system/system.cpp:494-507、src/dev_ng/game/system/system.cpp:156
7fwInstanceBuffer::InitClass;pgStreamer::InitClass(cpuId, PRIO_STREAMER_THREAD)(Xenon/Durango 的 cpuId=1,否则 0)src/dev_ng/game/system/system.cpp:509-516
8InitAfterConfigLoad():从配置读取 fwArchetypePooledMap、Peds、Vehicles 池大小src/dev_ng/game/system/system.cpp:957-968
9图形参数默认值:D3D 开 HDR;Orbis/Durango 默认 1920×1080、multiSample=4、multiFragment=1;PS3 无 MSAA、720psrc/dev_ng/game/system/system.cpp:537-583
10grcDevice::InitSingleton;PC/主机加载 CSettingsManager 设置并设定窗口尺寸;创建 grmSetup 并 Initsrc/dev_ng/game/system/system.cpp:592-659
11gRenderThreadInterface.Init(PRIO_RENDER_THREAD, RenderThreadInit) 创建渲染线程并等待其接管设备,然后主线程清除 SYS_THREAD_RENDER 标记src/dev_ng/game/system/system.cpp:675-685
12PC:VideoResManager、焦点查询线程;fwTimer::Init、CControlMgr::Init(INIT_CORE);双肩键同时按下则清 HDD 缓存src/dev_ng/game/system/system.cpp:687-712
13创建游戏任务调度器 "gtaSched";按 gameconfig 的 Update 线程配置设置主线程优先级/亲和;PC 低配机交换主线程与渲染线程优先级;线程注册表;注册 dlCmdBeginRender/dlCmdEndRendersrc/dev_ng/game/system/system.cpp:716-779

CSystem::RenderThreadInit() 是在渲染线程上同步执行的初始化回调:GetRageSetup()->BeginGfx、非 PC 设置帧锁、创建默认工厂、CShaderLib::PreInit、SetBacktraceVersionInfo、CSprite2d::Init、CStreaming::InitStreamingEngine()(必须早于 drawlist 管理器)、gDrawListMgr = rage_new CDrawListMgr()、遮挡查询、CGtaRenderThreadGameInterface::InitClass() src/dev_ng/game/system/system.cpp:345-436。

8.7 ShutdownGame() 与 ShutdownSystem()#

  • ShutdownGame:PS3 sceNp2Term;删除 s_perfControl;CGame::Shutdown()(依次关会话/关卡、写档案、停渲染线程、SHUTDOWN_CORE、销毁工厂)→ State_ShutdownSystem src/dev_ng/game/Core/app.cpp:1846-1859、src/dev_ng/game/Core/game.cpp:1187-1237。
  • ShutdownSystem:Orbis 注销 coredump 处理器;CSystem::Shutdown()(服务、parser、着色器库、控制、pgStreamer、grmSetup 工厂、fwConfigManager);netRemoteLog::ShutdownClass;打印 "Goodbye",返回 fwFsm::Quit src/dev_ng/game/Core/app.cpp:1862-1882、src/dev_ng/game/system/system.cpp:973-1019。

9. 退出与重启路径#

9.1 平台差异#

平台触发结果证据
PC窗口关闭(GRCDEVICE.QueryCloseWindow())、SCUI 请求关闭、暂停菜单“退出到 Windows/重启”CApp::CheckExit() 走网络保存/警告确认流程,最终 ExitProcess(0),不经过 State_ShutdownGamesrc/dev_ng/game/Core/app.cpp:1527-1731
主机CSystem::WantToExit()非 PC 下 bWantToExit = false,注释写明“INTENTIONALLY not hooked up to user quit any longer”;仅非 final 的冒烟测试结束会退出src/dev_ng/game/system/system.cpp:1319-1344
PS3(遗留)grmSetup::WantExit()(用户退出/弹盘)在渲染线程 CSystem::EndRender 中设 g_IsExiting、停电影、等奖杯线程,然后 _Exit(0)src/dev_ng/game/system/system.cpp:1257-1270

CApp::CheckExit() 不只在 RunGame 末尾调用:会话状态机每次内部迭代、各个阻塞等待循环、CGame::InitLevel/InitSession 的阶段之间都会调用它(PC),使加载期间也能响应关闭窗口 src/dev_ng/game/Core/gamesessionstatemachine.cpp:573-575、src/dev_ng/game/Core/game.cpp:1339、src/dev_ng/game/Core/game.cpp:1365、src/dev_ng/game/Core/game.cpp:1371、src/dev_ng/game/Core/game.cpp:1517。

9.2 PC CheckExit 流程#

图中证据:

  • 触发条件与三种分支:src/dev_ng/game/Core/app.cpp:1532-1567
  • 转圈/确认警告与 YES/NO 处理:src/dev_ng/game/Core/app.cpp:1573-1671
  • IsShutdownConfirmed()(sbWarningAccept || CPauseMenu::GetGameWantsToExitToWindows()):src/dev_ng/game/Core/app.cpp:1517-1520
  • 最终关闭序列:src/dev_ng/game/Core/app.cpp:1681-1730
  • GracefullyShutdownNetwork:Steam 结束认证会话、Epic ShutdownClass、CNetwork::Bail(BAIL_EXIT_GAME)、ShutdownConnectionManager、rlShutdown、netShutdown:src/dev_ng/game/Core/app.cpp:1477-1495

另外两个细节:权益(Entitlement)管理器的工作线程“not safe, and must be allowed to finish”,因此在其未就绪前退出确认只显示转圈不显示 YES/NO src/dev_ng/game/Core/app.cpp:1603-1616;CSystem::WantToExit() 为真时会清除窗口关闭请求 src/dev_ng/game/Core/app.cpp:1673-1676。

9.3 重启与启动器握手#

  • CApp::Restart():取当前模块路径与命令行中 .exe 之后的参数,用 ShellExecuteEx("open") 启动新进程 src/dev_ng/game/Core/app.cpp:1733-1775。若启动器(downloader pipe)已连接,则改由 g_rlPc.GetDownloaderPipe()->RestartGame() 处理,不自行重启 src/dev_ng/game/Core/app.cpp:1684-1699。
  • CApp::WriteExitFile(error):只有当 AppData 根目录下存在 in-<pid> 文件(即由启动器启动)时,才创建/追加 out-<pid> 并写入错误文本;同一会话第二次起改为追加,无错误信息且已写过则直接返回 src/dev_ng/game/Core/app.cpp:1777-1841。它既是正常退出的最后一步,也通过 diagTerminateCallback 在致命错误时被调用。

10. 崩溃上下文与 Backtrace#

10.1 编译开关#

宏定义证据
EXCEPTION_HANDLING!__RESOURCECOMPILER && !__TOOL && !__GAMETOOL && !__MASTERsrc/dev_ng/rage/base/src/system/exception.h:10
BREAKPAD_ENABLED仅 RSG_PC && __MASTER(非工具)src/dev_ng/rage/base/src/system/exception.h:13
BACKTRACE_DUMP_AND_UPLOAD_ENABLEDRSG_DURANGOsrc/dev_ng/rage/base/src/system/exception.h:14
BACKTRACE_ENABLEDCRASHPAD_ENABLED(0) || BREAKPAD_ENABLED || BACKTRACE_DUMP_AND_UPLOAD_ENABLEDsrc/dev_ng/rage/base/src/system/exception.h:12-17
BACKTRACE_TEST_CRASH!__MASTER 时在 ShouldCrashForBacktraceTest() 为真时写空指针src/dev_ng/rage/base/src/system/exception.h:167-182

即:PC master 用 Breakpad,Durango 用自带的 dump+上传,Orbis 用系统 coredump 回调;EXCEPTION_HANDLING(RAGE 自己的异常处理)只在非 master 构建中启用。

10.2 CoredumpHandler:崩溃时写出的游戏状态#

同一个函数服务两条路径:Orbis 通过 sceCoredumpWriteUserData 写入 coredump(用户数据上限 512 KB),Backtrace 平台则写到 s_crashContextHandle 指向的文件 src/dev_ng/game/Core/app.cpp:192-221。内容分节如下:

节内容证据
SYSTEM INFOCApp 状态、系统时间、帧号/是否暂停、流式请求数、HDD/光驱是否锁定、流式内存物理/虚拟/外部占用src/dev_ng/game/Core/app.cpp:230-260
MISC INFO版本号、玩家/相机位置、游戏时钟、通缉等级、IK 调试值、逮捕/死亡状态、系统语言、当前任务与载具;Bank 构建附加过场名与 CDebugScene 摘要,非 Bank 统计 ped/载具/物理实例数src/dev_ng/game/Core/app.cpp:262-615
室内主室内实例与房间名(!__NO_OUTPUT)src/dev_ng/game/Core/app.cpp:617-648
NETWORK INFO网络开/关状态、会话与过渡状态、人口预算、网络事件池占用、各同步树节点是否更新src/dev_ng/game/Core/app.cpp:650-746
SCALEFORM / SCRIPT / MOVIE最后的影片与 ActionScript 方法、活跃影片、活跃脚本线程及其栈回溯、Bink 影片状态src/dev_ng/game/Core/app.cpp:748-803

边界:CApp::GetInstance() 在 RunGame_OnEnter 末尾才设置,而 gameState 缓冲只在 app != NULL 时写入 src/dev_ng/game/Core/app.cpp:234-247。这表明在 InitSystem/InitGame 期间崩溃时,“Game State” 一行会打印未初始化的栈缓冲(推断;没有运行验证)。

10.3 Backtrace 上报决策#

GameBacktraceConfig : sysException::BacktraceConfig 的回调链:

  1. ShouldSubmitDump(path) 先 CApp::WriteCrashContextLog(path)(打开文件并调用上面的 CoredumpHandler)与 CApp::CollectAdditionalAttributes() src/dev_ng/game/Core/main.cpp:686-689、src/dev_ng/game/Core/app.cpp:2015-2024。
  2. 若 Tunables 已实例化,读取全局 tunable ENABLE_BACKTRACE(关闭则 sysException::DisableBacktrace())与 BACKTRACE_RATE_LIMIT(默认 100%)src/dev_ng/game/Core/main.cpp:691-725、src/dev_ng/game/Core/main.cpp:675-684。
  3. 用系统毫秒时间播种 mthRandom、丢弃前 100 个值后按比例抽样决定是否上传 src/dev_ng/game/Core/main.cpp:727-749。
  4. UploadDump:Durango 调 BacktraceDumpUpload::UploadDump,其他平台返回 false(注释说明放在游戏层是为了避免 RageCore 依赖 RageNet 且需 WinRT 文件)src/dev_ng/game/Core/main.cpp:752-765。PC 上 Breakpad 的实际上传实现位于 RAGE exception.cpp,本页未追踪。

CollectAdditionalAttributes() 设置的注解键:game_lifetime、last_launched_script_id、time_since_last_launched_script、network_state、active_players、entity_count_peds/vehicles/objects,Scarlett 还有 rendering_mode src/dev_ng/game/Core/app.cpp:2026-2118。构建信息注解(build_type、build_version[_a..d]、PC os_version)则在渲染线程初始化时由 SetBacktraceVersionInfo() 设置 src/dev_ng/game/system/system.cpp:294-343。

开发构建还有两个异常显示钩子:__BANK 下 sysException::GameExceptionDisplay = CGame::GameExceptionDisplay、sysMemOutOfMemoryDisplay = CGame::OutOfMemoryDisplay src/dev_ng/game/Core/game.cpp:975-978,前者打印玩家/相机位置与 ped/载具数量 src/dev_ng/game/Core/game.cpp:3779-3825;OOM 路径见 memory-and-pools.md。

11. game/Core 中的其他文件#

文件状态证据
dongle.h、dongle_*.cpp加密狗校验代码保留,但 ENABLE_DONGLE 两个分支都为 0,ENABLE_DONGLE_TOOL 也为 0;CApp::CheckDongle 因此不执行任何检查src/dev_ng/game/Core/dongle.h:15-21、src/dev_ng/game/Core/app.cpp:1893-1911
memorytest.h/.cppXenon 开机内存测试(按住 LT+RT+LB+RB),但 __INCLUDE_MEMORY_TEST_IN_XENON_BUILD 为 0src/dev_ng/game/Core/memorytest.h:10-12、src/dev_ng/game/Core/app.cpp:1135-1138
watermark.hWATERMARKED_BUILD 仅 PC 的某种非 bank、非 final 构建;颜色/位置从一个被禁止优化剥离的字符串解析(字符串内容不复制)src/dev_ng/game/Core/watermark.h:4-42
UserEntitlementService.*、IUserEntitlementDataProvider.hGen9 平台权益查询服务(AreEntitlementsUpToDate/RequestEntitlements/TryGetEntitlements),通过 CUserEntitlementServiceWrapper 注册到 INIT_CORE(仅 GEN9_STANDALONE_ENABLED)src/dev_ng/game/Core/UserEntitlementService.h:24-61、src/dev_ng/game/Core/game.cpp:1820-1825
core_channel.h声明 core 通道与 core_game 子通道及 coreXxx/gameXxx 日志宏,定义在 game.cppsrc/dev_ng/game/Core/core_channel.h:12-38、src/dev_ng/game/Core/game.cpp:491-492

12. 约束与边界行为#

  • 单翻译单元约束:system/main.h 定义了 main、operator new/delete、InitGameHeap 等符号,只能由 game/Core/main.cpp 包含;堆宏必须在包含前定义 src/dev_ng/game/Core/main.cpp:194-220。
  • 初始化顺序依赖静态构造优先级:堆用 init_seg(compiler)/init_priority(101) 保证最先建立;Orbis 旧 SDK 与 RGSC DLL 退化为在首次 operator_new 时懒初始化 src/dev_ng/rage/base/src/system/main.h:2090-2105。
  • fwFsm::SetState 不可重入:同一转换未完成前再次 SetState 会断言 src/dev_ng/rage/framework/src/fwsys/fsm.cpp:29-33。
  • 退出不对称:PC 用 ExitProcess(0) 跳过 CGame::Shutdown/CSystem::Shutdown 和全局析构;因此依赖析构才落盘的数据必须在 CheckExit 序列里显式处理(如 FinishAllUnfinishedSaveGameOperations)。
  • 主线程身份:CommonMain_Prologue 把调用线程标为 UPDATE;CSystem::Init 在渲染线程创建前临时把它也标为 RENDER,之后清除 src/dev_ng/rage/base/src/system/main.cpp:692-699、src/dev_ng/game/system/system.cpp:457-459、src/dev_ng/game/system/system.cpp:680。

13. 未决问题 / 证据不足#

  • RSG_SCARLETT/RSG_PROSPERO(Gen9)在 main.cpp 与 CommonMain 中出现分支,但本快照中 Gen9 代码似乎不完整(例如 livemanager.cpp 引用了会话状态机中不存在的 SHUTDOWN_FOR_IIS 状态,见 session-state-machine.md),Gen9 的启动路径无法完整验证。
  • PC Breakpad 上传、sysException::Init 内部行为属于 RAGE 诊断子系统,本页未展开(见 ../02-rage-core/system-and-diagnostics.md)。
  • 未运行任何构建或程序;第 8.1 节的“逐次迭代时间线”是根据 fwFsm::Update 源码推导的。