XNB Content Pipeline
Alpha.1 includes a real read-side XNB loader. RegisterAllBuiltInXnbReaders() installs 50 built-in readers when FFmpeg is available, or 49 when the Video reader is excluded. The set includes a real compiled-effect EffectReader. CNA does not author XNB files, and it does not discover arbitrary game types through .NET reflection; games can register explicit C++ ContentTypeReader<T> factories instead.
Overview
The XNA Content Pipeline compiled every asset — textures, fonts, sounds, models, effects — into a binary .xnb container. An .xnb file carries a header, an optional compressed body, a table of the type readers needed to deserialise its contents, and then the object graph itself, written by the reader for the asset's root type.
CNA implements that format in the CNA::Internal::Xnb namespace. The reader is not a shim or a converter: it decompresses the body, walks the type-reader table, and deserialises the object graph into live CNA objects — Texture2D, SpriteFont, SoundEffect, Model and so on — using the same ContentReader API surface FNA exposes, so reader code written against FNA's shape ports across directly.
The practical consequence: content already built by the original Microsoft tooling (or by MonoGame's mgcb) can be loaded as-is, without an offline conversion step, for every type in the registered reader set. Content that falls outside that set still has to go through CNA's loose-file path — raw images and audio, JSON descriptors, or the offline gltf_to_cnj converter for models.
| Capability | Detail |
|---|---|
| Reader size | Module-owned implementation under modules/content, including parser, decompression and built-in readers |
| Registered type readers | 50 with FFmpeg; 49 when VideoReader is excluded |
| Decompression | A real 31 KB LZX decompressor — not a stub, not a shell-out to an external tool |
| Reader API | The full FNA ContentReader surface |
| Shared resources | Two-pass resolution, so assets that reference each other deserialise correctly |
| Robustness | Fuzz-hardened against malformed and adversarial files |
ContentManager integration |
Prefers a .xnb when one is present; falls back to loose files otherwise |
Registering the built-in readers
The reader registry is empty by default. You must call CNA::Internal::Xnb::RegisterAllBuiltInXnbReaders() exactly once at startup, before the first Load<T>() that should resolve an .xnb. Without it, no type reader is known and XNB loading will not work. This is a deliberate design decision documented in the header — not an oversight and not a bug: registration is explicit so that an application which never touches XNB does not pay for 50 readers it will not use.
#include <CNA/Internal/Xnb/XnbReaderRegistry.hpp>
// Call once at startup — e.g. at the top of main(), or in your
// Game subclass's Initialize(), before any content is loaded.
int main() {
CNA::Internal::Xnb::RegisterAllBuiltInXnbReaders();
MyGame game;
game.Run();
return 0;
}
Registration is global and idempotent in intent: do it once for the lifetime of the process. After that, ContentManager needs no further configuration — the XNB path is transparent from the caller's point of view.
How ContentManager resolves an asset
ContentManager does not require you to choose between pipelines. For a given asset name it prefers a .xnb when one exists, and otherwise falls back to the loose-file path (raw images and audio, or a JSON descriptor) documented on the ContentManager page.
// Content/textures/player.xnb exists → loaded through the XNB reader.
// Content/textures/player.png exists but no .xnb → loaded via SDL3_image.
// Either way, the call site is identical:
auto& tex = Content.Load<Texture2D>("textures/player");
This makes incremental migration practical in both directions. You can drop a folder of pipeline-built .xnb assets into an existing loose-file project and they will take precedence, or delete an .xnb and let the loose file take over, without touching any calling code.
The 50 built-in type readers
RegisterAllBuiltInXnbReaders() installs 50 readers. They cover the asset categories a real XNA game actually ships:
| Category | Covered | Status |
|---|---|---|
| Primitives | All the primitive value types the pipeline emits | Implemented |
| Math types | All of them — vectors, matrices, quaternions, rectangles, bounding volumes, colour | Implemented |
| Textures | Texture2D, Texture3D, TextureCube |
Implemented |
| Fonts | SpriteFont |
Implemented |
| Audio | SoundEffect, Song |
Implemented |
| Stock effects | The five stock effects the content pipeline can emit | Implemented |
| Model family | The full model reader family (meshes, mesh parts, bones, buffers) | Implemented |
Compiled Effect bytecode |
EffectReader reads XNA/FNA Effect Framework bytecode |
Implemented; renderer capability required |
Stock-effect readers restore native CNA effect state. The general EffectReader instead reads the already-compiled Direct3D 9 Effect Framework payload and builds a reflected runtime effect. That succeeds only when the active renderer reports GraphicsCapability::CompiledEffects; it is not a universal renderer feature. See Effects System.
Container format support
Three parts of the container format are worth calling out because they are where naive XNB readers usually stop short:
- LZX decompression. XNA compressed most shipped
.xnbbodies with LZX. CNA carries a genuine 31 KB LZX decompressor, so compressed assets from real games load directly rather than requiring a decompression pass through external tooling. - Two-pass shared-resource resolution. XNB object graphs can contain shared resources referenced from multiple points — a material referenced by several mesh parts, for instance. CNA reads the graph in two passes so those references resolve to the same object instead of being duplicated or left dangling.
- Fuzz hardening. The reader has been fuzzed against malformed and adversarial input. Content files are attacker-controlled data in any game that loads mods or user downloads, and a binary deserialiser is exactly the wrong place to trust your input.
Known gaps
These are real limitations, not roadmap language. If your content depends on any of them, the XNB path will not load it today.
| Gap | Consequence |
|---|---|
No ReflectiveReader |
XNA used reflection to discover arbitrary user-defined readers. CNA cannot do that automatically. A game may implement ContentTypeReader<T> and register a factory through ContentTypeReaderManager::AddTypeCreator(), but every canonical reader name must be registered explicitly. |
No EnumReader |
Enum-typed fields in an XNB graph cannot be read. |
| External references are typed | ContentReader::ReadExternalReference<T>() is implemented for the explicit types CNA instantiates; there is no reflective catch-all for arbitrary game types. |
VideoReader follows FFmpeg availability |
It is registered on FFmpeg-enabled builds. Platforms that exclude the video translation units also exclude the reader, reducing the built-in count from 50 to 49. |
EffectReader is renderer-qualified |
The reader is real, but construction throws on a renderer/build that does not expose CompiledEffects. HLSL .fx source and MGFX are still not accepted. |
| Collection readers implemented but unregistered | Array, List, Dictionary and Nullable readers exist in the codebase but are not registered by default. Without reflection each closed generic instantiation has to be hand-registered, so there is no way to register them all up front. |
Two adjacent Content namespace caveats belong here as well: ResourceContentManager is a pure stub — the only one of its kind in the repository — and ContentManager::Unload() clears its internal maps without disposing the assets they held.
Testing
The alpha.1 Content module contains 112 C++ test sources with 978 statically discoverable GoogleTest-family definitions, including XNB container, reader-registration, real fixture, effect, audio, texture and model cases. The executable subset remains configuration-dependent. Tests against real pipeline output are especially valuable because they check compatibility beyond hand-authored byte arrays.
Those fixtures sit inside CNA's broader Content test area. For how the project verifies itself overall — including the pixel-exact XNA oracle corpus and differential testing against a real FNA build — see Verification & Known Issues.