Creating Your Own XNB Files
Just to be on the safe side, let me preface my article with what this post is not. This post will not show you how to build content on a computer that does not have XNA Game Studio installed on it. This post will not show you the structure of the XNB file nor much at all of the innards of the content pipeline. This post will show how you can use the content pipeline classes to build your own XNB files rather than building at compile time.
So when exactly is this functionality useful? For most people: it isn’t. This isn’t going to give you anything that adding a file to your Content project wouldn’t. What this lets you do is skip the “source file” part of the content pipeline. To explain this, we have to think about how the content pipeline works.
When your content goes to build, the content pipeline takes your file and passes it to a ContentImporter, an object whose sole job is to read the actual file into some sort of data. This data is then passed to a ContentProcessor which takes that data and assembles a more accurate representation of the object (or perhaps not; this step is somewhat optional). The data then gets passed to a ContentTypeWriter which will write the data to the XNB file. Then when you call Content.Load in your game, a ContentTypeReader will execute to read that XNB file back into your object.
What we’re doing here is eliminating the importer and processor from the equation. The goal is to never have a source file. The goal is to simply go from “object in memory” to “compiled XNB file”, ready to load into your game. The primary use for such functionality would be your game editors. Personally I find it to be a pain to have to edit a level with an editor, save it as XML, then bring up Visual Studio, compile the content, and finally run the game. With this functionality, our editor can write the XNB file directly and we can just keep running the game over and over to test the new content. No more compiling in Visual Studio since the content has already been built.
When the content pipeline gets ready to go from ContentProcessor to ContentImporter, it needs to get ahold of a ContentCompiler object. It can then add the ContentTypeWriter to the ContentCompiler and compile the content. This is the functionality we wish to have. Unfortunately we have three blocking points:
- The only constructor for the ContentCompiler object is internal.
- The ContentCompiler.AddTypeWriter method is internal.
- The ContentCompiler.Compile method is internal.
We can see the team never really intended for us to dig into this ;). Note: As a result of using reflection to work with internal methods, we have to remember that these details could change at any time and are not considered a “stable” solution. These work now, but are in no way guaranteed to work with future version of XNA Game Studio.
So what all do we need to achieve our goal? Well, like any data that goes into an XNB file, we need the class for the data, a ContentTypeWriter and a ContentTypeReader. Here are the ones I used for my test:
public class MyObject
{
public int Something;
public bool SomethingElse;
public float Another;
}
public class MyObjectReader : ContentTypeReader<MyObject>
{
protected override MyObject Read(ContentReader input, MyObject existingInstance)
{
MyObject obj = new MyObject();
obj.Something = input.ReadInt32();
obj.SomethingElse = input.ReadBoolean();
obj.Another = input.ReadSingle();
return obj;
}
}
public class MyObjectWriter : ContentTypeWriter<MyObject>
{
protected override void Write(ContentWriter output, MyObject value)
{
output.Write(value.Something);
output.Write(value.SomethingElse);
output.Write(value.Another);
}
public override string GetRuntimeReader(TargetPlatform targetPlatform)
{
return typeof(MyObjectReader).AssemblyQualifiedName;
}
}
Now that we have those we can get down to actual process of compiling the content. Much to my surprise, this was a very simple process:
- Use reflection to get the constructor, AddTypeWriter method, and Compile method of the ContentCompiler class.
- Invoke the constructor to create an instance of the ContentCompiler.
- Create an instance of our ContentTypeWriter and add it to the ContentCompiler using the AddTypeWriter method.
- Open a stream and use the Compile method to write out our file with a set of given parameters.
So what does this look like in code? Here’s a small test I wrote up:
ConstructorInfo[] constructors = typeof(ContentCompiler).GetConstructors(
BindingFlags.NonPublic | BindingFlags.Instance);
MethodInfo addWriter = typeof(ContentCompiler).GetMethod(
"AddTypeWriter",
BindingFlags.NonPublic | BindingFlags.Instance);
MethodInfo compileContent = typeof(ContentCompiler).GetMethod(
"Compile",
BindingFlags.NonPublic | BindingFlags.Instance);
MyObject obj = new MyObject();
obj.Something = 10;
obj.SomethingElse = true;
obj.Another = 15.3943f;
ContentCompiler compiler = constructors[0].Invoke(null) as ContentCompiler;
MyObjectWriter writer = new MyObjectWriter();
addWriter.Invoke(compiler, new object[] { writer });
using (FileStream stream = new FileStream(
"C:\\Users\\Nick\\Desktop\\test.xnb",
FileMode.Create))
{
compileContent.Invoke(
compiler,
new object[]
{
stream,
obj,
TargetPlatform.Windows,
true,
"C:\\Users\\Nick\\Desktop\\",
"C:\\Users\\Nick\\Desktop\\",
});
}
The only real confusing part is the parameters for the Compile method. I’m using the XNA GS 3.0 Beta so the parameters are, in this order:
- The stream to write to.
- The object to write out.
- The target platform to build for.
- Whether or not to compress the content.
- The root directory for the content.
- The reference relocation path for the content.
The last two I can really only guess at but I believe the root directory is used for resolving any relative paths found in the object. The reference relocation path I assume is to tell the writer where to write out external references to. Again, these classes are not documented so you won’t be able to find a definitive answer. The only way to figure it out is just play with it and see what happens. ![]()
Again, this doesn’t remove the need for a full XNA Game Studio installation so this isn’t something you can put into a title you plan to ship on Xbox LIVE Community Games or on PC. This is simply one way you can make your “Editor -> Game” experience a little faster and easier to work with.

