入门
本指南将逐步引导您完成从安装 OZero Security 到应用首个安全功能的整个过程。即使没有安全专业知识,约 3 分钟即可完成基本设置。
按目标开始
选择当前要完成的任务,即可跳转到本手册的相关章节或 API 参考。
概述
OZero Security 通过在一般黑客工具难以访问的 Native C++ 层执行安全逻辑来保护 Unity 游戏。只需在 Unity 编辑器内的仪表板中激活模块,即可运行核心保护功能。无需场景设置或编写额外代码。
- 构建完整性检查(应用篡改检测)
- 速度黑客及时间黑客检测
- 内存注入监控
- 加密的游戏内变量类型(Secure Types)
- 加密的存档文件及 PlayerPrefs
- 激活的检测器将在 SDK 启动时自动准备就绪。无需在场景中放置独立对象或编写重复的初始化代码。
- 安全设置在构建过程中受到保护,因此明文设置不会暴露在一般玩家的发布版本中。
- Native C++ 运行时守卫在托管 Unity 代码之外提供了额外的验证层。
- 检测结果可通过回调、日志和 Pro 遥测进行确认,便于在测试和运营中追踪原因。
1
导入包
打开 Unity 编辑器并导入 OZero Security 包。您可以通过 Unity 包管理器或双击 .unitypackage 文件进行导入。
当出现 Import Unity Package 对话框时,在勾选所有项目的情况下点击 Import。所需的脚本、原生插件和编辑器工具将自动添加。
2
打开仪表板
导入完成后,从 Unity 菜单栏打开 Config Dashboard:
打开 Config Dashboard 后,可以在此屏幕上一次性管理预设、安全模块、许可证和构建完整性设置。无需在 Project 窗口中直接查找并修改文件。
Unity 编辑器菜单说明
OZeroSecurity 将设置窗口放在 Window > OZero Security,将诊断和 bake 命令放在 Tools > OZero Security。安全预设会在 Config & Dashboard 中应用,方便您在保存前确认实际变更的设置。
| 菜单 | 作用 | 使用场景 |
|---|---|---|
| Window > OZero Security > Config & Dashboard | 打开或创建主 OZeroSecurityConfig 资产。 | 首先在这里应用预设、启用模块、配置响应策略并检查构建完整性设置。 |
| Window > OZero Security > License Settings | 打开或创建 OZeroLicenseConfig。 | 用于输入 Standard、Plus 或 Pro 许可证设置、服务器端点和遥测选项。 |
| Window > OZero Security > Check Setup | 在编辑器中诊断常见的项目和发布配置问题。 | 在导入包后、发布构建前,或模块行为不符合预期时运行。 |
| Tools > OZero Security > Check Integrity Manifest | 打开 OZero 完整性清单并显示解码后的内容。 | 用于构建完整性故障排查或支持调查。 |
| Tools > OZero Security > Bake Security Config Blob | 在 StreamingAssets 中重新生成受保护的安全配置 blob。 | 用于高级调试或 CI 工作流。普通播放器构建会自动 bake。 |
| Tools > OZero Security > Bake Assembly Hash | 为最近一次构建输出重新生成 oz_ahash.bin。 | 仅在未重新运行完整构建流水线、只进行了代码重建之后使用。 |
| Tools > OZero Security > Keystore SHA Extractor | 从 Android keystore 中提取 SHA-1 和 SHA-256 指纹。 | 在填写 Build Integrity 的 Android 签名指纹时使用。 |
| Tools > OZero Security > Add Debug SHA Key to Config | 查找 Android debug keystore,并将其 SHA-256 指纹加入 config。 | 仅用于本地 Android 调试构建。发布构建应使用发布签名密钥指纹。 |
| Tools > OZero Security > Steam Anti-Piracy > Scan Steam Redistributable | 扫描 PC 构建或插件文件夹中的 Steam redistributable 文件和已知 emulator artifact。 | 在 Steam 发布打包前,或检查是否包含可疑 Steam 文件时使用。 |
| Tools > OZero Security > Check Time.timeScale Usage | 扫描可能与 OZero 时间保护冲突的直接 Time.timeScale 写入代码。 | 在发布前,或 Speed & Time Hack 模块报告项目侧 time-scale 策略问题时运行。 |
3
激活模块
在仪表板内可以查看安全模块列表及切换开关。请激活您要使用的模块。以下是推荐的初始配置。
选择安全预设
首先选择一个预设,然后根据项目进行个别模块调整。对于一般的实时在线游戏,建议从 Standard 开始。这能在保护级别、性能和误报率之间取得最佳平衡。
| 预设 | 推荐用途 | 适用策略摘要 |
|---|---|---|
| Low | 原型、开发版本、初期 QA | 仅保留轻量的核心检查。放宽平台原生检查及强制关闭策略,以免测试环境过早被封锁。 |
| Standard | 大多数发售游戏的推荐默认值 | 启用核心保护套件、启动验证、运行时再验证、模拟器检查以及推荐的 IL2CPP 文件覆盖。是一个平衡了兼容性与保护级别的配置。 |
| Strict | 高风险实时服务、PvP、竞技类构建 | 采用最广泛的覆盖范围,将更多的失败视为致命违规。请在充分测试平台、签名及商店发布流程后再采用。 |
| 模块 | 功能说明 | 是否推荐 |
|---|---|---|
| Build Integrity Validator | 检测应用二进制是否被篡改 | 推荐 |
| Speed Hack Detector | 检测时间操控作弊 | 推荐 |
| Injection Detector | 监控内存 Hook 工具 | 推荐 |
| Install Source Validator | 拦截非法 APK(仅限 Android) | 选择 |
只需在仪表板中开启要使用的模块并保存 OZeroSecurityConfig 资产即可。播放器启动时,SDK 会自动准备已激活的模块。
许可证模型 — Standard / Plus / Pro
OZero Security 提供 Standard、Plus、Pro 三种等级。Standard 模式不与服务器联动,仅使用本地保护功能。Plus 模式在此基础上加入了各项目特定的 Native Variant 包及清单 / Bundle ID 绑定。Pro 模式包含 Plus 的配置,并增加了遥测、签名验证的服务器时间、远程策略、服务器校验以及设备限制等运营功能。
| 项目 | Standard | Plus | Pro |
|---|---|---|---|
| 许可证密钥 | — (无) | OZ-PLS-XXXX ×6 |
OZ-PRO-XXXX ×6 |
| 启动时网络连通 | 无需 — 可离线执行 | 无需运行时服务器 — 仅从门户下载 Variant 即可 | 每设备进行 1 次 POST /v1/activate 后缓存 |
| 10 个保护模块 | 全部 10 个保护模块激活 | 全部 10 个(本地保护模块与 Standard 相同) | 全部 10 个(与 Standard 相同) |
| 原生 Variant | 共用的原生模块 | 各应用特定的 Variant + 清单绑定 | 包含 |
| 云端遥测 | 不发送 | 关闭 (无服务器) | 开启 — 将威胁事件发至 /v1/telemetry |
| 签名时间 (防时钟篡改) | 关闭 — WebTime 仅使用 HTTPS HEAD |
关闭 — 与 Standard 相同 | 开启 — 使用签名的 /v1/time 响应 |
| 设备限制 | 无限制 (无密钥,不强制) | 归属项目的许可证,无运行时设备限制 | 默认 5 台 / 可调节 |
| 源代码权限 | 仅托管 C# | 仅托管 C# | 仅托管 C# |
OZeroLicenseConfig 的服务器功能。
Plus / Pro 许可证密钥注册
Standard 不需要单独的许可证设置即可使用。使用 Plus 或 Pro 时,请在 Unity 中创建 OZeroLicenseConfig 资产,并输入为项目签发的许可证密钥。Plus 密钥用于从门户下载项目专用 Native Variant 包,并在构建时确认该包属于当前项目。Pro 密钥还用于运行时服务器激活和 Pro 专用服务器功能。
1. 创建设置资产
从 Unity 顶部菜单打开 Window → OZero Security → Config & Dashboard。在 License & Server 区域点击 Create OZeroLicenseConfig 按钮。该按钮会把 OZeroLicenseConfig 资产自动创建到正确的 Resources/ 文件夹中,因此不需要手动创建文件夹或移动资产。
2. 填写 Inspector 字段
| 字段 | 是否必需 | 说明 |
|---|---|---|
| tier | 所有层级 | 选择此构建要使用的许可证层级。Standard 使用共享 Native 模块。Plus 增加项目专用 Native Variant 和 manifest 验证。Pro 在 Plus 基础上增加服务器激活、遥测和远程策略等运营功能。 |
| licenseKey | Plus / Pro | 为项目签发的许可证密钥。Plus 密钥采用 OZ-PLS-... 格式,Pro 密钥采用 OZ-PRO-... 格式。Plus 使用该密钥确认 Native Variant 包是否属于当前项目。Pro 还会用同一个密钥进行服务器激活和 Pro 服务器功能。 |
| appIdentifier | 自动传输 | 发送 Pro 激活请求时,SDK 会同时发送 Unity 的 Application.identifier。如果它与客户门户中登记的 Bundle ID 或 Package Name 不一致,激活可能会被拒绝。请先确认 Unity Player Settings 中的 Identifier。 |
| serverBaseUrl | Pro runtime | Pro 功能与 OZero 服务器通信时使用的基础地址。激活、遥测、signed time、attestation 和服务器策略检查都会使用它。Standard 和 Plus 在运行时不会调用此 URL。除非 OZero 支持团队提供了其他地址,否则保留默认值 https://api.ozerosecurity.com。 |
| serverPublicKeyHex | Pro runtime | 客户门户 > Server Key 菜单中显示的服务器签名公钥。Pro 运行时用它确认 activation、signed time、attestation 和 offline policy 响应是否来自 OZero 服务器。Plus 不使用此字段。Plus/Pro Variant manifest 会通过 SDK 内置的 OZero Variant signing key 单独验证。 |
| previousServerPublicKeyHex | Pro 可选 | 仅在 Pro 服务器签名密钥切换的短暂期间使用的旧公钥。只有 OZero 支持团队要求轮换密钥时才填写,平时请留空。Plus 不使用此值。 |
| tokenTtlSeconds | Pro runtime | Pro 激活成功一次后,离线状态下可以继续使用该结果的时间。默认值 604800 表示 7 天。超过该时间后,本地保护仍会运行,但遥测、signed time 等 Pro 服务器功能会关闭,直到下一次激活成功。 |
| offlineProPolicyMode | Pro | 这是 Pro 选项,用来决定设备离线时是否仍然阻止在门户中被封锁的构建或版本。大多数正式运营游戏建议使用 ApplyCachedBlockPolicies。只有在没有最新策略就必须停止运行的在线游戏中,才考虑 RequireFreshPolicy。IgnoreCachedBlockPolicies 适合兼容性确认或特殊测试,不建议用于正式构建。每个值的含义可在 OZeroOfflineProPolicyMode 中查看。 |
| activationTimeoutSeconds | Pro runtime | Pro 激活请求 /v1/activate 最多等待几秒。默认值为 6.0。超过该时间也不会阻塞场景加载;如果有可用的 Pro 缓存就使用缓存。即使没有缓存,也只有已选择的服务器功能不可用,本地保护仍会继续。 |
| enableLog | 可选 | 开启后,许可证缓存使用、激活成功、超时、签名不一致等流程会写入 OZeroSecLog。集成阶段建议开启,方便排查问题。发布构建如果希望减少日志,可以关闭。 |
| Pro 服务器功能 | ||
| enableDevicePolicyHeartbeat | Pro | 定期向 Pro 服务器确认当前设备是否已在客户门户中被阻止。如果服务器返回 DEVICE_BLOCKED,SDK 会清除保存的 Pro 权限信息并阻止应用运行。 |
| devicePolicyHeartbeatInterval | Pro | 检查设备是否被阻止的基础间隔。默认值为 300 秒。设为 0 则不进行周期检查。 |
| devicePolicyHeartbeatJitterPercent | Pro | 为避免大量设备在同一时刻请求服务器,给检查时间增加少量错开的比例。默认值 20 表示 SDK 会在配置间隔前后略微调整时间。可输入范围为 0 到 75。 |
| enableSecurityLevelCheck | Pro | 开启后,应用启动时会通过 /v1/security-level 向服务器确认此构建的安全设置。如果低于门户要求的最低等级,服务器可能拒绝该构建。默认值为 false。 |
| declaredSecurityLevel | Pro | 此构建向服务器报告的安全等级。默认值为 Standard。Low 用于原型或内部测试。Strict 只应在 QA 确认更强策略不会阻止正常用户后使用。 |
| failOnSecurityLevelReject | Pro | 开启后,如果服务器明确拒绝安全等级或配置 hash,应用会执行阻止流程。普通网络错误、服务器维护或许可证维护状态不会被当作篡改,只会停用 Pro 服务器功能。 |
| securityLevelCheckInterval | Pro | 应用运行中重新检查安全等级的间隔。默认值 0 表示只在启动时检查一次。 |
| securityLevelCheckJitterPercent | Pro | 为避免大量设备同时重新检查安全等级,给检查间隔增加少量错开的比例。默认值为 20,可输入范围为 0 到 75。 |
3. 构建与验证
不需要额外编写代码。应用启动时,SDK 会自动读取 OZeroLicenseConfig 资产。首次运行后,请在 Player 日志中确认是否出现类似 [OZeroLicense] activated; tier=pro caps=5 的行。如果已设置为 Pro 但日志显示 Standard 模式,请先确认资产的位置和名称。资产必须位于 Resources/ 文件夹下,名称必须正好是 OZeroLicenseConfig。
服务器激活流程
Pro 许可证会在应用启动时由 SDK 自动检查。该过程设计为不会阻塞游戏启动流程。如果服务器响应较慢或网络暂时不可用,SDK 会先检查保存的激活信息;如果该信息仍可使用,就继续运行。
引导序列
- SDK 会在应用启动时自动初始化许可证运行时。
- Standard 和 Plus 不需要运行时服务器激活,因此会立即启动本地保护功能。
- 配置可选的 Pro 服务器功能后,SDK 会在后台发送
/v1/activate请求,以检查当前许可证和设备状态。 - 激活成功后,可以使用遥测、签名服务器时间(signed time)、构建证明(attestation) 等 Pro 服务器功能。
- 如果发生服务器维护、临时网络错误或超时,SDK 会尽可能使用已保存的激活信息。如果没有可用的保存信息,或许可证已过期、暂停、撤销,则只有已选择的 Pro 服务器功能会不可用或受限。已获取的 Native Variant 和本地保护保持不变。
传输的信息
激活请求是一个小型 JSON POST。基本请求包含 licenseKey、deviceId、sdkVersion 和 platform。当 Unity 能提供值时,也会发送 appIdentifier、companyName、productName 和 webglOrigin。deviceId 默认使用 Unity 设备标识符;如有需要,可以通过 OZeroLicenseRuntime.DeviceIdProvider 改为游戏账号 ID 或自定义 UUID。
POST https://api.ozerosecurity.com/v1/activate
Content-Type: application/json
{
"licenseKey": "OZ-PRO-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX",
"deviceId": "<DeviceIdProvider result; default SystemInfo.deviceUniqueIdentifier>",
"sdkVersion": "<OZeroSdkVersion.ManagedVersion>",
"platform": "<android | ios | windows | macos | linux | webgl | ...>",
"appIdentifier": "<Application.identifier>",
"companyName": "<Application.companyName>",
"productName": "<Application.productName>",
"webglOrigin": "<WebGL origin only>"
}
服务器会返回签名令牌、确定的层级、可用功能列表、服务器功能启用状态、令牌过期时间,以及服务器不可达时的处理策略。SDK 会使用配置的公钥验证签名令牌,然后才信任 Pro 功能。
无法使用服务器功能时
服务器维护、网络超时、许可证过期/暂停/撤销、Bundle ID 不匹配不会立即被视为篡改。这种情况下只有已选择的 Pro 服务器功能会不可用或受限,已获取的 Native Variant 和本地保护保持不变。相反,构建不匹配、被阻止的构建、注入、调试器等明确篡改状态会按照配置的响应策略处理。
await OZeroLicenseRuntime.Initialize();,然后查询 OZeroLicenseRuntime.Entitlement?.cachedAtMillis 的值。如果数值落在几秒前则代表服务器刚作答,否则是在以缓存基础运行。
离线操作
先看结论:网络断开不会停止游戏或本地保护。Standard 原本就不使用服务器,Pro 也保留相同的离线本地保护。只有明确选择的服务器功能可在一定时间内使用已保存的激活信息。
Standard — 无需互联网即可启动
Standard 不会向服务器请求许可。它只运行应用内置的本地保护模块,所以玩家离线时游戏也能启动。只有遥测、signed time 等需要 Pro 服务器的功能不可用。
Pro — 本地保护保持离线,所选服务器功能使用缓存
启用可选的 Pro 服务器功能后,SDK 会在联网时通过 /v1/activate 确认设备和许可证,并把结果加密保存到 PlayerPrefs,使所选服务器功能能够容忍短暂断网。此激活不会限制使用已获取 Native Variant 的构建或本地保护。
tokenTtlSeconds 是已保存服务器功能结果的有效期,默认值为 604800 秒,即 7 天。离线过期后,只有遥测上传、signed-time 验证等依赖服务器的功能在下次成功激活前不可用。游戏、已获取的 Native Variant 和本地检测器保持不变;SDK 不会静默地把产品层级切换为 Standard。
Pro — 门户中阻止的版本离线也会被阻止
临时服务器或网络问题下继续以基础保护启动,和允许运营人员在门户中明确阻止的版本运行,是两件不同的事。Pro 激活成功时,服务器会同时下发带签名的阻止列表,例如被阻止的构建哈希、SDK 版本和应用版本。SDK 会把这份列表与激活结果分开保存。
使用推荐的 ApplyCachedBlockPolicies 模式时,只要保存的阻止列表仍然有效,即使玩家开启飞行模式,Build Integrity 也会拒绝被阻止的版本。你在门户中修改阻止规则后,每台设备都需要成功在线激活一次,才能取得新规则。
| 状态 | 探测器 | 遥测 | Signed Time |
|---|---|---|---|
| 连线,刚完成激活 | 10 项皆起作用 | 开启 | 开启 |
| 离线,保存的 Pro 信息仍有效 | 10 项皆起作用 | 关闭 (不保存离线事件) | 可用时使用 WebTime 作为替代 |
| 离线,保存的 Pro 信息已过期 | 10 项皆起作用 | 关闭 (直到下次在线激活) | 关闭 (直到下次在线激活) |
| 首次启动 + 完全离线 | 10 项皆起作用 | 关闭,直到首次在线启动 | 关闭,直到首次在线启动 |
故障排除
出现问题时,请先在 Unity Player 日志中搜索 [OZeroLicense]。这个日志会告诉你 SDK 是以 Standard 启动,还是 Pro 激活成功;如果失败,也会说明为什么降级到 Standard。安全事件上传问题则同时搜索 [OZeroTelemetry]。
| 日志中显示的内容 | 含义 | 处理方法 |
|---|---|---|
[OZeroLicense] Standard / serverless mode. |
SDK 以 Standard 模式启动。 | 如果本来就要使用 Standard,这是正常情况。如果预期是 Pro,请确认 OZeroLicenseConfig.asset 是通过 Unity 菜单 Window → OZero Security → Config & Dashboard 创建的,并且位于 Resources/ 下。资源名称必须正好是 OZeroLicenseConfig,同时 tier=Pro 和 licenseKey 也要填写。 |
Pro->Standard downgrade: /v1/activate returned LICENSE_NOT_FOUND |
服务器找不到这个许可证密钥,或该密钥当前不可用。 | 请把 licenseKey 与客户门户中显示的许可证密钥重新对照。大小写和短横线必须完全一致。如果门户中的状态是 pending、suspended、revoked 或 expired,Pro 激活不会成功。 |
ANDROID_BUNDLE_ID_MISMATCH / WINDOWS_PRODUCT_NAME_MISMATCH |
当前构建的应用信息与门户中登记的值不同。 | Android/iOS 请检查 Player Settings 中的 Application.identifier,Windows 请检查 Company/Product 名称,WebGL 请检查运行 origin。如果这些值与门户登记不一致,/v1/activate 会被拒绝。修改后请用新构建重新测试。 |
activation token signature verification failed |
服务器有响应,但 SDK 判断该响应不能被信任。 | 此项只适用于 Pro 激活。请将客户门户 > Server Key 的 Active publicKey 填入 OZeroLicenseConfig.serverPublicKeyHex。即使门户同时显示 kid,Unity 中也只填 64 位 hex publicKey。若值完全正确仍失败,请在不经过公司代理/MITM 设备的直接网络上再测试一次。 |
private native variant manifest signature is invalid |
Variant 包的 manifest 缺失、被编辑,或不是由受信任的 OZero Variant signing key 签名。 | 不要通过填写 serverPublicKeyHex 来修复此问题。对于 Plus/Pro Variant 包,请重新下载分配给该项目的 Variant 包,并确保 manifest 与 native plugin 文件来自同一个包。如果重新下载后仍失败,请将 manifest 文件和 Unity 构建日志发送给 OZero 支持团队。 |
DEVICE_BLOCKED |
这台设备已在客户门户中被阻止。 | 请在门户中查看该 deviceId 的安全事件。如果是真实攻击或违反策略,请保持阻止;如果是 QA 设备或误报,请在门户中解除阻止。SDK 收到这个响应后,会清除保存的 Pro 权限并执行阻止流程。 |
ACTIVATION_LIMIT |
该许可证可新激活的设备数量已经超出上限。 | 请在门户中清理不再使用的测试设备。如果这是由真实用户增长造成的,就需要提高该许可证的设备上限。 |
/v1/activate timed out / network error |
设备无法连接服务器,或服务器响应太慢。 | 请确认设备能访问 https://api.ozerosecurity.com/health。同时检查公司防火墙、代理和 DNS。高延迟地区可以稍微提高 activationTimeoutSeconds 后再测试。如果已经有保存的 Pro 信息,SDK 会优先使用它。 |
cached entitlement past TTL; clearing. |
保存的 Pro 信息已过有效期。 | 如果设备离线时间超过 tokenTtlSeconds,保存的 Pro 信息就不再被信任。游戏会继续以 Standard 级别运行,下一次在线激活成功后 Pro 功能会恢复。如果游戏经常长时间离线,可以提高 TTL。 |
SERVER_NOT_CONFIGURED / SIGN_FAILED |
服务器无法为激活令牌签名。 | 大多数情况下,这不是客户端设置问题,而是服务器 Server Key 或加密密钥状态问题。请检查客户门户中的 Server Key 状态。生产环境发生时,请把日志时间、部分 licenseKey 和发生平台提供给 OZero 支持团队。 |
enableLog。这些日志对 QA 很有帮助,但面向普通用户的构建不需要暴露 tier、功能状态或检测流程细节。
项目设置
在调整各个安全模块之前,请先检查影响原生插件、商店构建和平台验证的 Unity Player Settings。
最低构建目标
| 平台 | 最低目标 | 说明 |
|---|---|---|
| iOS | 12.0+ |
在 iOS 构建中,请将 Project Settings > Player > iOS > Target minimum iOS Version 设置为 12.0 或更高。OZero 不会强制覆盖此 PlayerSettings 值,因此您可以根据应用的支持策略进行保留。 |
| Android | API 21+ |
对于 Android 构建,请将 Project Settings > Player > Android > Minimum API Level 设置为 Android 5.0 Lollipop (API level 21) 或更高版本。建议商店发布构建使用 IL2CPP 和 ARM64。 |
PrivacyInfo.xcprivacy。Unity post-process 会将其加入 iOS Xcode 应用 target,并复制到 macOS 的 Contents/Resources。由于 OZero 使用 Unity PlayerPrefs 保存应用内 SDK 状态,清单声明了 UserDefaults CA92.1 理由。请在最终应用中保留该清单,并在提交前检查 Xcode privacy report。
发布构建的安全与体积优化设置建议
发布最终版本时,OZero 建议使用以下 Unity 设置来增强构建安全性并减少应用体积。
安全强化(IL2CPP 设置):在 Android、iOS 等 Unity 支持的环境中,请将脚本后端设置为 IL2CPP。IL2CPP 不能完全阻止逆向分析,但可以减少 C# 代码和元数据的直接暴露范围,并帮助 OZero 安全检查在更稳固的发布环境中运行。路径:Project Settings > Player > Other Settings > Scripting Backend
体积优化(Managed Stripping 设置):Managed Stripping 会移除未使用的 managed code,从而减少应用体积。如果一开始移除过多代码,应用可能在运行时出错,因此建议先从 Low 或 Medium 开始。
注意事项:修改设置后,请先在真实设备上测试场景切换、数据保存、Addressables,以及支付/广告等外部 SDK 是否正常工作,再提高 stripping level。
Android ProGuard / R8 设置
OZero Security 不需要单独的 Java SDK 包。但是,如果您的 Android 部署版本启用了 Minify、ProGuard 或 R8,则必须保留 Unity Java 桥接以及项目中使用的自定义 Android 桥接类。这样才能确保获取包信息、安装来源、APK 签名证书检查和启动资产加载所需的 JNI 调用在混淆后依然稳定运行。
proguard-user.txt 中。如果不使用 Minify,则无需单独进行 ProGuard 设置。
# OZero Security - Unity Android ProGuard/R8 keep rules
-keep class com.unity3d.player.UnityPlayer { *; }
-keep class com.unity3d.player.UnityPlayerActivity { *; }
-keep class com.unity3d.player.UnityPlayerGameActivity { *; }
-keep class com.unity3d.player.UnityPlayerForActivityOrService { *; }
-keepattributes *Annotation*,InnerClasses,EnclosingMethod,Signature
# If your game adds custom Java/Kotlin bridge classes that OZero or your code
# calls through AndroidJavaClass / AndroidJavaObject, keep those classes too.
# Replace the package below with your own bridge package.
# -keep class com.yourcompany.yourgame.bridge.** { *; }
- 不要重命名、删除或重新打包
libOZeroSecurity.so。将插件保留在Assets/OZeroSDK/Plugins/Android/arm64-v8a下。如果您要分发 32 位版本,也请保留armeabi-v7a。 - 如果您使用 Build Integrity > Check Platform Native 和 Android SHA Keys,请务必在应用 Minify/R8 后,使用最终签名的 APK 或 AAB 进行测试。调试密钥库的指纹与发布密钥库的指纹是不同的。
- 如果只有在开启 Minify 之后,Android 日志中才出现 JNI 查找失败、安装来源检测失败或 APK 签名检查结果为空的情况,请先检查您的自定义桥接的 keep 规则。
- 对于商店分发的构建版本,请基于 IL2CPP、Release 签名、
Android Sha Keys中注册的预期 Android SHA-256 指纹,并在至少一台真机上进行干净运行测试后再做判断。
5
OZeroSecurityConfig 设置
OZeroSecurityConfig ScriptableObject 资产会在包导入时包含。在 Project 窗口中选择它即可查看并调整所有安全模块设置。在运行时通过 OZeroSecurityConfig.Instance 进行访问。
通用设置
这些是所有安全模块共享的顶级设置。本表中的默认值以代码中定义的序列化初始值为准。
| 字段 | Type | 默认值 | 说明 |
|---|---|---|---|
| developerSecret | string | "" | 用于保护 OZeroSV_File 和 OZeroSafePlayerPrefs 数据的项目级 secret。请使用 Config Dashboard 中的 Generate Secure Secret 按钮生成,不要手动随意输入字符串。每个游戏应使用不同值,并避免出现在文档、日志或公开仓库中。发布后如果更改该值,新版本将无法解密旧版本保存的受保护数据。 |
| enableLog | bool | false | 开启 Enable Debug Logs 会将 SDK 初始化、设置加载、许可证/遥测流程、检测事件等内部状态输出到 Unity Console 和 Player 日志中。这在开发/QA 阶段有助于查明原因,但可能暴露检测流程和模块状态,建议在生产构建中关闭。 |
| enableFailureDiagnostics | bool | false | 开启 Enable Failure Diagnostics 时,如果发生安全违规,本地诊断文件会保存到 Application.persistentDataPath。因文件内可能包含模块名、哈希、设备状态、安装包名以及部分运行时配置,建议仅在 QA 或客户服务支持时开启。按平台的默认保存位置:
|
developerSecret。发布后不要更改。更改该值后,新版本将无法解密现有 OZeroSV_File 和 OZeroSafePlayerPrefs 数据。
验证用 UI 管理
Pro Only 功能。配置 Build Integrity 和 Steam DRM 等流程中使用的在线重新验证、网络重试以及阻止/退出提示对话框。导入 optional OZero TMP dialog package 前,请先通过 Package Manager 安装 Unity UI 和 TextMeshPro。Unity 6 的 TextMeshPro 已包含在 Unity UI 2.x 中。不使用 TextMeshPro 的项目请使用 Custom UI / Callback Only。
BuiltInBlockingDialog 或 BuiltInNonBlockingDialog 后,每次 player build 都必须具备 TextMeshPro、TMP Essential Resources 以及已导入的 OZeroSecurity_BuiltInDialog_TMP.unitypackage。缺少任一条件时,preflight 会停止构建。打开 Window > OZero Security > Check Setup,在 Built-In TMP dialog 错误项中点击 Import Package,等待 Unity script compilation 完成后再构建。若游戏提供自定义 UI,请选择 Custom UI / Callback Only。
Application.systemLanguage 显示对应语言。
| 字段 | Type | 默认值 | 说明 |
|---|---|---|---|
| managedVerificationUiPolicy (Pro) | enum | BuiltInBlockingDialog | Pro Only 功能。选择 OZero Managed Build Integrity 和 Steam DRM 状态的用户界面流程。内置阻止型/提示型对话框需要 TextMeshPro,并且必须 import optional OZeroSecurity_BuiltInDialog_TMP.unitypackage。如果项目不 import 该 package,或要替换成自有 UI,请使用 Custom UI / Callback Only。 |
| managedVerificationDialogPrefabResourcePath (Pro) | string | "" | 复制并自定义的 OZero Managed Verification UI prefab 的 Resources 路径。留空时使用 optional TMP dialog package 中的默认 prefab。请先 import OZeroSecurity_BuiltInDialog_TMP.unitypackage,如需修改视觉设计,再创建项目自有副本。 |
| managedVerificationRetryTimeoutSeconds (Pro) | int | 15 | 用户点击 Retry 后,进入 retry-timeout 状态前等待的最长时间。用于避免玩家无限期停留在验证等待状态。 |
| managedVerificationOnlineRequiredTimeoutSeconds (Pro) | int | 120 | 显示需要联网对话框后,在应用所配置 timeout action 前等待的最长时间。 |
| managedVerificationTimeoutAction (Pro) | enum | BlockSession | 验证未能在限定时间内恢复时的处理方式:继续显示对话框、阻止受保护会话、终止应用,或仅调用 callback 以便自定义流程处理。 |
| autoRetryManagedVerificationWhenNetworkRestored (Pro) | bool | false | 网络连接恢复后自动重试 Managed Verification。仅当游戏流程可以在没有玩家明确操作的情况下安全重试时启用。 |
| managedVerificationLanguageCode (Pro) | string | auto | 内置验证 UI 的语言代码。使用 auto 可跟随 Application.systemLanguage,也可指定 ko、en、ja、zh-CN、zh-TW 或自定义 JSON 文件代码。 |
| managedVerificationFallbackLanguageCode (Pro) | string | en | 请求的语言 JSON 不存在时使用的 fallback 语言。自定义文件使用 Assets/OZeroSDK/Resources/OZeroLocalization/ozero_ui_text_{code}.json 格式。 |
Global Threat Response
决定确认安全威胁后游戏的响应方式。在测试期间请先检查回调和日志,在实际部署构建中请根据项目的运营策略选择给玩家的提示时间和终止策略。
| 字段 | Type | 默认值 | 说明 |
|---|---|---|---|
| forceQuitOnDetection | bool | true | forceQuitOnDetection 决定在检测到确切威胁时 SDK 是否自动终止游戏。关闭后只检查回调和日志,便于 QA,但在实际部署构建中可能导致被绕过的客户端继续运行,请慎重选择。 |
| fatalCallbackGraceSeconds | float | 10 | fatalCallbackGraceSeconds 是在检测到威胁后,游戏侧安全回调向玩家显示提示 UI 的最长容许时间。默认值为 10 秒。设置为 0 时会恢复之前的无提示直接退出行为。 |
接入问题自查指南
如果启用 OZero Security 后应用退出,或只在特定环境中出现问题,请先通过本地诊断文件确认是哪个模块触发。不要先凭感觉修改设置,而是根据文件中的 module、subCode、reason 按下面的表逐步排查。
应用反复退出时先做什么
| 步骤 | 操作 | 如何查看 |
|---|---|---|
| 1 | 在 Unity 菜单中打开 Window > OZero Security > Config Dashboard,然后在 OZeroSecurityConfig 的 Key Common Settings > Enable Failure Diagnostics 中开启该选项。 | 仅在 QA 或支持排查原因时使用,不要在公开发布构建中保持开启。 |
| 2 | 在继续修改其他设置之前,用同一个构建复现一次问题。 | 如果复现前同时修改多个设置,会更难判断原因。请先保留当前状态下的诊断文件。 |
| 3 | 在 Application.persistentDataPath 中打开最新的 ozero_*_failure.log 文件。 | Build Integrity 也可能生成 ozero_integrity_failure.log 和更具体的文件,例如 ozero_integrity_platform_native_failure.log。如果应用在非常早的阶段退出而没有 failure log,请同时检查 ozero_abort.txt 和 Player log。 |
| 4 | 收集文件后,再次关闭 Enable Failure Diagnostics。 | 除非你的支持流程明确需要,否则不要在公开发布构建中保持本地诊断文件开启。 |
常见诊断字段
| 字段 | 含义 | 下一步检查 |
|---|---|---|
| module | 产生事件的保护区域。 | 先查看同一类 FAQ 行,必要时再打开对应的手册章节,例如 Build Integrity、Speed & Time Hack、Injection、Install Source 或 License。 |
| subCode / checkName | 较短的原因代码或检查名称。 | 用它在下方选择同一类症状。例如 platform_native 表示先检查平台、模拟器、签名、root 或越狱相关设置。 |
| reason / subReason | SDK 触发原因的可读摘要。 | 这只是排查提示,不是最终结论。请先看 module 和 subCode,再在下方症状表中查找相同关键词的行。 |
| platform / buildType | 应用运行的平台以及构建类型。 | 区分 QA 模拟器/开发构建问题和真实发布设备问题。 |
常见症状和第一步检查
| 症状 | 可能区域 | 先尝试什么 |
|---|---|---|
| 应用 OZero 后,应用启动后立即退出。 | 启动过程中可能触发了安全响应。 | 开启 Failure Diagnostics,复现一次,然后读取 module、subCode 和 reason。如果没有 failure log,请检查 ozero_abort.txt 和 Player log,并在 Unity 中运行 Window > OZero Security > Check Setup。 |
| Android 模拟器中应用反复退出。 | Build Integrity 或平台原生检查可能将模拟器视为不支持的运行环境。 | 用于模拟器 QA 时,可放宽 blockEmulator (Android) 或相关平台检查策略。如果你的游戏发布版需要阻止模拟器,请在商店发布前重新启用发布策略。 |
| 许可证激活失败,或可选的 Pro 服务器功能不可用。 | 可能是许可证密钥、应用身份、网络或服务器签名密钥问题。 | 打开 License Troubleshooting,检查实际日志,例如 LICENSE_NOT_FOUND、ANDROID_BUNDLE_ID_MISMATCH、timeout 或 signature verification failure。 |
| Build Integrity 或 manifest 验证失败。 | manifest 可能缺失、过旧、未签名,或与当前构建不匹配。 | 运行 Check Setup,重新生成 integrity manifest,验证 signing key,并进行 clean build。代码、IL2CPP 输出、watched file 或 manifest signing key 改变后都需要重新生成。 |
| 暂停、慢动作或倍速后应用退出。 | 直接写入 Time.timeScale 的代码可能看起来像时间操控。 | 使用 OZeroTime.timeScale。TimeScaleTamperExemptions 只用于已验证的插件或 legacy adapter script。 |
| 长时间加载画面中应用退出。 | 主线程被阻塞时,native Watchdog 可能收不到 heartbeat。 | 仅将可信的长加载边界包在 OZeroWatchdog.BeginLoadingGrace 中。不要用它隐藏正常 gameplay 卡死。 |
| Android 在启用 Minify/ProGuard/R8 前正常,启用后退出。 | Unity 或自定义 Android bridge class 可能被移除或重命名。 | 应用 Android ProGuard / R8 keep rules,尤其要保留 Unity bridge class 和项目调用的自定义 Java/Kotlin bridge class。 |
| Windows Standalone 在干净 PC 上立即退出。 | OZero native plugin 或 VC++ runtime 可能未能加载。 | 安装 Microsoft Visual C++ Redistributable 2015-2022 (x64),并确认 OZero native plugin 已包含在构建输出中。 |
| 正常 overlay 或录屏工具触发 Injection 事件。 | Injection/Hooking 正在观察已加载模块。 | 请先从 Failure Diagnostics 或 Pro telemetry 中确认被检测到的模块路径、hash 和 signer fingerprint。只有在 QA 确认该模块可信后,才应注册例外。本地例外请添加到 Injection 设置的 injectionWhitelistEntries;Pro 客户可以通过客户门户的服务器管理 whitelist 在运营中更新同类策略。 |
| 构建因 optional Built-In TMP Dialog package 尚未就绪而停止。 | 已选择 Built-In Managed Verification UI policy,但缺少 TextMeshPro、TMP Essential Resources、OZero 对话框组件或默认 prefab。 | 打开 Window > OZero Security > Check Setup,在 Built-In TMP dialog 错误项中点击 Import Package。等待 package import 与 script compilation 完成,重新执行 Check Setup 后再构建。若游戏提供自定义 UI,请选择 Custom UI / Callback Only。 |
构建完整性验证器设置
配置构建完整性检查器,以检测被修改的游戏文件、调试器连接以及异常的运行环境。
| 字段 | Type | 默认值 | 说明 |
|---|---|---|---|
| Activate Build Integrity | checkbox | On | Dashboard 中用于启用 Build Integrity 模块的复选框。内部会映射到 useIntegrity。 |
| validateOnStartup | bool | true | 游戏启动后立即执行构建完整性检查。 |
| validateInEditor | bool | false | 在 Unity 编辑器中也执行验证(对测试有帮助,建议在常规开发期间关闭)。 |
| enablePeriodicValidation | bool | true | 在游戏运行期间也循环进行完整性验证。如果您的构建不足以通过启动时的单次验证,建议开启此功能。 |
| periodicCheckInterval | float | 300 s | 游戏运行期间重复执行 Build Integrity 验证的基础间隔(秒)。设置为 0 或更小值可关闭定期检查。 |
| periodicCheckJitterPercent | float | 35% | 给验证周期添加随机变化,使检查时机更难被预测。公开文档中未提供具体周期与范围,推荐使用默认值。 |
| timingAnomalyConsecutiveRequired | int | 7 | 确定基于时间的异常信号需要累积多少次才判定为调试器时序漂移。强烈的调试器信号可能仍会导致立即失败。 |
| timingAnomalyWindowSeconds | float | 900 s | 用于累加时间异常信号的时间窗。越长容错越高,Strict 使用更短的时间窗。 |
| timingAnomalyFrameHitchSuppressionSeconds | float | 20 s | 在发生了如场景加载、着色器编译、GC 或 OS 调度延迟等大规模帧卡顿后,在一定时间内放宽对基于时间的调试器检测。 |
| checkAssemblyHash | bool | true | 根据构建时生成的 manifest 验证已编译程序集的哈希值。 |
| checkDebugger | bool | true | 检测调试器连接,以及类似调试器行为的运行时计时异常。 |
| failOnDebugBuild | bool | false | 将 Unity 的 Debug 构建视为违规(建议用于发布构建)。 |
| checkPlatformNative | bool | true | 执行平台原生检查。根据平台不同,可能检查 root/jailbreak、APK 签名、运行环境、代理或分析工具信号。 |
| failIfManifestMissing | bool | false* | Inspector 默认关闭,方便开发阶段迭代。但在非 development 的 player build 中会强制开启;如果 oz_manifest.ozero 缺失或无法读取,将被视为违规。 |
| failIfAssemblyHashBlobMissing | bool | false* | Inspector 默认关闭,方便开发阶段迭代。但在非 development 的 player build 中会强制开启,避免攻击者通过删除生成的程序集哈希 blob 来静默绕过哈希验证。 |
| requireCodeSignature (Windows) | bool | false | 主执行文件必须具有代码签名。仅限 Windows。 |
| blockVirtualMachine (Windows) | bool | false | 阻止游戏在虚拟机内部运行。仅限 Windows。 |
| blockHyperV (Windows) | bool | false | 拦截 Hyper-V VMBus 信号。WSL2、Docker Desktop 或 Windows Sandbox 用户可能也会被阻止,因此仅限在受控环境中审慎使用。 |
| blockNetworkProxies (Windows) | bool | false | 检测疑似网络代理、数据包检测或流量分析工具的运行信号。用于竞技型构建,但请事先测试是否存在误报可能。 |
| blockReverseEngineeringTools (Windows) | bool | false | 检测疑似逆向工程或调试工具的运行信号。请在开发/QA 环境与线上环境分别进行验证。 |
| blockSystemMonitorTools (Windows) | bool | false | 检测疑似进程/系统监控工具的运行信号。请注意普通用户环境中可能存在的误报情况。 |
| il2cppHashGameAssembly | bool | true | 对 Windows IL2CPP 构建的 GameAssembly.dll 进行哈希验证。这是 IL2CPP 文件保护的最低建议项。 |
| il2cppHashGlobalGameManagers | bool | false | 将 globalgamemanagers 加入 Windows IL2CPP 文件哈希范围。代码默认关闭,但 Standard 和 Strict 预设会开启。 |
| il2cppHashSharedAssets | bool | false | 将 sharedassets* 文件加入 Windows IL2CPP 文件哈希范围。代码默认关闭,但 Standard 和 Strict 预设会开启。 |
| il2cppHashSceneFiles | bool | false | 将 level* 等 Unity 场景文件加入 Windows IL2CPP 文件哈希范围。代码默认关闭,但 Standard 和 Strict 预设会开启。 |
| il2cppHashResourcesAssets | bool | false | 将 resources.assets 添加到 IL2CPP 验证范围内。有助于 Strict 模式,但请先测试补丁流程。 |
| il2cppAdditionalWatchedFiles | List<string> | — | 当项目包含额外的原生 payload 时,指定需要受监控的 Windows IL2CPP 额外输出文件。 |
| blockEmulator (Android) | bool | true | 仅限 Android。将模拟器或不支持的运行时信号视为完整性违规。建议在 QA/模拟器测试期间放宽,而在生产构建中使用更严格的策略。 |
| blockSystemRwMount (Android) | bool | true | 仅限 Android。若系统分区处于可写状态,或出现类似 root 的 mount 状态,将被视为完整性违规。Standard 预设会放宽此项,以减少在 unlocked/rooted QA 设备上的误报。 |
| androidShaKeys | List<string> | — | 预期的 APK 签名证书 SHA-256 指纹列表。如果安装的 APK 没有用这些密钥之一签名,验证将失败(仅限 Android)。 |
| expectedBundleIds (iOS) | List<string> | — | 允许的 iOS Bundle ID 列表。留空则跳过 Bundle ID 检查。 |
| excludedAssemblies | List<string> | — | 要从哈希验证中排除的程序集名称列表(不带 .dll 扩展名)。用于在运行时发生改变的程序集(例如生成的代码)。 |
| checkIntegrityWithServer (Pro) | bool | false | 开启 Pro 的 nonce → attest 流程。本地 Build Integrity 通过后,SDK 会向 OZero 提交构建完整性证据。与 attestationVerificationMode=OZeroManaged、optional OZero TMP dialog package 和内置 Managed Verification UI 一起使用时,面向用户的验证流程不需要游戏代码。 |
| attestationVerificationMode (Pro) | enum | CustomerGameServer | 选择由谁对 OZA 令牌做最终判定。OZeroManaged 让 SDK 向 OZero 请求托管客户端会话判定,因此没有自有后端的团队也可以通过内置 UI 使用服务器 attestation。CustomerGameServer 适用于由客户后端通过 OZero API 验证 OZA 令牌的项目。 |
| attestationNetworkPolicy (Pro) | enum | BestEffort | 控制需要最新服务器重新验证但网络或服务器不可用时的处理。与 OZeroManaged 和内置 UI 一起使用时,RequireOnlineRevalidation 会通过 SDK 对话框自动显示需要联网、重试、timeout 和阻止状态。 |
| manifestSigningPublicKey | string | "" | 用于验证已签名 Build Integrity manifest 的 Base64 RSA-2048 公钥。请在 Window > OZero Security > Config & Dashboard 中通过 Generate Key Pair 生成。它不同于 OZeroLicenseConfig 中填写的客户门户服务器密钥。 |
| requireManifestSignature | bool | false* | Inspector 默认在生成密钥前关闭,方便开发。在发布 player build 中,manifest 签名验证会被强制开启,因此发布前必须生成密钥对。 |
| manifestSigningPrivateKeyPath | string | "" | 构建期间用于 manifest 签名的私钥 PEM 路径。留空时,OZero 使用 [ProjectRoot]/OZeroSigningKeys/manifest_private_key.pem。该路径仅用于 Editor,不会包含在 player build 中。 |
使用默认 UI 的 OZero 服务器 attestation
无代码流程:开启checkIntegrityWithServer,将 attestationVerificationMode 设为 OZeroManaged,先 import optional OZero TMP dialog package,并在 General Settings 中保持 Managed Verification UI 策略使用内置对话框。SDK 会获取 nonce、提交 attest 证据、向 OZero 请求托管会话判定,并通过默认对话框显示需要联网、重试、timeout、警告或阻止状态。如果要替换默认 UI、不使用 TextMeshPro,或添加自有 telemetry/session gate,请注册 RegisterUserManagedVerificationStateCallback。
💡 Tip: 用更强策略保护线上构建
Pro Strict Attestation 是可在客户门户的 Pro 策略中开启的强化模式。在此模式下,OZero 服务器不会立刻签发 OZA 令牌,而是先确认请求是否来自门户中已登记的有效构建。服务器还会检查提交的 nonce、manifest hash、platform、SDK version 和应用标识是否属于同一次请求流程。同时,debugger、platform、speedhack、injection 检查结果必须正常,并且 assembly/file/IL2CPP hash 至少有一项已验证。若条件不满足,OZA 令牌不会签发,因此发布新构建前,请先在客户门户登记该构建版本与 manifest hash。Events
| Event | Description |
|---|---|
| OnValidationPassed | 当本地完整性检查顺利完成而无违规时发生。此时 Pro 服务器证明 (attestation) 可能仍在进行中。 |
| OnAttestationPassed | 仅在实际颁发了 Pro OZA 证明 (attestation) 令牌后触发。请使用此事件或 AttestationToken.IsValid(nowMillis) 来校验,只有通过后才能进行游戏服务器登录、PvP、排名、货币结算等流程。 |
| OnValidationFailed | 当完整性检查检测到违规时触发。同时也会随着 ModulationType.BuildIntegrity 触发全局的 onHackDetected 事件。 |
生成 RSA 签名密钥(Build Integrity Manifest)
Build Integrity 可以为构建时生成的 manifest 添加 RSA 签名。该 manifest 包含运行时要验证的 assembly hash 与文件完整性信息。对于发布构建,如果 manifest 本身被替换,验证基准就不再可信,因此发布前请在 Window > OZero Security > Config & Dashboard 中点击 Generate Key Pair 生成 manifest 签名密钥对。
构建时,OZero 会使用私钥为 manifest 签名。私钥不会包含在 player build 中,构建中只包含存储在 OZeroSecurityConfig 里的公钥。运行时,SDK 会用公钥确认 manifest 签名。如果签名有效,SDK 会认为“构建时生成的 manifest 仍保持原样”,并以该 manifest 作为 assembly/file hash 检查的可信基准。如果签名缺失或不匹配,则 manifest 可能已被替换或编辑,因此 Build Integrity 会将其视作失败。之后的处理会按照 Response Settings 执行,例如记录日志、触发回调或关闭应用。
生成密钥对
- 在 Unity 编辑器中打开 Window > OZero Security > Config & Dashboard。
- 在检查器 (Inspector) 中展开 Build Integrity 选项卡。
- 勾选 Require Manifest Signature 复选框。
- 点击 Generate Key Pair 按钮。
- OZero 会生成用于 Build Integrity manifest signing 的密钥对。公钥存储在
OZeroSecurityConfig中,私钥 默认保存在以下路径:[ProjectRoot]/OZeroSigningKeys/manifest_private_key.pem - 将显示指示私钥位置的确认对话框。点击 OK 关闭它。
- 私钥文件存储在
Assets/文件夹 外部,以防止 Unity 将其打包进构建中。切勿将其移入Assets/中。 - 请将
OZeroSigningKeys/从版本控制中排除。使用 Git 时加入.gitignore,使用 SVN 时加入svn:ignore。将私钥提交到仓库是严重的安全风险。 - 请将私钥备份到安全位置。任何拥有此文件的人都可以为该项目创建有效的 Build Integrity manifest。
- 如果丢失私钥,已发布的旧构建仍会继续运行。但是下一次更新构建无法再用旧密钥签署新的 manifest。请生成新的密钥对,重新生成 integrity manifest,并通过 clean build 发布更新。公钥、fingerprint、manifest 和 signature 必须互相匹配,否则 Build Integrity 验证可能失败。
自定义私钥路径(CI/CD 和团队环境)
Manifest Signing Private Key Path 字段不是用来选择 Generate Key Pair 生成新密钥的位置。它用于告诉构建和验证工具从哪里读取 PEM 私钥。Generate Key Pair 会在项目默认路径生成密钥,并把该路径记录到 Config 中。对于 CI/CD 或团队构建,请先把同一个 PEM 还原到安全路径,再让此字段指向该路径。
- CI/CD 管道 — 将 PEM 保存为 CI Secret,并在 Unity 构建前还原。构建 runner 不需要长期把私钥保存在磁盘上。
- 团队环境 — 使用只有发布构建负责人或构建 runner 可访问的安全挂载路径或 Secret Manager。所有发布构建机器都必须使用同一个 PEM。
验证现有密钥对
如果要确认磁盘上的私钥是否与 OZeroSecurityConfig 中保存的公钥一致,请点击 Validate Key Pair。OZero 会从 PEM 重新推导公钥,并与 Config 中的公钥比较。如果不匹配,请还原对应的私钥,或生成新的密钥对,然后进行 clean build。
- 当私钥丢失或泄露时。
- Validate Key Pair 报告不匹配(密钥未同步)时。
- 作为既定安全策略的一部分,有意轮换密钥时。
重新生成后,已经发布的旧构建仍会使用自身内置的密钥和 manifest 继续运行。下一次更新构建必须让新的公钥、fingerprint、manifest 和 signature 作为同一套匹配数据使用。请重新生成 integrity manifest,并通过 clean build 发布。
双指纹密钥轮换
点击 Generate Key Pair 按钮后,公钥会保存在 OZeroSecurityConfig 中,同时识别该密钥的 fingerprint 也会记录在 Assets/OZeroSDK/Scripts/Security/BuildIntegrity/OZeroManifestTrustAnchor.cs 中。运行时 SDK 会先确认 asset 中的公钥是否匹配受信任的 fingerprint,然后才接受已签名的 manifest。
ExpectedPublicKeyFingerprintHex 是当前构建使用的密钥 fingerprint。PreviousPublicKeyFingerprintHex 是只在密钥轮换期间使用的辅助槽位。它不能替代 manifest 重新生成,也不是服务端密钥管理功能。OZeroSecurityConfig 中的公钥、fingerprint、manifest 和 signature 仍然必须基于同一把密钥组成一套匹配数据。
安全的密钥轮换步骤
- 备份当前指纹。 打开
Assets/OZeroSDK/Scripts/Security/BuildIntegrity/OZeroManifestTrustAnchor.cs,将ExpectedPublicKeyFingerprintHex的值复制到临时记录中。 - 注册到旧密钥槽。 在同一文件中,将第 1 步复制的值粘贴到
PreviousPublicKeyFingerprintHex中并保存。 - 生成新的密钥对。 打开 Window > OZero Security > Config & Dashboard,在 Build Integrity 区域点击 Generate Key Pair。新的公钥和 Expected fingerprint 会自动更新。请保留第 2 步写入的 Previous 值。
- 重新生成 integrity manifest,并制作 clean build。 新的 manifest 和 signature 必须基于新的私钥生成。
- 保留更新过渡期。 已发布的旧构建会继续使用自身内置的密钥和 manifest 运行。Previous 槽位是在新构建发布过程中减少回滚或构建产物混用问题的安全措施。对于在线运营游戏,通常可以按 1 到 4 周作为参考。
- 清空旧密钥槽。 当旧版本的使用量足够低后,将代码改回
PreviousPublicKeyFingerprintHex = "",再次发布后轮换即告完成。
已经发布的旧构建不需要知道新密钥也能继续运行,因为旧构建中已经包含当时的公钥、fingerprint、manifest 和 signature。Previous 槽位是在新的发布线中进行受控密钥轮换时使用的安全措施。只在需要过渡时保留,之后的版本中建议清空。
故障排除
遇到问题时,先按简单顺序检查:没有密钥就执行 Generate Key Pair,已有密钥就执行 Validate Key Pair,然后重新生成 integrity manifest 并制作 clean build。大多数问题来自以下四项之一不同步:私钥 PEM、OZeroSecurityConfig 中的公钥、OZeroManifestTrustAnchor 中的 fingerprint、生成出的 manifest/signature 文件。
ExpectedPublicKeyFingerprintHex 可能为空。发布构建在缺少 fingerprint 时会为了安全直接失败。解决:执行 Generate Key Pair,等待 Unity 重新编译完成后制作 clean build。如果该值已经存在,请执行 Validate Key Pair,并清空构建输出文件夹后重新构建。
当 OZeroSecurityConfig.Integrity.ManifestSigningPublicKey 为空时触发。解决:打开 Window > OZero Security > Config & Dashboard,在 Build Integrity 区域点击 Generate Key Pair,然后重新构建。
当 OZeroSecurityConfig 的公钥与 OZeroManifestTrustAnchor 中记录的 fingerprint 不一致时发生。只还原了密钥对的一部分,或手动修改了生成文件时经常出现。解决:执行 Validate Key Pair。如果报告不一致,请还原匹配的 PEM,或生成新的密钥对,然后重新生成 integrity manifest 并制作 clean build。如果只有已发布构建出现此消息,也请确认 APK 或可执行文件是否被重新打包。
轮换后的密钥数据中可能有一部分仍是旧值。请确认公钥、fingerprint、manifest、signature 是否都基于同一把密钥。解决:只有在需要过渡期时才把旧 fingerprint 保留在 PreviousPublicKeyFingerprintHex,然后依次执行 Validate Key Pair、重新生成 integrity manifest、清理构建输出文件夹、重新进行 clean build。
CI 不会自动拥有本地 PC 上的 OZeroSigningKeys/manifest_private_key.pem 文件。解决:将 PEM 保存在 CI Secret 中,并在 Unity 构建前还原到默认路径,或还原到 Manifest Signing Private Key Path 指定的路径。所有发布 runner 都必须使用同一个 PEM,并建议在发布环境中执行一次 Validate Key Pair 进行确认。
安装来源设置
限制游戏仅在通过已批准的商店或途径安装时才能运行。这有助于防止侧载或重新打包的 APK。
| 字段 | Type | 默认值 | 说明 |
|---|---|---|---|
| Activate Install Source | bool | true | 激活 Install Source 模块。 |
| allowGooglePlayStore | bool | true | 允许通过 Google Play 商店安装。 |
| allowSamsungGalaxyStore | bool | false | 允许通过 Samsung Galaxy Store 安装。 |
| allowAmazonAppstore | bool | false | 允许通过 Amazon Appstore 安装。 |
| allowHuaweiAppGallery | bool | false | 允许通过华为 AppGallery 安装。 |
| allowOneStore | bool | false | 允许通过 ONE Store (韩国) 安装。 |
| allowXiaomiGetApps | bool | false | 允许通过小米 GetApps 安装。 |
| allowOppoAppMarket | bool | false | 允许通过 OPPO App Market 安装。 |
| allowVivoAppStore | bool | false | 允许通过 Vivo App Store 安装。 |
| allowADB | bool | false | 允许通过 ADB (Android Debug Bridge) 安装。仅供内部测试时启用。 |
| allowDetectionFailed | bool | false | 当 Android installer package 查询本身失败时仍允许启动。这不同于 ADB 返回空 installer 值,表示 JNI 或平台 API 没有完成查询。发布构建中通常应保持关闭。 |
| allowUnknownSources | bool | false | 允许不在内置商店列表、也不在 customAuthorizedPackages 中的 installer package。仅在需要区域商店分发并完成真机测试后使用。 |
| enableServerSync (Pro) | bool | false | 仅限 Pro。本地检测后将 installer package 发送到 /v1/install-source/verify,由服务器应用托管 allowlist 并记录审计日志。如果服务器尚未配置规则,会按 opt-in 行为允许该来源。 |
| customAuthorizedPackages | List<string> | — | 额外允许的安装程序包名称(例:com.yourcompany.launcher)。 |
| reportViolationToCallback | bool | true | 若检测到未被许可的安装来源,则向服务器发送报告。 |
| logRawInstallerPackage | bool | true | 记录原始 installer package name。QA 阶段查找自定义商店包名时很有用,但发布前请确认日志中暴露这些信息是否合适。 |
Steam 防盗版配置
对在 Steam 发售的 PC 端查明 Steam 调用通道、App ID、权限情况、发布档设定。Standard 是局端核实,要想通过 Steam 取得牢固权证唯有依赖 Pro 级别之内的 Steam Attestation 来供给。
| 字段 | Type | 默认值 | 说明 |
|---|---|---|---|
| Activate Steam Anti-Piracy | checkbox | Off | 启动 Steam 防盗版侦察的勾选位。有鉴于 Steam App ID 及铺货形态随项目多变,初始定作 Off。理应首先处 observe/QA 情境查其状,尔后针对发行端上紧限制。 |
| expectedSteamAppId | int | 0 | 项目的 Steam App ID 标识。设使之前倚仗除错用 AppID 480 则应在交差前必定修正为正当的 App ID。 |
| requireSteamLaunch | bool | true | 断明是否借由 Steam 而非执行文件独立启航。需厘清研发内部自测操作与外发政策。 |
| requireSteamApiInit | bool | true | 探察 Steam API 奠定起手成效。研发时期直接试玩因 Steamworks 配定生变有败局几率,决断还得以确实的 Steam 散播流为基准。 |
| requireSubscribedCurrentApp | bool | true | 核准当前挂号的 Steam 主能否掌握此件及赋予进入权限。此是机内考核,至于服端盘查必得用 Pro 解决。 |
| requiredDlcAppIds | List<int> | — | 需要检查所有权的 Steam DLC App ID 列表。如果有付费 DLC 或必需 DLC,请添加对应的 App ID。 |
| blockSteamAppIdTxtInRelease | bool | true | steam_appid.txt 是仅用于不通过 Steam 进行本地启动测试的开发文件。如果它留在发布构建中,可能允许绕过 Steam 启动,因此 OZero 会将其视为违规。发布前请从项目根目录和构建输出中移除它。 |
| validateSteamApiDllHash | bool | false | 通过将 Steam API DLL 的哈希值与已知的 SHA-256 值进行比较来进行验证。 |
| allowFamilySharing / allowFreeWeekend / allowTimedTrial | bool | true | 控制是否允许 Steam 家庭共享、免费周末以及限时试用权限状态。 |
| requireSteamBuildIdNonZero | bool | false | 如果 Steam 返回的 Build ID 为 0,或无法取得 Build ID,则按违规处理。请先确认 Steam depot/build 发布流程能正确取得 Build ID 后再开启。 |
| expectedSteamApiDllSha256Hashes | List<string> | — | 启用 validateSteamApiDllHash 后用于比对的 Steam API 文件允许 SHA-256 列表。请按实际发布的 Steamworks SDK 版本、平台和发布分支分别登记哈希。 |
| requireValveSignedSteamApi | bool | true | 要求 Steam API 可再分发文件匹配 Valve 的平台身份。Windows 使用 Authenticode 签名者验证,macOS 使用 Valve Team ID 验证,其他平台应使用 SHA-256 验证作为 fallback identity check。 |
| detectKnownSteamEmulators | bool | true | 在可执行文件附近查找 Goldberg、CreamAPI、SmartSteamEmu、ColdClientLoader 等 Steam 模拟器常留下的文件或文件夹。发现这些痕迹时,会视为可能不是正常 Steam 启动。 |
| requireSteamEnvironmentConsistency | bool | true | 在发布构建中,检查 Steam 是否返回有效的个人 SteamID 和非 0 的 Build ID。Steam 客户端/库路径的预检查只记录警告日志,SteamID 或 Build ID 不正确时才作为违规处理。 |
| observeSteamAuthTicketHeuristic | bool | true | 只检查本地 Steam 认证票据的大小和基本格式,作为参考信号。OZero 不会记录票据内容,也不会仅凭这一项检查阻止游戏运行。 |
| detectionAction | OZeroSteamDetectionAction | Callback | 决定 Steam 验证失败时 SDK 在本地处理到什么程度。Observe 只记录日志和诊断,Callback 会调用游戏回调,Block 会把该事件作为阻止策略处理。应用是否真正退出仍由 Global Threat Response 设置决定。 |
| reportViolationToCallback | bool | true | 在 Callback 模式下 Steam 验证失败时,是否把事件发送到普通 OZero 安全回调。只有在 QA 构建中想保留日志但不触发游戏回调时才关闭。 |
| forceSteamAntiPiracyObserveOnly | bool | false | 临时将 Steam 验证失败只记录为日志和诊断信息,不触发回调或阻止行为。仅用于测试特殊 Steam 启动环境,发布前请重新关闭。 |
| checkSteamDrmWithServer (Pro) | bool | false | Pro 专用。启用基于服务器证据的 Steam DRM 验证。需要用服务器证据确认 Steam 所有权,而不是只依赖本地 Steamworks 状态时使用。 |
| steamDrmVerificationMode (Pro) | OZeroSteamDrmVerificationMode | OZero Managed | 选择 Steam DRM 验证由谁负责。OZeroManaged 由 SDK 直接调用 OZero /v1/steam/attest、管理 activation cache,并在 import optional OZero TMP dialog package 后将面向用户的状态交给默认 Managed Verification UI。CustomerGameServer 适用于客户服务器接收 OZA token 和 Steam ticket 后调用 OZero server API 的结构。 |
| steamDrmNetworkPolicy (Pro) | OZeroSteamDrmNetworkPolicy | Best Effort | 当需要最新服务器再验证,但网络或服务器不可用时的处理策略。与 OZeroManaged 和已 import 的内置 UI package 一起使用时,RequireOnlineRevalidation 会在无需游戏代码的情况下显示需要联网和重试流程。 |
OZeroManaged 时,受保护的首次启动会通过 OZero 服务器在线取得有效 activation token。之后,只要 cache token 有效且离线宽限期仍然存在,就可以允许离线运行。如果令牌已过期、宽限期已结束,或需要重新验证时用户处于离线状态,import OZeroSecurity_BuiltInDialog_TMP.unitypackage 后的默认 Managed Verification UI 会自动处理需要联网提示、Retry 按钮、retry timeout 和阻止状态。
OZero 服务器托管的 Steam DRM
启用 checkSteamDrmWithServer,将 steamDrmVerificationMode 设置为 OZeroManaged,import OZeroSecurity_BuiltInDialog_TMP.unitypackage,并在 General Settings 中保持 Managed Verification UI 策略使用内置对话框。SDK 会把 Steam auth ticket 发送到 OZero,管理 activation token cache,并通过默认 UI 显示需要联网、重试、timeout、警告或阻止状态。保留默认 UI 时,不需要编写 callback 或 retry 代码。
客户游戏服务器 Steam 验证
启用 checkSteamDrmWithServer,并将 steamDrmVerificationMode 设置为 CustomerGameServer。客户端会把 OZA token 和 Steam Auth Ticket 发送到你的服务器,由服务器调用 /v1/validate 和 /v1/steam/attest。Steam Web API Key 和 OZero Server API Key 只应保存在服务器端。玩家恢复网络后,应重试你自己的登录或会话请求,而不是调用 SDK 托管的重试。
设备绑定设置
保存首次信任设备的识别信息,并在后续启动时与当前设备进行比较。可用于发现同一账号或受保护存档是否被移动到其他设备。
运营中的使用方式
在 Pro 中,Device Binding 可以作为运营工具使用。服务器会记录每个许可证对应的设备指纹,因此可以阻断反复出现风险信号的设备,减少简单的许可证共享,并在不禁用整个许可证的情况下支持合法的设备更换。
阻断反复出现风险的设备
请在查看 SpeedHack、Injection、Install Source、Build Integrity 等安全事件后使用。被阻断的设备在下一次激活、设备注册、设备验证或策略检查时会收到 DEVICE_BLOCKED。
支持合法设备更换
当真实玩家更换手机、重装 OS、更换硬件,或因正常环境变化导致设备信息变化时,使用 Reset Token。令牌仅 5 分钟有效,只能使用一次,并绑定到准确的许可证和设备。
限制可注册设备数量
在 Pro 许可证中,maxDevices 限制一个许可证可注册的活动设备指纹数量。被阻断的设备不会自动变成安全的空槽位;重置或删除应在客服确认后处理。
Reset Token 支持流程
- 先通过正常客服流程确认玩家账号,以及为什么需要重置设备。
- 进入 Customer Portal → Device Binding,选择目标设备并发放 Reset Token。
- 令牌只能通过已认证的支持渠道发送。不要留在公开聊天、截图或长期保存的工单中。
- 游戏或支持用 UI 应将令牌传递给
OZeroDeviceBindingDetector.Instance?.ClearStoredFingerprint(token.Trim())。 - SDK 接受令牌后,重启应用或再次运行受保护的启动流程,当前设备指纹就会重新注册。
| 字段 | Type | 默认值 | 说明 |
|---|---|---|---|
| Activate Device Binding | bool | true | 开启 Device Binding 检查。开启后,SDK 会在启动时创建本地设备指纹,并与已保存的值比较。 |
| hardwareChangeTolerance | int (0–3) | 1 | 决定设备指纹中的多少项发生变化时仍视为同一设备。0 表示严格,3 对较大的硬件/OS 变化更宽容。默认值 1 用于吸收较小的 OS 或固件变化。 |
| storageKey | string | "ozero_dfp" | SDK 用来保存加密本地设备指纹的 PlayerPrefs 键。只有当游戏已经使用相同键时才需要修改。 |
| enableServerSync (Pro) | bool | false | 仅限 Pro。首次使用时通过 /v1/device/register 注册设备指纹,后续启动时通过 /v1/device/verify 与服务器记录比较。网络或服务器故障本身不会阻止游戏,但服务器明确返回 DEVICE_BLOCKED、FINGERPRINT_MISMATCH、DEVICE_LIMIT_REACHED 等拒绝时,会作为 Device Binding 违规处理。 |
| maxDevices (Pro) | int | 0 | 显示一个许可证可注册的设备数量参考值。0 表示不限制。实际上限由服务器许可证记录或 Customer Portal 策略执行,因此只修改客户端中的这个值不会提高正式运营上限。 |
Speed & Time Hack 设置
对比 Unity 时间、原生基准时钟、后台计时器以及 Web/Pro 服务器时间,用来检测加速或减速游戏的操控,以及设备时钟变化。
| 字段 | Type | 默认值 | 说明 |
|---|---|---|---|
| Activate Speed & Time Hack | bool | true | 开启 Speed & Time Hack 检查。开启后,SDK 会在运行时监控时间流速比例和设备时钟变化。 |
| autoStart | bool | true | 游戏执行时自动开始检测。如果禁用,需通过 OZeroSpeedHackDetector.StartDetection() 手动启动。 |
| checkInterval | float | 1.0 s | Unity 时间与基准计时器的比较周期。数值越低反应越快,但 CPU 成本和误报可能增加。 |
| requiredDetections | int | 3 | 连续出现多少次异常信号后才按违规处理。若不稳定设备上误报较多,可适当调高。 |
| ratioTolerance | float | 0.15 | Unity 时间与原生基准时间之间允许的正常差异比例。请先使用默认值,只在真实游玩中出现误报时再调整。 |
| maxAllowedRatio | float | 4.0 | 时间流速比例超过此值时,会被视为强烈的速度作弊信号。普通游戏建议保留默认值。 |
| detectSlowHack | bool | false | 不仅检测加速,也检测减速操控。默认值为 false。请先测试游戏中的慢动作、过场和有意速度变化,再决定是否开启。 |
| enableTimeScaleDetection | bool | true | 检查 Time.timeScale 是否被未授权代码或内存编辑修改。游戏内有意改变速度时,请使用 OZeroTime.timeScale。 |
| hackDetectMultiplier | float | 1.3 | 当时间流速远高于 maxAllowedRatio 时,将其作为更强可疑信号的判断标准。一般建议保留默认值。 |
| enableThreadTimerCheck | bool | true | 使用后台线程计时器作为额外的基准时钟,有助于捕获只操控主线程的工具。 |
| useWebTimeValidation | bool | true | 使用 HTTPS 时间响应的 Date 头或 Pro 服务器时间,交叉检查设备时钟篡改。默认值为 true。 |
| webSyncInterval | float | 15 s | 与 Web/服务器时间重新比较的周期。设置过短会增加网络请求。 |
| webRatioTolerance | float | 0.15 | 设备时间与 Web/服务器时间之间允许的正常差异比例。请结合地区网络延迟在真机上验证。 |
| timeOffsetTolerance | float | 60 s | 设备时钟与 Web/服务器时间的绝对差值超过此秒数时,会被视为时间篡改。 |
| focusIgnoreTime | float | 4 s | 应用重新获得焦点后忽略检测的秒数。可防止操作系统暂停应用时产生的误报。 |
| loadingGraceTime | float | 6 s | 应用启动或场景加载后,在这段时间内忽略检测结果。用于减少重负载加载阶段的误报。 |
| lagSpikeIgnore | float | 0.5 s | 帧时间超过此值的样本会被视为延迟并丢弃,避免把真实卡顿误认为时间篡改。 |
| buildFailIfTimeScaleTampered | bool | true | 如果受保护代码疑似在已批准策略之外直接修改 Time.timeScale,则让构建/验证阶段失败。 |
| timeScaleTamperExemptions | List<string> | — | 允许直接修改 Time.timeScale 的脚本/方法名称模式。只登记已经确认过的暂停、子弹时间、过场等代码,并尽量保持列表最小。 |
| webTimeUrls | string[] | — | 用于 Web/服务器时间检查的 HTTPS 地址列表。建议加入两个以上由您运营或信任的地址,避免一个地址被阻断后检查就停止。 |
| minSuccessfulEndpoints | int | 2 | 一次检查中,至少有多少个地址返回有效 Date 标头后才信任该时间。不要设置得比已配置地址数量更大。 |
| maxConsecutiveFailures | int | 6 | Web/服务器时间检查连续失败达到此次数后,执行 onWebTimeUnavailable 策略。它用于给临时网络故障留出缓冲,不会马上当作安全违规。 |
| onWebTimeUnavailable | WebTimeUnavailablePolicy | WarnOnly | 多次无法确认 Web/服务器时间时的处理策略。WarnOnly 只记录警告并继续运行。Strict 会把反复失败视为可疑情况并触发 SpeedHack 回调。Silent 不记录日志,除特殊测试外不要使用。 |
| detectTimeHack | bool | true | 检查设备时钟变化以及与 Web/服务器时间的差异。它独立于速度比例检查,用于捕获系统时钟篡改。 |
| webSyncJitterPercent | float | 20% | 让 Web/服务器时间检查不要在所有设备上同时发生,而是在设定周期附近分散执行。例如 20% 表示 15 秒周期大约会在 12 到 18 秒之间执行。 |
| sustainedLagThreshold | float | 0.15 | 如果最近平均帧时间慢于此值,SDK 会先按真实卡顿处理,不会立刻把这段时间的时间检查结果确认为违规。默认 0.15 秒约等于平均 6-7 FPS。 |
| sustainedLagGraceDuration | float | 3 s | 判断为卡顿后,暂时推迟严格时间判定的时长。卡顿持续时这个推迟时间会继续延长,运行时最多限制在 10 秒内。 |
| overloadStrictMultiplier | int | 3 | onWebTimeUnavailable 为 Strict 时,如果失败看起来是普通 timeout,而不是明确的服务器拒绝,就用这个倍数决定多等几次。默认 3 表示最多等到 maxConsecutiveFailures 的 3 倍后再触发回调。 |
| enableRemoteSpeedHackConfig (Pro) | bool | false | 通过 Pro 服务器策略在无需更新应用的情况下调整部分 Speed & Time Hack 基准值。若服务器不可达,会继续使用本地 Inspector 值,因此保护不会关闭。 |
| remoteSpeedHackConfigInterval | float | 300 s | 从 Pro 服务器重新获取 Speed & Time Hack 远程设置的周期。0 表示只在应用启动时获取一次,正数表示定期检查新策略。 |
| remoteSpeedHackConfigJitterPercent | float | 20% | 让多个设备不要同时请求 Pro 远程设置,而是把设置刷新时间稍微分散开。 |
| enableSignedServerTime (Pro) | bool | false | Pro 激活可用时,优先使用签名的 /v1/time 响应作为可信时间源。请在服务器密钥和时间端点配置并测试完成后再开启。 |
使用 OZeroTime.timeScale,而不是 Time.timeScale
启用 Speed & Time Hack 保护模块时,项目代码不得直接修改 Unity 内置的 Time.timeScale。如果绕过 OZero 直接修改,OZero detector 可能会将其视为外部作弊工具造成的可疑运行时时间篡改,并执行已配置的响应策略。
OZeroSecurityConfig.TimeScaleTamperExemptions 是用于登记仍需直接访问 Time.timeScale 的脚本的例外列表。请尽量保持该列表最小化。
仅在现有结构无法避免直接访问 Time.timeScale 时,将例外限制分配给已验证的第三方插件或 legacy adapter script。
除此之外,项目中的所有游戏逻辑都应无例外地优先使用 OZeroTime.timeScale。暂停、慢动作、过场速度控制、倍速播放等游戏内有意变速场景,都必须使用 OZeroTime.timeScale。
using OZeroSDK.Security;
// Correct: the detector knows this is an intentional game-side change.
OZeroTime.timeScale = 0.5f;
// Avoid: this bypasses OZero's expected time-scale tracking.
Time.timeScale = 0.5f;
OZeroTime.timeScale。
防止长时间加载时 Watchdog 意外终止应用
OZero 的原生 Watchdog 会通过定期 heartbeat 确认 Unity 主线程是否仍在正常运行。在发布构建中,非 Android 平台大约使用 6 秒 heartbeat deadline,Android 在启动宽限后大约使用 10 秒 deadline。如果在该时间内没有收到 heartbeat,应用可能会被判定为卡死,并按配置的响应策略终止。
大型场景加载、同步资源解压、shader warmup 或 Addressables 准备等正当工作,也可能因为主线程被有意阻塞数秒而触发这种情况。
请使用 OZeroWatchdog.BeginLoadingGrace(maxGraceMs) 包住这类可信加载边界。加载开始时 grace 开始,scope 被 dispose 或调用 End() 时会立即结束,因此不需要提前知道准确的加载时长。
using OZeroSDK.Security;
using UnityEngine.SceneManagement;
public void LoadLargeScene()
{
using (OZeroWatchdog.BeginLoadingGrace(60000))
{
SceneManager.LoadScene("Battle", LoadSceneMode.Single);
// Grace ends as soon as the using scope exits.
}
}
public void WarmUpLargeAssets()
{
OZeroWatchdog.RunWithLoadingGrace(() =>
{
BuildLargeRuntimeCache();
}, 60000);
}
这个功能只是一个临时宽限期,用于避免应用在正常的长时间加载过程中被系统强制结束。它不能用于绕过安全检查,也不能用于让应用无限期保持运行。
如果某个较重的加载任务可能超过允许时间,请把任务拆成更小的步骤处理,或使用 Unity Coroutine / async 加载,避免画面和主线程长时间卡住。
物理黑客检测器
OZeroSecurityConfig.PhysicsHack 只控制全局开关和 Pro telemetry。速度限制、距离容差、穿墙检查和加速度检查都在各自的 OZeroPhysicsHackDetector 组件上配置。请把组件添加到玩家预制体,根据角色或载具的真实移动规则调整数值,并在生成后调用 Initialize(playerId)。| 字段 | Type | 默认值 | 说明 |
|---|---|---|---|
| physicsHack.useGlobalPhysicsHack | bool | true | 所有 Physics Hack 组件的全局开关。只有在确实想停止全部物理移动检查时才关闭。 |
| physicsHack.enableServerTelemetry (Pro) | bool | false | 仅限 Pro,默认关闭。只有项目明确同意后,才会发送一般安全事件与详细 PhysicsHack telemetry。关闭后将停止发送新的 telemetry;服务器策略可以关闭传输,但不能在没有本地同意的情况下启用传输。 |
| physicsHack.telemetryThrottlePerMinute (Pro) | int | 30 | 限制此客户端每分钟可发送的 PhysicsHack telemetry 数量。避免阈值设置错误时向服务器发送过多事件。 |
| 字段 | Type | 默认值 | 说明 |
|---|---|---|---|
| maxAllowedSpeed | float | 15 u/s | 此对象正常情况下可达到的最大移动速度(Unity units/second)。请设置为该角色或载具在游戏中合法可达到的最快速度。 |
| distanceTolerance | float | 2.0 u | 在根据 maxAllowedSpeed 计算出的可移动距离之外,额外允许的距离。用于吸收网络位置校正、物理计算误差和控制器的轻微抖动。 |
| obstacleLayer | LayerMask | — | 作为墙体或实体障碍物处理的 Layer。OZero 会检查从上一个安全位置到当前位置的路径;如果路径穿过这些 Layer,就可以视为穿墙。 |
| checkInterval | float | 0.05 s | 此组件检查位置、穿墙和加速度异常的周期。数值越小反应越快,但 CPU 使用量可能增加。 |
| maxDeltaTimeCap | float | 0.1 s | 距离计算中使用的最大帧时间。避免单个长帧让允许移动距离变得过大。 |
| violationThreshold | int | 2 | 触发回调前需要连续出现多少次异常检查。一次正常检查会降低计数,因此单次轻微物理抖动不会立刻变成违规。 |
| castRadius | float | 0 | 穿墙检查使用的半径。0 会尝试从 CharacterController 或 CapsuleCollider 自动推算;如果找不到,则退回为线段检查。 |
| enableAccelerationCheck | bool | true | 检查 Rigidbody 速度是否突然大幅变化。对于 kinematic 对象、服务器权威移动,或通过自定义逻辑正常改变速度的对象,请考虑关闭此项。 |
| maxAllowedAcceleration | float | 60 | 两次检查之间允许的最大速度变化量。如果游戏中有冲刺、击退、跳板、载具等正常的瞬间加速,请按这些移动调整数值。 |
| enableLog | bool | true | 调试和调参时显示此组件的日志。不过 obstacle layer 为空等重要配置问题,即使关闭此项也会警告。 |
注入检测器设置
检测异常的原生模块加载、可执行 private memory、inline hook、remote-thread 类型注入、高风险外部进程 handle,以及 WebGL 运行时篡改信号。
只有当正常 overlay、录制工具、合作伙伴 DLL,或随游戏一起分发的模块被 Injection 检测命中时,才应从 Failure Diagnostics 或 Pro telemetry 确认模块路径、文件 hash、signer fingerprint 后加入 whitelist。不要用 whitelist 大范围允许客户 PC 上随机安装的程序,否则会削弱保护范围。
| 字段 | Type | 默认值 | 说明 |
|---|---|---|---|
| Activate Injection & Hooking | checkbox | On | Config Dashboard 中 Injection & Hooking 的开关项,内部对应 useInjection。在玩家构建中会执行原生 Injection 检查;开发构建会使用更偏诊断的宽松流程;编辑器中通常不检查。 |
| injectionWhitelistEntries | OZeroInjectionWhitelistEntry[] | empty | 所有 tier 都可以使用的本地可信模块列表。每个条目通过模块文件的 SHA-256 hash 和可选 signer fingerprint,允许一个特定文件。只添加随游戏一起分发,或通过 QA、诊断文件、Pro telemetry、OZero 支持团队确认过的模块。 |
| enableServerWhitelist (Pro) | bool | false | 仅限 Pro。从服务器下载托管的可信模块列表,并与 SDK 基础列表、本地列表合并。即使无法连接服务器,保护也不会关闭;检测器会继续使用最后已应用的列表和本地规则。 |
| serverWhitelistRefreshInterval | float | 0 s | 重新获取 Pro 服务器 whitelist 的周期。0 表示只在应用启动时获取一次。可信模块列表通常不会频繁变化,因此多数项目使用 0 即可。 |
| requireSignerForNativeWhitelist | bool | false | 要求 Windows PE 和 Apple Mach-O whitelist 条目除了文件 hash 外,还必须包含 signer fingerprint。这样更强,但只有在能收集准确 signer 值时才建议开启。Android/Linux 共享库通常按 hash-only 处理。 |
| enableRemoteInjectionConfig (Pro) | bool | true | 仅限 Pro。允许通过门户策略在不发布新版本的情况下调整部分 Injection 设置,例如扫描周期、signer 要求和单个检查开关。 |
| remoteInjectionConfigInterval | float | 300 s | 重新检查 Pro 远程 Injection 设置的周期。0 表示只在应用启动时获取一次。 |
| enableWindowsModuleIdentityScan | bool | true | 检查新加载的原生模块是否可信。字段名保留了 Windows,但支持的原生目标会使用同一设置;Windows/macOS 可使用 hash 加 signer,Android/iOS/Linux 通常使用 hash-only。 |
| scanIntervalSeconds | float | 1 s | 执行 Injection 检查的基础周期。数值越低检测越快,但运行时开销可能增加。实际应用值会被限制在安全范围内。 |
| windowsModuleIdentityScanIntervalSeconds | float | 5 s | 较重的原生模块身份检查的独立周期。如果模块枚举、hash 计算或 signer 检查在目标设备上成本较高,可以调大此值。 |
| scanJitterPercent | float | 20% | 让周期性 Injection 检查不要总是在完全相同的秒数执行,而是在配置周期附近稍微分散执行时间。 |
| enableExecutablePrivateMemoryScan | bool | true | 检测没有作为正常 DLL/.so/.dylib 加载、但具有执行权限的 private memory 区域。它有助于发现 shellcode 类型注入,但如果可信运行时会主动创建可执行内存,需要先做 QA 确认。 |
| enableInlineHookScan | bool | false | 检查重要原生 API 入口点是否出现被 patch 的痕迹。这是较强信号,但 overlay 或安全软件也可能 hook API,因此建议只在严格构建中经过兼容性测试后开启。 |
| enableThreadStartAddressScan | bool | true | 检查原生 thread 是否从可信模块之外的可疑内存开始执行。可帮助在支持的平台上发现 remote-thread 类型注入。 |
| enableExternalProcessHandleScan | bool | true | 检查调试器、内存编辑器等外部进程是否持有游戏进程的高风险 handle。主要用于 Windows。 |
| detectionConfidenceThreshold | int | 70 | 将强 Injection 信号立即视为违规所需的最低可信分数。分数较低的信号仍可能通过连续检测或累计判断逻辑处理。 |
| enableWebRuntimeTamperScan (WebGL) | bool | true | WebGL 专用的浏览器运行时检查。WebGL 无法使用 Native C++ 模块扫描,因此这些检查应理解为浏览器侧篡改信号的辅助客户端证据。 |
| webRuntimeScanIntervalSeconds (WebGL) | float | 1.5 s | WebGL 运行时检查的基础周期。为了避免过于频繁地检查浏览器,实际应用值会限制在 0.5 到 30 秒。 |
| webRuntimeScanJitterPercent (WebGL) | float | 20% | 让 WebGL 运行时检查不要总是在同一时刻执行,而是在配置周期附近稍微分散执行时间。 |
| WebGL probes | bools | true | WebGL 构建中用于分别开启/关闭 DevTools、clock hook、network hook、WebAssembly hook、storage hook、crypto API hook 检查的开关。 |
PlayerPrefs 加密
对于需要保护的新 key,请使用 OZeroSafePlayerPrefs,而不是 Unity 的 PlayerPrefs。常见的 Get/Set、HasKey、Delete、Save 方法名保持熟悉的形式,但加密数据与已有的普通 PlayerPrefs 数据不兼容。
using OZeroSDK.Security;
// Save a value
OZeroSafePlayerPrefs.SetInt("score", 4200);
OZeroSafePlayerPrefs.SetFloat("volume", 0.8f);
OZeroSafePlayerPrefs.SetString("username", "Hero");
// Read a value
int score = OZeroSafePlayerPrefs.GetInt("score", 0);
float volume = OZeroSafePlayerPrefs.GetFloat("volume", 1.0f);
string username = OZeroSafePlayerPrefs.GetString("username", "");
存档文件加密
要加密存档文件,请使用 OZeroSV_File 代替 File.ReadAllText / File.WriteAllText。文件在写入时自动加密,在读取时自动解密。加载时还会检查是否被篡改。
using OZeroSDK.Security;
string path = Application.persistentDataPath + "/save.json";
// Write encrypted file
OZeroSV_File.WriteAllText(path, jsonString);
// Read and decrypt file
string json = OZeroSV_File.ReadAllText(path);
developerSecret。developerSecret 是生成 OZeroSafePlayerPrefs 和 OZeroSV_File 数据加密密钥的基准值。若发布后更改此值,新版本将无法解密旧版本保存的受保护数据。
4
游戏内变量保护(Secure Types)
Secure Types 会将常规 C# 变量类型替换为加密类型。因为值存储在 Native C++ 堆中,诸如 Cheat Engine 的工具即使扫描内存也无法找到。只需更改类型名称,其余代码即可照常运行。
示例代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using OZeroSDK.Security;
public class PlayerStats : MonoBehaviour
{
[SerializeField] OZeroSV_Int Gold = 5000;
[SerializeField] OZeroSV_Float Speed = 3.5f;
[SerializeField] OZeroSV_Int HP = 1000;
}
支持类型
| 现有 | 应用 OZero 后 |
|---|---|
| int | OZeroSV_Int |
| long | OZeroSV_Int64 |
| uint | OZeroSV_UInt |
| ulong | OZeroSV_UInt64 |
| short | OZeroSV_Short |
| ushort | OZeroSV_UShort |
| byte | OZeroSV_Byte |
| float | OZeroSV_Float |
| double | OZeroSV_Double |
| decimal | OZeroSV_Decimal |
| bool | OZeroSV_Bool |
| string | OZeroSV_String |
| Vector2 | OZeroSV_Vector2 |
| Vector3 | OZeroSV_Vector3 |
| byte[] | OZeroSV_Buffer |
7
安全事件处理(可选)
默认情况下,当 OZero 检测到威胁时,它会按照配置的响应策略来终止应用或只留存日志。如果您需要直接显示警告屏幕、发送您自己的服务器日志或在退出之前执行保存操作,则可以注册回调。该回调将通过 OZeroSecurityEvent 提供修改类型、中止代码、消息键、详细消息以及是否即将退出等信息。
using UnityEngine;
using OZeroSDK.Security;
public class SecurityHandler : MonoBehaviour
{
void OnEnable()
{
// RegisterUserCallback runs on the user chain only.
// The built-in default handler runs independently and cannot be silenced.
OZeroSecurityManager.Instance.RegisterUserCallback(OnThreatDetected);
}
void OnDisable()
{
OZeroSecurityManager.Instance.UnregisterUserCallback(OnThreatDetected);
}
void OnThreatDetected(OZeroSecurityEvent evt)
{
// evt contains Type, AbortCode, AbortCodeHex, MessageKey, Message, and WillAbort.
Debug.LogWarning(
$"OZero threat: {evt.Type} {evt.AbortCodeHex} {evt.Message}");
switch (evt.Type)
{
case ModulationType.SpeedHack:
// e.g. kick the player, show warning, report to server
break;
case ModulationType.BuildIntegrity:
// evt.WillAbort is usually true for fatal build integrity violations.
break;
case ModulationType.Injection:
break;
}
if (evt.WillAbort)
{
// Last chance to flush your own analytics or save state.
}
}
}
OZeroSecurityEvent、ModulationType 以及 OZeroAbortCode 的完整列表已记录在 API 参考中。
evt.WillAbort 为 true,按照当前响应策略,应用将在回调之后退出。在这种情况下,请只执行简短的自定义 analytics flush 或是保存操作。
当检测到威胁时,OZero 会首先应用 SDK 基础的响应策略,并将同样的安全事件派发给项目中注册的回调和 Inspector 事件。如果希望直接显示警告画面或留下服务器日志,请注册回调。关于应用是否退出,可以通过 Config Dashboard 的 Response 设置以及 evt.WillAbort 的值来确认。
门户策略回调
Customer Portal 策略可以分阶段运营。Observe 只记录证据,Callback 将服务器下发的 policy action 传递给游戏,Device Block 会在后续服务器验证中阻止选中的设备。在使用更强的设备封禁之前,如需由游戏显示警告、限制匹配、要求重新登录或引导玩家联系支持,请使用 Callback。
using UnityEngine;
using OZeroSDK.Security;
public class PolicyActionHandler : MonoBehaviour
{
void OnEnable()
{
OZeroSecurityManager.Instance.RegisterUserPolicyActionCallback(OnPolicyAction);
}
void OnDisable()
{
OZeroSecurityManager.Instance.UnregisterUserPolicyActionCallback(OnPolicyAction);
}
void OnPolicyAction(OZeroPolicyActionEvent evt)
{
Debug.LogWarning(
$"Policy callback: {evt.Module}, score={evt.Score}, reason={evt.Reason}");
if (evt.Score >= 80)
{
// Show your own UI, limit sensitive actions, or ask the player to reconnect.
}
}
}
下一步
现在 OZero 的核心防线已经在为您保驾护航了。翻查文档,全面探究每个接口,类、或控制选项的真谛。