Phigros 4.0.1 第九章客户端全解:从启动到退出,这一章在程序里究竟发生了什么

- 入场:第八章《Distorted Fate》880,000 分 →
MainStory9;12 首曲各有一把C9*Unlocked钥匙。 - 两台状态机:进度
Step(5 步,存档键Chapter9Phase2Step)与演出RuntimeStage(8 段),推进发生在结算界面。 - 密码不是比对,而是 SHA-512 切片派生 AES-256-CBC 的 Key/IV,解得开
c9s.*即正确(8 个秘密包,含 S6 终章与 458 音符秘密谱)。 - 退出应用是流程的一步:
QuitForMessageResume()会先落盘再Application.Quit(),冷启动由Phigros.JumpToMessage把你送回 Message。 - 噪域 / LIFE / 课题乱码都是真实机制:噪域=按音乐时间轴推的 noise 通道;LIFE 档位 10/50/100/200;课题界面的乱码是 8–12 位随机占位串。
这篇是程序向的完整拆解:不是零散知识点,而是「第九章这一章在客户端里,从你按下启动到它把你请回标题界面,程序依次做了什么」。调用关系由 bl 指令反查(c9_callgraph.py,§12),RVA 来自 dump.cs,都可复核。剧情向整理在剧情篇。
出处与工具:Phigros_4.0.1_TapTap.apk.1(Unity 2022.3.62f2 / IL2CPP / Addressables);Il2CppDumper 6.7.46(.NET 8 用 DOTNET_ROLL_FORWARD=Major);libil2cpp.so + global-metadata.dat + data.unity3d + catalog.json + *.bundle。

1. 数据地图
| 内容 | 位置 |
|---|---|
| 章节表 / 328 首歌(曲名·曲师·定数·谱师·解锁键) | data.unity3d → level0 场景的 GameInformation 组件(16 万字节,无 TypeTree,手写解析) |
| 类·字段·方法·RVA | dump.cs(74,912 个带 RVA 的方法;第九章 242 个类块) |
| 六语言文本(档案名、剧情页) | data.unity3d 内 MonoBehaviour 的 MultiLanguageTextString(简中/繁中/EN/JP/KR 内联) |
| 归档与收藏目录 | level22、sharedassets22.assets pid=143(SaturnOS 收藏库) |
| 谱面 | Assets/Tracks/{songsId}/Chart_{EZ,HD,IN,AT,SP}.json(dependency_key 才指向真实 bundle) |
| 加密秘密资产 | 8 个 c9s.*(C9SecretAssetBundleProvider,§8) |
章节名与更新节奏:官方公告(TapTap 4.0 更新公告)+ Phigros Wiki(JP) 更新履历(4.0.0 = 09-25 追加第九章;4.0.1 = 10-02 追加后篇)→ 与客户端本地化条目 穹顶孤舟 / Solivault / 穹窿の孤舟 一致。
LIFE 档位:客户端读出 10/50/100/200;社区攻略(全部歌曲解锁流程、解密流程)描述一致,但「Bad/Miss 扣 2」只有社区来源。
12 首曲:官方公告只放 3 首 + 3 段谜题串(Atbash 类),其余靠解密;客户端 Phigros2.Chapter9.Key 给出全部 12 个 songsId,与社区汇总一致。
2. 全流程:从启动到退出

主线是一条单向状态机(进度 5 步 + 演出 8 段)。每次跨步都发生在「结算 / 特定场景启动 / 玩家输入」这三个时机。下面按玩家实际经历的顺序走,并标出谁触发、切了哪个场景、写了什么存档。
2.1 启动:先把解锁与密钥恢复回来
GameInformation.Awake() [RVA 0x1CB7C6C] └─ C9SecretAssetBundleProvider.SetPassword(...) // ★ 开机即恢复密码GameProgressSaveModule.LoadFromLocal → <LoadFromLocal>g__LoadChapter9Keys|38_7 [0x1D65C08] └─ Chapter9Phase2Progress.LoadCurrentStep() // 读回 Chapter9Phase2StepGameProgressSaveModule.Apply → <Apply>g__SaveChapter9Keys|36_7 [0x1D64168] └─ SetPassword(...) + 写回 C9*Unlocked // 存档应用时重设密钥要点:密码与解锁状态进存档(GameProgressSaveModule 另有 LoadV1 / LoadV5,经 BinaryUtils.GetString 解二进制存档)。所以你输过一次的密码重启后依然生效,秘密包不会再问你第二遍。
2.2 进章:Phase 1(选曲)
LaunchControl.ToChapter9() // DontDestroyOnLoad 保活控制器 ├─ EnterPhase1Coroutine() / EnterPhase2Coroutine() // 按存档决定进哪一形态 └─ 淡出 BGM(AddressableAudioSource.Volume/CancelFadeIn)→ 进 Chapter9SelectMusicChapter9Phase1Control.Start() // 选曲页总控 ├─ 地图:Chapter9MapElement.Sync() / Chapter9MapElementLink.<FillRoutine> → AddMapFocus/RemoveMapFocus ├─ 解锁与收藏:Chapter9CollectionUnlockSprite.UpdateDisplay → UnlockAnimation → ApplyUnlockedAppearance ├─ 曲目详情:Chapter9SongDetailsControl.Open/UpdateInfo(不同难度 LoadDifferentCoverImage) └─ 开谱:Chapter9SongLevelStartControl.PrepareBackground → <Start…> → SceneManager.LoadScene- 选曲元素三态:
Locked / Unlocked / Read;地图聚焦由IChapter9MapFocusProvider+AddMapFocus/RemoveMapFocus驱动。 - 离开时成对释放:
Chapter9Phase1Control.OnDestroy → ReleasePhase1EndingAmbient();Back() → Chapter9SongLevelStartControl.DestroyBackground()(RenderTexture.Release()+Destroy)。
2.3 难度动画:选难度是「六路视频 + 定格 + 畸变」
DesultorySignalsIntroControl.Start() → MainVideoPlayer() // 开场 CG → PlayDiffVideo(string diff) → DiffVideoPlayer() // 难度选择画面 → 所选难度:ez / hd / ins / at 对应 VideoPlayer → NextScene() → RawImage.set_texture → SceneManager.LoadScene- 字段:
introCG / diffSelect / ez / hd / ins / at(6 个VideoPlayer)、videoRT(RenderTexture)、lensDistortion、freezeFrame、selectButtons[]、PlayStartWindow = 0.5、waitTimeOut / introVideoTime / diffSelectTime / diffVideoTime。 - 转场:
CutInAnimator(切入)、TriFrameCutOutAnimator(三角框切出)、BlackScreenTransition(BeginTransition里DontDestroyOnLoad,<TransitionRoutine> → LoadSceneAsync)。
六段视频(字段与触发时机一一对应;点开播放,全部资产见素材馆):
| 段 | 字段 | 何时播 |
|---|---|---|
| 开场 CG | introCG | 进入该曲的演出开场 |
| 难度选择画面 | diffSelect | 「开始」后、选难度那一屏(点 selectButtons) |
| EZ / HD / IN / AT | ez / hd / ins / at | 选中对应难度时各自的切入短片(定数 10 / 14 / 17 / 18) |




▶ 六段视频(EZ / HD / IN / AT / diffSelect / introCG)
- 转场:
CutInAnimator(切入)、TriFrameCutOutAnimator(三角框切出)、BlackScreenTransition(BeginTransition里DontDestroyOnLoad,<TransitionRoutine> → LoadSceneAsync)。
六段视频(原样放出)——字段与触发时机一一对应,全部资产见素材馆:
| 段 | 字段 | 何时播 |
|---|---|---|
| 开场 CG | introCG | 进入该曲的演出开场 |
| 难度选择画面 | diffSelect | 「开始」后、选难度那一屏(点 selectButtons) |
| EZ / HD / IN / AT | ez / hd / ins / at | 选中对应难度时各自的切入短片 |

