L3S DRM Developer Guide
Upgrade Guide: Version 1.0.X → 1.1.X
This guide walks you through upgrading an existing L3DRM 1.0.X integration to 1.1.X. The main additions are anti-crack protection APIs that make bypassed DRM cause broken aircraft behavior — rather than just failing an authentication check that can be patched out.
Why this matters: A partial crack of a DRM-protected aircraft demonstrated that the existing DRM implementation — which simply called DRM check functions — could be bypassed by removing those calls from the WASM binary file. The new methods laid out below make critical calculations depend on DRM state, so removing them would break aircraft behavior.
Step 1: Update Include Files & Tools
Replace your existing include folder with the one from the 1.1.X release. This contains updated headers and the new DRMCore2.a static library with the anti-crack APIs.
Also replace HashGenerator.exe and WasmSigner.exe in your project's scripts folder with the versions from the Tools/ folder of the 1.1.X release.
Step 2: Update ModuleDrmGauge.js
Replace ModuleDrmGauge.js in your aircraft's instrument folder with the new version from the html folder of the 1.1.X release.
Note:
ModuleDrmGauge.htmlandModuleDrmGauge.cssare unchanged in 1.1.X — you do not need to replace them.
Step 3: Remove VerifySignature (Deprecated)
DRMPublish::VerifySignature() is deprecated in 1.1.X and can be removed from your code. Signature verification is now handled internally by the library — there is no need to call it manually.
Before (1.0.X)
DRMPublish::VerifySignature(OBF(".\SimObjects\...\SampleCommBus.wasm"));After (1.1.X)
// Remove the VerifySignature call entirely.
// Signature verification is now handled internally.Tip: The method still exists for backward compatibility, but calling it is unnecessary. Removing it simplifies your code.
Step 4: Add Anti-Crack Protection — ConfirmAuth
DRMPublish::ConfirmAuth() is a value passthrough gate. It returns the real value when the user is authenticated, or a developer-specified default when not. This lets you protect any runtime-computed value by routing it through the DRM.
How it works
- Authenticated: returns the
valueparameter unchanged - Not authenticated (or cracked): returns the
defaultValueparameter
Available overloads
static int ConfirmAuth(int value, int defaultValue);
static float ConfirmAuth(float value, float defaultValue);
static double ConfirmAuth(double value, double defaultValue);
static bool ConfirmAuth(bool value, bool defaultValue);
static const char* ConfirmAuth(const char* value, const char* defaultValue);Example: IAS Hold autopilot speed
Protect an autopilot IAS hold value so a cracked copy always gets 0:
// Without protection:
double iasHoldValue = getIASHoldSpeed();
// With ConfirmAuth protection:
double iasHoldValue = DRMPublish::ConfirmAuth(getIASHoldSpeed(), 0.0);Result: An authenticated user gets the real IAS hold speed. A cracked copy gets
0.0, causing the autopilot to behave incorrectly without any obvious error message.
Development bypass pattern
During development you may want to bypass DRM checks so your code works without activation. Use a preprocessor flag:
#ifndef BYPASS_ACTIVATION
iasHoldValue = DRMPublish::ConfirmAuth(iasHoldValue, 0.0);
#endifDefine BYPASS_ACTIVATION in the preprocessor definitions of any build configuration where you want to bypass activation. Make sure to remove it for Release builds.
Step 5: Add Anti-Crack Protection — GetAuthMultiplier
DRMPublish::GetAuthMultiplier() returns a floating-point multiplier that is 1.0f when authenticated and 0.0f when not (or garbage if the DRM state is tampered with).
static float GetAuthMultiplier();Example: EPR (Engine Pressure Ratio) display
Multiply an engine display value by the auth multiplier. This calculation is for the engine display only, so a cracked copy will indicate EPR 0:
double epr = computeEPR(n1, n2, ambientTemp);
// Protect with GetAuthMultiplier:
epr *= DRMPublish::GetAuthMultiplier();Why this works: Multiplying by
0silently zeroes out the display value. The EPR gauge will show 0, but there is no error message or obvious DRM indication — the instrument simply reads incorrectly. If the DRM state is tampered with, the multiplier may return unpredictable garbage values, making cracking even harder.
Tip: Apply
GetAuthMultiplier()to multiple independent calculations (thrust, fuel flow, hydraulic pressure, etc.) so that cracking requires patching every single one.
Step 6: Add Anti-Crack Protection — GetAuthValue (Builder Registration)
DRMPublish::GetAuthValue() is the most secure method for protecting static or constant values. Values are registered at initialization time via the Builder, stored in an internal encrypted registry, and retrieved at runtime by key. The real values never appear as plain literals in your code.
Builder side — register values at init
Use .addAuthValue() on the builder to register key-value pairs:
auto builder = DRMPublish::Builder()
.url(...)
.productName(...)
// ... other builder calls ...
.addAuthValue(OBF("FILL_COLOR_R"), 10)
.addAuthValue(OBF("FILL_COLOR_G"), 190)
.addAuthValue(OBF("FILL_COLOR_B"), 60)
.addAuthValue(OBF("MAX_MACH"), 0.82f);Runtime side — retrieve values
Use DRMPublish::GetAuthValue() with the same key and a default fallback:
int r = DRMPublish::GetAuthValue(OBF("FILL_COLOR_R"), 0);
int g = DRMPublish::GetAuthValue(OBF("FILL_COLOR_G"), 0);
int b = DRMPublish::GetAuthValue(OBF("FILL_COLOR_B"), 0);
float maxMach = DRMPublish::GetAuthValue(OBF("MAX_MACH"), 0.0f);Available overloads
// Builder registration
Builder& addAuthValue(const std::string& key, int value);
Builder& addAuthValue(const std::string& key, float value);
Builder& addAuthValue(const std::string& key, double value);
Builder& addAuthValue(const std::string& key, const std::string& value);
// Runtime retrieval
static int GetAuthValue(const std::string& key, int defaultValue);
static float GetAuthValue(const std::string& key, float defaultValue);
static double GetAuthValue(const std::string& key, double defaultValue);
static std::string GetAuthValue(const std::string& key, const std::string& defaultValue);
static const char* GetAuthValue(const std::string& key, const char* defaultValue);How it works: When authenticated,
GetAuthValue()returns the value registered via the Builder. When not authenticated (or cracked), it returns thedefaultValueyou specify. A typo in the key name also returns the default silently (with a warning in the debug log).
Why this is the most secure option: The real values are stored in the DRM's internal registry and never appear as plain literals in your compiled code. An attacker examining the WASM binary cannot simply search for the constant — they would need to reverse-engineer the DRM registry to extract it.
Best for: Constants that should be wrong if DRM is bypassed — colors, limits, tuning parameters, configuration strings, feature flags.
Choosing the Right Method
Use the following table to decide which anti-crack method to use for each value:
| Method | Best for | When not authenticated |
|---|---|---|
ConfirmAuth() | Runtime-computed values (sensor readings, calculations) | Returns developer-specified default |
GetAuthMultiplier() | Multiplying into existing calculations (thrust, fuel, pressure) | Returns 0.0f (or garbage if tampered) |
GetAuthValue() | Static constants (colors, limits, config strings) | Returns developer-specified default; real value hidden from binary |
Tip: Use a mix of all three methods throughout your codebase for maximum protection. The more places an attacker needs to patch, the harder it becomes to produce a working crack.
Need help? For a complete working example of all these APIs, see the Demo Aircraft page. The existing 0.X → 1.0.X Upgrade Guide covers the initial migration from the legacy API.