Table of Contents

Securing saves and preferences

Protecting local storage is crucial when your game relies on client-side data for progression, in-app purchases, or cooldown timers. ACTk ships with three complementary systems—ObscuredFile, ObscuredFilePrefs, and ObscuredPrefs—plus editor tooling to inspect data during development.

Choosing the right storage solution

Feature ObscuredPrefs ObscuredFilePrefs ObscuredFile
Backend Unity PlayerPrefs File system File system
API Style PlayerPrefs-like PlayerPrefs-like Raw byte arrays
Performance PlayerPrefs + Binary Serialization + Encryption File I/O + Binary Serialization + Encryption File I/O + Encryption
Data Size Small values only Moderate size (caches data in memory) Any size (no cache, direct I/O)
Thread Safety Main thread only Background thread safe Background thread safe
Auto-save Automatic Automatic (configurable) Manual
Use Cases Settings, small progress data Moderate save files, complex data Large save files and raw data

ObscuredFile

ObscuredFile provides an encrypted file wrapper that detects tampering and optionally locks data to a specific device or custom identifier.

Basic Usage

var file = new ObscuredFile();
var result = file.WriteAllBytes(data);
if (result.Success) Debug.Log("Saved!");

Advanced Features

  • Device lock: Use DeviceLockSettings to bind data to specific devices
  • Custom encryption: Configure EncryptionSettings with custom passwords
  • Async operations: Call UnityApiResultsHolder.InitForAsyncUsage(true) for background operations
  • Event handling: Subscribe to DataFromAnotherDeviceDetected and NotGenuineDataDetected

For detailed API reference and examples, see ObscuredFile API documentation.

Key Capabilities

  • Encrypts payloads (optional) to hide sensitive information from plain-text inspection.
  • Validates integrity even when you disable encryption—cheaters cannot silently edit stored values.
  • Locks the file to a device ID or your own identifier. See Device lock for configuration tips.
  • Works with raw byte arrays so you can integrate any serialization stack.
Warning

Always call UnityApiResultsHolder.InitForAsyncUsage(true); from the main thread before interacting with ObscuredFile from background threads.

ObscuredFilePrefs

ObscuredFilePrefs builds on top of ObscuredFile and emulates the familiar PlayerPrefs API while automatically encrypting values:

Basic Usage

ObscuredFilePrefs.Init();
ObscuredFilePrefs.Set("coins", 100);
var coins = ObscuredFilePrefs.Get("coins", 0);

Advanced Features

  • Custom settings: Initialize with custom file name, encryption, and device lock settings
  • Data types: Supports all Unity serializable types including Vector3, Color, DateTime, byte arrays
  • Async operations: Use UnityApiResultsHolder.InitForAsyncUsage(true) for background operations
  • Key management: HasKey(), GetKeys(), DeleteKey(), DeleteAll()
  • Memory management: LoadPrefs(), Save(), UnloadPrefs() for memory control

For detailed API reference and examples, see ObscuredFilePrefs API documentation.

Example implementations

  • Basic usage: See ObscuredFilePrefsExamples.cs in Examples/API Examples/Scripts/Runtime/UsageExamples/
  • DOTS integration: See UIActionSystem.cs in Examples/DOTS ECS Examples/Scripts/UI/Systems/

Advantages

  • Supports all basic C# types, BigInteger, byte arrays, DateTime, and common Unity structs.
  • Emits events when data is tampered with or loaded from another device.
  • AutoSave feature enabled by default to prevent data loss on app quit (desktop) and app losing focus (mobile).
Tip

Mix regular PlayerPrefs and ObscuredFilePrefs judiciously. Encrypt only the values that matter to control performance overhead.

ObscuredPrefs

ObscuredPrefs provides a direct replacement for Unity's PlayerPrefs with built-in encryption and tamper detection:

using CodeStage.AntiCheat.Storage;

ObscuredPrefs.Set("currentLifeBarObscured", 88.4f);
var currentLifeBar = ObscuredPrefs.Get("currentLifeBarObscured", 0f);

// same as
ObscuredPrefs.Set<float>("currentLifeBarObscured", 88.4f);
var currentLifeBar = ObscuredPrefs.Get<float>("currentLifeBarObscured");

Key features:

  • Drop-in replacement for PlayerPrefs with automatic encryption
  • Extended type support including all basic C# types, BigInteger, byte[], DateTime, and Unity types
  • Device Lock support to prevent save sharing between devices
  • Migration tools to convert existing PlayerPrefs data automatically
  • Event system with NotGenuineDataDetected for tamper detection

For detailed API reference and examples, see ObscuredPrefs API documentation.

Example implementations

  • Basic usage: See ObscuredPrefsExamples.cs in Examples/API Examples/Scripts/Runtime/UsageExamples/
  • DOTS integration: See UIActionSystem.cs in Examples/DOTS ECS Examples/Scripts/UI/Systems/

Migration tips:

  • Replace PlayerPrefs calls with ObscuredPrefs throughout your project
  • Use preservePlayerPrefs flag to keep original PlayerPrefs keys during migration
  • Mix regular PlayerPrefs and ObscuredPrefs for different data types (use different key names)
  • Use ObscuredPrefs.DeleteAll() instead of PlayerPrefs.DeleteAll() to properly clear internals
Warning

ObscuredPrefs is slower than regular PlayerPrefs due to encryption overhead. Use it for sensitive data only, not for large datasets like maps or databases.

Prefs Editor

Use Tools > Code Stage > Anti-Cheat Toolkit > Prefs Editor to inspect and edit both PlayerPrefs and ObscuredPrefs directly in the editor.

Prefs Editor window

Highlights:

  • Add, edit, encrypt, and delete preferences without writing code
  • Filter and sort large datasets (50 records per page) with pagination support
  • Copy values to the clipboard and monitor progress when parsing large preference sets
  • Works on Windows, macOS, and Linux editors
  • Supports both PlayerPrefs and ObscuredPrefs in a unified interface
  • Overwrite confirmation and progress bars for large datasets (1000+ records)
  • Ignores Unity's internal prefs (UnitySelectMonitor, UnityGraphicsQuality, etc.)
Note

On Windows, prefs stored in Editor and standalone player are in different locations, so the Prefs Editor only works with Editor-saved preferences. Windows PlayerPrefs are stored in the registry at HKEY_CURRENT_USER\Software\[Your Company]\[Your Product].

Device lock

ObscuredFile, ObscuredFilePrefs, and ObscuredPrefs can restrict data to a device or custom ID via DeviceLockSettings. This prevents save games from being shared between devices.

Lock levels

  • None – data remains unlocked but can read both locked and unlocked data
  • Soft – existing data stays readable; all new saves lock to the current device
  • Strict – only accepts data locked to the current device; all new saves lock to the current device

Event handling

Subscribe to DataFromAnotherDeviceDetected to intercept foreign payloads:

  • In ObscuredPrefs: fires once per session
  • In ObscuredFile/ObscuredFilePrefs: fires every time a file is read

Platform considerations

Warning

iOS does not provide a reliable persistent device identifier. If you rely on Device Lock, set a stable, app-defined identifier via DeviceIdHolder.DeviceId (for example, an authenticated account/user ID). Plan recovery flows using DeviceLockTamperingSensitivity when an identifier changes (SIM swap, device restore, reinstall). See related notes in Troubleshooting.

Android: Device Lock may require additional permissions depending on your setup. See Permissions and compliance for symbols to avoid unnecessary permissions in builds where Device Lock or Time Cheating Detector are not used.

Performance optimization

  • Call DeviceIdHolder.ForceLockToDeviceInit() during loading screens to avoid CPU spikes on first device ID access
  • Use DeviceIdHolder.DeviceId to set custom identifiers (useful for server-side account systems)

Sensitivity control

When using Strict mode, you can adjust DeviceLockTamperingSensitivity:

  • Normal – default strict behavior, rejects foreign data and fires detection event
  • Low – detection event still fires but data remains readable
  • Disabled – no event fired, useful for data recovery when Device ID changes unexpectedly