启动入口与 CApp 应用状态机
本页解释 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)。
背景与相关页面#
- 每帧的游戏更新(
CGame::Update/Render、gameSkeleton的 update 模式):game-skeleton-and-main-loop.md - 首帧中同步执行的引导流程(社交俱乐部检查、自动读档、换关等):session-state-machine.md
- 渲染线程的创建与主线程同步:threading-and-render-thread.md
- 堆尺寸宏与
InitGameHeap:memory-and-pools.md fwFsm、fwTimer、fwConfigManager等框架类的总体介绍:../04-rage-framework/framework-systems.md- 命令行参数(
PARAM)、诊断通道、断言、hang detect:../02-rage-core/system-and-diagnostics.md - 安全模块(
rageSec、TamperActions等)只在本页提及存在位置:../23-debug-security/security-overview.md - 平台服务、设置、权益(Entitlement):../22-input-platform/platform-services-and-settings.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.cpp | CommonMain、CommonMain_Prologue/OneLoopIteration/Epilogue、ExceptMain_*、响应文件解析 |
src/dev_ng/rage/base/src/system/applicationview_durango.winrt.cpp | Xbox One 的 WinRT CoreApplication 入口与游戏线程派生 |
src/dev_ng/rage/framework/src/fwsys/fsm.h/.cpp、app.h | fwFsm 有限状态机与空壳 fwApp |
src/dev_ng/game/Core/app.h/.cpp | CApp 状态机、PC 退出/重启流程、崩溃上下文写入 |
src/dev_ng/game/system/system.h/.cpp | CSystem::Init/Shutdown/BeginUpdate/EndUpdate/RenderThreadInit 等底层系统初始化 |
src/dev_ng/rage/base/src/system/exception.h | EXCEPTION_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-2083SET_APP_ENTRY_POINTS用静态对象构造函数写入三个函数指针:src/dev_ng/rage/base/src/system/main.h:477-486,游戏侧调用点:src/dev_ng/game/Core/main.cpp:601CApp 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_pProjectEpilogue | src/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 && __WINMAIN | WinMain | 取 __argc/__argv 直接调用 rage::CommonMain | src/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 的游戏线程入口有两点特殊:
- 由于“当前分配器”是线程局部的(
__THREAD sysMemAllocator* sysMemAllocator_sm_Current等,见src/dev_ng/rage/base/src/system/memory.h:344-346),新线程必须用s_pTheAllocator重新设置 Current/Master/Containersrc/dev_ng/rage/base/src/system/applicationview_durango.winrt.cpp:205-209。 - 游戏线程以
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>.exesrc/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=1src/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_MainReturnValuesrc/dev_ng/rage/base/src/system/main.cpp:1375-1388。
4.2 CommonMain_Prologue(RAGE 层启动顺序)#
| 顺序 | 动作 | 证据 |
|---|---|---|
| 1 | g_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_MainThreadId | src/dev_ng/rage/base/src/system/main.cpp:686-699 |
| 3 | ArchInit、fiDeviceTcpIp::InitClass,主机平台读取 rfs.dat 并初始化远程文件系统(RFS) | src/dev_ng/rage/base/src/system/main.cpp:701-777 |
| 4 | ResponseFile(@file 响应文件,可递归)与 sysParam::Init | src/dev_ng/rage/base/src/system/main.cpp:779-786 |
| 5 | RAGE_TRACKING 下按 -memvisualize 建立远程内存追踪器,并为各堆登记范围 | src/dev_ng/rage/base/src/system/main.cpp:789-981 |
| 6 | PC 非 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 |
| 8 | sysException::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 |
| 10 | diagChannel::InitClass、sysBootManager::InitClass、pgBase::InitClass(g_KnownRefPoolSize) | src/dev_ng/rage/base/src/system/main.cpp:1069-1075 |
| 11 | -breakonalloc/-breakonaddr、sysStack::InitClass、atHashStringNamespaceSupport::InitNamespaces | src/dev_ng/rage/base/src/system/main.cpp:1092-1117 |
| 12 | sysTaskManager::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)时自检IsConstStringsrc/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)的关键值:
| 宏 | PC | Durango | Orbis | 证据 |
|---|---|---|---|---|
SIMPLE_HEAP_SIZE(游戏虚拟堆) | 474 MB(USE_SPARSE_MEMORY 的 PC dev 构建为 620 MB) | 596 MB | 596 MB | src/dev_ng/game/Core/main.cpp:35-45 |
SIMPLE_PHYSICAL_SIZE(资源堆) | 256 MB | 2482 MB | 2482 MB | src/dev_ng/game/Core/main.cpp:73-81 |
SIMPLE_VIRTUAL_SIZE | 1024 MB | 0 | 0 | src/dev_ng/game/Core/main.cpp:63-71 |
DEBUG_HEAP_SIZE | 220 MB | 220 MB | 220 MB | src/dev_ng/game/Core/main.cpp:191 |
REPLAY_HEAP_SIZE | 196 MB | 196 MB | 196 MB | src/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 / OnMessage | src/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;否则 OnUpdate | src/dev_ng/rage/framework/src/fwsys/fsm.cpp:41-84 |
| 宏 | fwFsmBegin/fwFsmState/fwFsmOnEnter/OnUpdate/OnExit/OnMsg/fwFsmEnd 展开为 if-else 链;未知状态时断言并返回 Quit | src/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;PCExitProcess(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
- Orbis:用 16 KB 栈注册
CoredumpHandler(sceCoredumpRegisterCoredumpHandler)src/dev_ng/game/Core/app.cpp:920-925、src/dev_ng/game/Core/app.cpp:177。 - PC:断言构建中把
diagLogBugCallback设为CDebug::OnLogBug;diagTerminateCallback = CApp::WriteExitFile(致命错误时写退出文件给启动器)src/dev_ng/game/Core/app.cpp:928-935。 - 平台商店:
__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(...),失败仅Errorfsrc/dev_ng/game/Core/app.cpp:937-982。源码在此处硬编码了 Epic 产品/沙箱/客户端凭据,本 Wiki 不复制。 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。这是发行保护的一部分,本页不展开其判定细节。- 计时条:
-dumptimebarsonlongmainthread[=ms](默认 200 ms);PF_INIT_TIMEBARS("Main", ...)、启动条 “Startup/System Init”src/dev_ng/game/Core/app.cpp:992-1013。 pm_Init()(GCM 性能监视)、CDebug::SetPrinterFn()、InitNYDrawListDebug()(须早于 drawlist 管理器)、netRemoteLog::InitClass("gta5")src/dev_ng/game/Core/app.cpp:1015-1023。CSystem::Init(name):非 final 或非 PC 时窗口名为"Fuzzy",PC final 为"Grand Theft Auto V";失败 →State_ShutdownGamesrc/dev_ng/game/Core/app.cpp:1034-1042。详见 8.6。- 加密狗检查(
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
- 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。 CGame::PreLoadingScreensInit():载入片头视频、着色器、启动 RPF、DLC/内容补丁、文本等“显示加载画面之前必须就绪”的东西(逐项见 game-skeleton-and-main-loop.md)。- 开发构建的内存挤占: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。 - Bugstar 集成、
gTracingStats;CText::Init(INIT_CORE);CLoadingScreens::Init(LOADINGSCREEN_CONTEXT_LEGALMAIN)src/dev_ng/game/Core/app.cpp:1131-1148。 - 若流式安装(
StreamingInstall)未完成:初始化警告屏与游戏流后阻塞等待安装完成;Durango 安装已完成则StreamingInstall::Shutdown()src/dev_ng/game/Core/app.cpp:1150-1167。 CGame::Init():注册全部gameSkeleton函数并执行INIT_COREsrc/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:
- 帧标记、卡顿检测(暂停菜单/淡出/加载画面期间不计)、
-autoMemTrack第 60 帧请求内存 dumpsrc/dev_ng/game/Core/app.cpp:1278-1315。 - “System begin”:
s_perfControl->StartFrame()、CReplayMgr::StartFrame()、CSystem::BeginUpdate()(含fwTimer::Update())src/dev_ng/game/Core/app.cpp:1318-1350。 CGame::Update();截图请求;pm_Display()src/dev_ng/game/Core/app.cpp:1352-1359。- 渲染线程交接:
CTaskScheduler::StartBatchingTasks(5)→gRenderThreadInterface.GPU_IdleSection()→gRenderThreadInterface.Synchronise()→CControlMgr::StartUpdateThread()→CGame::Render()(只构建 drawlist)→CControlMgr::WaitForUpdateThread()src/dev_ng/game/Core/app.cpp:1361-1370。 - 遥测;“AudioWait”:非开局自动读档且落地页不活跃时
audNorthAudioEngine::FinishUpdate()src/dev_ng/game/Core/app.cpp:1372-1385。 - 回放导出时追赶音频帧、
CReplayMgr::PostRender()src/dev_ng/game/Core/app.cpp:1387-1433。 s_perfControl->EndFrame();PCCApp::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。按调用顺序:
| 步骤 | 动作 | 证据 |
|---|---|---|
| 1 | InitMemoryBuckets():把纹理/模型→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 |
| 3 | fiDevice::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 |
| 4 | INIT_PARSER(必须在 fwConfigManager 之前)、sysUserList、g_SysService、Orbis NP 服务委托 | src/dev_ng/game/system/system.cpp:469-479 |
| 5 | CStreaming::InitStreamingVisualize、CFileMgr::Initialise、sysAppContent::Init(Durango 设置 Title ID) | src/dev_ng/game/system/system.cpp:481-492 |
| 6 | fwConfigManagerImpl<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 |
| 7 | fwInstanceBuffer::InitClass;pgStreamer::InitClass(cpuId, PRIO_STREAMER_THREAD)(Xenon/Durango 的 cpuId=1,否则 0) | src/dev_ng/game/system/system.cpp:509-516 |
| 8 | InitAfterConfigLoad():从配置读取 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、720p | src/dev_ng/game/system/system.cpp:537-583 |
| 10 | grcDevice::InitSingleton;PC/主机加载 CSettingsManager 设置并设定窗口尺寸;创建 grmSetup 并 Init | src/dev_ng/game/system/system.cpp:592-659 |
| 11 | gRenderThreadInterface.Init(PRIO_RENDER_THREAD, RenderThreadInit) 创建渲染线程并等待其接管设备,然后主线程清除 SYS_THREAD_RENDER 标记 | src/dev_ng/game/system/system.cpp:675-685 |
| 12 | PC:VideoResManager、焦点查询线程;fwTimer::Init、CControlMgr::Init(INIT_CORE);双肩键同时按下则清 HDD 缓存 | src/dev_ng/game/system/system.cpp:687-712 |
| 13 | 创建游戏任务调度器 "gtaSched";按 gameconfig 的 Update 线程配置设置主线程优先级/亲和;PC 低配机交换主线程与渲染线程优先级;线程注册表;注册 dlCmdBeginRender/dlCmdEndRender | src/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:PS3sceNp2Term;删除s_perfControl;CGame::Shutdown()(依次关会话/关卡、写档案、停渲染线程、SHUTDOWN_CORE、销毁工厂)→State_ShutdownSystemsrc/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::Quitsrc/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_ShutdownGame | src/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 结束认证会话、EpicShutdownClass、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 && !__MASTER | src/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_ENABLED | RSG_DURANGO | src/dev_ng/rage/base/src/system/exception.h:14 |
BACKTRACE_ENABLED | CRASHPAD_ENABLED(0) || BREAKPAD_ENABLED || BACKTRACE_DUMP_AND_UPLOAD_ENABLED | src/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 INFO | CApp 状态、系统时间、帧号/是否暂停、流式请求数、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 的回调链:
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。- 若 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。 - 用系统毫秒时间播种
mthRandom、丢弃前 100 个值后按比例抽样决定是否上传src/dev_ng/game/Core/main.cpp:727-749。 UploadDump:Durango 调BacktraceDumpUpload::UploadDump,其他平台返回 false(注释说明放在游戏层是为了避免 RageCore 依赖 RageNet 且需 WinRT 文件)src/dev_ng/game/Core/main.cpp:752-765。PC 上 Breakpad 的实际上传实现位于 RAGEexception.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/.cpp | Xenon 开机内存测试(按住 LT+RT+LB+RB),但 __INCLUDE_MEMORY_TEST_IN_XENON_BUILD 为 0 | src/dev_ng/game/Core/memorytest.h:10-12、src/dev_ng/game/Core/app.cpp:1135-1138 |
watermark.h | WATERMARKED_BUILD 仅 PC 的某种非 bank、非 final 构建;颜色/位置从一个被禁止优化剥离的字符串解析(字符串内容不复制) | src/dev_ng/game/Core/watermark.h:4-42 |
UserEntitlementService.*、IUserEntitlementDataProvider.h | Gen9 平台权益查询服务(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.cpp | src/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源码推导的。