Consoles
Rusteal builds for Linux and Windows; consoles are not supported yet. This chapter is how Rusteal gets to PlayStation 5 and Xbox Series X|S (and to any platform whose Unreal support is private), what can be prepared now in the public repository, and what can only be done once a project has console access. It comes from two research reports made on 11 October 2026 from public sources and from Rusteal’s own code, without access to any console SDK: what they could confirm is marked as such, and what depends on the SDK is listed as open.
The goal behind the plan is concrete: a game made with Rusteal ships on PC, does well, and its publisher gets it console access. From that moment the publisher wants a release date, so the work left after access has to be as small and as predictable as possible.
Conclusions
What the research and the review of Rusteal’s own code concluded, on 11 October 2026:
- No Rust support from the platform holders is needed. Rust produces native code; what matters is whether that code links and runs in the platform’s environment. Looking for an official “Rust SDK” for a console is the wrong question.
- Linking Rust into a console build needs no engine fork. A static library consumed by an external module, built by Rusteal’s CLI before UBT runs, uses the engine’s documented mechanisms for every platform. Patching UBT is justified only by a limitation measured with the SDK in hand.
- The artifact is a
staticlibper target. Whether a console links it into the executable (route S) or turns it into a dynamic library (route D), the console build starts from it. Some consoles do load dynamic libraries, but that does not remove the need for a Rust target the platform’s linker accepts. - The biggest obstacles are in Rusteal, not in the consoles. The runtime relies on each library being a separate dynamic library: fixed export names, one table per runtime instance, positional function ids, short-name type ids, link-time registration, identical bindings package names. An aggregated static build breaks all of them, and all of them can be fixed on desktop, in public, without an SDK.
- The largest console-specific unknown is the Rust target and its standard library, above all on PS5. Making the runtime take its services from the engine (allocation, tasks, time), which is already ported to every console, shrinks that unknown to “the crates compile and link for the target”.
- The repository stays public; console specifics do not. Rusteal keeps a platform-agnostic structure and an extension point; each console’s target, flags, build rules and CI live in a private platform pack.
- The aim is a short, predictable path after access. Everything that does not need the SDK is done before it, so that once a publisher obtains access, the remaining work is the toolchain bring-up and validation on the devkit, not a redesign.
What console access is
Three separate permissions, none of which replaces the others:
| From | What it gives | What it does not give |
|---|---|---|
| The platform holder (Sony, Microsoft) | Developer status for the organization, the SDK, devkits, the submission process | Epic’s console code |
| Epic | Access to the engine’s Platform Extensions for the approved platforms, through the Epic Developer Portal, after the platform holder’s approval | The SDK, which Epic does not redistribute |
| The publisher | The relationship, the title, often the request for access | Permissions for each organization and person, which are granted by the parties above |
The console build of Unreal is the engine built from source with the platform extension added, against the SDK version that extension supports; Epic’s AutoSDK layout and Turnkey help install and verify it. That is the normal way any studio builds for a console; it is not a fork of the engine.
What Rusteal adds on top must not depend on anything else from that list. In particular, no Rust support from the platform holders is needed or expected: Rust produces native code, and the question is only whether that code links and runs in the platform’s environment.
Public structure, private platform packs
Rusteal stays open source; console code cannot be. The line between them:
| Public, in this repository | Private, per platform, under the agreements |
|---|---|
| The static backend, the module contract, the services the runtime takes from the engine | The platform’s Rust target definition (target JSON, sysroot), flags and build-std settings |
| The CLI’s notion of a target platform and its extension point for platform definitions | The definition files the CLI loads for each console |
| The external module that consumes a static library | The Build.cs branches and staging rules for the console |
| The desktop fixture and its tests | Patches to the Rust standard library, if a console needs them |
| This chapter | CI with the SDK and the devkits, and the platform’s own documentation |
A private platform pack per console holds the right-hand column, and is given only to developers who show they have signed Epic’s console access agreement and the platform holder’s. Its licensing is a separate decision.
How Rust reaches a console build
The engine’s build is not changed. The parts:
- Rust compiles to a static library. For each target and configuration, Cargo builds a
staticlib: an archive of native objects the platform’s linker can consume like any C++ library. - An external module consumes it. A module of type
ModuleType.Externaladds the archive withPublicAdditionalLibraries, and its include paths and system libraries; the module that needs the Rust code depends on it. This is the documented way to integrate a third-party library, and it works the same way for every platform. - The CLI orchestrates.
rustealalready runs steps before and after the engine’s build (see The CLI). For a console it runs Cargo for the target, checks the result against a manifest, and only then runs UBT and UAT. In CI the same sequence can be a BuildGraph graph (Spawnthe Rusteal step, thenCompilethe game). - UBT links, with the platform’s linker. The game’s binary contains the engine, the plugins’ C++ and the Rust archive; cooking, staging and packaging are the engine’s and the platform extension’s, unchanged.
Why not patch UBT. UBT’s documented hooks (external modules, PreBuildSteps, BuildGraph) are enough to link a library built beforehand. A patch would only be justified by a need measured with the SDK in hand: Rust actions inside UBT’s own graph and distributed executor, for instance. It would have to be kept up with every engine version, and it would not help if the platform rejected the Rust objects themselves, which is a toolchain question.
Why a static library and not loose objects or bitcode. A staticlib carries every object a crate graph needs, in the form the platform’s linker expects; loose objects (--emit=obj) do not include the dependencies, and an rlib is Rust’s internal format. Bitcode would tie the Rust compiler’s LLVM version to the platform compiler’s. Cross-language LTO is out of scope until a first build works.
Two routes from the same archive
| Route | What ships | Rusteal changes | Depends on |
|---|---|---|---|
| S, static (the default) | One aggregated Rust archive linked into the game’s executable | The module contract below | Only the platform’s linker accepting the archive |
| D, dynamic | One dynamic library per Rusteal library, produced by the platform’s linker from its archive | Little: the libraries stay isolated as today | The platform allowing game code in dynamic libraries, and the engine’s FPlatformProcess::GetDllHandle working there |
Unreal builds consoles monolithically, and that is the path platform certification knows best, so S is the one to prepare; D stays a fallback where a platform supports it. Microsoft documents DLLs for Xbox titles; for PS5, nothing is publicly verifiable. Either way the console build starts from the same staticlib per library.
The editor keeps the dynamic libraries and hot reload: the static backend is for packaged games only, built from the same crates.
What the static backend has to change
Today each library (the game’s and each Rusteal plugin’s) is a cdylib loaded on its own, and several parts of the runtime rely on that isolation. In one aggregated archive they would collide:
| Today | In one archive | The change |
|---|---|---|
entry!() exports the same four symbols (rusteal_init…) from every library | Duplicate symbols, or the linker keeps one of them | One entry point for the aggregate; each library exposes a descriptor instead |
rusteal-core/src/api.rs keeps one table pointer in a OnceLock | The second library to initialize panics, or uses the first one’s table | A context per library (module), with the shared engine services apart from the library’s own function table |
| The generated functions’ ids are positions in each library’s function table | Index 7 of plugin A is a different function from index 7 of plugin B | Each bindings crate resolves its own table through its module’s context |
| A type’s id hashes its short name | Inventory::Item and Quest::Item collide (already a known issue) | Ids qualified by module and path |
Registration relies on inventory’s link-time collection | Nothing guarantees the linker keeps every registration from an archive | An explicit list of descriptors, referenced by the aggregate, checked for completeness at startup |
Every generated bindings crate is the package bindings 0.1.0 | Cargo refuses a crate graph with two of them (reproduced with a minimal workspace) | A unique package name per library, imported as bindings through a Cargo alias |
| The handshake compares the Rusteal version | Two generations of bindings with the same version are not told apart | A fingerprint of the bindings (functions and signatures) checked before any call |
on_shutdown runs twice per unload | A module without a library handle has no unload to rely on | One shutdown state machine, safe to call once or twice, after a partial init too |
The C++ side keeps its per-library tables and package paths (/Script/<Plugin>): those must not change, since cooked Blueprints refer to Rust classes by them. What changes is that a library is identified by a context, not by a loaded file.
Depend on the engine, not on the operating system
The Rust standard library talks to the operating system: threads, time, locks, thread-local storage, the allocator, unwinding. On a console those parts of std are the hardest to port. Unreal, on the other hand, is already ported to every console it supports. The runtime can take its services from the engine through the API table instead of from std:
| Service | Today | Through the engine |
|---|---|---|
| Allocation | The system allocator | A GlobalAlloc over FMemory::Malloc/Free, available before the first Rust allocation |
| Background work | task::spawn on std::thread | The engine’s task system |
| Time | std::time::Instant | FPlatformTime |
| Logging | The log crate and ulog! already go to the engine | (unchanged) |
With those, the runtime needs little more than core and alloc on a console, and a port of std becomes a smaller and optional piece of the platform pack. Game code can keep using std on PC; on a console the rule is that it uses Rusteal’s services rather than the operating system directly.
Panics. Consoles’ C++ is usually built without exceptions, and unwinding may not be available to Rust there. If a console needs panic = "abort", ffi_boundary’s catch_unwind no longer turns a panic into a logged error: the process ends, through the platform’s crash reporting. Expected failures are already RustealResults; the change is a documented policy per platform, not a different API.
The Rust toolchain per console
The open part of the plan, and the one each platform pack carries:
- A target. No public Rust target names a console, and none is assumed. A console’s target starts from the nearest public one (
x86_64-pc-windows-msvcfor Xbox,x86_64-unknown-freebsdfor PS5) as a custom target, then matched to the platform’s ABI: calling convention, relocation model, TLS model, object format, unwinding. - The standard library. Building
coreandallocfor a custom target is routine withbuild-std;stdneeds an implementation of its system layer for the platform, which is why the runtime leans on the engine for services. On Xbox, the Game OS exposes a partition of the Windows API: imports outside it show up at link time, and the C runtime must match the engine’s. - Proof before scale. The first console experiment links one function with no heap, threads or I/O into a minimal Unreal project that already runs in C++ alone, then adds allocation, a callback from the engine, registration, tasks.
Road ahead
The port has two phases. The first is done now, on desktop and in public, before any console access: it is where most of the work is, and doing it first removes it from the critical path of a console release. The second starts the day a publisher obtains access, and is kept as short as possible by the first.
Before access: on desktop, in public
| Milestone | Work | Done when |
|---|---|---|
| 1. Module contract | Contexts per library, qualified type ids, explicit registration, unique bindings package names, bindings fingerprint, one shutdown state machine; the dynamic backend keeps working | A fixture with the game and two Rusteal plugins passes: calls from A to B and back reach the right library, two classes with the same short name coexist, a library’s tasks and delegates keep their owner |
| 2. Static backend on desktop | rusteal package --static: one aggregated staticlib, an external module consuming it, a manifest of the artifact; the editor stays dynamic | The fixture, packaged in Development and Shipping on Linux and Windows, starts from a clean machine with no Rust dynamic library, cooks and loads a Blueprint derived from a Rust class |
| 3. Target platforms in the CLI | Host and target separated; unknown targets refused instead of falling back to .so; platform definitions loaded from outside the repository | A platform definition file for desktop drives the same build as the built-in one |
| 4. Engine services | The allocator, tasks and time through the API table | The runtime builds without the std features it no longer needs, and the desktop fixture behaves the same |
After access: with the SDK and the devkits
In the private platform pack, on top of the four milestones above:
| Step | Done when |
|---|---|
| 5. C++ baseline | A minimal Unreal project builds and runs on the devkit with the platform extension, with no Rust |
| 6. Smallest Rust slice | One function from Rust links and runs in that project |
| 7. The fixture | The desktop fixture runs on the devkit: allocation, callbacks, registration, cooked content, tasks, shutdown |
| 8. The game | The title’s own build, its certification requirements, its crash and debugging tools |
Without the first phase, steps 5 to 8 would also carry the module contract, the static backend and the runtime services, under the publisher’s date; with it, the work left is the toolchain bring-up and validation. A firm estimate needs the SDK, but the schedule risk is concentrated in one item: each console’s Rust target.
A failure at each step has a known owner: a context, registration or cook problem on PC is Rusteal’s; a symbol or service missing at the console’s link is the platform pack’s; a cold start failing after a good build points to registration order or runtime services; a project that works for its author but not from a clean checkout is a reproducibility problem, to fix before calling anything shippable.
Open until there is access
- Whether each platform accepts objects from the Rust compiler as they are, and which target definition matches its ABI.
- Which parts of
stdand which C runtime the Rust objects can use on each console. - Whether PS5 allows game code in dynamic libraries (route D).
- Debugging and profiling Rust code with the consoles’ tools: symbols, mixed C++ and Rust stacks, crash dumps.
- The exact build, packaging and submission steps of each platform extension, which are not public.
Sources
The two research reports behind this chapter (11 October 2026), and the public documentation they rely on, among it: