想给 F1 Manager 2024 做个 mod 制造点节目效果 —— 超车率拉满、事故概率翻十倍、DRS 一开就飞,这种风格。撞了几堵墙才搞清楚:这游戏的 UE 是 5.1.1,而不是它发布时间(2024 年 7 月)对应的 5.3 或 5.4。
把这一晚上的考古留个底。
第零层:游戏架构
打开 F1Manager24/Content/Paks/ 看了一眼:
39 个 .ucas/.utoc 容器(IoStore) 1 个 pakchunk0-Windows.pak(4.7G,legacy 散文件)37 个 365 字节的 pak 占位文件(IoStore loader 引用 stub)UE5 标准的 IoStore + 1 个 legacy pak 混合。Oodle 压缩,AES 加密。
AES key 不用自己 dump —— F1 系列从 22 到 24 都没换过,公开在 OverTake.gg:0x43A5CEC244E89A3E109E14EDE787569E79C9CDECBB09857C6FEF080F14898EF9。
沙箱里装好 repak + retoc,把全部 74,563 个 chunk 拖出来索引:其中 51,926 个 ExportBundleData(cooked uasset/umap),16,475 条独立路径。
第一层:这游戏没有车辆物理
最开始的设想很朴素 —— 改 [/Script/Engine.PhysicsSettings] 里的 DefaultGravityZ,从 -980 改成 -100,让车飘起来。
抽出 F1Manager24/Config/DefaultEngine.ini 一看,字段确实在:
[/Script/Engine.PhysicsSettings]DefaultGravityZ=-980.000000DefaultTerminalVelocity=4000.000000打了个 _P.pak 丢进去,启动游戏 —— 没任何变化。意料之中,UE5 shipped build 在 cook 时把 PhysicsSettings 烤进了 CDO,runtime 不再重读 ini。
但跑 fallback 之前突然反应过来:全文搜索 16475 条 uasset 路径,Gravity 关键字 0 命中。Physics 命中 135 个,逐一翻看,全是司机 ragdoll、布料、头盔挂饰 —— cinematic 镜头里那些飘动的东西。
F1 Manager 2024 没有车辆物理模拟。圈速、超车、事故,全部由数据驱动。即使把 gravity 改成 -10,影响范围也只是 cinematic 视角里司机围巾飘慢一点。压根算不上节目效果。
第二层 文件夹
换关键字搜:Overtake(1)、DRS(85,大多是赛道 DRS 牌子贴图)、Tyre(1386)、Crash(9)、Incident(15)。其中 DRS 命中里出现一个特别的:
F1Manager24/Content/RaceSim/DRSAccelerationSpeedCurce.uasset注意 Frontier 拼错了 —— Curce,不是 Curve。这个拼写错误一直没改,后面 UAssetGUI 里找文件也得按错的拼。
顺着 RaceSim/ 文件夹一摸,整整 101 个数据资产。这就是 Frontier 的赛事模拟引擎参数库:
OvertakeProbabilityAtSkillDifference.uasset—— 超车概率主曲线DRSAccelerationSpeedCurce.uasset—— DRS 加速量Incidents/Distributions/CrashDistribution.uasset等四个事故概率分布Incidents/DT_EngineFaults/DT_GearboxFaults/DT_ERSFaults—— 三大故障表DriverConfidenceDataAsset.uasset—— 信心系统DriverTacticsDataAsset.uasset—— Push/Conserve/Attack/Defend 指令参数- 每种 tyre compound(C0-C5、Inter、Wet)各三条 heating/cooling/wear 曲线
Tyres/TyreWear/PunctureChancePerLap.uassetRaceSimAIDataAsset.uasset—— AI 车手行为- 还有天气、燃油、刹车、变速箱一整套
整个比赛的”节目效果旋钮”全在这 101 个资产里。改 OvertakeProbability 的曲线 → 超车狂欢;放大 CrashDistribution × 50 → 赛道屠宰场;PunctureChancePerLap × 100 → 每圈一次爆胎;AccelerationWearAdditive × 100 → 一圈废胎。
第三层 to-legacy 翻车
定位完资产,下一步是把 IoStore 里的 Zen 序列化格式转回可编辑的 legacy .uasset。retoc 自带 to-legacy 命令,理论上就是干这事的。
实际跑:
$ retoc -a $KEY to-legacy --filter RaceSim --version UE5_4 paks/ out/memory allocation of 8289885329768060352 bytes failed8.3 × 10^18 字节。把 5_4 换成 5_3、5_5、5_2、5_1、5_0,跨整个 UE5 版本范围全部一样的错。
数字这么离谱一定不是真的分配请求,是 retoc 把某个字段读成了 size。换言之 —— F1M24 的 IoStore 容器格式跟 retoc 内置的任何一个版本都对不上。要么是 Frontier 改了 header,要么 retoc 还没追上 5.4。这条路在沙箱里堵死,只能切到 Windows 侧 GUI 工具链。
第四层 FModel 报 Pos:186646596
回到 Windows 装 FModel。AES key 加进去,UE Version 选 GAME_UE5_3,目录指对,左侧 archives 树正常加载,9078 + 39 个容器全部解密 —— 这说明 AES 没问题、IoStore TOC 也能读。
双击 OvertakeProbabilityAtSkillDifference.uasset:
[ERR] Read size is smaller than zero.FByteArchive Info: F1Manager24/Content/RaceSim/OvertakeProbabilityAtSkillDifference.uasset| Pos:186646596 Length:391 (47735702.3% done, can't serialize.换 GAME_UE5_4:
[ERR] Read size is smaller than zero.FByteArchive Info: F1Manager24/Content/RaceSim/DRSAccelerationSpeedCurce.uasset| Pos:186646596 Length:342 (54575028.1% done, can't serialize.Pos:186646596 在两个完全不同的资产里完全一致。47735702.3% 跟 54575028.1% 也都是荒谬数。这个 pattern 很有诊断价值 —— Pos 在两个不同文件里相同意味着 FModel 是从文件里某个位置读到了同一个值当 offset 用,然后试图 seek 过去。值本身是垃圾,不是文件破损。FModel 不认识这个版本的包头结构。
186646596 换成 hex 是 0x0B1F_A404,可能是 Frontier 自定义包头里的某个 magic 或 flags 字段。
第五层 定制 FModel 也不行
carefreeduck/F1ManagerModding 这个仓库针对 F1 Manager 22/23 做过 FModel 的定制 fork,2023 年 8 月发的最新 release。F1M24 跟 F1M23 应该不会差太多吧 —— 抱着这个希望试。
[ERR] System.Collections.Generic.KeyNotFoundException:Couldn't find LoaderGlobalNameHashes chunk in IoStore global.utoc at CUE4Parse.UE4.IO.IoGlobalData..ctor(IoStoreReader globalReader)这个错跟 stock FModel 的报错路径完全不同 —— 它已经在 global.utoc 里找新版本 IoStore 才有的 chunk 类型。LoaderGlobalNameHashes 是 UE 5.3+ 才引入的 chunk,carefreeduck 这个定制版基于 F1M23(UE 5.1)的 CUE4Parse,看不懂 5.3+ 的 IoStore 元数据。
到这儿才意识到事情有点意思:stock FModel 当 5.3/5.4 解析失败,说明包头不是 5.3/5.4 格式;F1M23 定制版当 5.1 解析失败,说明又比 5.1 新。F1M24 的 IoStore 在某个我之前没考虑过的中间地带。
用 UE4SS 把 usmap 从内存里掏出来
UE5.3+ 起想用 FModel 必须配 Mappings.usmap —— 这文件是从游戏反射数据生成的字段名映射表,在 Unofficial Modding Guide 上有详细解释。F1M24 没人公开传 usmap,所以自己用 UE4SS 注入游戏抓。
UE4SS 是个 DLL,改名 dwmapi.dll(Windows API 早期回环)跟在游戏 exe 旁边,游戏启动时 DLL 加载顺序会自动注入。下 experimental-latest 那个 build(2024-12-29 提交,比稳定版 v3.0.1 新一年),解压扔到 F1Manager24/Binaries/Win64/,改 UE4SS-settings.ini:
ConsoleEnabled = 1GuiConsoleEnabled = 1GuiConsoleVisible = 1Steam 启动游戏,黑窗弹出来。进主菜单后切到 UE4SS console,Dumpers 菜单 → Generate .usmap。三秒钟生成。
[00:48:08.5976986] Mappings Generator by OutTheShadeAttempting to dump mappings...Port of https://github.com/OutTheShade/UnrealMappingsDumper Commit SHA 4da8c66[00:48:08.6653964] Mappings Generation Completed Successfully![00:48:08.6655465] Output file: F1Manager24-5.1.1-498643+++volta24+game+1.11.0-7f7cc36f.usmap输出文件名直接把答案怼到脸上:
F1Manager24-5.1.1-498643+++volta24+game+1.11.0-7f7cc36f.usmap ↑↑↑↑↑ ↑↑↑↑↑↑↑↑ ↑↑↑↑↑↑ UE 版本 Frontier 内部分支名 游戏版本这游戏用的是 UE 5.1.1。Frontier 内部分支叫 volta24,游戏构建版本 1.11.0,引擎 commit SHA 7f7cc36f。
UE4SS 日志里也反复确认:
[PS] Found EngineVersion: 5.1[PS] Found GameEngineTick: 0x1441fee40Using engine version: 5.1回 FModel 重新配 —— UE Version 改 GAME_UE5_1,Enable Local Mapping File 勾上指向那个 usmap,重启。双击同样那个资产:
{ "Type": "CurveFloat", "Name": "OvertakeProbabilityAtSkillDifference", "Properties": { "FloatCurve": { "Keys": [ { "Time": -40.0, "Value": 0.1 }, { "Time": 0.0, "Value": 0.5 }, { "Time": 40.0, "Value": 0.9 } ] } }}完美。三个 key 控制全场超车 —— 技能差 -40(对手强 40 分)时 10%,势均力敌 50%,自己强 40 分时 90%,中间线性插值。
DRS 那条 curve 更简单:
{ "Name": "DRSAccelerationSpeedCurce", "Properties": { "FloatCurve": { "Keys": [ { "Time": 0.0, "Value": 0.0, "InterpMode": "RCIM_Cubic" }, { "Time": 360.0, "Value": 1.0, "InterpMode": "RCIM_Cubic" } ] } }}Cubic 插值,速度从 0 到 360 km/h 时 DRS 加速比例从 0 到 1。
到这个点,后续就是 UAssetGUI 改值 → repack 成 _P.pak 的标准流程。也就停在这一步,等下次再继续 —— 已经凌晨一点了,睡觉更重要。