L3S DRM Developer Guide
All required files for the C++/WASM module are located in the include folder.
Copy this folder to a known location of your choice.
Security Note: Make sure you're using the latest
include/files for enhanced security features. See the WASM Build & Security guide for important security improvements.
How It Works
Your C++/WASM code must pass a few parameters to the L3S DRM module using the Builder pattern.
This includes a DrmSuccessEvent() callback function.
Only when this function is called should you allow usage of your aircraft.
How you implement this is up to you.
Setting Up Visual Studio
The DRM Module is compiled as a static .wasm module in DRMCore2.a.
You need to link this file and reference the header files in Visual Studio:
Right-click the project containing the DRM integration → Properties
Ensure Configuration → All Configurations is selected.
Navigate to:
Configuration Properties → VC++ Directories
Include Directories: Add the path to theincludefolder.
Linker → General
Additional Library Directories: Add the path to theincludefolder.
Linker → Input
Additional Dependencies: AddDRMCore2.a
Click OK to apply.
Initializing DRM
At the top of your .cpp file:
#include "DRMCommon.h"
#include "DRMPublish.h"
#include "obfusheader.h" // For string obfuscation
#include "DRMFileHash.h" // For auto-generated file hashes (see File Hash Verification)Security Enhancement: Use the
OBF(...)macro to obfuscate sensitive strings. See the WASM Build & Security guide for details.
Callback Functions
Define the following DRM event handlers:
void DrmSuccessEvent(SResponse response)
{
// Called only when activation is successful.
std::cout << "DRM Success";
}
void DrmErrorEvent(FResponse response)
{
// Called when an error occurs during activation.
std::cerr << "DRM FAIL";
}
void DrmTamperEvent()
{
// Called when DRM tampering is highly likely.
// It's recommended to immediately terminate MSFS or disable the aircraft.
std::cerr << "DRM Tamper";
}DRM Initialization
Initialize the DRM using the Builder pattern. The initialization is guarded by IsInitialized(), so it is safe to call multiple times (e.g., from a callback that may fire more than once).
if (!DRMPublish::IsInitialized())
{
DRMPublish::Builder builder = DRMPublish::Builder()
// Required: server endpoint, product info, WASM path
.url(OBF("https://kdlkczwmscovjiftqlwq.supabase.co/functions/v1/"))
.productName(OBF("DRM Demo"))
.productVersion(OBF("Version X"))
.keyRegex("^[A-Za-z0-9]{10}$") // Client-side validation before sending to server
.wasmPath(OBF(".\SimObjects\Airplanes\MyCompany_CommBus_Aircraft\panel\SampleCommBus.wasm"))
.onSuccess(DrmSuccessEvent)
.onError(DrmErrorEvent)
.onTamper(DrmTamperEvent)
#ifdef _MARKETPLACE_PKG
.keyless(true) // MSFS Marketplace (incl. XBOX). No activation key needed
.clientApiKey(OBF("YOUR_MARKETPLACE_CLIENT_API_KEY"))
#else
.keyless(false) // Standalone activation-key-based auth
.clientApiKey(OBF("YOUR_DIRECT_SALES_CLIENT_API_KEY"))
#endif
#ifdef _DEBUG
.debug(true)
#endif
// Optional: behavior toggles
.autoAbort(false)
.transparentSuccessScreen(false);
// File-hash integrity checks (auto-generated by prebuild-msfs.bat and HashGenerator.exe)
for (const auto& [path, hash] : DRMFileHash::values) {
builder.addFileHash(path, hash);
}
builder.build();
}See the dedicated sections for keyRegex(), autoAbort(), and transparentSuccessScreen() below for details on the optional builder options.
Both Marketplace and Direct Sales builds require a Client API Key. You can create separate keys for each distribution method in the developer portal.
File Hash Verification
File hash verification detects whether an attacker has modified your aircraft files after release. The DRM module computes a hash of each registered file at runtime and compares it against the expected hash compiled into your WASM. If any file has been tampered with, activation will fail.
At a minimum, you should verify ModuleDrmGauge.html, ModuleDrmGauge.js, and panel.cfg. Any other files that contain critical code (e.g. custom .js files) should be added as well.
Option 1: Auto-generated hashes (recommended)
Use the DRMFileHash header together with the prebuild script to auto-generate file hashes at compile time. See the WASM Build & Security guide for the prebuild script setup.
for (const auto& [path, hash] : DRMFileHash::values) {
builder.addFileHash(path, hash);
}Option 2: Manual hashes
You can also add file hashes manually using OBF(...) for obfuscation:
builder.addFileHash(OBF(".\html_ui\Pages\VCockpit\Instruments\MyCompany_CommBus_Aircraft_HtmlGauge\ModuleDrmGauge.html"), OBF("hash1"));
builder.addFileHash(OBF(".\html_ui\Pages\VCockpit\Instruments\MyCompany_CommBus_Aircraft_HtmlGauge\ModuleDrmGauge.js"), OBF("hash2"));
builder.addFileHash(OBF(".\SimObjects\Airplanes\MyCompany_Wasm_Aircraft\common\panel\panel.cfg"), OBF("hash3"));Obtaining manual hash values:
Use the included GenerateHashes.bat script (found in the html folder of the release package). Place it alongside your gauge files and HashGenerator.exe, then run it. It will output the hash for each file in the folder.
- Copy
GenerateHashes.batandHashGenerator.exeinto the folder containing the files you want to hash. - Run
GenerateHashes.bat. The hash for each file will be printed in the console. - Copy the hash values into your code.
Important Note: If you modify a file after obtaining its hash, you will need to re-run the script to get the new hash. The auto-generated approach (Option 1) avoids this issue entirely.
DRM validation will begin automatically after
build(). Wait forDrmSuccessEvent()before unlocking aircraft features.
Builder Option: keyRegex()
Sets a regex pattern for client-side validation of activation keys before they are sent to the server. If the key does not match the pattern, the request is rejected locally without making a server call.
builder.keyRegex("^[A-Za-z0-9]{10}$");- Default: No validation (all keys are sent to the server)
- Use case: Prevents unnecessary server requests when the user enters an obviously invalid key (e.g., wrong length or invalid characters).
Builder Option: autoAbort()
When enabled, tampering detection will immediately abort/crash the WASM module. When disabled, the onTamper callback is called instead, giving you control over how to handle it.
builder.autoAbort(false); // Use onTamper callback instead of crashing- Default: Enabled (
true) — tampering causes an immediate crash - Use case:
autoAbort(true)— For aircraft with complex logic within WASM. Crashing the module effectively disables the aircraft.autoAbort(false)— For aircraft without much WASM logic. The DRM module will then try to block flight control inputs after some time.
Builder Option: transparentSuccessScreen()
When enabled, the DRM gauge will be transparent instead of continuously showing the activation success message.
builder.transparentSuccessScreen();- Default: Disabled (success message shown continuously)
- Use case: Enable this if you want the DRM gauge to become invisible after activation, allowing you to overlay it in front of your normal cockpit screen without it blocking the view.
Builder Option: debug()
When enabled, debug mode prints diagnostic information in the MSFS console, including file hashes if mismatched.
Do NOT enable in public releases!
The recommended approach is to add .debug(true) to the Builder, wrapped in an #ifdef _DEBUG block so it is automatically excluded from release builds:
#ifdef _DEBUG
.debug(true)
#endifWhen enabled, the MSFS console will display file hashes and other diagnostic information prefixed with [DRM].
For even more verbose console logging, you can also link against DRMCore2_debug.a instead of DRMCore2.a. This provides detailed logging of all DRM operations to the MSFS console.
IntervalUpdate()
This method should be called from your _system_update or _gauge_callback function:
if (DRMPublish::IsInitialized())
{
DRMPublish::IntervalUpdate(1);
}This function must be called regularly.
It's very lightweight and won't affect performance.
Integrity Checks
Integrity checks verify that your WASM module has not been tampered with at runtime. Unlike AssureAuthenticated, these can be placed anywhere in your code — even before DRM initialization or authentication. Place them in as many locations as possible to make bypassing more difficult.
DRMPublish::Integrity1();
DRMPublish::Integrity2();
DRMPublish::Integrity3();
DRMPublish::Integrity4();
DRMPublish::Integrity5();Example:
void OnStartup() {
DRMPublish::Integrity1();
// ... runs before DRM is even initialized
}
void OnGearSelected() {
DRMPublish::Integrity2();
// ... gear logic
}
void OnFlapsChanged(int position) {
DRMPublish::Integrity3();
// ... flaps logic
}Placement guidance: Spread these calls across different functions and systems in your codebase — startup routines, gauge callbacks, user actions like gear/flaps selection or button presses, etc. The more spread out they are, the harder it is for an attacker to bypass all of them.
AssureAuthenticated Checks
These checks will crash or block the aircraft if executed without the aircraft being activated first. They should only be placed in code paths where it's certain that the aircraft must have been activated (e.g., behind DRMPublish::IsAuthenticated() or in functions enabled via DrmSuccessEvent).
DRMPublish::AssureAuthenticated1();
DRMPublish::AssureAuthenticated2();
DRMPublish::AssureAuthenticated3();
DRMPublish::AssureAuthenticated4();
DRMPublish::AssureAuthenticated5();Example:
void DrmSuccessEvent(SResponse response) {
// Aircraft is now activated — enable protected systems
EnableAvionics();
EnableFlightModel();
}
void UpdateAvionics() {
DRMPublish::AssureAuthenticated1();
// ... avionics logic (only reachable after activation)
}
void UpdateFlightModel() {
DRMPublish::AssureAuthenticated2();
// ... flight model logic (only reachable after activation)
}Warning: Do not place these in code paths that can be reached before activation — this will crash or block the aircraft. Ensure they are only called after successful authentication.
Shutdown
Call Shutdown() when your module is unloaded or the aircraft is removed:
DRMPublish::Shutdown();