L3S DRM Developer Guide
Upgrade Guide: Version 0.X → 1.X
This guide walks you through migrating an existing L3DRM 0.X integration to 1.X. The biggest change is that DRMPublish is now fully static and uses a builder pattern instead of struct constructors. Several new security features have also been added.
Before you begin: Download the latest 1.X release from the Downloads page. You will need the updated
includefolder andModuleDrmGaugefiles.
Step 1: Update Include Files
Replace your existing include folder with the one from the 1.X release. The folder contains updated headers, the new DRMCore2.a static library, and DRMCore2_debug.a which provides more verbose console logging for debugging.
Your Visual Studio project should already reference this folder (see C++ Integration). No path changes are needed unless you moved the folder.
Step 2: Update ModuleDrmGauge Files
Replace the following files in your aircraft's instrument folder with the new versions from the release:
ModuleDrmGauge.htmlModuleDrmGauge.jsModuleDrmGauge.css
Important: The last row of
ModuleDrmGauge.htmlcontains a<script>tag with two attributes that must be configured for your aircraft:
<script type="text/html" import-script="/Pages/VCockpit/Instruments/MyCompany/MyAircraft_DRM/ModuleDrmGauge.js" data-expected-product-name="My Aircraft"></script>import-script— The path toModuleDrmGauge.jsinside your aircraft's instrument folder. Update this so it matches your folder structure.data-expected-product-name— A prefix filter that must match the beginning of the.productName()value you set in C++. The gauge will only activate when the product name starts with this value.
For example, if you ship two variants with.productName("L3 707-100")and.productName("L3 707-200"), you can setdata-expected-product-name="L3 707"to match both variants with a single HTML file.
Hashes: If you use file-hash verification (Step 6), remember that editing
ModuleDrmGauge.htmlchanges its hash. Rebuild or update your hashes after modifying this file.
LVAR name change: In 1.X, the LVAR names now include a product-name suffix derived from
data-expected-product-name(spaces → underscores, non-alphanumeric characters except-,_,.are stripped).
L:DRM_UI_VISIBLE→L:DRM_UI_VISIBLE_<ProductName>L:DRM_ACTIVATED→L:DRM_ACTIVATED_<ProductName>For example,
data-expected-product-name="DRM Demo"producesL:DRM_UI_VISIBLE_DRM_DemoandL:DRM_ACTIVATED_DRM_Demo. Update any references in yourpanel.cfgvisibility conditions or ModelBehavior XML accordingly.
Step 3: Add Compiler Options
Add the following compiler flags to prevent internal symbols from being exported in the WASM binary:
/clang:-fvisibility=hidden /clang:-fvisibility-inlines-hiddenTo add these in Visual Studio:
- Right-click your project → Properties
- Set Configuration to All Configurations
- Navigate to Configuration Properties → C/C++ → Command Line
- Paste the flags into the Additional Options text box
- Click OK
Security — Very Important: These flags prevent internal symbols from being exported in the WASM binary. Without them, attackers can inspect your code more easily. Apply this to both the main WASM project and any class library projects (if applicable) — repeat the steps above for each project in your solution.
Step 4: Migrate to Static DRMPublish + Builder
In 0.X, DRMPublish was an instance you allocated on the heap. In 1.X it is fully static — no pointers, no new/delete, no null checks.
Old (0.X) — Struct Constructors + Instance
static DRMPublish* drmPublish;
DRMInputConstants drmConstants(
OBF("https://kdlkczwmscovjiftqlwq.supabase.co/functions/v1/auth3"))
"DRM Demo",
"^[A-Za-z0-9]{10}$",
OBF(".\SimObjects\...\SampleCommBus.wasm"),
DrmSucessEvent,
DrmErrorEvent,
DrmTamperEvent,
false
);
DRMInputVariables drmVariables(
"CLIENT_API_KEY",
"0.1.2 BETA",
hashesVector
);
drmPublish = new DRMPublish(drmConstants, drmVariables);New (1.X) — Builder Pattern
auto builder = DRMPublish::Builder()
.url(OBF("https://kdlkczwmscovjiftqlwq.supabase.co/functions/v1/"))
.productName("DRM Demo")
.productVersion("1.0.0")
.keyRegex("^[A-Za-z0-9]{10}$")
.wasmPath(OBF(".\SimObjects\...\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
.autoAbort(true)
.transparentSuccessScreen(false);
// Add file hashes here if using auto-generation (see Step 6)
builder.build();Note: The
_MARKETPLACE_PKGpreprocessor flag lets you build separate Marketplace/Xbox and Direct Sales variants from the same codebase. Each variant uses its ownclientApiKeyandkeylesssetting. Define the flag in your Marketplace build configuration's preprocessor definitions.
Tip: The builder exposes additional options such as
.autoAbort()and.transparentSuccessScreen(). These are explained in the C++ Integration guide.
Step 5: Update Callback Signatures
The callback function signatures have changed in 1.X. Note the fixed spelling of Success and the switch from pass-by-reference to pass-by-value.
Old (0.X)
void DrmSucessEvent(DRMSuccessResponse& response)
{
std::cout << "DRM Success";
}
void DrmFailEvent(DRMFailResponse& response)
{
std::cerr << "DRM FAIL";
}New (1.X)
void DrmSuccessEvent(SResponse response)
{
std::cout << "DRM Success";
}
void DrmErrorEvent(FResponse response)
{
std::cerr << "DRM Error";
}Breaking changes:
DrmSucessEvent→DrmSuccessEvent(spelling fix)DrmFailEvent→DrmErrorEvent(renamed)DRMSuccessResponse→SResponse(renamed)DRMFailResponse→FResponse(renamed)- Parameters changed from reference (
&) to value
Step 6: Migrate File Hash Verification
File hash verification is required for a secure integration. In 0.X, file hashes were added manually via structs. In 1.X you have two options: auto-generated hashes (recommended) or manual hashes on the builder.
Old (0.X)
DRMHashVerification gaugeHtml("...\ModuleDrmGauge.html", "hash1");
DRMHashVerification gaugeJs("...\ModuleDrmGauge.js", "hash2");
DRMHashVerification panel("...\panel.cfg", "hash3");
std::vector<DRMHashVerification> hashesVector = {
gaugeHtml, gaugeJs, panel
};New (1.X)
Choose one of the two options below:
Option A: Auto-Generated Hashes (Recommended)
Add a Pre-Build Event in Visual Studio:
$(ProjectDir)scriptsprebuild-msfs.bat $(ProjectDir) $(SolutionDir)This script auto-generates the file hashes before each build. See the demo project for a working example of this setup.
Add the new include:
#include "DRMFileHash.h"Pass hashes to the builder:
auto builder = DRMPublish::Builder() .url(...) // ... other builder calls ... for (const auto& [path, hash] : DRMFileHash::values) { builder.addFileHash(path, hash); } builder.build();
Tip: The included sample aircraft project demonstrates this setup end-to-end, including the prebuild script, folder structure, and builder integration. Use it as a reference when configuring auto-generated hashes for your own project.
Option B: Manual Hashes
Add hashes directly on the builder using full relative paths from your package root:
builder.addFileHash(OBF(".\html_ui\Pages\VCockpit\Instruments\MyCompany_CommBus_Aircraft_HtmlGauge\ModuleDrmGauge.html"), OBF("2f6d1643043b0135..."));
builder.addFileHash(OBF(".\html_ui\Pages\VCockpit\Instruments\MyCompany_CommBus_Aircraft_HtmlGauge\ModuleDrmGauge.js"), OBF("7c42d66ac2376105..."));
builder.addFileHash(OBF(".\SimObjects\Airplanes\MyCompany_CommBus_Aircraft\panel\panel.cfg"), OBF("32d651461b8dd0f9..."));Important: To obtain the hash values, use the included
GenerateHashes.batscript (found in thehtmlfolder of the release package). Place it alongside your files andHashGenerator.exe, then run it — the hash for each file will be printed in the console.
Step 7: Add Integrity Checks
1.X introduces anti-tamper integrity checks that you can place anywhere in your C++ code:
DRMPublish::Integrity1();
DRMPublish::Integrity2();
// ... up to Integrity5()- Call them periodically at different points in your code.
- Use different numbers at different call sites for better coverage.
- The same number can be reused at multiple sites.
Example: In the demo project,
Integrity1()is called inPRE_INSTALLandIntegrity2()is called inPRE_DRAW.
Step 8: Add AssureAuthenticated Checks
These checks verify that the user is authenticated and must only be placed in code paths that execute when the user is authenticated:
DRMPublish::AssureAuthenticated1();
// ... up to AssureAuthenticated5()Critical: If an
AssureAuthenticatedcall executes when the user is not authenticated, the WASM module will crash or the aircraft will be locked. Only place these in code paths guarded by a successful authentication check.
- Use different numbers at different call sites (same rules as Integrity checks).
- The same number can be reused at multiple sites.
Example: In the demo project,
AssureAuthenticated1()is called at the end ofPRE_DRAW, which only runs after authentication succeeds.
Step 9: Update IntervalUpdate + Shutdown
The update loop and cleanup are now static calls:
Old (0.X)
// Update loop
if (drmPublish)
{
drmPublish->IntervalUpdate(dTime);
}
// Shutdown
delete drmPublish;New (1.X)
// Update loop
if (DRMPublish::IsInitialized())
{
DRMPublish::IntervalUpdate(1);
}
// Shutdown
DRMPublish::Shutdown();Quick Reference
Side-by-side comparison of the old and new API:
| Area | 0.X (Old) | 1.X (New) |
|---|---|---|
| Declaration | static DRMPublish* drmPublish; | No declaration needed (static class) |
| Initialization | new DRMPublish(constants, variables) | DRMPublish::Builder().url(...).build(); |
| Success callback | void DrmSucessEvent(SResponse&) | void DrmSuccessEvent(SResponse) |
| Error callback | void DrmFailEvent(FResponse&) | void DrmErrorEvent(FResponse) |
| File hashes | Manual DRMHashVerification structs | .addFileHash() or auto-generated DRMFileHash |
| Update loop | drmPublish->IntervalUpdate(dTime) | DRMPublish::IntervalUpdate(1) |
| Null check | if (drmPublish) | if (DRMPublish::IsInitialized()) |
| Shutdown | delete drmPublish; | DRMPublish::Shutdown(); |
| Integrity checks | N/A | DRMPublish::Integrity1() – Integrity5() |
| Auth assurance | N/A | DRMPublish::AssureAuthenticated1() – 5() |
| Compiler flags | N/A | /clang:-fvisibility=hidden /clang:-fvisibility-inlines-hidden |