GooglyBlox/netflix-offline-patcher
GitHub: GooglyBlox/netflix-offline-patcher
移除 Netflix 游戏中的登录门禁,使游戏在无账号状态下离线运行的跨平台补丁工具。
Stars: 0 | Forks: 0
# Netflix 离线补丁工具
作为 [Netflix 游戏恢复项目](https://netflix.notaspider.dev) 的辅助工具而开发。
移除 Netflix 游戏中的 Netflix Games SDK 登录门禁,使其无需 Netflix 账号即可离线运行。
- **Android** (`.apk`, `.apkm`, `.xapk`):重写 SDK 的 smali 代码。输出为已签名的 APK。
- **iOS** (`.ipa`):注入 dyld-interpose 垫片 (shim)。输出为未签名的 IPA,需自行侧载 (sideload)。
## 用法
```
python unlock_netflix.py INPUT [-o OUTPUT]
```
Android 端,生成已签名的离线 APK:
```
python unlock_netflix.py game.apkm
adb install -t game-offline.apk
```
请先卸载应用商店版本;因为输出的文件使用了不同的密钥进行签名。
iOS 端,生成未签名的离线 IPA:
```
python unlock_netflix.py game.ipa
```
使用 AltStore 或 Sideloadly 安装 `game-offline.ipa`,它们会使用您的 Apple ID 对其进行重新签名。iOS 流程仅需 Python(无需 JDK、apktool 或 keystore)。输入文件必须是*已解密*的 IPA;App Store 下载的文件经过 FairPlay 加密,无法进行补丁修补。
### 选项
```
-o PATH output path (default: -offline.apk or .ipa)
--keep-cloud-save leave the cloud-save APIs unpatched (Android only)
--no-sign skip signing (Android)
--keep-work keep the temp working directory
```
## 工作原理:Android
登录检查存在于 Netflix Games SDK 中,而非游戏本身。每个游戏都在 `classes.dex` 中打包了相同的 SDK,并通过 JNI 调用它,因此无论使用什么游戏引擎,一套 smali 补丁即可通用。该工具使用 apktool 解码基础 APK,检测其面对的 SDK 代次和引擎,打补丁后进行重建,使用 APKEditor 合并所有拆分包,进行 zipalign 对齐,最后签名。如果关键方法发生偏移导致无法定位,工具将中止运行。
Netflix 发布过三代 SDK。通过 Unity 桥接器 (`com.netflix.unity.impl.NfUnitySdkInternal`):
- **较新版本 (2025),`com.netflix.games`:** `doRequestPlayerAccess` 在本地返回已授权结果;`startSdkErrorActivity` 变为空操作 (no-op);`readBlob`/`getBlobs`/`writeBlob` 返回离线的“无云存档”结果,而不是卡死挂起。
- **较旧版本 (2024):** 没有 `doRequestPlayerAccess`;该门禁是一个访问 UI。将错误界面设为空操作,从 `getCurrentProfile` 返回一个合成的用户配置 (profile),并触发一次 access-granted 和 UI-dismissed 事件。混淆的类通过其 SDK 父类型 (supertype) 进行查找。
- **最旧版本 (2022-2023),`com.netflix.android.api`:** 身份验证是由 `onUserStateChange` 事件响应的 `checkUserAuth` 调用。返回一个虚拟玩家,强制进入已登录状态,并将失效的云存档槽位 API 替换为以稳定的离线身份标识为密钥 (key) 的本地文件存储(这些游戏仅在云端保存进度,如果不这样做,存档将无法持久化)。
非 Unity 引擎通过游戏自身编写的粘合类 (glue class) 访问 SDK,而不是通过 Unity 桥接器;该工具通过解析以下内容来找到它:
- **GameMaker** (`libyoyo.so`):GameMaker 扩展暴露了 `Nfxa*`/`Netflix*` 方法,这些方法将结果作为异步事件返回给 GML。支持最旧的 SDK(向粘合类的事件接收器传递合成的已登录状态,将云存档*槽位*重定向到本地存储)和第二代 SDK(合成已授权的访问,重定向云存档 *blob*)。该引擎仅支持 ARM,因此不带引擎代码的 x86/x86_64 库将被丢弃。
- **Unreal** (`libUE4.so`):最旧 SDK 上的 Unreal JNI 粘合层(输入为 `Thunk_*`,输出为 `native nativeOn*`)。该工具重写该粘合层,将合成的已登录用户配置直接传递给原生回调。仅处理身份验证;进度保存在引擎自身的存档系统中。
- **无引擎粘合层:** 直接修补 SDK 自身的 AccessApi 和 BlobStoreApi 实现。
## 工作原理:iOS(目前仅支持第二代 Unity)
**目前仅支持通过 `ngp_*` C ABI(Unity 插件桥接)访问的第二代 `com.netflix.games` SDK。** 更旧的版本和其他引擎绑定暂不支持;工具遇到无法识别的内容时会中止运行。
在 iOS 上,SDK 以原生的 `NetflixGames.framework` 形式提供,而不是 smali,引擎通过扁平的 C ABI 访问它:`extern "C"` 的 `ngp_*` 函数,每个结果都以 JSON 字符串的形式返回。门禁完全存在于该边界之后。`ngp_*` 函数是上述 Android SDK 方法的 iOS 对等实现:
| Android SDK 方法 | iOS C 函数 |
| --- | --- |
| `doRequestPlayerAccess` | `ngp_request_player_access` |
| `readBlob` / `getBlobs` / `writeBlob` | `ngp_blob_store_read` / `_get_blobs` / `_write` |
Mach-O 文件无法像 smali 那样重新组装,因此该工具不重写 SDK,而是使用 dyld interposing 覆盖那些 C 函数。它附带了一个预构建的 arm64 dylib(`netflix_patcher/ios/shims/gen2_unity/`),可在本地响应调用:`ngp_request_player_access` 为具有稳定玩家 ID 的合成离线成员返回已授权的 `PlayerAccessInfo`(以便本地存档持久化);访问 UI 调用是空操作;blob-store 调用返回离线的“无云存档”结果。SDK 的回调通过 C ABI 编组 JSON 数据,引擎在发起原生调用之前会存储其待处理任务,因此垫片 (shim) 会使用引擎期望的确切 JSON 进行同步响应:无需服务器,无需等待。
该工具将垫片框架复制到 `Frameworks/` 目录中,并在主可执行文件和引擎框架中添加一个 `LC_LOAD_DYLIB`,仅修改 Mach-O 头部的填充 (padding),从而保持每个框架的代码字节完全一致。SDK 符号是 `weak_import` 的,因此一个预构建的二进制文件适用于任何第二代游戏。输出是未签名的;侧载工具会对整个包(包括垫片)进行重新签名。要从源代码重建垫片,请参阅其 `BUILD.md`。
## 项目结构
```
unlock_netflix.py CLI entry point
netflix_patcher/
cli.py argument parsing, Android/iOS dispatch
android/
smali.py shared smali primitives and SDK type names
unity_gen2.py gen-2 SDK, Unity bridge
access_ui.py older access-UI SDK
gen0.py oldest com.netflix.android.api SDK
gamemaker.py GameMaker glue (all shapes)
unreal.py Unreal JNI glue
gen2_nobridge.py gen-2 SDK, no engine glue
tools.py, pipeline.py toolchain resolution, orchestration
ios/
handlers.py one handler per (SDK generation x framework)
macho.py Mach-O load-command injection
pipeline.py orchestration
shims/gen2_unity/ prebuilt dylib + source
```
每一代 SDK 和引擎都有其对应的模块。在任一平台上添加新支持,只需添加一个新模块(Android),或在 `ios/handlers.py` 中注册一个新的处理器(iOS)。
## 环境要求
iOS 流程仅需 Python 3。Android 流程还需要:
- **JDK 11+**,可从 [Adoptium](https://adoptium.net) 获取。使用 `--java` 指向它,设置 `JAVA_HOME`,或将其留在 `PATH` 的 `java` 中交由系统自动识别。
- **apktool**,可从 [apktool.org](https://apktool.org/docs/install) 获取 `apktool.jar`,通过 `--apktool` 指定。
- **APKEditor**,可从其 [releases](https://github.com/REAndroid/APKEditor/releases) 获取 `APKEditor.jar`,通过 `--apkeditor` 指定。用于合并拆分包。
- **Android build-tools** (`zipalign`, `apksigner`),可从 Android Studio 的 SDK Manager 或 [命令行工具](https://developer.android.com/tools/releases/cmdline-tools) 获取。当设置了 `ANDROID_HOME` 时会被自动识别。
- **一个 keystore。** 任何 keystore 均可;Android Studio 在 `~/.android/debug.keystore` 提供了一个默认库(别名为 `androiddebugkey`,密码为 `android`)。要创建自己的 keystore:
keytool -genkeypair -v -keystore debug.keystore -storepass android -keypass android -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=Android Debug, O=Android, C=US"
工具路径按以下顺序解析:CLI 参数、位于 `unlock_netflix.py` 旁的 `config.local.json`、环境变量,然后是从 `ANDROID_HOME`/`JAVA_HOME`/`PATH` 进行自动检测。配置文件只需设置一次:
```
{
"java": "/path/to/jdk/",
"apktool": "/path/to/apktool.jar",
"apkeditor": "/path/to/APKEditor.jar",
"android_sdk": "/path/to/Android/Sdk",
"keystore": "/path/to/debug.keystore",
"ks_pass": "android", "ks_alias": "androiddebugkey", "key_pass": "android"
}
```
## 限制
这仅移除了 Netflix 的登录门禁。如果游戏本身具有服务器依赖项,在门禁移除后仍可能无法运行:例如 Firebase Remote Config 经济系统(重新签名时也会导致损坏)、失效的游戏服务器或流媒体内容。独立单机游戏通常可以正常运行。请运行输出文件进行检查验证。
## 免责声明
旨在为您拥有的游戏提供保存和离线个人使用。它仅仅移除了一个失效的身份验证门禁,未涉及其他任何内容。
标签:Android逆向, APK修补, iOS逆向, JS文件枚举, Python, 无后门, 破解工具, 移动端逆向, 逆向工具