Get Started
This guide walks you through installing OZero Security and setting up your first security protections. No prior security experience is needed — you will be up and running in about 3 minutes.
Overview
OZero Security protects Unity games by running security logic in the Native C++ layer, where most hacking tools cannot reach. The SDK provides ready-to-use modules that you enable through a simple dashboard inside the Unity Editor. No manual scene setup or boilerplate code is required to get the core protection running.
- Build integrity check (app tampering detection)
- Speed hack and time hack detection
- Memory injection monitoring
- Encrypted in-game variable types (Secure Types)
- Encrypted save files and PlayerPrefs
- Enabled detectors are prepared automatically when the SDK starts — no scene setup and no boilerplate calls required.
- Security settings are protected during the build so plaintext configuration is not exposed in normal player deployments.
- A Native C++ runtime guard adds an additional validation layer beyond managed Unity code.
- Threat response is centralized through SDK policy and Pro telemetry, making field investigation easier without exposing implementation details.
1
Import the Package
Open the Unity Editor and import the OZero Security package. You can import it via the Unity Package Manager or by double-clicking the .unitypackage file.
In the Import Unity Package dialog that appears, leave all items checked and click Import. All required scripts, native plugins, and editor tools will be added to your project automatically.
2
Open the Dashboard
Once the import is complete, open the OZero Security Dashboard from the Unity menu bar:
A custom Editor window will open. This is the central control panel for all OZero security modules. You do not need to touch anything in the Project hierarchy — everything is managed here.
3
Enable Modules
Inside the Dashboard, you will see a list of available security modules with toggle switches. Enable the modules you want to use. The recommended starting set is shown below.
Security Presets
Choose a preset first, then adjust individual modules only when your project needs a custom policy. For most live games, start with Standard because it balances protection, performance, and false-positive risk.
| Preset | Recommended use | Policy summary |
|---|---|---|
| Low | Prototype, development builds, or early QA | Keeps only lightweight checks active. Platform-native checks and force quit are relaxed so test environments do not get blocked too early. |
| Standard | Recommended default for most released games | Enables the core protection set, startup validation, runtime re-validation, emulator checks, and recommended IL2CPP file coverage while keeping compatibility practical. |
| Strict | High-risk live service, PvP, or competitive builds | Turns on maximum coverage and treats more failures as fatal. Use after testing your platform, signing, and store distribution flow. |
| Module | What it does | Recommended |
|---|---|---|
| Build Integrity Validator | Detects if the app binary was modified | Yes |
| Speed Hack Detector | Catches time-manipulation cheats | Yes |
| Injection Detector | Monitors for memory hooking tools | Yes |
| Install Source Validator | Blocks pirated APKs (Android only) | Optional |
You do not need to press any "Auto Setup" button. Toggle the modules you want enabled in the Dashboard and save the OZeroSecurityConfig asset. At player launch, the SDK prepares enabled detectors automatically before gameplay starts.
OZeroSecurityConfig asset, and every enabled module will be auto-created at the next launch. No scene placement or button clicks required.
License Model — Standard / Plus / Pro
OZero Security ships in three tiers. Standard is fully serverless with the shared native module. Plus stays serverless but adds app-specific Native Variant packages bound by manifest and Bundle ID. Pro includes Plus and unlocks server-side capabilities such as Cloud Telemetry, Signed Time, remote policy, attestation, and per-device limits.
| Aspect | Standard | Plus | Pro |
|---|---|---|---|
| License key | — (none) | OZ-PLS-XXXX ×6 |
OZ-PRO-XXXX ×6 |
| Network at boot | Never — fully offline | No runtime server required; portal download only | One POST /v1/activate per device, then cached |
| 9 detector modules | All 9 — Build / Speed / Inject / Device / Source / Physics / Memory / File / PlayerPrefs | All 9 (identical local modules) | All 9 (identical to Standard) |
| Native Variant | Shared native module | App-specific Variant + manifest binding | Included |
| Cloud Telemetry | Off (silent no-op) | Off (serverless) | On — abort events posted to /v1/telemetry |
| Signed Time (anti-clock-tamper) | Off — WebTime uses HTTPS HEAD only |
Off — same as Standard | On — digitally-signed /v1/time |
| Per-device cap | Unlimited (no key, no enforcement) | Project-bound license; no runtime device cap | Default 5 / adjustable |
| Source code access | Managed C# only | Managed C# only | Managed C# only |
OZeroLicenseConfig.
Registering a Plus / Pro License Key
Standard tier needs no setup. For Plus or Pro, add one ScriptableObject asset and paste the key you received after purchase. Plus uses the key for project-bound Variant validation and portal downloads; Pro also uses it for runtime server activation.
1. Create the config asset
In the Project window, Assets → Create → OZero → License Config. Place the asset under any folder named Resources/ and rename it to exactly OZeroLicenseConfig (case sensitive — the runtime calls Resources.Load<OZeroLicenseConfig>("OZeroLicenseConfig")).
Assets/OZeroSDK/Resources/OZeroLicenseConfig.asset, but any Resources/ folder anywhere in the project works.
2. Fill in the Inspector fields
| Field | Required for | Description |
|---|---|---|
| tier | All | Pick Standard, Plus, or Pro from the dropdown. Standard uses the shared native module. Plus requires a project-bound Variant manifest. Pro includes Plus and enables server features. |
| licenseKey | Plus / Pro | The key you received by email. Plus keys use OZ-PLS-...; Pro keys use OZ-PRO-.... Leaving this empty makes the SDK behave as Standard/serverless even if the tier field says Plus or Pro. |
| appIdentifier | Auto | The SDK sends Unity's Application.identifier during activation. The server compares this value with the bundle / package id registered for the license, so make sure the Player Settings identifier matches your issued key. |
| serverBaseUrl | Pro | Default https://api.ozero.security — leave it unless OZero Support has given you a private endpoint. Must be HTTPS in release builds. Trailing slash is trimmed automatically. |
| serverPublicKeyHex | Pro | The 32-byte digital signature public key (64 hex characters) of the OZero License Server. Used by the SDK to verify the activation token's signature (Phase 1.5 / N-1 hardening). If left empty, signature verification is silently skipped — only do that for transitional builds; production should always set this value. Get the canonical hex string from your purchase email. |
| tokenTtlSeconds | Optional | How long (in seconds) a cached activation result is trusted while offline. Default 604800 (7 days). After expiry the SDK keeps running but server-only features (telemetry / signed time) turn off until the next successful activation. |
| offlineProPolicyMode | Pro | Controls how signed Pro portal block policies are applied while the device is offline. Recommended: ApplyCachedBlockPolicies. Use RequireFreshPolicy only for online-only games that must reject stale policy. IgnoreCachedBlockPolicies is a legacy compatibility mode and is not recommended for live builds. |
| activationTimeoutSeconds | Optional | Maximum time (seconds) the SDK waits for /v1/activate before giving up and falling back to the cached entitlement. Default 6.0. The activation call never blocks scene load — this only bounds how long the background Task waits before logging a timeout. |
| enableLog | Optional | When true, license-flow events are written through OZeroSecLog (cache hit / activation success / timeout / digital signature mismatch). Default true; turn off for shipping if you want a quiet log. |
3. Build & verify
No code change is required. OZeroLicenseBootstrap wires itself via [RuntimeInitializeOnLoadMethod(AfterAssembliesLoaded)] and reads the asset at app start. Watch the Player log on first launch for a line like [OZeroLicense] activated; tier=pro caps=5. If you see [OZeroLicense] Standard / serverless mode. with a Pro config in place, double-check that the asset is under a Resources/ folder and named OZeroLicenseConfig (no extension, no rename suffix).
Server Activation Flow
The activation flow runs in the background at app start. The SDK never blocks scene load on it — even on a 6-second timeout the cached entitlement (or Standard fallback) is used so your players never sit on a black screen waiting for HTTP.
Boot sequence
- The SDK initializes the license runtime automatically at startup.
- Standard and Plus continue without a runtime server call.
- Pro sends a lightweight activation request in the background.
- If activation succeeds, Pro server features such as telemetry, signed time, and attestation become available.
- If activation fails, expires, or times out, gameplay continues in Standard mode without showing an error to players.
What goes over the wire
The activation request is a small JSON POST. Required fields are licenseKey, deviceId, sdkVersion, and platform. appIdentifier, companyName, productName, and webglOrigin are added only when Unity provides non-empty values. You can override the device id source via OZeroLicenseRuntime.DeviceIdProvider.
POST https://api.ozero.security/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>"
}
The server replies with a JSON body containing a JWT-shaped signedToken (header.payload.signature, base64url, no padding), the resolved tier, a capabilities array, a serverFeaturesEnabled flag, and (optionally) an expiresAt unix-ms. The SDK calls OZerodigital signature.VerifySignature on the signedToken using your serverPublicKeyHex and only then accepts the entitlement — anything else is fail-closed.
Graceful degradation
Server maintenance, network timeouts, license expiry, suspension, revocation, and bundle mismatch are not treated as tamper. They downgrade Pro-only features to Standard/serverless mode so the player session stays alive. Confirmed tamper states such as build mismatch, blocked build, injection, and debugger detection follow the configured threat-response policy.
await OZeroLicenseRuntime.Initialize(); from your own bootstrap and inspect OZeroLicenseRuntime.Entitlement?.cachedAtMillis. A value within the last few seconds means the server answered fresh; an older value means you are running on cache.
Offline Behaviour
OZero is designed so your players can always launch your game, regardless of network state. The exact behaviour depends on the tier:
Standard — always offline
No server is contacted, ever. All nine detector modules run with no degradation. The license runtime sets IsServerless = true and HasEntitlement = false. OZeroTelemetryReporter attaches but no-ops on every event.
Pro — offline within TTL
After the first successful /v1/activate on a given device, the entitlement is cached in PlayerPrefs (encrypted with a device-bound key — see Cache Security below). On subsequent launches the cache is read synchronously before the network call even starts, so if the player is offline the SDK still knows what tier they are on and which capabilities are unlocked.
The cached entitlement is trusted for tokenTtlSeconds from the cachedAtMillis stamp written at activation time. Default is 7 days (604800). After TTL expiry on a device that has been offline the whole time, the cache is dropped and the SDK degrades to Standard capabilities until the next successful activation. Detectors keep working — only telemetry posting and signed-time validation turn off.
Pro — signed offline block policy
Graceful degradation is for license, network, and server availability failures. It does not mean that an explicitly blocked build becomes allowed offline. When Pro activation succeeds, the server signs the current portal block policy into the activation token and the SDK caches that policy separately from the entitlement.
With the recommended ApplyCachedBlockPolicies mode, blocked manifest hashes, SDK versions, and app versions are still rejected by Build Integrity while the signed policy is valid, even if the player turns on airplane mode. The policy follows the signed token TTL, so run one successful online activation after changing portal block rules.
| State | Detectors | Telemetry | Signed Time |
|---|---|---|---|
| Online, fresh activation | All 9 on | On | On |
| Offline, cache < TTL | All 9 on | Queued (delivered next online) | Falls back to WebTime HTTPS HEAD |
| Offline, cache > TTL | All 9 on | Off (degraded) | Off (degraded) |
| No cache (first launch + offline) | All 9 on | Off until first online launch | Off until first online launch |
Troubleshooting
Every license event is logged through OZeroSecLog, which routes to Unity's Debug.Log by default (configurable in OZeroSecurityConfig). Filter the Player log by the [OZeroLicense] prefix to see the boot-time decisions; [OZeroTelemetry] for the reporter. Common symptoms and fixes:
| Symptom (log line) | Likely cause | What to check |
|---|---|---|
[OZeroLicense] Standard / serverless mode. |
Asset missing or tier=Standard or empty key | Confirm OZeroLicenseConfig.asset is under a Resources/ folder, named exactly OZeroLicenseConfig, with tier=Pro and a non-empty licenseKey. |
/v1/activate failed: 401: invalid_key |
Key typo or wrong tier prefix | Re-paste the key from the original purchase email. The format is case-sensitive, dashes are required, the prefix OZ-PRO- must match the asset's tier dropdown. |
/v1/activate failed: 403: bundle_mismatch |
appIdentifier mismatch |
The server-side license record binds the key to a specific bundle / package id. Ensure Application.identifier in Player Settings matches the id registered for the license. |
activation token signature verification failed |
Wrong serverPublicKeyHex or MITM |
Compare the hex string in the asset against the canonical pubkey from your purchase email — character for character, all 64 hex chars. If correct and still failing, suspect a corporate proxy/MITM appliance and try a direct mobile-data connection. |
/v1/activate timed out |
Network slow / firewall | SDK falls back to cache automatically — only an issue on first-launch / no-cache. Confirm the device can reach https://api.ozero.security/health; raise activationTimeoutSeconds if you operate in high-latency regions. |
cached entitlement past TTL; clearing. |
Player offline > TTL | Expected. Detectors keep working; telemetry / signed-time turn off until next online launch. Raise tokenTtlSeconds if your audience routinely runs offline for > 7 days. |
cache hit; tier=pro on a different machine than expected |
Device fingerprint changed | If SystemInfo.deviceUniqueIdentifier rotates (some Android ROMs / OS reset), the v2 cache becomes unreadable and the SDK simply re-activates. No user action required — the next online launch heals it. |
enableLog = true if you are concerned about leaking which detectors fired in your shipping log files. The license-flow logs reveal tier / capability state, which is fine to expose, but pair-disable the global OZeroSecurityConfig.enableLog as well so detector internals stay quiet.
Project Settings
Before configuring individual security modules, check the Unity Player Settings that affect native plugins, store builds, and platform validation.
Minimum Build Targets
| Platform | Minimum target | Notes |
|---|---|---|
| iOS | 12.0+ |
For iOS builds, set Project Settings > Player > iOS > Target minimum iOS Version to 12.0 or later. OZero does not overwrite this PlayerSettings value; keep it aligned with your app support policy. |
| Android | API 21+ |
For Android builds, set Project Settings > Player > Android > Minimum API Level to Android 5.0 'Lollipop' (API level 21) or later. Use IL2CPP and ARM64 for store release builds. |
Android ProGuard / R8 Settings
OZero Security does not require a separate Java SDK package, but Android release builds that enable Minify, ProGuard, or R8 must preserve Unity's Java bridge and any custom Android bridge classes used by your project. This keeps JNI calls for package information, installer source, APK signing certificate checks, and boot asset loading stable after obfuscation.
proguard-user.txt. If Minify is disabled, no extra ProGuard setup is required.
# 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.** { *; }
- Do not rename, remove, or repackage
libOZeroSecurity.so. Keep the plugin underAssets/OZeroSDK/Plugins/Android/arm64-v8aand, if you ship 32-bit builds,armeabi-v7a. - When Build Integrity > Check Platform Native and Android SHA Keys are enabled, test the final signed APK or AAB after Minify/R8. Debug keystore fingerprints and release keystore fingerprints are different.
- If Android logs show JNI lookup failures, installer-source detection failures, or APK signature checks returning empty results only after Minify is enabled, review your custom bridge keep rules first.
- For store releases, build with IL2CPP, release signing, the expected Android SHA-256 fingerprint registered in
Android Sha Keys, and a clean launch test on at least one real device.
5
Configure OZeroSecurityConfig
The OZeroSecurityConfig ScriptableObject asset is included when you import the package. Select it in the Project window to inspect and adjust all security module settings. The config is a singleton — access it at runtime via OZeroSecurityConfig.Instance.
General Settings
These top-level fields apply globally across all modules.
| Field | Type | Default | Description |
|---|---|---|---|
| developerSecret | string | — | A unique passphrase used as the key-derivation function input for OZeroSV_File and OZeroSafePlayerPrefs encryption. Set a unique value before release and never change it after launch — doing so will make existing encrypted data unreadable. |
| enableLog | bool | false | Enables internal debug logging from OZero Security. Disable in production builds to avoid leaking detection logic details. |
| enableFailureDiagnostics | bool | false | Writes local security failure diagnostic files to Application.persistentDataPath. Enable only for QA or customer support sessions because files can include module names, hashes, device state, installer package names, and runtime configuration. |
developerSecret before your first release. Do not use the default value and do not change it after launch.
Response Settings
Controls what happens when any security module fires a detection event.
| Field | Type | Default | Description |
|---|---|---|---|
| forceQuitOnDetection | bool | true | Automatically force-quits the application when a confirmed threat is detected. In Standard and Strict presets this is enabled so the internal fallback receiver can enforce the response even if a managed callback is patched or missing. Disable only for controlled QA flows where you intentionally want to observe detections without terminating the app. |
Build Integrity Settings
Configures the build integrity validator that detects modified game files, debugger attachment, and suspicious runtime environments.
| Field | Type | Default | Description |
|---|---|---|---|
| Activate Build Integrity | checkbox | On | The Dashboard activation checkbox. Internally this maps to useIntegrity, but users should enable or disable the module with the Activate checkbox. |
| validateOnStartup | bool | true | Runs the integrity check immediately when the game launches. |
| validateInEditor | bool | false | Also validates in the Unity Editor for testing. Keep this off during ordinary development unless you are intentionally testing integrity behavior. |
| enablePeriodicValidation | bool | true | Repeats integrity checks while the game is running. Disable only when startup-only validation is enough for your build. |
| periodicCheckInterval | float | 300 s | Base interval in seconds for periodic validation. Set to 0 or less to disable periodic checks. |
| periodicCheckJitterPercent | float | 35% | Randomizes the periodic interval so attackers cannot predict exact validation timing. For example, 300 seconds with 35% jitter runs roughly between 195 and 405 seconds. |
| timingAnomalyConsecutiveRequired | int | 7 | How many timing-only anomalies must accumulate before debugger timing drift is treated as a violation. Strong debugger signals can still fail immediately. |
| timingAnomalyWindowSeconds | float | 900 s | Time window used to accumulate timing-only anomalies. A longer window is more tolerant; Strict uses a shorter window. |
| timingAnomalyFrameHitchSuppressionSeconds | float | 20 s | Temporarily suppresses timing-only debugger checks after a large frame hitch, such as scene loading, shader compilation, GC, or OS scheduling stalls. |
| checkAssemblyHash | bool | true | Verifies managed assembly hashes against the manifest generated at build time. |
| checkDebugger | bool | true | Detects debugger attachment and debugger-like runtime timing behavior using native platform checks. |
| failOnDebugBuild | bool | false | Treats a Unity Debug build as a violation. Enable this for release-only validation flows after QA is complete. |
| checkPlatformNative | bool | true | Runs native platform checks such as emulator, debugger, virtualization, signing, proxy, or tool-process signals depending on platform. |
| failIfManifestMissing | bool | true | Triggers a violation if OZeroIntegrityManifest.ozero is missing. |
| failIfAssemblyHashBlobMissing | bool | true | Triggers a violation if the assembly hash blob inside the manifest is missing. |
| requireCodeSignature (Windows) | bool | false | Requires the Windows main executable to be code-signed. |
| blockVirtualMachine (Windows) | bool | false | Blocks common VM environments such as VirtualBox, VMware, or QEMU. Hyper-V has a separate switch. |
| blockHyperV (Windows) | bool | false | Blocks Hyper-V VMBus signals. Use carefully because it can also block WSL2, Docker Desktop, and Windows Sandbox users. |
| blockNetworkProxies (Windows) | bool | false | Detects network proxy and packet inspection tools such as Wireshark, Fiddler, Charles, and HTTPDebugger. Enable for controlled competitive builds. |
| blockReverseEngineeringTools (Windows) | bool | false | Detects reverse-engineering tools such as ILSpy, dotPeek, Ghidra, WinDbg, and IDA Pro. |
| blockSystemMonitorTools (Windows) | bool | false | Detects system monitoring tools such as Process Explorer, Process Monitor, and Process Hacker. Consider false-positive risk for power users. |
| il2cppHashGameAssembly | bool | true | Hashes GameAssembly.dll on Windows IL2CPP builds. This is the minimum recommended IL2CPP file guard. |
| il2cppHashGlobalGameManagers | bool | true | Adds globalgamemanagers to IL2CPP manifest coverage. |
| il2cppHashSharedAssets | bool | true | Adds sharedassets* files to IL2CPP coverage. |
| il2cppHashSceneFiles | bool | true | Adds Unity scene files such as level* to IL2CPP coverage. |
| il2cppHashResourcesAssets | bool | false | Adds resources.assets to IL2CPP coverage. Useful in Strict mode, but test patch workflows first. |
| il2cppAdditionalWatchedFiles | List<string> | — | Additional Windows IL2CPP output files to include in manifest hashing when your project has custom native payloads. |
| blockEmulator (Android) | bool | true | Android only. Treats emulator or unsupported runtime signals as integrity violations. |
| blockSystemRwMount (Android) | bool | true | Android only. Treats writable system partitions or root-like mount state as an integrity violation. |
| androidShaKeys | List<string> | — | Allowed SHA-256 fingerprints for APK/AAB signing certificates. Leave empty to skip this check; include both debug and release keys if development builds must pass. |
| expectedBundleIds (iOS) | List<string> | — | Allowed iOS bundle identifiers. Leave empty to skip the bundle ID check. |
| excludedAssemblies | List<string> | — | Assembly names without .dll to exclude from manifest generation and runtime hash verification. Unity/Mono framework assemblies are excluded automatically. |
| checkIntegrityWithServer (Pro) | bool | false | Enables Pro server attestation. The client requests a nonce, submits integrity evidence, and receives a signed OZA token that your game server can validate. |
| enableManagedVerification (Pro) | bool | false | Lets OZero validate the issued OZA token and return an allow/warn/block verdict plus a short managed session for teams without their own backend. The SDK refreshes before that managed session expires. Requires checkIntegrityWithServer. |
| requireManagedVerification (Pro) | bool | false | When enabled, a managed-verification network failure is treated as a validation failure. Keep this off unless your launch flow can tolerate strict online gating. |
| attestRefreshLeadSeconds (Pro) | float | 300 s | How early before OZA token expiry the client should attempt refresh. Set to 0 to disable pre-expiry refresh. |
| attestRefreshCooldownSeconds (Pro) | float | 30 s | Minimum cooldown between attestation refresh attempts. |
| manifestSigningPublicKey | string | — | Base64 RSA-2048 public key used to verify the signed integrity manifest. Generate it from the OZero key-pair tool. |
| requireManifestSignature | bool | false | Treats a missing or invalid manifest signature as an integrity violation. Enable for release builds after generating a key pair. |
| manifestSigningPrivateKeyPath | string | OZeroSigningKeys | Editor-only path to the private key PEM used at build time. This path is not included in player builds. |
Events
| Event | Description |
|---|---|
| OnValidationPassed | Fires when local integrity checks complete successfully. Pro server attestation may still be pending. |
| OnAttestationPassed | Fires only after the Pro OZA attestation token is issued. Use this, or AttestationToken.IsValid(nowMillis), before game-server login, PvP, ranking, or currency flows. |
| OnValidationFailed | Fires when the integrity check detects a violation. Also triggers the global onHackDetected event with ModulationType.BuildIntegrity. |
Generate RSA Signing Keys (Build Integrity)
If you have enabled Build Integrity with Require Manifest Signature turned on, OZero uses public-key signature asymmetric signing to guarantee that the integrity manifest has not been replaced or tampered with. Before making any release build you must generate a key pair from inside the Unity Editor.
At build time, OZero signs the assembly manifest with your private key and embeds the resulting signature in the build. At runtime, the public key stored in OZeroSecurityConfig verifies that signature. If the signature does not match — meaning the manifest or the binary was altered — the game treats it as tampering and responds according to your Response Settings.
Generating the key pair
- In the Unity Editor, select
OZeroSecurityConfigin the Project window. - In the Inspector, expand the Build Integrity section.
- Enable the Require Manifest Signature checkbox.
- Click the Generate Key Pair button.
- OZero generates an public-key signature key pair. The public key is written directly into
OZeroSecurityConfig(stored in your Assets). The private key is saved to:[ProjectRoot]/OZeroSigningKeys/manifest_private_key.pem - A confirmation dialog shows the private key location. Click OK to dismiss it.
- The private key file is stored outside the
Assets/folder so Unity does not include it in builds. Never move it insideAssets/. - Add
OZeroSigningKeys/to your.gitignoreimmediately. Committing the private key to version control is a critical security risk. - Back up the private key to a secure, offline location (encrypted USB drive, password manager, etc.). Anyone with this file can forge a valid manifest for your game.
- If you lose the private key, you must generate a new key pair. The new public key will be embedded in the next build. All previously shipped builds that carry the old signature will fail manifest verification and must be replaced.
Custom private key path (CI/CD & teams)
The Manifest Signing Private Key Path field (visible in the Inspector under Manifest Signing — Editor Only) lets you specify an alternative location for the private key. This is useful in two scenarios:
- CI/CD pipelines — Store the private key as a CI secret and inject its path at build time so the build machine never needs the key on disk permanently.
- Team environments — Keep the key on a shared secure server or secrets manager and point the field at the mounted path. Only the team member running the release build needs access.
Validating an existing key pair
If you are unsure whether the private key on disk still matches the public key stored in OZeroSecurityConfig, click the Validate Key Pair button. OZero will sign a small test payload with the private key and verify it with the public key. If they match, a success notice appears. If not, you must generate a new pair.
- Private key is lost or compromised.
- Validate Key Pair reports a mismatch (keys are out of sync).
- Deliberately rotating keys as part of a scheduled security policy.
After re-generating, the new public key is embedded in the next build automatically. Any existing shipped builds will fail manifest verification with the new public key — you will need to push an update.
Dual-fingerprint key rotation (zero-downtime)
The Generate Key Pair button stores the public key in two places at once: inside OZeroSecurityConfig (a ScriptableObject in your Assets), AND as a SHA-256 fingerprint constant inside Assets/OZeroSDK/Scripts/Security/BuildIntegirity/OZeroManifestTrustAnchor.cs. At runtime the SDK first checks that the public key in the asset matches the pinned fingerprint in the assembly — if they don't match, the manifest is rejected even before the RSA signature is checked. This closes the repacker attack where someone swaps both the asset and the .sig file with attacker-owned keys.
Inside OZeroManifestTrustAnchor.cs there are two const slots: ExpectedPublicKeyFingerprintHex (the current production fingerprint) and PreviousPublicKeyFingerprintHex (an optional second fingerprint, empty in steady state). The Matches() verifier returns true if the live SPKI hash equals either value, using a constant-time XOR compare. This dual-slot model is the foundation of the "zero-downtime key rotation" feature on the homepage: a freshly built SDK can trust BOTH the new key (Expected) AND the old key (Previous) for a bounded grace window, so old shipped builds keep verifying their old manifests while you push the new release.
Rotation procedure (manual, but takes 5 minutes)
- Note the current fingerprint. Open
Assets/OZeroSDK/Scripts/Security/BuildIntegirity/OZeroManifestTrustAnchor.csand copy the value ofExpectedPublicKeyFingerprintHexinto your clipboard (or a scratch file). - Move it into the previous slot. In the same file, paste the value into
PreviousPublicKeyFingerprintHex(which is normally""). Save. - Generate the new key pair. In the Inspector for
OZeroSecurityConfig, click Generate Key Pair. The dialog will warn that this invalidates previously baked manifests — click Regenerate. The new private key is written to the PEM file, the new public key replaces the asset value, and the new SHA-256 fingerprint is auto-injected intoExpectedPublicKeyFingerprintHex. YourPreviousPublicKeyFingerprintHexfrom step 2 is left untouched. - Build and ship the new SDK release through your normal patch / store channel. New manifests in this build are signed with the new private key.
- Wait for fleet rollover. During the grace window the new build trusts both kids; the old build (still pinning only the old fingerprint as Expected) keeps verifying its own old manifest. Neither path breaks. Typical window: 1–4 weeks for a live game.
- Clear the previous slot. Once telemetry confirms the old build is at 0%, set
PreviousPublicKeyFingerprintHex = ""and ship one more SDK release. Rotation complete.
The old shipped binary already contains an old manifest signed by the old key, AND an embedded copy of the old fingerprint as its Expected slot — it does not need to know anything about the new key to keep working. The new build needs the Previous slot only so that, if a player force-reinstalls or rolls back to an old version mid-window, the cached old manifest still verifies. Once the old build is gone from the field, you can clear Previous in the next release.
Troubleshooting
All five symptoms below come from the same defence stack — the build-time PreBuild validator (OZeroBuildPreflightValidator), the boot-time anchor guard (OZeroManifestTrustAnchor.ValidateConfigurationOrFail), and the runtime verifier (OZeroBuildIntegrityValidator.VerifyManifestSignature). The fix in each case is to bring the three artefacts (private key PEM, public key in OZeroSecurityConfig, fingerprint in OZeroManifestTrustAnchor) back into sync.
ExpectedPublicKeyFingerprintHex is an empty string. Editor and Development builds tolerate this with a warning, but release builds fail-fast at boot before any untrusted manifest can be accepted. Fix: run Generate Key Pair once in the Editor — it auto-injects the fingerprint into the const. If the const is already populated but the build still aborts, the assembly hash blob (oz_ahash.bin) may be out of date — rebuild from a clean state.
The PreBuild validator (callbackOrder 51) caught that OZeroSecurityConfig.Integrity.ManifestSigningPublicKey is empty. Fix: open OZeroSecurityConfig in the Inspector → expand Build Integrity → click Generate Key Pair, then rebuild. Note: this check is skipped on Android / iOS (OS-level signing) and on IL2CPP Standalone (no per-DLL files to sign), so a missing key only blocks Mono Standalone targets.
The public key string in OZeroSecurityConfig hashes to a value that matches neither ExpectedPublicKeyFingerprintHex nor PreviousPublicKeyFingerprintHex. In a legitimately built binary this happens after a manual edit got the two artefacts out of sync. Fix: in the Editor, run Tools → OZero → Validate Manifest Signing Keys — if the key pair is consistent the tool will auto-rewrite the const for you. If it reports a mismatch, regenerate the pair via Generate Key Pair. In a tampered binary, this message is the intended fail-closed signal — investigate whether the APK / .exe was repacked.
You forgot step 2 of the rotation procedure. The new SDK release shipped with PreviousPublicKeyFingerprintHex = "", so a player who already had the old build cached and force-restarts hits a manifest signed under the new key (which their old build doesn't trust) — or, worse, a force-reinstall pulls down a freshly cached old manifest that the trust anchor in their old build can't verify because the PEM has rotated. Fix: ship a hotfix SDK release with PreviousPublicKeyFingerprintHex set to the old fingerprint; the next manifest fetch will verify under either kid.
CI machines do not have OZeroSigningKeys/manifest_private_key.pem on disk because it is correctly excluded by .gitignore. Fix: store the PEM as a CI secret (GitHub Actions Secret, GitLab Variable, Jenkins Credentials, etc.) and have the pipeline write it to OZeroSigningKeys/manifest_private_key.pem in the workspace before the Unity build step. Alternatively, set Manifest Signing Private Key Path in OZeroSecurityConfig to a path the CI runner can mount (e.g. a secrets-manager mount). Make sure every CI runner uses the SAME PEM — different PEMs produce different signatures even when the public key matches.
Install Source Settings
Restricts the game to only run when installed from authorized stores or sources. Useful for preventing sideloaded or repackaged APKs.
| Field | Type | Default | Description |
|---|---|---|---|
| Activate Install Source | bool | true | The Dashboard activation checkbox for the Install Source module. Internally this maps to useInstallSource. |
| allowGooglePlayStore | bool | true | Permits installs from the Google Play Store. |
| allowSamsungGalaxyStore | bool | false | Permits installs from the Samsung Galaxy Store. |
| allowAmazonAppstore | bool | false | Permits installs from the Amazon Appstore. |
| allowHuaweiAppGallery | bool | false | Permits installs from the Huawei AppGallery. |
| allowOneStore | bool | false | Permits installs from ONE Store (Korea). |
| allowXiaomiGetApps | bool | false | Permits installs from Xiaomi GetApps. |
| allowOppoAppMarket | bool | false | Permits installs from the OPPO App Market. |
| allowVivoAppStore | bool | false | Permits installs from the Vivo App Store. |
| allowADB | bool | false | Permits installs via ADB (Android Debug Bridge). Enable only for internal testing. |
| allowDetectionFailed | bool | false | Allows the game to continue when Android installer-package detection fails because the OS or JNI query was unavailable. Keep disabled in release builds. |
| allowUnknownSources | bool | false | Allows installer packages that are not in the built-in or custom allowlist. Use only for regional stores after testing. |
| enableServerSync (Pro) | bool | false | Sends the detected installer package to the OZero server for allowlist validation and audit. Pro only. |
| customAuthorizedPackages | List<string> | — | Additional authorized installer package names (e.g. com.yourcompany.launcher). |
| reportViolationToCallback | bool | true | Routes unauthorized install-source detections to the SDK response callback/security manager path. |
| logRawInstallerPackage | bool | true | Logs the raw installer package name to the console. Useful during development to identify the correct package name for a custom store. |
Steam Anti-Piracy Settings
Checks Steam launch path, App ID, entitlement state, and release hygiene for PC builds distributed through Steam. Standard performs local validation; stronger ownership verification through Steam server evidence is provided by Pro server Steam Attestation.
| Field | Type | Default | Description |
|---|---|---|---|
| Activate Steam Anti-Piracy | checkbox | Off | Activation checkbox for Steam Anti-Piracy. It is off by default because Steam App ID and distribution flow are project-specific. First verify the status in observe/QA mode, then tighten the release-build policy. |
| expectedSteamAppId | int | 0 | Your project’s Steam App ID. If you used development AppID 480 for local checks, replace it with the real App ID before release. |
| requireSteamLaunch | bool | true | Checks whether the game was started through Steam rather than direct executable launch. Keep development direct-run tests separate from release policy. |
| requireSteamApiInit | bool | true | Checks whether Steam API initialization succeeds. Local development launches can fail depending on Steamworks setup, so final judgement should be verified through the actual Steam distribution path. |
| requireSubscribedCurrentApp | bool | true | Checks whether the current Steam account owns or is entitled to the app. This is local validation; server-side ownership verification is handled by Pro. |
| requiredDlcAppIds | List<int> | — | Optional DLC App IDs that must be owned for protected DLC flows. |
| blockSteamAppIdTxtInRelease | bool | true | Fails release builds that still contain a development steam_appid.txt file. This file is only for local development checks and should not be distributed. |
| validateSteamApiDllHash | bool | false | Optionally validates the Steam API DLL hash against known-good SHA-256 values. |
| allowFamilySharing / allowFreeWeekend / allowTimedTrial | bool | true | Controls whether Steam family sharing, free weekend, and timed trial entitlement states are accepted. |
| enableServerSteamAttestation (Pro) | bool | false | Pro only. Submits Steam entitlement evidence to OZero server for stronger server-side ownership verification than local client results. |
Device Binding Settings
Binds a game account to the original device using a hardware fingerprint. Detects account transfers to different hardware.
Commercial operations purpose
Device Binding is most useful as a Pro operations layer: it connects a license to server-registered hardware fingerprints so your team can isolate risky devices, reduce casual license sharing, and support legitimate device changes without turning the entire license off.
Block repeat-risk devices
Use after reviewing Security Events such as SpeedHack, Injection, Install Source, or Build Integrity evidence. A blocked device receives DEVICE_BLOCKED on the next activation, registration, verify, or policy check.
Reset legitimate users
Use Reset Token for phone replacement, OS reinstall, hardware replacement, or a normal fingerprint mismatch. The token is 5-minute, single-use, and bound to the exact license and device.
Control device slots
For Pro licenses, maxDevices limits how many active server fingerprints can register. Blocked fingerprints do not count as reusable trust; use reset or deletion only after support verification.
Reset Token support flow
- Confirm the player account and the reason for reset through your normal support process.
- Open Customer Portal → Device Binding, find the target device, and issue a Reset Token.
- Send the token only through an authenticated support channel. Do not store it in long-lived public chat or screenshots.
- Your game or support UI should pass the token to
OZeroDeviceBindingDetector.Instance?.ClearStoredFingerprint(token.Trim()). - After the SDK consumes the token, restart or re-enter the protected startup flow so the current hardware fingerprint is registered again.
| Field | Type | Default | Description |
|---|---|---|---|
| Activate Device Binding | bool | true | The Dashboard activation checkbox for the Device Binding module. Internally this maps to useDeviceBinding. |
| hardwareChangeTolerance | int (0–3) | 1 | How many hardware component changes are tolerated before triggering a violation. 0 = strict (no changes), 3 = lenient (major hardware swap). A value of 1 accommodates minor OS or firmware updates. |
| storageKey | string | "ozero_dfp" | The PlayerPrefs key under which the encrypted device fingerprint is stored. Change only if this key conflicts with an existing key in your game. |
| enableServerSync | bool | false | Syncs the device fingerprint with your backend server for server-side validation. |
| maxDevices (Pro) | int | 0 | Documents the intended server-side device limit. The authoritative limit is enforced by the server license record, so changing this local value alone does not raise the production limit. |
Speed & Time Hack Settings
Detects time-scale manipulation (speed hacks) by comparing Unity Time.realtimeSinceStartup against native platform timers and optionally a trusted web time source.
| Field | Type | Default | Description |
|---|---|---|---|
| Activate Speed & Time Hack | bool | true | The Dashboard activation checkbox for Speed & Time Hack detection. Internally this maps to useSpeedHack. |
| autoStart | bool | true | Starts detection automatically on game launch. Disable to start manually via OZeroSpeedHackDetector.StartDetection(). |
| checkInterval | float | 1.0 s | How often (in seconds) the detector samples and compares timers. |
| requiredDetections | int | 3 | Number of consecutive anomalous samples required before a violation is triggered. Increase to reduce false positives on unstable devices. |
| ratioTolerance | float | 0.15 | Allowed deviation (±15%) between Unity time and native timer before a sample is considered anomalous. |
| maxAllowedRatio | float | 4.0 | Maximum allowed time ratio. A ratio above this value is immediately flagged as a speed hack, regardless of requiredDetections. |
| detectSlowHack | bool | true | Also detects tools that slow down time (ratio below 1.0 minus tolerance), not just speed-ups. |
| enableTimeScaleDetection | bool | true | Detects abnormal Time.timeScale values set programmatically (e.g. by memory editors). |
| hackDetectMultiplier | float | 1.3 | A sample exceeding maxAllowedRatio × hackDetectMultiplier is counted as a detection even before requiredDetections is reached. |
| enableThreadTimerCheck | bool | true | Uses a background thread timer as an additional reference clock, making it harder for hacks that only affect the main thread. |
| useWebTimeValidation | bool | false | Periodically fetches the current time from webTimeUrl and compares it to the device clock to detect device-level time manipulation. |
| webTimeUrl | string | — | URL of a trusted time API endpoint (must return a Unix timestamp or RFC 2616 Date header). Required when useWebTimeValidation is enabled. |
| webSyncInterval | float | 15 s | How often (in seconds) to sync with the web time server. |
| webRatioTolerance | float | 0.15 | Allowed deviation (±15%) between device time and web server time. |
| timeOffsetTolerance | float | 60 s | Maximum absolute clock offset (in seconds) allowed between device and web server before triggering a violation. |
| focusIgnoreTime | float | 4 s | Seconds to ignore after the app regains focus (e.g. returning from multitasking). Prevents false positives when the OS freezes the app. |
| loadingGraceTime | float | 6 s | Grace period (in seconds) at startup during which detection results are ignored to avoid false positives during scene loading. |
| lagSpikeIgnore | float | 0.5 s | Samples where the frame delta exceeds this value are discarded (considered a genuine lag spike, not a time manipulation). |
| buildFailIfTimeScaleTampered | bool | true | Build-time guard that fails validation when protected code appears to alter Time.timeScale outside the configured policy. |
| timeScaleTamperExemptions | List<string> | — | Class or method patterns that are allowed to change Time.timeScale intentionally, such as pause or bullet-time systems. |
| webTimeUrls | string[] | — | Optional multiple trusted time endpoints. The detector can require more than one successful endpoint before trusting web time. |
| minSuccessfulEndpoints | int | 2 | Minimum number of web-time endpoints that must respond successfully when multiple endpoints are configured. |
| maxConsecutiveFailures | int | 6 | Number of consecutive web-time failures tolerated before applying the unavailable policy. |
| onWebTimeUnavailable | enum | WarnOnly | Policy used when trusted web time cannot be reached. Choose a stricter mode only after testing network conditions. |
| detectTimeHack | bool | true | Enables device-clock and web-time manipulation checks in addition to speed ratio checks. |
| webSyncJitterPercent | float | 20% | Randomizes web-time sync intervals to avoid predictable polling. |
| sustainedLagThreshold | float | 0.15 | Threshold for treating repeated slow frames as sustained lag rather than cheat evidence. |
| sustainedLagGraceDuration | float | 3 s | Grace duration applied while sustained lag is being classified. |
| overloadStrictMultiplier | int | 3 | Multiplier used to make overload handling stricter after repeated abnormal timing conditions. |
| enableRemoteSpeedHackConfig (Pro) | bool | false | Allows Pro server policy to override selected Speed & Time Hack thresholds without changing client code. |
| remoteSpeedHackConfigInterval | float | 300 s | How often remote Speed & Time Hack configuration is refreshed. |
| enableSignedServerTime (Pro) | bool | false | Uses signed server time when available so time validation cannot be spoofed with an unsigned response. |
Physics Hack Detector
OZeroPhysicsHackDetector is designed for multiplayer/MMORPG games where each
player object needs independent detection thresholds. Attach the component directly to your
player prefab and configure the settings per object in the Inspector.
Call Initialize(playerId) in your spawn logic to activate detection.
| Field | Type | Default | Description |
|---|---|---|---|
| maxAllowedSpeed | float | 15 u/s | Maximum allowed movement speed (Unity units per second). Tune to match your game's maximum legitimate player speed. |
| distanceTolerance | float | 2.0 u | Extra distance allowance to compensate for network latency or physics inaccuracies. |
| obstacleLayer | LayerMask | — | Layer mask for walls and obstacles. Used to detect wall-clipping via linecast. |
| checkInterval | float | 0.05 s | How often (in seconds) the physics validator samples player position and velocity. |
| maxDeltaTimeCap | float | 0.1 s | Caps delta time for distance calculations to prevent lag spikes from masking teleport detection. |
Injection & Hooking Settings
Detects unauthorized code injection such as assembly injection, DLL hijacking, or IL2CPP patching.
| Field | Type | Default | Description |
|---|---|---|---|
| Activate Injection & Hooking | checkbox | On | The Dashboard activation checkbox for Injection & Hooking detection. Internally this maps to useInjection. |
| enableServerWhitelist (Pro) | bool | false | Downloads trusted native module policy from the server, so approved overlays or partner modules can be allowed centrally. |
| serverWhitelistRefreshInterval | float | 0 s | Refresh interval for server-managed whitelist policy. 0 means refresh only at startup or policy bootstrap. |
| requireSignerForNativeWhitelist | bool | false | Requires native whitelist entries to include signer identity, not only file hash. Stronger, but needs clean module signer data. |
| enableRemoteInjectionConfig (Pro) | bool | true | Allows Pro server policy to tune Injection thresholds and probes without client code changes. |
| remoteInjectionConfigInterval | float | 300 s | How often remote Injection policy is refreshed. |
| enableWindowsModuleIdentityScan | bool | true | Scans loaded Windows native modules and compares hash/signer identity against trusted policy. |
| scanIntervalSeconds | float | 1 s | Base runtime scan interval for injection probes. |
| windowsModuleIdentityScanIntervalSeconds | float | 5 s | Separate interval for the heavier Windows module identity scan. |
| scanJitterPercent | float | 20% | Adds random jitter to scan timing so probes are harder to predict. |
| enableExecutablePrivateMemoryScan | bool | true | Detects executable private memory regions that are commonly used by injected shellcode or runtime patchers. |
| enableInlineHookScan | bool | false | Detects suspicious inline patches at known code entry points. Strict mode enables this after compatibility testing. |
| enableThreadStartAddressScan | bool | true | Checks thread start addresses for suspicious locations outside trusted modules. |
| enableExternalProcessHandleScan | bool | true | Detects external processes that hold suspicious handles to the game process. |
| detectionConfidenceThreshold | int | 70 | Minimum confidence score required before the detector treats combined injection signals as a violation. |
| enableWebRuntimeTamperScan (WebGL) | bool | true | WebGL-only lightweight browser tamper checks. This is not native C++ protection; treat it as limited client-side signal collection. |
| WebGL probes | bools | true | Controls DevTools, clock hook, network hook, WASM hook, storage hook, and crypto hook checks for WebGL builds. |
Encrypt PlayerPrefs
To protect data stored in Unity's PlayerPrefs (settings, user preferences, etc.), replace PlayerPrefs with OZeroSafePlayerPrefs. The API is identical.
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", "");
Encrypt Save Files
To encrypt save files, use OZeroSV_File instead of File.ReadAllText / File.WriteAllText. The file is automatically encrypted on write and decrypted on read. Tampering is detected on load.
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 after launch. It is one of the values used to protect OZeroSafePlayerPrefs and OZeroSV_File data. If you change it after release, protected data saved by older versions may no longer open.
4
Protect In-Game Variables (Secure Types)
Secure Types replace ordinary C# variable types with encrypted equivalents. Values are stored in the Native C++ heap so tools like Cheat Engine cannot find them by scanning memory. The replacement is a simple name swap — all operators and implicit conversions work the same way.
Supported types
| Before | After (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 |
Example
// ── Before: plain C# variable ──────────────────────────────
using UnityEngine;
public class PlayerStats : MonoBehaviour
{
public int gold = 0;
public float speed = 5.0f;
public int hp = 100;
}
// ── After: OZero Secure Types (only the type name changes) ──
using UnityEngine;
using OZeroSDK.Security;
public class PlayerStats : MonoBehaviour
{
public OZeroSV_Int gold = 0;
public OZeroSV_Float speed = 5.0f;
public OZeroSV_Int hp = 100;
// All arithmetic works exactly as before — no code changes needed
void TakeDamage(int dmg) => hp -= dmg;
void AddGold(int amount) => gold += amount;
}
7
Handle Security Events (Optional)
By default, OZero force-quits the application when it detects a threat (if Force Quit On Detection is enabled). If you prefer to handle threats yourself — for example, to show a warning screen, log the event to your server, or apply in-game penalties — you can register a callback.
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.
}
}
}
The full list of ModulationType values is documented in the API Reference.
Force Quit On Detection enabled in OZeroSecurityConfig. Your callback fires first, then the application exits.
A violation fans out to: (1) OZeroSecurityManager.BuiltinResponse — the default handler that always runs; (2) your OZeroSecurityReceiver.HandleModulation override and Inspector onHackDetected event — optional scene-level hook; (3) OZeroInternalFallbackReceiver — reads Response.ForceQuitOnDetection and enforces it even with no receiver in the scene; (4) OZ_ReportViolation on the native side — optional C++ export that bypasses the managed patch surface entirely. Patching or stubbing any single path no longer silences the SDK.
Next Steps
You now have the core OZero protection running. Explore the full API Reference to learn about every class, method, and configuration option in detail.