The mount system is extensible - anyone can write a mount to add support for a new game or engine and contribute it to s&box via pull request. A mount detects a game's install directory via Steam, scans its files, and converts assets into s&box compatible assets, all at runtime.
The mounting system is built on two key classes:
BaseGameMount — detects the game and registers assets during mountResourceLoader — loads and converts individual assets on demandMounts live in the s&box engine under the engine/Mounting/ folder. To add support for a new game or engine, create a new mount project, code it and submit a pull request.
Extend BaseGameMount and implement the required properties and methods:
public class MyGameMount : BaseGameMount
{
public override string Ident => "rust";
public override string Title => "Rust";
private string GameDir;
protected override void Initialize( InitializeContext context )
{
// Check if the game is installed via Steam
if ( !context.IsAppInstalled( 252490 ) )
return;
GameDir = context.GetAppDirectory( 252490 );
IsInstalled = true;
}
protected override Task Mount( MountContext context )
{
// Scan game files and register each resource
foreach ( var file in Directory.GetFiles( GameDir, "*.mymodel", SearchOption.AllDirectories ) )
{
var relative = Path.GetRelativePath( GameDir, file );
context.Add( ResourceType.Model, relative, new MyModelLoader( this, file ) );
}
IsMounted = true;
return Task.CompletedTask;
}
}
Ident is a unique lowercase identifier for your mount (e.g. "quake", "hl1"). It's used in mount:// paths and project config. Title is the display name shown in the editor.
Called at startup to detect whether the game is installed. Use context.IsAppInstalled(appId) to check Steam, and context.GetAppDirectory(appId) to get the install path. Set IsInstalled = true if the game is found.
Called when the user enables the mount. Scan the game's files and register each resource with context.Add(). Set IsMounted = true when done.
Each resource type needs a ResourceLoader that converts the game's native format into something s&box understands. Resources are loaded lazily - Load() is only called when the asset is actually used.
public class MyModelLoader : ResourceLoader<MyGameMount>
{
private string FilePath;
public MyModelLoader( MyGameMount host, string path )
{
FilePath = path;
}
protected override object Load()
{
// Read the file and convert it to an S&box model
var data = File.ReadAllBytes( FilePath );
return ConvertToModel( data );
}
}
Use the generic ResourceLoader<T> to get typed access to your mount instance via the Host property.
For resources that take time to load, override LoadAsync instead:
protected override async Task<object> LoadAsync()
{
var data = await File.ReadAllBytesAsync( FilePath );
return ConvertToModel( data );
}
Register resources using the ResourceType enum:
| Type | S&box Extension | Description |
|---|---|---|
Model |
.vmdl |
3D models with meshes, skeletons, animations |
Texture |
.vtex |
Textures and images |
Material |
.vmat |
Materials referencing textures |
Sound |
.vsnd |
Audio files |
Scene |
.scene |
Scene files |
PrefabFile |
.prefab |
Prefab files |
You can set flags on resources to control their behavior:
var loader = new MyModelLoader( this, file );
loader.Flags = ResourceFlags.DeveloperOnly; // Only visible in asset browser, not in-game
| Flag | Description |
|---|---|
DeveloperOnly |
Hidden from game, only visible in the asset browser |
NeverRagdoll |
Don't treat this model as ragdollable |
Effect |
Non-solid, no physics |