2.4 打关:进度不在关卡里推进,而在结算里推进
LevelOverControl.<>c__DisplayClass43_0.<Start> ├─ g__HandleTrueHomeTrueWorldEvent|15() → Chapter9Phase2Progress.TryAwaitAmetrine() ├─ g__HandelAmetrineEvent|16() → …TryCompleteAmetrine(bool judgeCheckFailed) ├─ g__HandlePetrichorEvent|17() → …TryCompletePetrichor() └─ PlayerPrefs.Save()TryAdvance(expected, next)内部:LoadCurrentStep()比对 →SaveStep(expected)落盘(Chapter9Phase2Step)。没调用TryAdvance就等于没推进。- 语义参数:Ametrine 的判定参数叫
judgeCheckFailed(要「挑战失败」参与),Petrichor 要「挑战通过」(IsPetrichorChallengePassed)。 - 挑战模式进出:
ChallengeModeControl.<Start>d__21/<EnterLevel>→LoadScene;ChallengeModeOverControl.<TurnToNextScene>→LoadScene。
2.5 Phase 2:换形态,并进入谜题 / 推演
Chapter9Phase1Control.ToPhase2() → Progress.TryBeginSecondPhase() + PlayerPrefs.Save() → SceneManager.LoadSceneAsync(...) // ★ 异步切形态Chapter9Phase2Control.Start() → Progress.TryBeginSecondPhase()(兜底)+ ApplyMapCover()Chapter9Phase2Control.<EnterPuzzle…> / <TurnToNextScene…> → SceneManager.LoadScene- 形态标记
Chapter9LastSelectMusicIsPhase2;两套封面#ChapterCover/MainStory9.jpg与MainStory9_2.jpg。 - 首次进入走
FirstEnterPhase2Control:挂VideoPlayer的prepareCompleted / loopPointReached,OnFinished()→TryAdvance(...)+PlayerPrefs.Save();超时走WatchVideoTimeout(日志[ToPhase2] Video timed out. Entering Chapter9Phase2.)。 - 启动器(OS 风格):
SaturnOSLaunchControl.<ToChapter8Coroutine>/<ToCollectionCoroutine>→LoadSceneAsync;<OpenLoadCoroutine>里另有Application.Quit(...)分支。
2.6 谜题与推演
Chapter9PuzzlePiece.Awake() → Image.alphaHitTestMinimumThreshold = 0.2Chapter9PuzzleControl.OnPieceCompleted() → 完成音效 + TweenCompletePostEffects → PlayShowPopupExposure() → Chapter9PuzzlePopupControl.TextReplacement → ConfirmNextStage() → <PlayNextStage…> → Chapter9Phase2Session.TryBeginBaselineChallenge(config) → SceneManager.LoadSceneC9DeductionControl.Start()/Update() → 打字机(UpdateTypingText / RenderPage)C9DeductionControl.<FinishAndWhiteOut…> → SceneManager.LoadScene // 推演收尾 → 白屏 → 下一场景C9AfterDeductionControl.<Start>d… → PlayerPrefs.Save + VideoPlayer.Prepare/Play → <StartNextScene> → LoadSceneC9DeductionControl:EnsureSceneReferences、PrepareVisualLayers(CreateVignetteTexture/Sprite.Create)、RenderPage(StopCoroutine+Destroy旧行);OnDestroy→StopAllCoroutines+DestroyRuntimeVisuals。- 白屏:
Chapter9WhiteoutTransition.—TryCaptureScreen(Texture2D.ReadPixels+Graphics.Blit)→BlitToMask→AttachToCamera(PostProcessingManager.AddEffects)→<PlayOut…>→LoadScene;Teardown / ReleaseTexture / EndGlitchAudio成对释放,故障音DontDestroyOnLoad` 跨场景保留。
2.7 密码:一次输入,换来整包秘密资产
Chapter9InputPasswordControl.Submit() [0x1D486FC] ├─ C9SecretAssetBundleProvider.SetPassword(string) [0x1D26B70] │ SHA512 + UTF8 → ComputeHash → GetSubArray[0:32] / [32:48] → Aes.Key / Aes.IV │ → Addressables.LoadAssetAsync<object>(...) // 触发秘密资产重载 └─ <Submit>b__3 → FadeToBlackThenStartC9S6() [0x1D48B34] → SceneManager.LoadScene(string)- 失败演出:
PlayWrongPasswordOptics()(OpticsStartFov = 110)→RunWrongPasswordOptics();PlayChangeColor()→RunChangeColor()(配Chapter9ColorOverlayControl.ToRed/ToBlue);音效correctPasswordClip / wrongPasswordClip(PlayPasswordSeClip里DontDestroyOnLoad)。 - 输入界面
Start()只做一件事:关掉passwordSe.playOnAwake;OnDestroy()收协程 +Destroy。
2.8 收束:S6 → Hate → DS → Message → HappyEnding → Credits
C9S6Unlock.EnterLevel() → RawImage.set_texture → SceneManager.LoadScene // 进 S6DesultorySignalsIntroControl.NextScene() → LoadScene // DS 开场后进关HateGlitchEffect.Start() → Chapter9Phase2Progress.LoadCurrentStep() // 故障强度=当前步Chapter9Phase2Session.TryResumeMessage(levelIndex) → LoadCurrentStep() // Message 续玩C9StoryControl.<TurnToNextScene…> → LoadScene // 秘密剧情收尾- 演出层:
HateLevelEffects/MessageLevelEffects/DesultorySignalsLevelEffects/C9S6LevelEffect都是Start() → PostProcessingManager.AddEffects(),Update() → AnimationDirector.Evaluate(nowTime);MessageLevelEffects.Update还会Destroy临时对象。 - 收尾断言:
Tried to complete Chapter 9 outside the valid credits state.、Song {0} cannot complete while the session stage is {1}.;Credits之后可从「关于」页补完(CompleteChapterFromAboutUs→LoadCurrentStep)。
2.9 退出:程序在哪里主动退出(共 5 处)
「退出应用」本身是流程的一步,而且不止一处。全库 Application.Quit 的静态调用点(bl 反查)一共 5 个:
| # | 调用者 | 退出条件 | 证据 |
|---|---|---|---|
| 1 | BaselineCourseLevelMod.QuitForMessageResume() | 基准课判定后走「退出再续 Message」分支:先 SaveManagement.SaveBool/SaveInt + PlayerPrefs.Save(),再 Application.Quit(exitCode≠0) | ResolveNextScene() RVA 0x1D1E43C(bl 0x1D1E540)→ QuitForMessageResume() RVA 0x1D1E540(bl 0x1CC311C/0x1CC31D0/0x3C53F20/0x3C168C8) |
| 2 | SaturnOSLaunchControl.<OpenLoadCoroutine>d__23.MoveNext() | 屏幕宽高比 ≥ 3 就退出(Screen.width/height,cmp w8,#3,mov w0,#-103) | 状态机 RVA 0x1D3A404,sdiv+cmp #3 @0x1D3A4C0-C8,bl Application.Quit @0x1D3A4F0 |
| 3 | SplashScene.<GameStart>d__21.MoveNext() | 隐私政策 / 防沉迷检查返回 false → Application.Quit(0) | RVA 0x1DB1168;CheckPrivacyPolicy @0x1DB12C4、AntiAddiction.CheckAntiAddiction @0x1DB1398、tbnz @0x1DB1470、Quit @0x1DB1494 |
| 4 | SimpleCameraController.Update() | 按 Esc(KeyCode.Escape = 27)直接退出——Unity 官方示例脚本残留且仍在构里生效 | RVA 0x32AA9F0;mov w0,#0x1b @0x32AAA44、bl Application.Quit() @0x32AAA70 |
| 5 | (非游戏)LCIMConversation.Quit() | IM 会话退出,与游戏无关,列出以防误判 | RVA 0x3505AD4 |
第 1 处的完整机制(这才是「退出」在第九章里的正式用途):
BaselineCourseLevelMod.OnEnable() → ProgressControl.LevelBreak(nextScene, ResolveNextScene 委托) // 0x1D86620BaselineCourseLevelMod.ResolveNextScene() [0x1D1E43C] ├─ IsExpectedLevel() // 是不是该在的关卡 ├─ Chapter9Phase2Session.TryCompleteCurrentSong() ├─ Chapter9Phase2Session.Reset() └─ QuitForMessageResume() [0x1D1E540] SaveManagement.SaveBool(key, bool) + SaveManagement.SaveInt(key, int) + PlayerPrefs.Save() → Application.Quit(exitCode)也就是:「下一场景」由一个委托动态解析(Func<string> resolveScene),它的一个返回值分支就是「退出游戏」——用「落盘 + 退出 + 冷启动重放」来代替一次场景切换。
- 落盘:
C9PendingMessageAfterQuit/C9PendingMessageLevel(是真正的ldstr字面量) - 读回:
BootSceneControl.TryEnterPendingMessage()0x1CCDAB8→SceneManager.LoadScene0x1CCDC3C - 跳转基建:
JumpToTargetSession:LevelScenePath = "Assets/Phigros2/Level/Level.unity"、GameInformationPrefabPath = "…/BootScene/GameInformation.prefab"、DifficultyIndexIn = 2;DSInfo / HateInfo / MessageInfo各带PrefKey = "Phigros.JumpToDS / JumpToHate / JumpToMessage"、LevelModAddress、SongsId;配套*SessionBootStrap(只有IsActive) - 结论:「退出 → 重进 → 直接落在指定歌曲的课题套件里」是设计好的机制,不是崩溃。
离开时的清理(与保活成对):
| 界面 / 组件 | 退出动作 |
|---|---|
Chapter9Phase1Control.OnDestroy / Back() | ReleasePhase1EndingAmbient() / DestroyBackground() |
Chapter9SongLevelStartControl.DestroyBackground | RenderTexture.Release() + Destroy |
Chapter9WhiteoutTransition.Teardown | ReleaseTexture / EndGlitchAudio |
C9StoryControl / C9DeductionControl / C9StoryPostEffects.OnDestroy | StopAllCoroutines + DestroyRuntimeVisuals / DestroyRuntimeEffect |
Chapter9InputPasswordControl.OnDestroy | StopCoroutine + Destroy |
GameInformation.DestroyMainForRestart() | Object.Destroy(重启用) |
LaunchControl / WhiteoutTransition / BlackScreenTransition | 反向:DontDestroyOnLoad 保活 |
2.10 场景名:确证的四个 + 共用槽位
SceneManager.LoadScene 的调用者共 48 处(第九章相关 20 处)。把每条调用点回溯 adrp+ldr 取字面量槽位后可见:全部应用侧切场景只用到 21 个不同槽位,同槽位=同场景名。可确证的:
| 场景名 | 出处 | 触发者 |
|---|---|---|
Chapter9SelectMusic | Chapter9UnlockNavigation.SelectMusicScene | LaunchControl.EnterPhase1Coroutine(LoadSceneAsync) |
Chapter9Phase2 | Chapter9UnlockNavigation.Phase2SelectMusicScene | Chapter9Phase1Control.<ToPhase2>…(LoadSceneAsync) |
Chapter9Puzzle | 字面量表 | Chapter9Phase2Control.<EnterPuzzle>d__99 |
Deduction | BaselineCourseLevelMod.DeductionSceneName | C9DeductionControl.<FinishAndWhiteOut>d__76 |
Level | JumpToTargetSession.LevelScenePath 末端名 | C9S6Unlock.<EnterLevel>、ChallengeModeControl.<EnterLevel>、Chapter9SongLevelStartControl.<StartAnimation>、DesultorySignalsIntroControl.NextScene()、Chapter9PuzzleControl.<PlayNextStage>…(12 个类共用) |
SaturnOS / BootScene / Setting / SplashScene* | 字面量表 idx 7795 / 2493 / 7936 / 8124… | 启动器与收藏馆 |
第九章切场景的完整链路:Chapter9SelectMusic →(ToPhase2)Chapter9Phase2 →(EnterPuzzle)Chapter9Puzzle →(FinishAndWhiteOut)Deduction →(StartNextScene)Level / 结算 → Chapter9Phase2 → … → Level(S6/Hate/DS/Message)→ Credits。
3. 第九章的全部条件
3.1 章节解锁
{ "chapterCode": "MainStory9", "banner": "Chapter 9", "unlockInfo": { "unlockMode": 1, "unlockFlag": "DistortedFate.Sakuzyo.0" } }Tooltip 原文:1: Lock, required specified song reaching 88w → 第八章《Distorted Fate》880,000 分。
3.2 12 首曲 = 12 个键
| 曲目 | 解锁键 | 定数(EZ/HD/IN/AT) |
|---|---|---|
| About The Universe | C9AboutTheUniverseUnlocked | 4.5 / 9.7 / 14.4 / — |
| Implexrough | C9ImplexroughUnlocked | 5.5 / 10.2 / 15.3 / — |
| Evanescent | C9EvanescentUnlocked | 6.5 / 12.6 / 15.7 / — |
| Entrance to the Chaos | C9EntranceToTheChaosUnlocked | 8.8 / 13.7 / 16.8 / 17.6 |
| Exoplanetary Mirage | C9ExoplanetaryMirageUnlocked | 8.7 / 13.4 / 16.8 / 17.9 |
| True Home, True World (Rework) | C9S6Unlocked | 5 / 11.9 / 15.8 / — |
| Ametrine | C9AmetrineUnlocked | 6 / 12.2 / 15 / 16.2 |
| Petrichor | C9PetrichorUnlocked | 7.5 / 13.8 / 16.5 / 17.5 |
| ハテ(Hate) | C9HateUnlocked | 9 / 13.9 / 16.7 / 17.9 |
| Desultory Signals | C9DesultorySignalsUnlocked | 10 / 14 / 17 / 18.0 |
| Message | C9MessageUnlocked | 3 / 13.2 / 15.6 / 16.5(+ 全游戏唯一 Chart_SP) |
| What do you want more than a Happy ending? | C9HappyEndingUnlocked | 0.5 / 11.5 / 16.1 / 17.1 |
3.3 两台状态机
进度 Step(存档键 Chapter9Phase2Step) Phase2Entry(0) → AwaitTrueHomeTrueWorld(1) → AwaitAmetrine(2) → AwaitPetrichor(3) → BaselineChallenge(4)演出 RuntimeStage None → HatePlaying → DesultoryUnlockVideo → DesultoryPlaying → MessagePlaying → MessageStory → HappyEndingVideo → Credits| 方法 | RVA |
|---|---|
Chapter9Phase2Rules.CanAdvance(current, target) | 0x1D1C2E8 |
Chapter9Phase2Progress.TryAdvance(expected, next)(内含 SaveStep) | 0x1D1BFBC / 0x1D1BEC0 |
…LoadCurrentStep() | 0x1D1BCD4 |
…TryAwaitTrueHomeTrueWorld() | 0x1D1A604 |
…TryAwaitAmetrine / …TryCompleteAmetrine(bool judgeCheckFailed) | 0x1D1C1C4 / 0x1D1C1D0 |
…TryCompletePetrichor() | 0x1D1C1F4 |
…CompleteChapterFromAboutUs() | 0x1D1C200 |
Chapter9Phase2Rules.AreKeyCollectionsComplete(requirements, isOn) | 0x1D1C344 |

Step(进度)决定能进什么(存档键 Chapter9Phase2Step);RuntimeStage(演出)决定现在演什么(静态属性 Chapter9Phase2Session.Stage,只有它自己的 set_Stage 能写)。前者被成绩/结算推动,后者被场景与事件推动——互不写对方的键。
4. 逻辑与判定:状态、条件、触发
第二章讲「谁在哪一步动了手」,这一节讲判定规则本身长什么样。第九章的逻辑集中在四个类里,全是纯函数或静态会话。
4.1 会话核心 Chapter9Phase2Session(TypeDefIndex 4404)
public static class Chapter9Phase2Session { private const int BaselineSongCount = 3; // ★ 基准课固定 3 首 private const string CourseLevelModAddress = "LevelMod/BaselineCourseLevelMod"; private const string MessageSpChartAddress = "Assets/Tracks/Message.くるぶっこちゃん.0/Chart_SP.json"; // ★ 唯一 SP 谱
public static bool IsActive { get; set; } public static Chapter9Phase2State.RuntimeStage Stage { get; set; } // 演出阶段(private set) public static int CurrentSongIndex { get; set; } public static int SelectedLevelIndex { get; set; } public static bool IsCurrentLevelPrepared { get; } public static Chapter9Phase2Config Config { get; } public static Chapter9Phase2Config.BaselineSongDefinition CurrentSongDefinition { get; }
public static bool TryBeginBaselineChallenge(Chapter9Phase2Config sessionConfig, int selectedLevelIndex); public static bool TryResumeMessage(int levelIndex); public static bool TryPrepareCurrentLevel(out LevelStartInfo levelStartInfo, int? selectedLevelIndex); public static bool TryApplyCurrentLevel(); public static bool TryCompleteCurrentSong(); public static void Reset(); private static bool TryValidateConfig(Chapter9Phase2Config sessionConfig, int selectedLevelIndex); private static bool SupportsLevel(SongsItem song, int levelIndex); private static bool Fail(string message);}Stage只有一个写入者:[CompilerGenerated] private static void set_Stage(...)(在Chapter9Phase2Session里)。演出阶段只能经这个会话切换,别的类改不到。- 两套状态分离:
Stage(演出:Hate→DS→Message→HappyEnding→Credits)与Step(进度:五步)互不写对方的存档键。 - 关卡准备两段式:
TryPrepareCurrentLevel(out LevelStartInfo, levelIndex)先组信息,TryApplyCurrentLevel()再应用——对应选曲页的「预览信息 → 进关」。 - 三重闸:
TryValidateConfig→SupportsLevel(song, levelIndex)(这首歌支不支持该难度)→ 失败Fail(message)(即Song {0} cannot complete while the session stage is {1}.)。 Message的 SP 谱走硬编码路径(不走常规难度索引),基准课固定 3 首并挂BaselineCourseLevelMod。
4.2 判定纯函数 Chapter9Phase2Rules
public static bool IsAmetrineConditionSatisfied(bool judgeCheckFailed);public static bool AreKeyCollectionsComplete(IReadOnlyList<CollectionRequirement> requirements, Func<string,int,bool> isUnlocked);public static bool CanAdvance(Chapter9Phase2State.Step current, Chapter9Phase2State.Step target);CanAdvance是TryAdvance的第一道关;IsAmetrineConditionSatisfied的入参是bool:Ametrine 必须把「挑战是否失败」传进来,两种结果都能推进但语义不同。AreKeyCollectionsComplete把「收藏要求」与「是否解锁」解耦:判定函数不改状态,只回答是/否。
4.3 每首曲的附加条件(unlockInfo 数据原文)
12 首曲默认全开(unlockType = 0),唯一例外:
// Desultory Signals · AT"difficulty": 18.0, "levels": ["EZ","HD","IN","AT"],"unlockInfo": [ …, { "unlockType": 2, "unlockInfo": ["Chapter9Phase2Passed"] } ]→ AT 难度要等「二阶段已通关」(Chapter9Phase2Passed);这是全章唯一的难度级门槛。
同一张 Key 表里还有两个不是曲目的解锁 id(原文):
| 常量 | 值 | 用途 |
|---|---|---|
RandomKey | Random.SobremSilentroom.0 | 「随机曲」抽取用的占位 id |
DoppelgangerKey | Doppelganger.LeaF.0 | 「打 Doppelganger」这一解谜步骤的钥匙(与 DoppelgangerLevelEffect 呼应) |
4.4 LevelMod 清单(global-metadata 字面量)
LevelMod/001UiChange ← 第一形态 UI 变更 LevelMod/100UiChange ← 第二形态 UI 变更LevelMod/AmetrineLevelMod LevelMod/PetrichorLevelModLevelMod/BaselineCourseLevelMod LevelMod/ChallengeModeLevelModLevelMod/DesultorySignalsLevelEffects LevelMod/DoppelgangerLevelEffectLevelMod/HateLevelEffects LevelMod/MessageLevelEffectsLevelMod/SecretChallengeLifeMod LevelMod/QzkLevelMod/DFLevelEffect LevelMod/Destruction321LevelEffect LevelMod/LuminescenceLevelEffectLevelMod/RetributionEffect(SecondPhase) LevelMod/RrharilEventEnter LevelMod/TheChariotLevelEffect第九章是「一首曲一个 LevelMod」;第八章的 DF / TheChariot / Luminescence / Retribution / Destruction321 在同一张表里(4.0 的关卡系统共用)。
4.5 噪域的触发条件(结论来自代码)

噪域不是全局开关,而是由曲目 LevelMod 在后处理链上按时间轴推:
| 曲目 | 驱动者 | 字段 |
|---|---|---|
| True Home, True World (Rework)(S6) | C9S6LevelEffect | AnimationDirector vignetteNoiseBrightness (0x78)、vignetteNoiseSaturation (0x80) |
| ハテ(Hate) | HateLevelEffects | vignettePlus + AnimationDirector vignetteNoiseBrightness (0x90) |
| Message | MessageLevelEffects | vignettePlus(+ lensDistortion / glitch / bloom / lightBand) |
| (对照)第八章 Distorted Fate | DFLevelEffect | VignettePlusSettings vignettePlus + AnimationDirector noiseBrightness/noiseSaturation |
效果本体:
public class VignettePlusSettings : PostEffectSettings { public Vector2 center; public float smoothness, radius, darkness; [Range(0,10)] public float noiseBrightness; // ← 被上面的 AnimationDirector 驱动 [Range(0,10)] public float noiseSaturation; [Range(0,1)] public float lineOpacity; [Range(0.1,1)] public float noiseSampleRate; // 采样率(颗粒粗细) private static RenderTexture _noise; // 全局噪声图}public class GlitchDogSettings : PostEffectSettings { // “近亲”:线噪 public float noiseFrameCount, noiseMoveSpeed, noiseEvolveSpeed; public Vector2 noiseScale, noiseOffset; private Texture2DArray UniversalWireNoiseTexArray, AndroidWireNoiseTexArray;}结论:噪域 = VignettePlus 的 noise 通道(+ 可选 GlitchDog 线噪),由该曲 LevelMod 的 AnimationDirector 在关卡时间轴上拉起;玩家侧的课题条件「不因噪域的影响而产生失误,并达到 A 等成绩」(四语言原文)就是把这条曲内演出写成通关要求。课题档案「破译·噪域 / [Decipher] Noise Field」是它的说明页。
4.6 档案(收藏品):44 条与它们的分类
CollectionDatabase(sharedassets22.assets,path_id 143)里第九章共 44 条(items[459]–[502])。每条带 key / subIndex / 标题(六语言)/ date / 分类 / supervisor:
| 分类 | 条目 |
|---|---|
main | m9beginning(D.O.M.E.)、qiongdingguzhou(行与憩)、liangrenhuihe(收获)、blackhole(答案 / The Answer) |
key | nizhidaoma#2–4、poyidomejiu#1–2、poyidomegino#1–2、guguthinking3、thechaos、thenoise(破译·噪域)、thewall、thewinner、themessage、thelier(现实) |
bold | themirage(系外幻象)、sundemimi#1–4(隼的笔记本)、guguthinking1/2 |
souvenir | domeabout1–6(关于:PhigrOS / 像素塔 / 林泊 / 穹顶 / 幽蓝边界 / 收藏品) |
nonsense | nizhidaoma#1(归零 / Rewind)、heimu1#1–2、unknownsignal1–10 |
日期也很说明问题:key/main 类几乎全是 773.A.P.??.??(塔内纪年),而 heimu1 是 2091.08.31 / 2092.02.20、thelier 是 2091.08.31——现实侧与塔内纪年是两套时间轴。
我先前在字面量/文本表里看到 C9*Unlocked 与档案键相邻,据此推断过「某档案由某曲的键解锁」。但那张表存在列错位(见剧情篇的订正说明),且 CollectionItem 结构里并没有可直接读出的「解锁表达式」字段。所以:「thenoise 由 C9PetrichorUnlocked 解锁」这类配对,目前只能算线索,不算结论;可靠的部分是上面的 key / 分类 / 日期 / 归属章节。
5. 新元素:噪域(Noise Field)
玩家可见条件(四语言原文):
简:不因噪域的影响而产生失误,并达到A等成绩繁:不因噪域的影響而產生失誤,並達到A或以上成績。日:ノイズフィールドの影響でミスをせず、A以上のランクを達成しなさい。EN:Do not make mistakes due to the influence of the noise field, and achieve a rank of A or above.实现层是「噪声纹理 + 后处理」:
- 颜色:
VignettePlusSettings.ShaderIDs里有_NoiseCol(还含_NoiseBrightness / _NoiseSaturation / _LineOpacity / _Center / _Smooth / _Radius / _Darkness)——噪声是带颜色的,实机上这一层是红色;设置类里只暴露亮度/饱和度/采样率/线透明度,颜色在 shader 属性(材质)里。 - 低通有先例:
AudioLowPassFilter.set_cutoffFrequency被用在Chapter9Phase1Control / Chapter9Phase2Control的DuckBgm / FadeInBgm / RestoreBgm(BgmDuckingCutoffFrequency = 1000、恢复BgmOpenCutoffFrequency = 22000、音量BgmDuckingVolume = 0.6)以及LevelControl.<Start>d__46——“把声音闷住”是这一章惯用的手法。 - 边界:C9 的 LevelMod 类里没有 lowpass 字段,所以”噪域期间音频被低通”目前算实机现象 + 近似实现(演示按 1000Hz 低通 + 红色噪点还原)。
public class VignettePlusSettings : PostEffectSettings { public Vector2 center; public float smoothness, radius, darkness; [Range(0,10)] public float noiseBrightness; // 亮度 [Range(0,10)] public float noiseSaturation; // 饱和度 [Range(0,1)] public float lineOpacity; [Range(0.1,1)] public float noiseSampleRate; // 采样率 private static RenderTexture _noise; // 全局噪声图}- 动画驱动:
MessageLevelEffects/C9S6LevelEffect(AnimationDirector vignetteNoiseSaturation)、DFLevelEffect(noiseBrightness / noiseSaturation)。 - 档案
thenoise的标题即 「破译·噪域 / [Decipher] Noise Field」;世界文本里「真理之珠总带着断断续续的噪声」。

5.1 噪域会不会「取消判定」?(把边界说清)
把四个带噪域的 LevelMod 全字段过一遍(C9S6LevelEffect / HateLevelEffects / MessageLevelEffects / DFLevelEffect)——里面只有后处理设置、粒子、材质、UI 遮罩,没有任何 JudgeControl / JudgeLineControl / Note 引用;噪点本体也只是 Texture2D(NoiseBg*)与 VignettePlusSettings 的后处理通道。所以「噪域」本身不会修改判定结果。
但你说的「会取消判定」在谱面层是真事,只是它不叫噪域,而是下面两套机制:
① 判定线会「消失」:谱面格式里每根线都带 judgeLineDisappearEvents,值是 1 ↔ 0 的插值段——1 = 正常显示,0 = 判定线从画面上消失(注意:是显示消失,音符照常判定)。第九章用得非常凶:
| 谱面 | 判定线消失次数(1→0 段) |
|---|---|
| Exoplanetary Mirage · AT | 673 |
| Desultory Signals · IN | 655 |
| ハテ · AT | 592 |
| Desultory Signals · AT | 558 |
| Evanescent · IN | 552 |
| Ametrine · AT | 538 |
| True Home, True World · IN | 398 |
| Ametrine · IN | 379 |
| …(全章合计) | 7,848 |
② 「幽灵音符」(不参与判定的音符):结算数据 LevelResultInfo 里有一个专门字段:
public int perfect, good, bad, miss; // 四类判定结果public int early, late; // 两类时相public int maxCombo;public Dictionary<int, bool> ghostNoteJudged; // ← 每根判定线上的“幽灵音符”是否被判到也就是说:客户端把「某些音符不参与判定/被单独记录」这件事写进了结算结构——这才是「取消判定」在数据里的样子;它属于谱面与演出层,不是噪域这个后处理效果。
③ 判定「皮肤」是可以换的(这大概是你记得的「几种状态」):
public GameObject Click, Drag, Hold, Flick; // 四种音符的显示对象public Sprite ClickHL, HoldHL0, HoldHL1, DragHL, FlickHL;public GameObject perfectJudge, goodJudge; // 判定提示字样也能被替换UiChange 是纯换皮 mod:OnEnable 时把音符贴图与 Perfect / Good 判定提示换成另一套。第九章两处 UI 变更(001UiChange、100UiChange)就是两套皮肤——同一个判定,视觉上可以长得不一样。
「3 种状态」如果指的是判定结果的档位,客户端里是 4 类结果(perfect / good / bad / miss)+ 2 类时相(early / late);如果指的是音符种类,则是 4 种(Click / Drag / Hold / Flick)。噪域与判定的任何直接耦合(例如「噪域期间判定失效」)在客户端代码里没有找到路径,若要下这个结论需要实机验证——目前只能说:噪域影响的是”看不看得清”,而不是”判不判得到”。
附:噪域触发 / 不触发的对比演示(用游戏内的 Message 曲目与包内贴图重构,非录屏)。强度分层:0–40% 红线噪 + 暗角 → 40–80% 叠加 glitch 方块纹理 → 80–100% 方块纹理满强度 + 水平切片错位(RGB 偏移);用到的包内纹理为 GlitchMap / Glitch4in1 / BlockNoise1 / NoiseBgLines / FD_Noise_00000(对应 GlitchSettings.glitchTex 与 GlitchDogSettings 的线噪阵列):
「不触发」与「触发」单版本(同一段音乐;触发侧是红色噪点、音频做了低通,所以两边听感不同):
6. 选曲界面怎么换成第二形态
| 机制 | 证据 |
|---|---|
| 形态标记 | Chapter9LastSelectMusicIsPhase2 |
| 两套封面 | #ChapterCover/MainStory9.jpg / MainStory9_2.jpg |
| 进入 | Chapter9Phase1Control.ToPhase2() → TryBeginSecondPhase() + PlayerPrefs.Save() + LoadSceneAsync |
| 首次 | FirstEnterPhase2Control.OnFinished → TryAdvance + PlayerPrefs.Save;超时 [ToPhase2] Video timed out. |
| 启动器 | SaturnOSLaunchControl(statusText/L/R/Err、progressBarProgress、loadingSlide、toCollectionAnim) |
| 里程碑 | Chapter9UnlockBegin / Chapter9UnlockSecondPhaseBegin / Chapter9Phase2Passed |

7. LIFE 挑战(秘密挑战)

public static class SecretChallengeLifeRules { public static readonly int[] LifeTiers; // ← 实数值 10 / 50 / 100 / 200 public const int InitialUnlockedTierIndex = 1; public static int GetLifeForTierIndex(int); public static int GetSavedTierIndex(); public static int GetStartingTierIndexAfterPreviousUnlock(int); public static void SetUnlockedTierIndex(int); public static int GetSelectedTierIndex(); public static void SetSelectedTierIndex(int); public static bool DeductsLifeOnGood(int t); // == GetLifeForTierIndex(t) < 100 public static void PromoteAfterFailure(GameInformation); // 失败 → 晋升 public static void ApplyAfterSuccess(GameInformation); // 成功 → 应用 public static bool TryConsumePendingUnlock(out int); // 领取“待解锁” public static void BeginRun(); public static void RestartRun(); public static void ClearSaved();}
LifeTiers 初值在 global-metadata.dat 偏移 0x5BAC1C:0A 00 00 00 32 00 00 00 64 00 00 00 C8 00 00 00 = 10 / 50 / 100 / 200,由 SecretChallengeLifeRules::.cctor()(RVA 0x1D237C8)的 RuntimeHelpers.InitializeArray(0x1D2383C)装载。
「Good 也扣命」的判定就一句 GetLifeForTierIndex(t) < 100(RVA 0x1D22EC8,cmp w0,#0x64)→ 10/50 档扣,100/200 档不扣。关卡内每次失误 SecretChallengeLifeMod.OnNonPerfect() 里 sub w8, w8, #1(RVA 0x1CE6B68,扣 1);扣到 0 时调 PromoteAfterFailure(@0x1CE6BBC)。
① LifeTiers 前 4 字节恰是 01 00 00 00,所以「数组其实是 {1,10,50,100,200}」不能排除(那样 index 1 = 10 血,与社区「首档 10 血」自洽)。② 社区说「Bad/Miss 扣 2」——客户端只确证了 -1 这一处,扣 2 可能由判定/结算模块处理。
- 存档键:
C9SecretChallengeLifeTier/-SelectedLifeTier/-PendingLifeUnlock。 - 关卡内:
SecretChallengeLifeMod——Start()→SetComboDisplayOverride+EnableTrueShadow(接分数事件),OnNonPerfect()→ 红闪 +FlashLifeText()→FadeLifeTextWhiteToBlack,AudioFadeOut;SecretChallengeLifeBar需 Level UI 相机 +RawImage+ 材质模板。 - 选曲/挑战页:
LifeBadgeControl(lifeSprites按档位;SwitchLifeTier()、GetLocalizedHint(tier)、PlayOverlayFade();静态NonPerfectHint / BadMissHint)、LifeUnlockControl(Start读本地化文案;Show(int)→TrySetImage;DismissLockSeconds = 3防误触)。 - 流程:选档位 → 提示 → 打关(扣命 / 晋升 / 应用)→ 解锁展示 → 领取(Pending → Tier)。
8. 课题界面:那些「乱码」是设计好的
private const int DummyPoolSize = 100;private const int DummyTextMinLength = 8;private const int DummyTextMaxLength = 12;private const string DummyChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";public float dummyTextInterval; public Text[] dummyTargetTexts;private void BuildDummyTextPool() → CreateDummyText(...) // 先把版面填满随机串public void PlayUnlock() → AudioSource.Playprivate static readonly int SecretSongRequirementMet; private static readonly string[] UnlockSongIds;- 乱码是占位态:
dummyTargetTexts按dummyTextInterval刷 8–12 位随机字母数字;满足秘密条件后才替换真数据。<…>MoveNext最后LoadScene。 - 配套:
ChallengeStartReveal(reveal+secretLayerName)、ChallengeStartBackgroundUv(BaseMeshEffect改 UV)、Chapter9CollectionGlitchRemover、场景ChallengeMode/ChallengeModeOver。
9. 密码与秘密包:算法、加载、卸载

9.1 判定链

Submit() [0x1D486FC] → SetPassword(string) [0x1D26B70] SHA512.Create → Encoding.UTF8 → ComputeHash → GetSubArray<byte>(0..32 / 32..48) → aes.Key / aes.IV → Addressables.LoadAssetAsync<object>(...) // 重载 → <Submit>b__3 → FadeToBlackThenStartC9S6() [0x1D48B34] → SceneManager.LoadScene| 环节 | 值 |
|---|---|
| KDF | SHA-512(password)(64B,无盐) |
| Key / IV | digest[0:32] / digest[32:48] |
| 模式 | AES-256-CBC + PKCS7(.NET Aes.Create() 默认) |
| 成功判据 | 去填充成功 / 明文头 UnityFS(双重自校验) |
坑:部分包首 16 字节是独立 IV,直接当密文解会坏首块;稳妥做法是按 find(b'UnityFS') 对齐。
9.2 秘密包的运行时生命周期(Addressables 侧)
Provide(ProvideHandle) [0x1D27850] └─ C9SecretAssetBundleResource GetLoadInfo / BeginOperation ├─ LocalRequestOperationCompleted() // 本地 ├─ WebRequestOperationCompleted() // 远程(BytesToDownload / GetDownloadStatus) ├─ GetEncryptedAssetLocalPath(name) // 密文路径 ├─ <LoadLocalAssetBundle>d__36.MoveNext → Decrypt(Stream) [0x1D271D4] → Task<MemoryStream> └─ WaitForCompletionHandler → AssetBundleUnloadOperation.WaitForCompletion()Release(IResourceLocation, object) └─ C9SecretAssetBundleResource.Unload(out op) → AssetBundle.UnloadAsync(false)三点值得记:
- 只有走这个 provider 的资产是密文,其余包走标准
AssetBundleProvider; - provider 支持远程下载(
BytesToDownload / GetDownloadStatus / WebRequestOperationCompleted)——秘密资产既可从包内、也可从 CDN 取; - 卸载走
UnloadAsync,unloadingBundles字典防重复卸载,与WaitUntilNextUpdate/RegisterUpdateReceiver(RuntimeInitializeOnLoadMethod)配合「等下一帧再收」。
9.3 8 个包里有什么
| 包 | 大小 | 内容 |
|---|---|---|
c9s.MYy0ioG2 | 1.0 MB | AudioClip: music(68.51 秒秘密曲) |
c9s.Qf9C6hd3 | 13.6 MB | C9S6 剧情场景 + VideoClip: LoopBackground(9s, 1920×1080)+ 黑洞音效 |
c9s.ilBx0rGL | 645 KB | TextAsset: Chart(458 音符) |
c9s.U8WX0Xsd | 70 KB | IllustrationBlur |
c9s.sXroRzXR | 557 KB | TrueHomeTrueWorldVignettePlusSettings、Exposure_Saturate_ContrastSettings、Glitch4in1 |
c9s.vK7mQp2H | 3.8 MB | CollectionHeader |
c9s.zFzUbVcZ | 6.0 MB | CollectionLocked + 字体/材质 |
c9s.test | 1.2 KB | TextAsset: test(内容 pass) |

展开:68 个脚本类各自干了什么(点击展开)
每条都来自调用图(
c9_callgraph.json);→后是它实际调到的关键 API。
入口与阶段
LaunchControl—ToChapter9()保活控制器并选 Phase1/2 协程;EnterPhase1/2Coroutine里DontDestroyOnLoad、淡出 BGM(AddressableAudioSource.Volume / CancelFadeIn)、Destroy旧对象。Chapter9Phase1Control— 选曲页总控:Start()起协程 + 切 BGM(StartBgmTransition)、PlayUnlockSE / PlayLinkSE、ToPhase2()→TryBeginSecondPhase+PlayerPrefs.Save+LoadSceneAsync;OnDestroy→ReleasePhase1EndingAmbient。Chapter9Phase2Control— 二阶段总控:Start→TryBeginSecondPhase+ApplyMapCover;ToSaturnOS / ToSettings / Back走协程;<EnterPuzzle…>/<TurnToNextScene…>→LoadScene。FirstEnterPhase2Control— 首入二形态:挂VideoPlayer.prepareCompleted / loopPointReached,OnFinished→TryAdvance+PlayerPrefs.Save;OnDestroy解绑。Chapter9Phase2Config—baselineSongs+CreateBaselineCourse()(基准课表:BaselineSongDefinition{songId, levelModAddresses})。BaselineChallengeStartControl— 基准课入口:SetLevel(int)、Play()、RefreshVisibility()→LoadCurrentStep()。
状态机与集合
Chapter9Phase2State— 两个枚举:Step/RuntimeStage。Chapter9Phase2Progress—TryAdvance(内含LoadCurrentStep+SaveStep)、TryAwait* / TryComplete*、CompleteChapterFromAboutUs。Chapter9Phase2Rules— 三条纯函数:CanAdvance/IsAmetrineConditionSatisfied/AreKeyCollectionsComplete。Chapter9Phase2Session— 会话层:TryBeginBaselineChallenge、TryResumeMessage(都先LoadCurrentStep)。Chapter9Phase2Effect—OnEnable()→PostProcessingManager.AddEffects(进关卡挂后处理,Update起协程/动画)。CollectionControl—DisplayCanvas()(展示收集品画布)。CollectionRequirement—{collectionKey, collectionSubIndex}的可序列化条件。
谜题 / 推演 / 剧情文本
Chapter9PuzzlePiece—Awake()设alphaHitTestMinimumThreshold = 0.2;TryEndDrag()→StartCoroutine(吸附判定)。Chapter9PuzzleControl—OnPieceCompleted()(完成音效 +TweenCompletePostEffects)、PlayShowPopupExposure()、ConfirmNextStage()、<PlayNextStage…>→TryBeginBaselineChallenge+LoadScene。Chapter9PuzzlePopupControl—Awake()取LocaleText;TextReplacement协程做文本置换。C9DeductionControl— 推演:PrepareVisualLayers(CreateVignetteTexture/Sprite.Create)、RenderPage(析构旧行)、UpdateTypingText;Awake→TryCompleteCurrentSong;<FinishAndWhiteOut…>→LoadScene;OnDestroy→StopAllCoroutines+DestroyRuntimeVisuals。C9AfterDeductionControl— 后日谈:VideoPlayer.Prepare/Play、PlayerPrefs.Save、<StartNextScene> → LoadScene。C9AfterDeductionCaptionControl— 字幕:VideoPlayer.get_time驱动 4 组字幕条件 +LocaleText。Chapter9StoryTextView— 通用剧情文本视图:Play / Advance / StopPlayback、ShowPage(CreateTextLine)、StartTypingNextLine。C9StoryControl(秘密包内) — 终章演出:Start配VideoPlayer(isLooping / audioOutputMode / Play)、RenderPage / UpdateTypingText、<TurnToNextScene> → LoadScene、OnDestroy清理。C9StoryPostEffects(秘密包内) — 以Cue编排后处理:EnsureDefaultCues、InstallEffects(AddEffect / CaptureState / PushCompositeToRuntime)、DestroyRuntimeEffect。
密码与秘密资产
Chapter9InputPasswordControl—Open()取本地化标题;Submit()→SetPassword;失败走RunWrongPasswordOptics / RunChangeColor;PlayPasswordSeClip(DontDestroyOnLoad音效)。C9SecretAssetBundleProvider— §8.2 的Provide / Release / SetPassword / Decrypt / EncryptAndWrite、RegisterUpdateReceiver。Chapter9LevelsSettings—PostEffectSettings子类(inBlack / inWhite / gamma / outBlack / outWhite),白屏演出的参数源。
选曲 / 地图 / 收藏 / 详情
Chapter9MapOverview—Enter()/Exit()(进/出地图,协程做相机移动)。Chapter9MapElement— 三态 +Sync()(同步状态、FadeInLockedScanEffect)。Chapter9MapElementLink— 连线/填充:<FillRoutine…>→AddMapFocus / RemoveMapFocus。IChapter9MapFocusProvider— 地图聚焦接口(与上两者互调)。Chapter9UnlockNavigation— 解锁跳转;Chapter9SongUnlockSprite—Awake()→SyncRaycastPadding+ 换 sprite。C9SecretCollectionControl— 秘密收藏点击域(六边形命中)。Chapter9CollectionUnlockSprite—UpdateDisplay→UnlockAnimation→ApplyUnlockedAppearance,并把地图聚焦交回Chapter9Phase1Control。Chapter9CollectionGlitchRemover/Chapter9CollectionDetailsControl/…IOLSizeControl— 解锁后消故障、收藏详情、插画尺寸自适应。Chapter9SongDetailsControl—Open / Close / UpdateInfo(含LoadDifferentCoverImage:不同难度不同曲绘)。Chapter9SongDetailsLockedControl/…IllustrationSizeControl— 锁定态文案、曲绘尺寸。Chapter9SongLevelStartControl— 开谱:PrepareBackground(截图 + RT +DontDestroyOnLoad)、DestroyBackground(RenderTexture.Release+Destroy)、<Start…> → LoadScene。Chapter9FakeChallengeSongSelector— §7 的乱码池 + 秘密替换 + 白屏GlitchSettings / Chapter9LevelsSettings。
演出 / 转场 / 特效
DesultorySignalsIntroControl— §2.3 的六路视频;ClearVideoRT()释放 RT;NextScene()→LoadScene。DesultorySignalsIntroCanvasScaller— 该场景的 Canvas 缩放校正。C9S6Unlock—TryBeginFadeInFromBlack()+EnterLevel()(RawImage.set_texture→LoadScene)——点下去就是 S6。DSUnlockControl/ExoplanetaryMirageUnlock— 解锁演出(后者含SetMirrored / SetTextMirrored镜像文字)。HateGlitchEffect—Start()→LoadCurrentStep()决定故障档位。ImplexroughRandomGlitch/Chapter9EntranceToTheChaosGlitch/Chapter9PoyidomejiuAltGlitch— 曲目专属故障(最后一个OnDisable会Destroy临时 sprite)。DoppelgangerLevelEffect—CreateVignette(Image.set_sprite/type/preserveAspect)、ApplyWeight、TryLoopPlayback(AddressableAudioSource.Seek)、OnDestroy→Destroy。DesultorySignalsLevelEffects/HateLevelEffects/MessageLevelEffects—Start→AddEffect、HideUIElements、Update按音频进度AnimationDirector.Evaluate(DS 另有UpdateSlashInfo / ApplySlash)。AmetrineLevelMod/PetrichorLevelMod— Ametrine 走PostProcessingManager.AddEffects+ 协程;Petrichor 是纯参数 Mod。C9S6LevelEffect— 秘密包内 S6 特效:Awake里Destroy/DontDestroyOnLoad双轨、Start→AddEffect、Update→Evaluate。BlackScreenTransition—BeginTransition(sceneName, …)里DontDestroyOnLoad,<TransitionRoutine…>→LoadSceneAsync。CutInAnimator/TriFrameCutOutAnimator— 切入 / 三角框切出动画驱动。WorldCanvasScroller— DOME 背景的世界画布滚动。Chapter9ColorOverlayControl—ToBlue()/ToRed()(覆盖层变色,配密码失败演出)。Chapter9WhiteoutTransition— §2.6 的白屏:截图 →Graphics.Blit→ 后处理挂载 →LoadScene;Teardown成对释放。
挑战 / LIFE UI
ChallengeStartReveal— 遮罩渐显(reveal+secretLayerName)。ChallengeStartBackgroundUv— 背景 UV 扰动(BaseMeshEffect.ModifyMesh)。LifeBadgeControl— 档位徽章:SwitchLifeTier / Refresh / PlayOverlayFade / GetLocalizedHint。LifeUnlockControl— 解锁弹窗:Start读文案、Show(int)→TrySetImage、<WaitForFadeOut>。SecretChallengeLifeMod— 关卡内生命:Start→SetComboDisplayOverride+EnableTrueShadow;OnNonPerfect→ 红闪 / 文字淡出;AudioFadeOut。SecretChallengeLifeBar— 生命条 UI(RawImage + 材质模板 + 相机会话)。SecretChallengeLifeRules— §6 的规则集。
其它 / 清单外但同属这一章的类
Key 的字段是 keyName / unlockedTimes / kindOfKey / unlockTimes;差集查出来的这些(命名空间确属第九章体系):
| 类 | 它干了什么 |
|---|---|
TwoFingerPinchSession | 双指捏合手势会话(解谜触控判定):hasBaseline / locked / initialDistance,InchesToCentimeters = 2.54、FallbackDpi = 96,Tick(touchCount, distance, triggerRatio, minInitialDistance)、PixelsToCentimeters |
BaselineChallengeStartControl | 基准课开始面板:config: Chapter9Phase2Config、startRoot、[Range(0,3)] selectedLevelIndex、startButton,SetLevel / Play / RefreshVisibility |
SecretChallengeLifeBar | 生命条:shader 参数 LifeId / NoiseSpeedId / EvolutionOffsetId / TurbulenceOffsetId / TurbulenceFlowId / TurbulenceFadeId,target: RawImage、materialTemplate、GetNormalizedLife(GameInformation)——注意它有 NoiseSpeedId(生命条本身也吃噪声贴图) |
LifeBarCanvasFit | 生命条画布适配:ReferenceHeight = 1080、MaxAspect = 1.7777778、ReferencePixelsPerUnit = 100 |
ChallengeModeLevelModControl | 课题模式的关卡 Mod 装载 |
ChallengeModeOverControl | 课题结算(字段里出现 C9HiddenSongUnlockKeys)→ <TurnToNextScene> 切场景 |
PopupControl / PopupEventHandler / VisionReplayButton | SaturnOS(收藏/资料馆)的弹窗基类、事件桥与「视觉回放」按钮 |
JumpToTargetSession + *SessionBootStrap | 退出后重进指定课题会话(§2.9) |
BootSceneControl | 冷启动读 C9PendingMessage* 决定是否直接进 Message 关卡 |
ProgressControl.LevelBreak(nextScene, Func<string>) | 关卡结束 → 下一场景的决策入口(ResolveNextScene 委托挂在这里) |
LevelOverControl(含嵌套 C9AboutTheUniverseUnlockConfig) | 结算时处理 C9AboutTheUniverseUnlocked 等解锁 |
GameInformation / GameProgressSaveModule / LevelControl / SaturnOSControl | 全局存档、关卡与收藏库主控(钩子) |
10. 声音:这一章的“声音”是在什么时候、以什么方式变的
第九章的音频不是「一首 BGM 从头放到尾」:音乐会被交给画面、会被低通滤波器按进水里、会在特定时刻被单独拉掉一条音轨、还会被设为跨场景对象;而关卡特效反过来读音乐的播放状态来驱动画面。下面每个例子都给到字段名/RVA/资源名。
10.1 骨架

| # | 事件 | 客户端行为 | 证据 |
|---|---|---|---|
| 1 | 启动 | 套用全局音频配置 | GameInformation.Awake() → AudioConfiguration.GetConfiguration()/Reset(config) |
| 2 | 进第九章 | 用独立于动画时长的 audioCutOutDuration 把章节选曲 BGM 音量降到 0;先 CancelFadeIn 再逐帧 set_Volume | LaunchControl.ToChapter9()(0x1D0B28C)、<EnterPhase1Coroutine>d__8 局部字段 5__3/5__4;callee 0x1DB56EC(CancelFadeIn)、0x1DB5500(set_Volume) |
| 3 | Phase 2 切换 | 改用全局总线:读 GameInformation.get_ListenerVolume → set_ListenerVolume | callee 0x1CB7B1C / 0x1CA58CC |
| 4 | 打开详情/收藏/密码框 | DuckBgm:同一个 BGM 同时被降音量 + 低通滤波 | DuckBgm(0x1CFBA54)→ AudioLowPassFilter.set_cutoffFrequency(0x3C12E54)+ AudioSource.set_volume(0x3C11410);常量 BgmDuckingVolume = 0.6、BgmDuckingCutoffFrequency = 1000、恢复值 BgmOpenCutoffFrequency = 22000;返回 Back() → RestoreBgm() |
| 5 | 选曲 UI 点击 | 解锁/连线/进入音 | PlayUnlockSE / PlayLinkSE / Chapter9CollectionUnlockSprite.UnlockAnimation / Chapter9FakeChallengeSongSelector.PlayUnlock → AudioSource.Play |
| 6 | 难度确认 | 6 路视频(CG / 选难度 / EZ·HD·IN·AT)自带音轨 | DesultorySignalsIntroControl 的 introCG / diffSelect / ez / hd / ins / at |
| 7 | 关卡内 | 画面跟着音乐走 | HateLevelEffects / DesultorySignalsLevelEffects / MessageLevelEffects.Update()(0x1D460F8 / 0x1D43B64 / 0x1D469EC)唯一音频 callee = AudioSource.get_isPlaying(0x3C11A80)→ 喂给 glitchRange 等 GlitchSettings 参数 |
| 8 | LIFE 挑战扣命 | 拉掉一条音轨 + 生命数字由白转黑 | SecretChallengeLifeMod.OnNonPerfect()(0x1CE6A90)→ AudioFadeOut(...)(0x1CE6C84)→ <AudioFadeOut>d__14 只做 get_Volume/set_Volume 线性到 0;同时 FadeLifeTextWhiteToBlack()(0x1CE6A08) |
| 9 | 拼图最后一块 | 音效对拍在音频时钟上(不是帧上) | Chapter9PuzzleControl.OnPieceCompleted()(0x1D3C2F0)→ AudioSettings.get_dspTime + AudioSource.PlayScheduled |
| 10 | 白屏过渡 | 故障音=跨场景对象,音量直接绑故障权重 | Chapter9WhiteoutTransition.PlayOut(0x1CFB8D4)→ CreateSession(0x1D053D0)→ BeginGlitchAudio(0x1D063B8);每帧 Present()(0x1D05A1C)→ SetGlitchAudioVolume(weight)(0x1D05C2C),内部调 Object.DontDestroyOnLoad(0x3C5E5F0);PlayIn 收尾 EndGlitchAudio(0x1D064A8);常量 PeakGlitchRange = 0.2 |
| 11 | 密码提交 | 正确/错误两条 clip;正确的那条跨场景活下来 | 字段 correctPasswordClip(0xD0) / wrongPasswordClip(0xD8) / passwordSe(0xE0)(dump.cs:345502);PlayPasswordSeClip(..., persistAcrossScenes)(0x1D48D80)→ AudioSource.PlayOneShot(clip, volumeScale)(0x3C11834)+ set_spatialBlend(0x3C11D80)+ DontDestroyOnLoad;随后 <Submit>b__3 → Chapter9Phase1Control.FadeOutBgmOnLeave()(0x1CFCB20)→ FadeToBlackThenStartC9S6() |
| 12 | 解开后进关 | 拉主音量再切场景 | <FadeToBlackThenStartC9S6>d__39.MoveNext() → GameInformation.set_ListenerVolume(...) + 延时 + SceneManager.LoadScene |
| 13 | 循环类曲目 | 每轮主动把播放位置拽回去 | DoppelgangerLevelEffect.TryLoopPlayback()(0x1D45870)→ AddressableAudioSource.Seek(float)(0x1DB5B90) |
10.2 三个“一等例子”

DuckBgm 不只降音量:它同时把 AudioLowPassFilter.cutoffFrequency 从 22000 Hz 压到 1000 Hz,音量压到 0.6。所以你在第九章点开歌曲详情/收藏/密码框时听到的「闷」不是错觉——那是低通滤波。关闭面板走 RestoreBgm 还原。

SetGlitchAudioVolume(weight) 自己就会 DontDestroyOnLoad,音量逐帧绑在 flash 权重上(PeakGlitchRange = 0.2)。也就是说,那声「嘶——」会跟着你穿过 LoadScene,在下一幕的入场里被 EndGlitchAudio 收掉。
秘密场景 c9s.Qf9C6hd3 的 VideoPlayer(LoopBackground) 把 m_TargetAudioSources 留成空数组(视频只出画);场内的声音全靠预制体硬连线:BGMPlayer → 黑洞音效(故障背景音) 16.286 s(m_PlayOnAwake = true,进场景即响)、KeySEPlayer → 黑洞音效(转场) 16.792 s(画面切换时由 visualImageSE 触发)、3 个 TypeSEPlayer → 文字浮现音效 0.140 s(打字音,按 TypeIntervalSeconds = 0.058 用 PlayScheduled + AudioSettings.dspTime 轮转)。9 条后处理 cue 全部绑在 LineTypingStarted(moment = 5),亮度从 1 一路推到 16(正好是第 13 页那句「我会来找你的!」)。
10.3 音频的容器与参数(怎么存的)

- 本体第九章的音频不在 Addressables 里:
data.unity3d→AudioClip.m_Resource指向sharedassets{0,1,14,15,19,23,26,28,29,30,35,36}.resource的字节区间,格式 FSB5(mode = 0x0F,Vorbis),全部 44100 Hz / 2 声道。 - 第九章专属 30 条:
表包氛围音乐(65.829s) /里包氛围音乐(65.829s) /FakeAboutUs0(61.492s) /演绎背景环境音(56.366s) /BG Loop(38.419s) /ChapterSelect0(27.429s) /Ambient_Retri(16.219s) /Ambient(15s) /拼图拼最后一个图块(10.286s) /拼图完成后环境音循环(6.857s) /拼图拼单个图块(4.821s) /地图解锁(4.228s) /openChapter9(4.2s) /演绎转场音_4(4.152s) /Clock(4s) … - 导入模式混用:
DecompressOnLoad25 条(一次性音效)+Streaming5 条(ChapterSelect0、FakeAboutUs0、BG Loop、表包氛围音乐、演绎背景环境音)——长音频走流式,短音效解到内存。 - 秘密包 4 条同规格;秘密曲
music= 68.5114 s。
听一下(已转码放本站):
拼图最后一块 / 地图解锁 / 时钟音效:
推演环境音(前 6 秒)与秘密场景的黑洞故障音(前 8 秒):
10.4 启动 BGM 会因为你「有没有通关第九章」而不同
这是全章最容易被忽略、也最”纪念向”的一处:开屏音乐有两条,取决于你的存档进度。
| 资源 | 时长 | 什么时候放 | 加载方式 |
|---|---|---|---|
NewSplashSceneBGM | 140.069 s | 未通关主线第九章时的启动 BGM | Streaming(流式) |
SplashScene4BGM | 152.329 s | 通关后的启动 BGM | Streaming(流式) |
未通关时:
通关后:
启动场景 SplashScene 本身就带按进度条件启用的对象列表——objectsToEnable 与 objectsToEnableC8SecondPhase(“第八章二阶段”专用),说明启动画面会按存档进度切换要显示的物件;第九章沿用同一思路(两条 BGM 也都在包里、都是 Streaming)。
边界:SplashScene 的字段里没有直接看到”选哪条 BGM”的判据(判据可能在场景里的播放器对象上),所以「通关前/后各放哪条」这一映射以实机表现为准(感谢主人的对照)。
10.5 音效一览(挑有代表性的)
第九章的音效小而密,下面是能听出”这是第九章”的那一批:
| 音效 | 时长 | 用在哪 |
|---|---|---|
openChapter9 | 4.20 s | 进入第九章的开门音 |
输入密码音效 | 2.19 s | 密码框(还有一条 1.93 s 的”课题组开始 / 疑似密码错误”) |
地图解锁(包含蓄力) | 3.70 s | 地图节点解锁 |
拼图点击继续故障音 | 3.41 s | 拼图阶段的白屏/故障点 |
拼图拼单个图块 / 拼图拼最后一个图块 / 拼图弹出确认窗口音效 | 4.82 / 10.29 / 4.18 s | 拼图三连(最后一个有 10 秒的收束音) |
Clock | 4.00 s | 钟表/时间类演出 |
演绎转场音_1~4(含”靴子""火车音效”) | 3.7–4.2 s | 推演与过场 |
文字浮现音效 | 0.14 s | 秘密终章打字(每 0.058 s 一次) |
Tap1–Tap7 / HitSong0–2 / Calibration / CalibrationHit | 0.10–2.0 s | UI 通用点击与判定校准 |
展开:第九章专属音频 30 条的完整清单(名 / 时长 / 加载方式)
| # | AudioClip | 时长(s) | 加载 |
|---|---|---|---|
| 1 | 表包氛围音乐 | 65.829 | Streaming |
| 2 | 里包氛围音乐 | 65.829 | DecompressOnLoad |
| 3 | FakeAboutUs0 | 61.492 | Streaming |
| 4 | 演绎背景环境音 | 56.366 | Streaming |
| 5 | BG Loop | 38.419 | Streaming |
| 6 | ChapterSelect0 | 27.429 | Streaming |
| 7 | Ambient_Retri | 16.219 | DecompressOnLoad |
| 8 | Ambient | 15.000 | DecompressOnLoad |
| 9 | 拼图拼最后一个图块 | 10.286 | DecompressOnLoad |
| 10 | 拼图完成后环境音循环 | 6.857 | DecompressOnLoad |
| 11 | 拼图拼单个图块 | 4.821 | DecompressOnLoad |
| 12 | 地图解锁(包含蓄力) | 4.228 | DecompressOnLoad |
| 13 | openChapter9 | 4.200 | DecompressOnLoad |
| 14 | 拼图弹出确认窗口音效 | 4.179 | DecompressOnLoad |
| 15 | 演绎转场音_4 | 4.152 | DecompressOnLoad |
| 16 | Clock | 4.000 | DecompressOnLoad |
| 17 | 演绎转场音_2_火车音效 | 3.92 | DecompressOnLoad |
| 18 | 演绎转场音_1 | 3.69 | DecompressOnLoad |
| 19 | 拼图点击继续故障音 | 3.41 | DecompressOnLoad |
| 20 | 输入密码音效 | 2.19 | DecompressOnLoad |
| 21 | Calibration | 2.00 | DecompressOnLoad |
| 22 | 课题组开始游戏音效 / 疑似密码错误音效 | 1.93 | DecompressOnLoad |
| 23 | Tap7 | 1.50 | DecompressOnLoad |
| 24 | 收集品打开 | 1.42 | DecompressOnLoad |
| 25 | 演绎转场音_3_靴子 | 1.29 | DecompressOnLoad |
| 26 | openSaturnOS | 1.06 | DecompressOnLoad |
| 27 | 收集品关闭 | 1.02 | DecompressOnLoad |
| 28 | Message | 1.00 | DecompressOnLoad |
| 29 | 歌曲详情打开 / 关闭 | 0.90 / 0.81 | DecompressOnLoad |
| 30 | songInfoShow / Hide、Tap1–Tap6、HitSong0–2、CalibrationHit、文字浮现音效 | 0.10–0.61 | DecompressOnLoad |
(完整导出见素材馆,c9_audio/ 里另有 52 条 wav。) |
10.5 两处纠正(自我否定)
Chapter9SongLevelStartControl完全没有音频字段(33 个字段全是Image/Text/Camera/RenderTexture),它的DontDestroyOnLoad是为了跨场景保留背景截图——「开谱界面播 BGM」这个假设不成立,开场 BGM 归Chapter9Phase1Control/LevelControl。- 「密码控件」的真名是
Chapter9InputPasswordControl;LifeUnlockControl/LifeBadgeControl里的fadeInTween/fadeOutTween是 UI 补间,不是音频淡出。
11. 用剧情解读程序:每个子系统对应哪一幕
这一节把两张表叠在一起看:左边的程序是客户端写死的机制,右边的剧情是它服务的叙事。剧本文本均来自游戏内档案(见剧情篇)。
| 程序里的东西 | 剧情里的它 | 证据 |
|---|---|---|
选曲元素三态 Locked / Unlocked / Read | 塔内的拣选:读过的、能进的、还没开的 | Chapter9MapElement.ElementState;thewall「收集……然后是拣选与摆放」 |
拼图(Chapter9PuzzlePiece 吸附)+推演(C9DeductionControl 打字机) | 把碎裂的信号拼回去、把页码一页页读出来 | thewinner:怪物破壳后是「几乎凝成固体的信号乱流,垒成一道无法逾越的高墙」 |
噪域(VignettePlusSettings 的 noise 通道 + 曲内 LevelMod 时间轴) | 同一批档案里就叫噪域:干扰、杂讯、够不够干净地穿过去 | 条件文案「不因噪域的影响而产生失误,并达到 A 等成绩」;档案「破译·噪域」 |
白屏 / 故障(Chapter9WhiteoutTransition,故障音跨场景) | 幽蓝边界——像素塔的清扫程序;记忆的最终归宿 | nizhidaoma:「关闭像素塔……启动了像素塔的清扫程序——幽蓝边界」;domeabout5:「所有记忆的最终归宿,便是幽蓝国」 |
LIFE 挑战(10 / 50 / 100 / 200,DeductsLifeOnGood) | 她的命数:挑战越大,允许的失误越多/越少;失败反而晋升 | SecretChallengeLifeRules;history:LIFE 是 4.0 的秘密挑战玩法 |
密码(SetPassword → AES) | 网页 ARG 的答案句;往回走 | 口令 Backward, go backward, …;解开的正是 S6 |
| S6《True Home, True World (Rework)》 | 回家:真实的世界 | GameInformation 键 C9S6Unlocked;终章对话 |
收藏品 / 档案系统(SaturnOS) | 收藏品 = 塔世界的记忆 | heimu1:「1.3.0 版本,Phigros 实装了收藏品系统……人们终于在 1.3.0 版本,记起了那个绮丽多姿的塔世界」 |
退出应用(BaselineCourseLevelMod.QuitForMessageResume → Application.Quit + 冷启动重放) | 「离开像素塔」:程序把”离开”实现成了字面上的退出,再由 Phigros.JumpTo* 把你送回去 | §2.9;blackhole Gino:「只要离开像素塔——一切的谜底就能揭晓了!」 |
JumpToTargetSession(Phigros.JumpToDS / Hate / Message) | 黑洞「会让你看见过去」——直接跳到指定的那段会话 | §2.9 的会话基建表 |
4.0 的第九章把「谜题 → 演出 → 台词」写进了同一套状态机(§3.3),所以程序里的每一次跨步,都能在档案里找到同名的一页:噪域、辟径、来信、再见、现实、答案。
12. 谱面与素材
- 45 个第九章谱面(
charts/):EZ/HD/IN 37 + AT 7 + Message 的Chart_SP1;ch9_charts_summary.json含音符数与时长(秒 = time × 60 / (32 × BPM))。 - 美术:15 张档案、12 张曲绘、两形态章节封面、
CollectionHeader。
第九章的「质感」是后处理,下面是包里的原始材质(噪域主噪点 / 线噪 / 故障图集 / 网格;完整 10 张见素材馆):




- 视频(1080p → 960×540;4.0 / 4.7 / 0.2 MB):
- 音频(点开播放):
▶ 秘密曲(68.51 s)
▶ 拼图最后一块 / 地图解锁 / Clock
▶ 推演环境音(6 s)/ 秘密黑洞故障音(8 s)
13. 复现命令
unzip -j Phigros_4.0.1_TapTap.apk.1 \ assets/bin/Data/Managed/Metadata/global-metadata.dat \ lib/arm64-v8a/libil2cpp.so assets/bin/Data/data.unity3d assets/aa/catalog.json -d apk_extractDOTNET_ROLL_FORWARD=Major dotnet Il2CppDumper.dll apk_extract/libil2cpp.so apk_extract/global-metadata.dat dump_out
python3 parse_gameinfo.py # 章节 / 歌曲 / 解锁键 -> gameinfo.jsonpython3 gen_c9_code_and_conds.py # 68 脚本 / 242 类 + c9_conditions.jsonpython3 c9_callgraph.py # ★ 本文调用图 -> c9_callgraph.jsonpython3 scan_c9_text.py && python3 gen_c9_texts.py # 六语言文本:::: code-group labels=[Python · 解密秘密包, Bash · 反汇编指定方法]
from hashlib import sha512from Crypto.Cipher import AESPW = 'Backward, go backward, turn back to the antemundane realm, go back to the -'d = sha512(PW.encode()).digest()ct = open('584281739b355980d23720afec155ffc.bundle','rb').read() # c9s.testpt = AES.new(d[:32], AES.MODE_CBC, ct[:16]).decrypt(ct[16:])print(pt[:8]) # b'UnityFS'llvm-objdump -d --arch-name=arm64 --start-address=0x1D26B70 --stop-address=0x1D26FC8 apk_extract/libil2cpp.so::::
14. 附:bl 反查与调用图
# c9_callgraph.py 核心四步:# 1) dump.cs 中 "// RVA: 0x…" + 签名 → 方法表(含所属类),排序后 bisect 求「地址属于哪个方法」# 2) ELF program header 取可执行段 (p_vaddr, p_offset, p_filesz) → vaddr↔file offset# 3) 逐方法扫 4 字节对齐的 BL:(w & 0xFC000000) == 0x94000000# target = a + (sign_extend(w & 0x03FFFFFF) << 2)# 4) target 与调用点都映射回「类::方法」→ 1624 条边的调用图15. 边界与存疑
CanAdvance/AreKeyCollectionsComplete/get_PasswordIsCorrect无直接bl(泛型或委托),语义来自签名与调用半径。LifeTiers的具体数值在静态数组初值(RuntimeHelpers.InitializeArray)里;策略函数(DeductsLifeOnGood/PromoteAfterFailure/ApplyAfterSuccess)已确认。LoadScene的参数字符串(场景名)未能从字面量一一对回去,本文只标「谁在切场景」。- 8 个
c9s.*里「首 16 字节是否 IV」不一致,按 §8.1 双策略处理。
剧情向:主线全章总结与解密出的终章原文,见《穹顶孤舟:主线的完整总结》。 素材向:视频 / 试听 / 曲绘 / 材质 / 截图全量归档,见《素材馆》。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!



