L3S DRM Developer Guide
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:
- Add
#include "obfusheader.h"to your C++ file - 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.wasmfile in a text editor and search forDRM(case-insensitive). You should find zero matches. If any remain, either wrap the corresponding string withOBF()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-hiddenWhere to set in Visual Studio:
- Right-click the project → Properties
- Navigate to C/C++ → Command Line
- 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:
- Right-click the project → Properties
- Navigate to Linker → Command Line
- Add the flags to Additional Options for every configuration
- Add one
--export=for everyMSFS_CALLBACKfunction in your module (gauge callbacks such as_gauge_callbackor_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 callswasm-lddirectly. - 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-producersTip: This step is easy to automate using a
.bator build script. See the automation examples below.
No signing step since 3.0.0: local WASM signing was removed and
WasmSigner.exeis no longer needed. If your post-build script still runs it, remove that step. The server checks the hash of your.wasmat activation when you setwasmPath()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
| Feature | Marketplace Build | Direct Sales Build |
|---|---|---|
| Keyless Mode | Set to true | Set 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.