Safe, idiomatic Rust bindings for the Microsoft Flight Simulator 2024 (MSFS 2024) WASM SDK.
Write MSFS gauges and systems in Rust with safe, idiomatic wrappers over the entire WASM SDK surface — SimVars, Comm Bus, async HTTP, file I/O, NanoVG rendering, simulation events, the flow lifecycle, map view, airport charts, VFX, and the planned route.
📖 Full docs: https://infinity-simulations.com/docs/developer/getting-started
This repo publishes four crates to crates.io. As a user you only ever depend on the first one and install the last one — the other two are pulled in automatically.
| Crate | crates.io | Role |
|---|---|---|
infinity-rs |
infinity-rs |
Main bindings crate — what you put in your Cargo.toml |
infinity_rs_derive |
infinity_rs_derive |
Proc-macros (VarStruct, …) — re-exported from infinity_rs under the derive feature |
infinity_rs_sdk |
infinity_rs_sdk |
Build-script helper that locates the MSFS 2024 SDK on disk |
infinity-msfs |
infinity-msfs |
CLI: SDK installer + WASM build/packaging — cargo install infinity-msfs |
The lib crate is named
infinity-rs(Rust path:infinity_rs). It used to bemsfs, which collided with the FlyByWire crate of the same name; the rename in0.2.0is intentionally breaking. Update imports fromuse msfs::...touse infinity_rs::..., andmsfs::export_gauge!/msfs::export_system!toinfinity_rs::export_gauge!/infinity_rs::export_system!.
| Requirement | Notes |
|---|---|
Rust 1.85+ with wasm32-wasip1 target |
rustup target add wasm32-wasip1 |
clang + llvm-ar |
Required for WASM compilation; install via LLVM or your system package manager |
wasm-opt |
Part of Binaryen; only required when wasm_opt.enabled = true in your config |
The MSFS 2024 SDK itself is fetched on demand by the infinity-msfs CLI (see below) — works on Windows, macOS, and Linux. You don't need to install the SDK manually unless you want to point at an existing install via MSFS2024_SDK.
The walkthrough below is the short version. For tutorials, configuration reference, and end-to-end project setup, see the full developer docs at https://infinity-simulations.com/docs/developer/getting-started.
cargo install infinity-msfsThis gives you infinity-msfs build, infinity-msfs sdk install, infinity-msfs doctor, and the project commands.
[dependencies]
infinity-rs = "0.2"That pulls everything you need by default — the WASM-runtime APIs and the #[derive(VarStruct)] macro are re-exported under infinity_rs::*.
infinity-msfs buildOn the first build, if no SDK is installed, the CLI prompts to download it from sdk.flightsimulator.com. Re-run infinity-msfs sdk install --force later to upgrade. To use an existing SDK install instead, set MSFS2024_SDK to its root path.
| Feature | Default | What it enables |
|---|---|---|
wasm |
yes | WASM-host runtime APIs (NVG, comm bus, IO, network, vars, gauge/system entry points). Disable for native-only builds. |
derive |
yes | Re-exports the infinity_rs_derive proc-macros (VarStruct, etc.). |
simconnect |
no | SimConnect bindings. Works on both WASM and native Windows. |
Common combinations:
# WASM gauge or system (the default)
infinity-rs = "0.2"
# Native SimConnect tool, no WASM target
infinity-rs = { version = "0.2", default-features = false, features = ["simconnect"] }
# WASM module that also uses SimConnect
infinity-rs = { version = "0.2", features = ["simconnect"] }In code:
use infinity_rs::prelude::*;The build tools supplied with the project compile Rust WASM targets, optionally run wasm-opt, and can list the configured projects before building. Copy infinity-msfs.toml.example to infinity-msfs.toml in your project root and adjust the package names and copy rules for your own workspace.
[build]
target = "wasm32-wasip1"
out_dir = "build/msfs"
copy = [
{ from = "manifest.json", to = "build/msfs/manifest.json" },
{ from = "layout.json", to = "build/msfs/layout.json" }
]
[[packages]]
package = "demo-gauge"
bin = "demo_gauge"
out_name = "demo-gauge.wasm"
[[packages]]
package = "demo-system"
bin = "demo_system"
out_name = "demo-system.wasm"
out_dir = "build/msfs/systems"
[wasm_opt]
enabled = true
args = [
"-O1",
"--signext-lowering",
"--enable-bulk-memory",
"--enable-nontrapping-float-to-int",
]
[scripts]
pre_build = [
"cargo fmt --check"
]
post_build = [
"python ./tools/fix_layout.py"
]some example build commands:
infinity-msfs projects
infinity-msfs list-projects
infinity-msfs build
infinity-msfs build --release
infinity-msfs build --release --no-wasm-opt
infinity-msfs build --only demo-gauge --release
infinity-msfs build --verboseEverything in MSFS runs as either a System or a Gauge. Implement the corresponding trait and export it with the matching macro.
System — logic-only, no rendering:
use infinity_rs::prelude::*;
pub struct MySystem { /* ... */ }
impl System for MySystem {
fn init(&mut self, ctx: &Context, install: &SystemInstall) -> bool { true }
fn update(&mut self, ctx: &Context, dt: f32) -> bool { true }
fn kill(&mut self, ctx: &Context) -> bool { true }
}
infinity_rs::export_system!(
name = my_system,
state = MySystem,
ctor = MySystem::new(),
);Gauge — rendered panel element with optional mouse input:
use infinity_rs::prelude::*;
pub struct MyGauge { /* ... */ }
impl Gauge for MyGauge {
fn init(&mut self, ctx: &Context, install: &mut GaugeInstall) -> bool { true }
fn update(&mut self, ctx: &Context, dt: f32) -> bool { true }
fn draw(&mut self, ctx: &Context, draw: &mut GaugeDraw) -> bool { true }
fn kill(&mut self, ctx: &Context) -> bool { true }
fn mouse(&mut self, _ctx: &Context, x: f32, y: f32, flags: i32) { /* optional */ }
}
infinity_rs::export_gauge!(
name = my_gauge,
state = MyGauge,
ctor = MyGauge::new(),
);The macros emit the correctly named extern "C" entry points expected by the simulator.
Read and write simulation variables via AVar (A-vars) and LVar (L-vars).
use infinity_rs::vars::{AVar, LVar};
// A-var: read-only aircraft variable
let airspeed = AVar::new("AIRSPEED INDICATED", "Knots")?;
let kts: f64 = airspeed.get()?;
// L-var: readable and writable local variable
let mut flag = LVar::new("L:MY_GAUGE_ACTIVE", "Bool")?;
flag.set(1.0)?;
let val = flag.get()?;Indexed A-vars (e.g. per-engine data):
use infinity_rs::vars::{AVar, VarParamArray1};
let eng_rpm = AVar::new("GENERAL ENG RPM", "RPM")?;
let rpm = eng_rpm.get_with(VarParamArray1::new(1), Default::default())?; // engine 1Bundle multiple vars into a single struct and snapshot them all at once:
use infinity_rs::VarStruct;
#[derive(Debug, Clone, Copy, VarStruct)]
struct FlightData {
#[var(name = "A:PLANE ALTITUDE", unit = "Feet", kind = "A")]
altitude_ft: f64,
#[var(name = "A:PLANE HEADING DEGREES TRUE", unit = "Degrees", kind = "A")]
heading_deg: f64,
#[var(name = "A:GENERAL ENG RPM", unit = "RPM", kind = "A", index = 1)]
eng1_rpm: f64,
#[var(name = "L:MY_CUSTOM_VALUE", unit = "Number", kind = "L")]
custom: f64,
}
let snapshot = FlightData::read()?;
println!("Alt: {} ft Hdg: {}° ENG1: {} RPM", snapshot.altitude_ft, snapshot.heading_deg, snapshot.eng1_rpm);Send and receive binary messages between WASM modules, JavaScript, and the sim.
use infinity_rs::prelude::*;
// Subscribe to a named event
let _sub = Subscription::subscribe("my.module/event", |bytes| {
println!("Received {} bytes", bytes.len());
})?;
// Broadcast a message
let payload = 42u32.to_le_bytes();
commbus_call("my.module/event", &payload, BroadcastFlags::JS | BroadcastFlags::WASM);Broadcast flags:
| Flag | Target |
|---|---|
BroadcastFlags::JS |
JavaScript gauges |
BroadcastFlags::WASM |
Other WASM modules |
BroadcastFlags::WASM_SELF |
This WASM module itself |
BroadcastFlags::ALL |
Everyone |
BroadcastFlags::DEFAULT |
SDK default |
Subscriptions automatically unsubscribe when dropped.
Make asynchronous HTTP GET and POST requests from within a WASM module.
use infinity_rs::prelude::*;
let params = HttpParams {
headers: vec!["Accept: application/json".to_string()],
post_field: None,
body: vec![],
};
http_request(Method::Get, "https://example.com/data.json", params, |resp| {
if resp.error_code == 0 {
let body = String::from_utf8_lossy(&resp.data);
println!("Got: {body}");
}
})?;Callbacks are invoked on the next simulator update tick after the response arrives.
use infinity_rs::io::fs;
// Async read — callback fires when data is ready
let req = fs::read("\\work/config.json", |data| {
println!("Read {} bytes", data.len());
})?;
// Async write
let req = fs::write("\\work/output.bin", &my_bytes)?;
// Poll completion in your update loop
if req.is_done() { /* ... */ }
if req.has_error() { eprintln!("{:?}", req.last_error()); }Full control via OpenFile, IoRequest, and OpenFlags for advanced use cases.
Supported open flags: RDONLY, WRONLY, RDWR, CREAT, TRUNC, HIDDEN
Vector graphics rendering inside a Gauge using the NanoVG API.
use infinity_rs::nvg::*;
use infinity_rs::prelude::*;
// In Gauge::init
let nvg = NvgContext::new(ctx).expect("NVG init failed");
let font = nvg.create_font("sans", "./data/Roboto-Regular.ttf");
// In Gauge::draw
nvg.frame(win_w, win_h, px_ratio, |nvg| {
// Shapes
Shape::rect(10.0, 10.0, 200.0, 100.0)
.fill(Color::rgb(0, 120, 255))
.draw(nvg);
Shape::circle(150.0, 150.0, 40.0)
.stroke(Color::rgba(255, 255, 255, 200), 2.0)
.draw(nvg);
// Text
nvg.text(font_id, 16.0, 50.0, 50.0, "Hello MSFS!");
// Transforms
nvg.scoped(|nvg| {
nvg.translate(cx, cy);
nvg.rotate(angle_rad);
// ... draw rotated content ...
});
});NvgContext is automatically cleaned up via Drop.
Subscribe to simulation lifecycle events (flight load, teleport start/done, replay boundaries, plane crash, …). Useful for resetting state on flight reload or pausing logic across slew/back-on-track windows. Subscriptions auto-unregister on Drop.
Subscribe to and trigger named simulation events (KEY_TOGGLE_MASTER_BATTERY, etc.) with typed parameter arguments via FsParamArg.
MapView wraps fsMapView*: spawn a host-side map texture, configure aerial / altitude shading, follow mode, isolines, and an integrated weather radar (top / horizontal / vertical with custom rain-rate color ramps and cone angle). MapView::image_pattern(...) hands the texture straight to NanoVG so you can paint it through any gauge.
Async access to the fsCharts* API. get_index → get_pages → get_page_image returns a ChartImage whose host id can be sampled by NanoVG via nvg_pattern(...). ChartIndex and ChartPages are owned wrappers that release the host allocations on Drop; ref-views (ChartCategoryRef, ChartMetadataRef, ChartPageRef) decode strings as &str borrowed from the owner.
Spawn particle effects in the world (spawn_in_world) or attached to a sim object node (spawn_on_sim_object) with optional VfxParam { name, rpn } graph bindings. VfxInstance owns the host id and destroys it on Drop; leak() detaches if the host should keep emitting after the handle goes away.
EFB / route-broadcast integration. current_efb_route() borrows the route the EFB currently holds; subscribe_broadcast(...) listens for pushes; subscribe_requests(...) receives RouteRequest handles when other modules ask for a route, and you reply via request.respond(route). Both subscriptions auto-unregister on Drop.
All examples are in msfs/examples/.
| Example | Description |
|---|---|
io_system_simple.rs |
Read and copy a file using the high-level io::fs API |
io_system.rs |
Full low-level file I/O API |
vars_full_api.rs |
A-vars, L-vars, indexed vars, VarStruct snapshot |
comm_bus_gauge.rs |
Gauge that publishes Comm Bus events on mouse click |
comm_bus_sytem.rs |
System that receives Comm Bus commands and broadcasts state |
network_fetch_system.rs |
Fetch JSON over HTTP on a Comm Bus trigger |
network_post_system.rs |
HTTP POST with a request body |
nvg_render.rs |
Attitude indicator rendered with NanoVG |
flow_system.rs |
Adjust logic based on sim events using the Flow API |
event_api.rs |
Subscribe to and trigger simulation events |
map_view_weather_radar.rs |
Texture-backed weather radar gauge with rain-rate color ramp + range overlay |
charts_gauge.rs |
Async charts pipeline (index → pages → image) painted through NanoVG |
vfx_system.rs |
Sim-object-attached particle effect driven by N1 via an RPN-bound graph param |
planned_route_system.rs |
Subscribe to EFB route broadcasts and respond to route requests |
msfs/src/
├── lib.rs — top-level re-exports
├── prelude.rs — convenient glob import
├── modules.rs — System / Gauge traits
├── exports.rs — export_system! / export_gauge! macros
├── context.rs — FsContext wrapper
├── types.rs — GaugeDraw, GaugeInstall, SystemInstall
├── sys.rs — raw bindgen bindings
├── abi.rs — ABI shims for host entry points
├── vars/ — AVar, LVar, VarKind, VarStruct
├── comm_bus/ — Subscription, BroadcastFlags, commbus_call
├── network/ — http_request, HttpParams, Method, HttpResponse
├── io/ — File I/O (low-level + fs high-level)
├── nvg/ — NanoVG: NvgContext, Shape, Color, Transform, …
├── events/ — Sim event helpers (subscribe / trigger / FsParamArg)
├── flow/ — Flow lifecycle subscription
├── map_view.rs — MapView (fsMapView*) — weather radar / terrain / 3D
├── charts.rs — Async charts API (fsCharts*) with owned wrappers
├── vfx.rs — VfxInstance (fsVfx*) with RAII destroy
├── planned_route.rs — EFB planned-route broadcast + request channels
├── host/ — Native testing shims (non-wasm targets)
├── utils/ — Internal utilities
└── bindgen_support/ — Headers consumed by the build script
| Project | Developer |
|---|---|
| T-38 Talon | Aero Dynamics |
| DC-10 | Aero Dynamics |
Every public header in MSFS 2024 SDK/WASM/include/MSFS/ is wrapped:
| Header | Module |
|---|---|
MSFS.h, MSFS_Core.h, MSFS_Utils.h |
infinity_rs::sys, internal helpers |
MSFS_Vars.h |
infinity_rs::vars |
MSFS_CommBus.h |
infinity_rs::comm_bus |
MSFS_Network.h |
infinity_rs::network |
MSFS_IO.h |
infinity_rs::io |
MSFS_Render.h, Render/nanovg.h |
infinity_rs::nvg |
MSFS_GaugeContext.h, MSFS_SystemContext.h |
infinity_rs::context, infinity_rs::modules |
MSFS_Events.h, Types/MSFS_EventsEnum.h |
infinity_rs::events |
MSFS_Flow.h |
infinity_rs::flow |
MSFS_MapView.h |
infinity_rs::map_view |
MSFS_Charts.h |
infinity_rs::charts |
MSFS_Vfx.h |
infinity_rs::vfx |
MSFS_PlannedRoute.h, MSFS_FlightPlan.h |
infinity_rs::planned_route (FlightPlan types re-exposed via sys) |
MSFS_Weather.h |
re-exported via infinity_rs::sys (typed wrapper TBD) |
Legacy/gauges.h |
infinity_rs::sys (legacy gauge ABI) |
SimConnect.h |
infinity_rs::sys under the simconnect feature (native only) |