L3S DRM Developer Guide
Upgrade Guide: Version 1.1.X → 2.0.X
This guide walks you through upgrading an existing L3DRM 1.1.X integration to 2.0.X. The headline change is that GetAuthValue() is now backed by online, server-delivered data — auth values are no longer compiled into your WASM binary at all. There is also a new DRM server URL, a reworked server-side file-hash integrity system, and changed builder options.
Why this matters: In 1.1.X, auth values were registered in C++ via
.addAuthValue(), which meant the real values still existed as literals in the compiled.wasm. In 2.0.X the values live on the DRM Portal and are delivered encrypted at activation time. A cracker inspecting the binary can no longer harvest them — they simply are not there.
Step 1: Update Include Files, Tools & Gauge Files
Replace your existing include folder with the one from the 2.0.X release. This contains updated headers and the new DRMCore2.a and DRMCore2_debug.a static libraries. DRMCore2_debug.a provides more verbose console logging for debugging.
Replace WasmSigner.exe in your project's scripts folder with the version from the Tools/ folder of the 2.0.X release. HashGenerator.exe is no longer used in 2.0.X — file hashes are computed automatically at activation (see Step 6). You can delete HashGenerator.exe and any prebuild hash-generation script from your scripts folder.
Replace the ModuleDrmGauge files in your aircraft's instrument folder with the new versions from the html folder of the 2.0.X release:
ModuleDrmGauge.htmlModuleDrmGauge.jsModuleDrmGauge.css
Important: After replacing
ModuleDrmGauge.html, re-edit the<script>tag at the end of the file — theimport-scriptpath anddata-expected-product-nameattributes must again match your aircraft. The fresh file ships with placeholder values.
Hashes: Editing
ModuleDrmGauge.htmlchanges its hash. In 2.0.X you no longer regenerate hashes by hand — the new hash is computed at activation and handled server-side. See Step 6.
Step 2: Update the DRM Server URL
The DRM server has moved to a dedicated domain. Update the .url() call on your builder.
Before (1.1.X)
.url(OBF("https://kdlkczwmscovjiftqlwq.supabase.co/functions/v1/"))After (2.0.X)
.url(OBF("https://sapi.drm.land3simulations.com/functions/v1/"))Step 3: Migrate Auth Values to the DRM Portal — Most Important
This is the most important DRM feature in 2.0.X. Online auth values are the single strongest anti-crack mechanism the library offers: the protected values never exist in your shipped binary, so there is nothing for an attacker to find, patch, or copy.
In 1.1.X you registered values in code with .addAuthValue() on the builder. In 2.0.X that method is removed. Instead, you define each auth value on the DRM Portal. The server delivers them encrypted at activation time, and your code reads them at runtime via GetAuthValue() exactly as before.
Before (1.1.X) — registered in code
auto builder = DRMPublish::Builder()
.url(...)
// ... other builder calls ...
.addAuthValue(OBF("FILL_COLOR_R"), 10)
.addAuthValue(OBF("FILL_COLOR_G"), 190)
.addAuthValue(OBF("MAX_MACH"), 0.82f);After (2.0.X) — defined on the DRM Portal
auto builder = DRMPublish::Builder()
.url(...)
// ... other builder calls ...
// No addAuthValue() calls. Values are defined on the DRM Portal.
builder.build();Migration steps:
- For every
.addAuthValue("KEY", value)call you currently have, create a matching entry on the DRM Portal for your project, using the same key name and value. - Delete all
.addAuthValue(...)calls from your builder — the method no longer exists and will not compile. - Leave your runtime
GetAuthValue()calls unchanged. The retrieval API is identical.
Runtime retrieval
int r = DRMPublish::GetAuthValue(DRM_AUTHKEY("FILL_COLOR_R"), 0);
float maxMach = DRMPublish::GetAuthValue(DRM_AUTHKEY("MAX_MACH"), 0.0f);
const std::string& edition =
DRMPublish::GetAuthValue(DRM_AUTHKEY("edition"), std::string("demo"));How it works: When authenticated,
GetAuthValue()returns the value defined on the Portal. When not authenticated (or cracked), it returns thedefaultValueyou specify. A key that is not defined on the Portal also returns the default silently, with a warning written to the debug log.
Key form (2.0.4+): the examples above use the compile-time-keyed
DRM_AUTHKEY("KEY")macro — an O(1) lookup with no per-call string build or hashing, safe to call every frame. Your existingOBF("KEY")calls still compile and work unchanged (they build, decrypt, and hash the key string on every call), so switch only the hot per-frame sites. Same Portal names, same values, no Portal change. SeeDRM_AUTHKEY()on the C++ Integration page.
Step 4: Use Array Auth Values (New)
2.0.X adds array auth values. An auth value defined on the Portal can hold multiple numeric elements, retrieved by index:
static int GetAuthValue(const std::string& key, size_t index, int defaultValue);
static float GetAuthValue(const std::string& key, size_t index, float defaultValue);
static double GetAuthValue(const std::string& key, size_t index, double defaultValue);
static size_t GetAuthValueArrayCount(const std::string& key);Example: an RGB color array
// Portal key "FILL_COLOR_RGB" holds [10, 190, 60]
int r = DRMPublish::GetAuthValue(DRM_AUTHKEY("FILL_COLOR_RGB"), 0, 250);
int g = DRMPublish::GetAuthValue(DRM_AUTHKEY("FILL_COLOR_RGB"), 1, 0);
int b = DRMPublish::GetAuthValue(DRM_AUTHKEY("FILL_COLOR_RGB"), 2, 0);Multi-dimensional arrays
The index formula below is only needed for multi-dimensional tables. For a simple 1D array (like the RGB example above), just pass the plain element index — no calculation required.
Arrays are stored flat. For a 2D table of cols columns, pass index = row * cols + col (row-major):
// 3x3 table on Portal key "BOX_COLORS"
int v = DRMPublish::GetAuthValue(DRM_AUTHKEY("BOX_COLORS"), row * 3 + col, 120);Use GetAuthValueArrayCount() to iterate without hardcoding the size:
size_t n = DRMPublish::GetAuthValueArrayCount(DRM_AUTHKEY("eng_fan_cr"));
for (size_t i = 0; i < n; ++i)
{
double v = DRMPublish::GetAuthValue(DRM_AUTHKEY("eng_fan_cr"), i, 0.0);
}Returns: The element at
indexwhen authenticated; thedefaultValuewhen unauthenticated, the key is missing, orindexis out of range.GetAuthValueArrayCount()returns0when unauthenticated or the key is not found.
Step 5: Optional — EULA / Terms Page (New Builder Option)
The builder now supports an .eulaUrl() option. If set, the user must accept the linked EULA/Terms page on first launch before DRM authentication proceeds. Acceptance is remembered via a marker file in the work folder.
auto builder = DRMPublish::Builder()
.url(...)
// ... other builder calls ...
.eulaUrl(OBF("https://www.example.com/terms"));Optional: Omit
.eulaUrl()entirely if you do not need an acceptance gate. Behavior is then identical to 1.1.X.
Step 6: File Hash Integrity Verification (Changed)
2.0.X reworks how DRM-protected files (the .wasm module and any .js/.html files you list) are integrity-checked. In 1.1.X you precomputed each file's hash with HashGenerator.exe and passed it to .addFileHash(file, hash); verification happened locally inside the WASM. In 2.0.X you pass only the file path — hashes are computed at activation and verified server-side.
Why this matters: A local hash compare can be patched out of the binary. Moving the check server-side means the verdict is no longer made on the user's machine, and the accepted hash set is signed by the server and re-checked offline against the cached activation.
Before (1.1.X) — path + precomputed hash
builder.addFileHash(OBF(".\\path\\to\\ModuleDrmGauge.js"),
OBF("480a494f3ecddf8a93851248b3ed6cdb..."));After (2.0.X) — path only
builder.addFileHash(OBF(".\\path\\to\\ModuleDrmGauge.js"));Migration steps:
- Change every
.addFileHash(file, hash)call to the single-argument.addFileHash(file)— drop the hash argument. - Delete
HashGenerator.exeand any prebuild hash-generation script. The generatedDRMFileHash.h/DRMFileHash.cpppair is no longer needed — list the file paths directly in your builder calls. - Leave the
.wasmfile itself as-is — its hash is always checked; you do not list it viaaddFileHash().
Removed builder option:
.debug()no longer exists in 2.0.X. Delete any.debug(...)call from your builder or it will not compile. Use theDRMCore2_debug.alibrary for verbose logging instead.
How verification works
On each activation the client computes the hash of every listed file (and the .wasm) and sends them to the DRM server. When hash validation is required, a hash must be marked valid on the DRM Portal to pass — with one exception: a hash first submitted by a whitelisted activation key is accepted automatically. The first username that submitted each hash is recorded so you can see who introduced a tampered file.
Enable hash validation on your Client API keys
Hash validation is a per-Client-API-key setting. After migrating to 2.0.X, turn it ON for every Client API key that serves a DRM Version 2 build — otherwise submitted hashes are recorded but never enforced, and a tampered file would still activate.
Caution — this affects builds already in the field: a Client API key is shared by every release that uses it. Turning hash validation ON applies immediately to your existing 1.1.X releases too, and their files have no valid hashes on the Portal yet — so those users would suddenly fail activation.
To avoid breaking shipped products, create new Client API keys for the DRM 2 update and enable hash validation only on those. Leave your old keys (used by 1.1.X releases) untouched, and point only your new 2.0.X builds at the new keys.
Developing with hash validation on: Use a whitelisted activation key for your own builds. Hashes first seen from a whitelisted key are accepted automatically and marked valid, so a new
.wasmor edited.js/.htmlnever blocks you during development — no manual whitelisting step.
Whitelist a key: Developer Portal → Activation Keys → search for the key → tick the WL checkbox on its row.
Use whitelisted keys carefully: a whitelisted key is not tied to one user — it can be activated by multiple users, and each one auto-accepts whatever hashes they submit. If such a key leaks, a tampered file from an attacker would be whitelisted automatically. Whitelist only keys you fully control (your own build machines) and revoke them once no longer needed.
Manage hashes on the Portal: The Client API Keys settings dialog lists every submitted hash, which file it belongs to, and the user that first submitted it. Mark a hash valid to whitelist it for non-whitelisted (end-user) keys.
Quick Reference
| Area | 1.1.X (Old) | 2.0.X (New) |
|---|---|---|
| Server URL | ...supabase.co/functions/v1/ | https://sapi.drm.land3simulations.com/functions/v1/ |
| Auth value source | .addAuthValue() in C++ (compiled into WASM) | Defined on DRM Portal, delivered encrypted at activation |
| Builder registration | .addAuthValue(key, value) | Removed — no build-time registration |
| Value retrieval | GetAuthValue(key, default) | Unchanged |
| Per-frame value lookup | GetAuthValue(OBF(key), default) (string built & hashed each call) | GetAuthValue(DRM_AUTHKEY(key), default) — compile-time key, O(1) (2.0.4+) |
| Array auth values | N/A | GetAuthValue(key, index, default), GetAuthValueArrayCount(key) |
| EULA gate | N/A | .eulaUrl(...) builder option |
| File hash registration | .addFileHash(file, hash) (precomputed hash) | .addFileHash(file) (path only) |
| File hash verification | Local compare; hashes baked into the WASM | Server-side; Portal whitelist (whitelisted keys auto-accept) |
HashGenerator.exe | Required (prebuild step) | Removed — hashes computed at activation |
| Debug logging | .debug() builder option | Removed — use DRMCore2_debug.a |
Need help? For a complete working example of all these APIs, see the Demo Aircraft page. Earlier migrations are covered by the 0.X → 1.0.X and 1.0.X → 1.1.X guides selectable at the top of this page.