WASM Build & Security

This guide covers the essential build process and security considerations for implementing L3S DRM in your WASM module. These security measures are crucial for protecting your aircraft against tampering and reverse engineering.

Important: These security measures are highly recommended for any public release. With default compiler settings, the generated .wasm file tends to be fairly human-readable, making it easier for attackers to locate and modify critical DRM-related logic.


Security Implementation

1. Using the Latest Include Files

Ensure you're using the latest files from the include/ directory provided with L3S DRM. These files contain the most current security features and should be copied to your project's include path.


2. String Obfuscation

L3S DRM includes a header-only library (obfusheader.h) that enables simple string obfuscation in C++. This makes it significantly harder for attackers to identify sensitive information in your compiled WASM.

Usage:

  1. Add #include "obfusheader.h" to your C++ file
  2. Wrap sensitive strings in OBF(...), for example: OBF("\work\settings.cfg")

Recommended strings to obfuscate:

  • Activation URLs
  • Local file paths
  • Regex patterns

Example implementation:

#include "obfusheader.h"
std::string apiKey = OBF("YOUR_API_KEY");

3. Remove All "DRM" References from Your Binary

Your compiled .wasm file contains every string literal from your C++ code in plaintext. An attacker can open the file in a text editor (e.g. Notepad++) and search for "DRM" to instantly locate security-critical code paths.

Wrap every string containing "DRM" with OBF():

// BAD: plaintext "DRM" will appear in the .wasm binary
std::cout << "Starting DRM check..." << std::endl;

// GOOD: obfuscated at compile time, invisible in the binary
std::cout << OBF("Starting DRM check...") << std::endl;

Avoid naming your own functions or variables with "DRM" in the name. Function and variable names can end up in the binary. The compiler visibility flags (see below) hide most internal symbol names, but not all. Instead of InitializeDRM() or drmStatus, use ambiguous names that do not hint at the purpose of the code.

Verification: After building and running wasm-opt (see build steps below), open the final .wasm file in a text editor and search for DRM (case-insensitive). You should find zero matches. If any remain, either wrap the corresponding string with OBF() or rename the function/variable.


4. Compiler Visibility Flags

To reduce the number of exported symbols in your WASM module (making reverse engineering harder), add the following compiler flags:

/clang:-fvisibility=hidden /clang:-fvisibility-inlines-hidden

Where to set in Visual Studio:

  1. Right-click the project → Properties
  2. Navigate to C/C++ → Command Line
  3. Add the flags to Additional Options

Important: These flags must be applied to your main WASM project and any class library projects that are linked into it, if applicable.


5. Linker Export Flags

The compiler visibility flags alone do not shrink the export table: the MSFS SDK link step always passes --export-dynamic, which exports every default-visibility symbol (in practice around 1,800 libc++, allocator and runtime functions). Turn it off in the link step and export only the callbacks MSFS needs:

--no-export-dynamic --export=MyGauge_gauge_callback --export=MyGauge_mouse_callback %(AdditionalOptions)

Where to set in Visual Studio:

  1. Right-click the project → Properties
  2. Navigate to Linker → Command Line
  3. Add the flags to Additional Options for every configuration
  • Add one --export= for everyMSFS_CALLBACK function in your module (gauge callbacks such as _gauge_callback or _gauge_init / _gauge_update / _gauge_draw / _gauge_kill, mouse callbacks, module_init / module_deinit). A missing export means the gauge or module silently does not load.
  • Use bare linker syntax (--no-export-dynamic), not -Wl,--no-export-dynamic: the SDK calls wasm-ld directly.
  • The SDK's own exports (malloc, free, __wasm_call_ctors, the memory and the function table) are still added by the SDK.
  • Check the result: afterwards the module should export only a few dozen names, not thousands.

6. Build Configuration Setup

It's recommended to set up separate build configurations for Marketplace (incl. XBOX) and Direct Sales releases. The _MARKETPLACE_PKG compiler flag should be defined for Marketplace builds.

This enables conditional compilation, allowing you to include or exclude certain security features based on your distribution method.


Complete Build Process

1. Build the WASM file

Compile your .wasm file using your standard build process.

2. Optimize the WASM file (optional but highly recommended)

This step makes reverse engineering more difficult:

wasm-opt INPUT_FILE.wasm -o optimized.wasm ^
  --strip-debug ^
  --strip-dwarf ^
  --strip-producers

Tip: This step is easy to automate using a .bat or build script. See the automation examples below.

No signing step since 3.0.0: local WASM signing was removed and WasmSigner.exe is no longer needed. If your post-build script still runs it, remove that step. The server checks the hash of your .wasm at activation when you set wasmPath() on the builder.


Build Automation Examples

No pre-build step is needed: file hashes are computed at activation and verified server-side. See File Hash Verification.

Visual Studio Post-Build Event

For Visual Studio projects, you can automate the optimization and copy process using a post-build event. Add this to your project configuration:

Post-Build Event Command:

$(ProjectDir)scripts\postbuild-msfs.bat $(SolutionDir) $(OutDir)

Example post-build script (postbuild-msfs.bat):

@echo off
setlocal enabledelayedexpansion

echo ---- BEGIN POST BUILD SCRIPT ----
echo solution dir = %1%
echo output dir = %2%

cd /d "%~dp0"
if errorlevel 1 exit /b 1

REM Find the first .wasm file
set "WASM_FILE="
for %%F in ("%2%\*.wasm") do (
    set "WASM_FILE=%%~fF"
    goto :found_wasm
)

echo No .wasm file found in %2%
exit /b 1

:found_wasm
echo wasm file = !WASM_FILE!

echo -- WASM-OPT ---
wasm-opt "!WASM_FILE!" -o "!WASM_FILE!" ^
  --strip-debug ^
  --strip-dwarf ^
  --strip-producers
if errorlevel 1 exit /b 1

echo -- COPY INTO AIRCRAFT SOURCE FOLDER --
copy /Y "!WASM_FILE!" "%1%..\..\PackageSources\SimObjects\Airplanes\MyCompany_CommBus_Aircraft\panel\"
if errorlevel 1 exit /b 1

echo ---- END POST BUILD SCRIPT ----

Tip: This script automatically finds the .wasm file, optimizes it, and copies it to your aircraft package folder, all in one step!


Marketplace/Xbox vs Direct Sales Summary

FeatureMarketplace BuildDirect Sales Build
Keyless ModeSet to trueSet to false

Troubleshooting

String obfuscation not working

Ensure you're including obfusheader.h and wrapping strings with OBF(...). Verify by inspecting the compiled .wasm file in a text editor for unobfuscated strings.

Build errors with visibility flags

Make sure the flags are added to Additional Options under C/C++ → Command Line, not under other compiler settings. Also ensure the flags are set for all linked projects.