scs-sdk-plugin
中文 | English
scs-sdk-plugin is the safe application framework and audited runtime boundary
for native SCS telemetry and input-device plugins. It combines the typed
scs-sdk layer with lifecycle management, transactional
registration, callback dispatch, panic containment, and stable foreign context
ownership so ordinary plugin source can remain safe Rust.
The crate deliberately re-exports both application-facing dependencies:
pub use scs_sdk as sdk;
pub use ;
A product plugin normally needs only scs-sdk-plugin in its manifest.
The framework provides independent safe runtimes for Telemetry API 1.00/1.01 and Input API 1.00.
This is an independent community crate and is not affiliated with or endorsed by SCS Software.
Application contract
Applications implement TelemetryPlugin with four explicit concerns:
metadatareturns stablePluginMetadatacontaining the product name and build version used in lifecycle logs;compatibilityreturnsPluginCompatibility, including the minimum Telemetry API and a per-gameGameCompatibilityschema requirement;initializeinitializes product state and explicitly declares every event and channel subscription; andchannel,event, andshutdownhandle delivered data and cleanup.
The runtime owns ABI entry points and foreign callback plumbing. Application
source does not need raw pointers, C strings, handwritten extern functions,
raw SCS unions, direct scs-sdk-sys access, or unsafe blocks.
Input applications implement InputPlugin, declare every device through
InputPluginContext::register_device, and return typed InputEvent values
from next_input_event. Activity notification remains opt-in on each
InputDeviceSpec. The input runtime validates names, device-local indices,
registered bool/float types, panic containment, generation isolation, and the
official automatic-unregistration-before-shutdown lifecycle.
Minimal safe plugin
use ;
use ;
static SUPPORTED_GAMES: = ;
;
export_plugin!;
The application passes an ordinary constructor expression. The proc macro generates exactly the two symbols required by the SCS loader and delegates both to this crate's runtime.
Explicit compatibility and game identity
PluginCompatibility keeps wrapper capability separate from product policy.
The runtime validates the declared minimum Telemetry API and each supported
game's minimum schema before product initialization. A later schema minor is
accepted only inside the declared major compatibility family; lower minima,
different majors, duplicated declarations, undeclared games, and ambiguous
Game::Other requirements are rejected deliberately.
GameInfo copies SCS's initialization-only strings into owned Rust values and
classifies the identifier as:
Game::EuroTruckSimulator2
Game::AmericanTruckSimulator
Game::Other
Unknown IDs retain their original textual ID and display name. Application
code can query descriptor history through GameInfo::minimum_schema_for and
GameInfo::supports without rebuilding a second ETS2/ATS version policy.
Telemetry API version, game telemetry schema, and public game version remain separate domains throughout initialization and logging.
Explicit subscriptions
PluginContext never infers subscriptions from implemented hooks and never
subscribes an entire catalog automatically. The product explicitly requests
each capability during initialize.
The API keeps registration domains visible:
- scalar channels:
subscribeandsubscribe_with_flags; - SDK-indexed channels:
subscribe_atandsubscribe_at_with_flagsusingSdkIndex; - numbered trailer channels:
subscribe_trailerusingTrailerIndex; and - numbered trailer plus SDK-indexed channels:
subscribe_trailer_atusing both strong index types.
Every family also has an explicitly named optional form. Required and optional registrations have narrow, different failure rules:
- optional channels tolerate only
NotFoundandUnsupportedType; - optional events tolerate only
UnsupportedandNotFound; and - every other failure aborts initialization and preserves transactional rollback.
The framework checks Telemetry API and per-game schema availability before it calls the SDK. A newer optional capability is skipped; a newer required capability rejects initialization. Counts reported after commit include only registrations SCS actually accepted.
Safe callback surface
Callbacks receive framework-owned safe views:
| Type | Purpose |
|---|---|
ChannelUpdate<'a> |
Registered channel identity, flags, strong indices, and typed value decoding. |
TelemetryEvent<'a> |
Frame, pause/start, configuration, and gameplay callback payloads. |
ConfigurationEvent<'a> |
Typed configuration attributes and legacy/numbered trailer identity. |
GameplayEvent<'a> |
Typed gameplay attributes and known-value helpers. |
Borrowed payloads cannot outlive the hook invocation. Generic string and
string_owned access remains available beside helpers such as shifter_type,
job_market, and fine_offence, so a future unknown SDK string is not silently
collapsed into a known value.
TelemetryEventKind is a re-export of scs_sdk::Event, not a duplicate event
catalog. Registration identity and callback payloads therefore have distinct
types without maintaining two raw discriminator maps.
Input callbacks return InputValue::Float(InputAxisValue) for float axes.
InputAxisValue is re-exported by the framework and makes the finite inclusive
-1.0 through 1.0 contract explicit at the application boundary; arbitrary raw
floats never reach the runtime through safe plugin code.
Runtime guarantees
The audited runtime provides the shared lifecycle mechanics which product plugins must not reimplement:
- deterministic idle, initializing, active, and shutdown state transitions;
- registration as a transaction, with partial failure rolled back in reverse completion order;
- normal shutdown unregistration in reverse order;
- plugin shutdown on both active teardown and rejected partial initialization;
- panic containment before every foreign ABI boundary;
- deliberate mutex-poison recovery rather than
unwraporexpect; - stable
Arc<Registration>allocations for foreign-visible callback contexts; - generation checks which isolate delayed callbacks from previous sessions;
- retained runtime backlinks and callback contexts when SDK unregistration fails; and
- callback-scope SDK calls kept on SCS's serialized game main thread.
Logical inactivity alone is not treated as proof that SCS discarded a pointer. That distinction is essential for context provenance and stale-callback safety.
Runtime logs identify the plugin name and version, framework version, detected game display name and ID, negotiated Telemetry API, game schema, committed event/channel counts, initialization failures, and clean shutdown. Product probe lines and business diagnostics remain application responsibilities.
Scope boundary
This crate is reusable infrastructure. It does not implement web transports, dispatch behavior, shared-memory protocols, save-game parsing, persistence, or other product features. A product repository should build those concerns on top of the safe hooks rather than moving them into the framework.
See the real application-boundary fixture at
examples/telemetry-plugin and the isolated
consumer macro fixture under
tests/fixtures/export-plugin.
Validation
Run from the repository root:
RUSTDOCFLAGS=-Dwarnings
MIRIFLAGS=-Zmiri-strict-provenance \
Changes to exports, macro integration, package names, or release linkage also require the repository's Windows, Linux, and macOS release artifact and symbol verification scripts.
License
Workspace-authored Rust code is available under either Apache License 2.0 or MIT, at your option.
SCS-facing lifecycle contracts, ABI identifiers, compatibility metadata, and related documentation derived from SDK 1.0 through 1.14 retain both original SCS Software notices: LICENSE-SCS-SDK-2013 for SDK 1.0-1.5 and LICENSE-SCS-SDK-2016 for SDK 1.6-1.14. The official SDK archive remains third-party material and is not relicensed under the workspace license.