Tatsh/expert-rotary-phone
GitHub: Tatsh/expert-rotary-phone
通过 Ghidra 逆向工程,将 Konami 已停更的 32 位 iOS 音乐游戏「pop'n rhythmin」重构为现代 Objective-C/C++ 源码,使其具备 64 位重编译能力。
Stars: 0 | Forks: 0
# pop'n rhythmin — 源码重构
[](https://github.com/Tatsh/expert-rotary-phone/tags)
[](https://github.com/Tatsh/expert-rotary-phone/blob/master/LICENSE.txt)
[](https://github.com/Tatsh/expert-rotary-phone/compare/v2.0.3...master)
[](https://github.com/dependabot)
[](https://tatsh.github.io/expert-rotary-phone/)
[](https://github.com/Tatsh/expert-rotary-phone/stargazers)
[](https://github.com/pre-commit/pre-commit)
[](https://prettier.io/)
[](https://bsky.app/profile/Tatsh.bsky.social)
[](https://buymeacoffee.com/Tatsh)
[](irc://irc.libera.chat/Tatsh)
[](https://hostux.social/@Tatsh)
[](https://www.patreon.com/Tatsh2)
为 **pop'n rhythmin** (`jp.konami.popnmusic`)
(Konami 的 iOS 伴侣/音乐节奏游戏)重构的 Objective-C 源码。已发布的二进制文件为**仅 32 位**
(`armv7`, `CFBundleShortVersionString` 2.0.3, 基于 iOS 8.1 SDK 构建),因此
在 iOS 11 放弃 32 位支持后它便停止了运行。此代码树的目标是尽最大努力使用现代 Objective-C 重构_原始_应用程序源码,以便能重新编译为 64 位 / 当前版本的 iOS。
## 出处
- **二进制文件:** `pop'n rhythmin 2.0.3-cracked/Payload/PopnRhythmin.app/PopnRhythmin`
(Mach-O, `ARM:LE:32:v8`, image base `0x4000`, 3997 个函数)。
- **Ghidra 项目:** `rb420`, 程序 `PopnRhythmin` (MCP bridge, TCP
`127.0.0.1:8091`)。注释中引用的地址均相对于该程序的 image base,且是此处重构内容的真实来源。
- **资源:** `.app` 包中的所有其他内容 (`*.acv`, `*.map`, `*.idx`,
`*.m4a`, fonts, `ScoreData.momd`, `DefaultUserData.plist`, `Settings.bundle`)。
## 重构与非重构内容
**范围内** — 原始应用程序代码:应用程序自身的 Objective-C 类
(view controllers, data models, managers, `ne*` OpenGL 引擎胶水代码) 以及支持它们的普通 C 辅助函数。
**范围外**(根据重构目标,特意_不_重新实现):
- 编译器/运行时生成的代码:SjLj 异常 thunks、ARC/`objc_msgSend`
stubs、`switchD_*` 跳转表、Obj-C 运行时元数据结构体
(`class_t`, `method_list_t`, `_Rep`, …)、libstdc++ 内部实现。
- 来自工具链或系统 SDK 的静态链接库代码:
`CF*`/Foundation/UIKit 内部实现、`libz`、`libstdc++`、`memcpy`/`__stack_chk`
风格的辅助函数。这些内容仅会被引用,绝不重定义。
**打包进二进制文件的第三方 SDK** 既_不_进行重构,也_不_进行 stub 处理。原始 Konami 代码通过其公共头文件引用它们,因此此代码树也照做:它 `#import` 真实的库头文件,并将这些库视为外部依赖。关于列表及各个库的识别方式,请参阅下方的**依赖项**。(唯一的例外是当类名仅仅与某个 SDK _冲突_ 时 —— 例如 Core Data 实体 `TreasureData` 是本地的 sugoroku 存档记录,与 TreasureData 分析 SDK 毫无关联。)
## 约定
- **全面采用现代 Objective-C:** ARC、`@property`/点语法、装箱字面量
(`@[]`, `@{}`, `@(x)`)、`instancetype`、`NSInteger`/`CGFloat`。
- **尽可能使用点语法**进行 property/getter 风格的调用,包括
类 getter:`UIDevice.currentDevice`、`NSUserDefaults.standardUserDefaults`、
`array.count`、`results.lastObject` —— 而不是 `[UIDevice currentDevice]`。括号语法仅保留用于带参数的调用或具有动词副作用的调用。
- **根据语言使用空字面量:** Obj-C 对象 → `nil`;Obj-C++/C++ C 指针
(`.mm`/`.cpp`) → `nullptr`;纯 C (`.c`) → `nullptr` (目标为 **C23**)。C++
目标为 **C++23**。(避免在旧有上下文之外使用 `NULL`。)
- **Include 分组:** 标准库,然后是第三方/Apple 框架,
接着是项目头文件 —— 各组之间用空行分隔(没有组标签注释),每组按字母顺序排序(ASCII/区分大小写,因此 `+`<`.` 且小写排在大写之后 —— 与 clang-format 匹配)。
- **C++ `auto`:** 在 C++/Obj-C++ 代码中,优先使用 `auto`(根据需要加上 `&`/`const`/`*`
限定符)而不是拼写出类型;仅在必要时或确实有助于可读性时才指明具体类型名。
- **使用十进制字面量以提高可读性:** 当十进制更易读时,使用十进制
(`24`, `64`, `31`) 而不是十六进制来编写计数/大小/掩码。十六进制仅保留用于注释/引用 (`@ 0xcae40`) 中的 Ghidra 地址和真实的地址值。
例外:**按位掩码/标志位使用十六进制或二进制**字面量 (`& 0x1f`,
`| 0x3`, `1 << n`),因为位模式才是关键。
- **按语言区分的文件扩展名:**
- `.m` — 纯 Objective-C。
- `.mm` — Objective-C++ (Obj-C 与 C++ 混合;例如 `ne*` 引擎封装)。
- `.cpp` — 纯 C++ 实现 (`ne` 引擎核心)。
- `.h` — 所有头文件 (C, C++, Obj-C, Obj-C++)。
Obj-C 代码通过 `Engine/NEEngineBridge.h` 中的 `extern "C"` 桥接调用 C++ 引擎,因此仅_调用_引擎的文件仍保持为 `.m`。
- **每个 Obj-C 头文件包含编辑器 modelines:** 每个 `.h` 文件末尾都有 Kate 和 vim
modelines,以便编辑器将其视为 Objective-C (或 Objective-C++),因为单纯的
`.h` 扩展名是有歧义的:
// kate: hl Objective-C;
// vim: set ft=objc :
// code: language=Objective-C
`code:` 行用于 VSCode 的 _vscode-modelines_ 扩展。Obj-C++
桥接头文件使用 `hl Objective-C++;` / `ft=objcpp` / `language=Objective-C++`;
C++ 头文件使用 `C++` / `cpp` 变体。所有 modelines 还设置了 4 空格的 tab 转空格缩进。
- **头文件保护符:** C++ 头文件使用 `#pragma once`;纯 C 头文件使用 `#ifndef UPPERCASE_NAME_H`
(全大写);Obj-C 头文件依赖 `#import` 并且不使用保护符。
- **Ghidra 数据库保持同步:** 当 `FUN_*` 函数被识别后,它们在 `rb420` 数据库中会被重命名以匹配此处使用的名称,并附带一个 plate
注释指回重构后的文件。因此,该数据库是一个实时的交叉引用;像 `Ghidra: NEAppEventCenter_shared (FUN_0000b150)`
这样的引用可以在任一方向上进行解析。
- **Core Data 模型** 位于 `Models/` 中,并且是从
`ScoreData.momd` 满精度恢复的(它们不需要反编译 —— schema 已被序列化)。
- **重命名的 C 函数:** 任何在 Ghidra 中仍为 `FUN_` 并且在此处重新实现的函数都会被赋予一个描述性名称,且其定义包含引用注释,这样回到 Ghidra 的映射就永远不会丢失:
// Ghidra: FUN_0000a9cc (project rb420, program PopnRhythmin)
// Original: unnamed; renamed here for readability.
static PersistentStore *rhythmin_makePersistentStoreCoordinator(void) { ... }
具名的 Objective-C 方法保留其真实的 selector 并引用其地址:
`// -[AppDelegate application:didFinishLaunchingWithOptions:] @ 0x8cf0`。
- 该容器**没有 Xcode / Objective-C 编译器**,因此此处的内容均未经过编译检查。代码的编写力求忠实且原则上可构建,并未经过编译器验证。
## 依赖项
这些内容在此处**不**进行重构 —— 将它们添加到项目中(通过 CocoaPods /
Carthage / SPM / vendored 源码),重构后的 `#import` 即可解析。
### 系统框架(链接 iOS SDK)
`Foundation`, `UIKit`, `CoreData`, `CoreGraphics`, `QuartzCore`, `OpenGLES`,
`AVFoundation`, `AudioToolbox`, `StoreKit`, `GameKit`, `MapKit`, `CoreLocation`,
`Social`, `AdSupport`, `Security`, `libz`, `libstdc++`。(源自 Ghidra
类列表 —— 即 `/System/Library/Frameworks/...` 和 `/usr/lib/...` 条目。)
### 打包的第三方库 — 功能性(vendor 真实的源码/头文件)
| Import as | Library | How identified |
| --------------------------------------------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `CJSONDeserializer.h`, `CJSONSerializer.h`, `CDataScanner.h`, … | **TouchJSON** (Jonathan Wight) | `CJSON*` / `CDataScanner` / `CSerializedJSONData` 类名是 TouchJSON 的确切公共 API。 |
| `ZipArchive.h` (a.k.a. `UnZipArchive`) | **ZipArchive / SSZipArchive** (minizip wrapper) | `UnZipArchive` 类 + `libz` 链接。 |
如果确切的上游版本有影响,请固定使用 2014 年中期左右(包的构建日期,
2014-07-18)的发布版本,以匹配发布版本的行为。
### 广告 / 分析 — 已打桩(STUB),移除依赖
根据项目策略,**不导入或发布任何与广告或分析相关的内容**。
这些依赖项已被移除;在原始代码调用它们的地方,调用点由 `Stubs/` 中的**空操作 stub (no-op stubs)** 提供,因此应用程序仍然可以构建和运行,并且所有的追踪/广告功能均处于无效状态。不要 vendor 真实的 SDK。
| Removed dependency | Kind | Handling |
| ------------------------------------------------------------------------- | ----------- | ------------------------------------- |
| **TreasureData** 分析 SDK (`TDClient` 等) | analytics | `Stubs/` 中的空操作 stub |
| **RewardNetwork** SDK (`RewardNetwork*`, `RewardNetworkResources.bundle`) | 广告奖励 | `Stubs/` 中的空操作 stub |
| `ASIdentifierManager` / `AdSupport` (IDFA) | 广告追踪 | 已移除;返回清零/缺失的 IDFA |
应用程序_自身_的交叉推广类 (`Recommend*`) 仍作为
原始源码进行重构,但其网络/广告调用已被中和为空操作,因此它们不会引入任何外部广告服务。
## 目录结构
此代码树反映了**原始源码结构**,该结构是从内嵌在二进制文件(断言宏)中的 `__FILE__`
字符串恢复而来的。真实的项目根目录曾为
`/Users/usr10013727/Documents/Project/Rhythmin/branches/v203/Project/`,因此此
代码树重现了该 `Project/` 结构:
```
Project/
AppDelegate.{h,mm} confirmed original path
System/src/
OpenGL/ neGLES11.cpp -> neIGLES (GL ES abstraction) [pending]
Render/ neTextTexture.mm [pending]
Sound/ caplayer.mm (CoreAudio AUGraph player) [pending]
Aep/ AepManager/AepOrderingTable/AepLyrCtrl.mm [pending]
Util/ RhCrypto.{h,c}, NSData+Crypt.{h,m} (crypto helpers)
neEngineBridge.h provisional C++ engine singleton interface
Game/
Note/ NoteMng.mm, AcNoteMng.mm [pending]
Task/ SugorokuMainTask.mm [pending]
Util/ Random.cpp [pending]
Data/
Chara/ CharaData.mm, SkillData.mm [pending]
TreasureMap/ SugorokuMap.mm [pending]
Save/ Core Data models + query categories +
UserSettingData (save/settings store) (done)
Stubs/ No-op replacements for removed ad/analytics SDKs (our addition)
```
标记为 `[pending` 的路径是已确认属于原始文件名但尚未重构的内容。
`Data/Save/` 是持久化类的临时分组(尚未从字符串中确认它们确切的原始子目录)。
命名遵循原始约定:系统层的 C++ 使用小写的 `ne` 前缀
(`neIGLES`, `neTextTexture`);游戏层的类使用 PascalCase (`NoteMng`,
`CharaData`, `SugorokuMap`)。标识符尽可能从内嵌的调试/断言
字符串和 C++ RTTI 中恢复,而不是凭空发明。
有关各类的重构状态看板,请参阅 `HANDOFF.md`。
标签:CVE监控, GitHub Pages, 前端, 游戏, 源码重构, 音乐游戏