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 include folder and ModuleDrmGauge files.


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.html
  • ModuleDrmGauge.js
  • ModuleDrmGauge.css

Important: The last row of ModuleDrmGauge.html contains 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 to ModuleDrmGauge.js inside 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 set data-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.html changes 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_VISIBLEL:DRM_UI_VISIBLE_<ProductName>
  • L:DRM_ACTIVATEDL:DRM_ACTIVATED_<ProductName>

For example, data-expected-product-name="DRM Demo" produces L:DRM_UI_VISIBLE_DRM_Demo and L:DRM_ACTIVATED_DRM_Demo. Update any references in your panel.cfg visibility 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-hidden

To add these in Visual Studio:

  1. Right-click your project → Properties
  2. Set Configuration to All Configurations
  3. Navigate to Configuration PropertiesC/C++Command Line
  4. Paste the flags into the Additional Options text box
  5. 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_PKG preprocessor flag lets you build separate Marketplace/Xbox and Direct Sales variants from the same codebase. Each variant uses its own clientApiKey and keyless setting. 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:

  • DrmSucessEventDrmSuccessEvent (spelling fix)
  • DrmFailEventDrmErrorEvent (renamed)
  • DRMSuccessResponseSResponse (renamed)
  • DRMFailResponseFResponse (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)
  1. 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.

  2. Add the new include:

    #include "DRMFileHash.h"
  3. 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.bat script (found in the html folder of the release package). Place it alongside your files and HashGenerator.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 in PRE_INSTALL and Integrity2() is called in PRE_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 AssureAuthenticated call 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 of PRE_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:

Area0.X (Old)1.X (New)
Declarationstatic DRMPublish* drmPublish;No declaration needed (static class)
Initializationnew DRMPublish(constants, variables)DRMPublish::Builder().url(...).build();
Success callbackvoid DrmSucessEvent(SResponse&)void DrmSuccessEvent(SResponse)
Error callbackvoid DrmFailEvent(FResponse&)void DrmErrorEvent(FResponse)
File hashesManual DRMHashVerification structs.addFileHash() or auto-generated DRMFileHash
Update loopdrmPublish->IntervalUpdate(dTime)DRMPublish::IntervalUpdate(1)
Null checkif (drmPublish)if (DRMPublish::IsInitialized())
Shutdowndelete drmPublish;DRMPublish::Shutdown();
Integrity checksN/ADRMPublish::Integrity1()Integrity5()
Auth assuranceN/ADRMPublish::AssureAuthenticated1()5()
Compiler flagsN/A/clang:-fvisibility=hidden /clang:-fvisibility-inlines-hidden