Sunday, April 25, 2010

XNB reverse engineering

XNB is XNA Game Studio Express XNA Framework Content Pipeline Binary File.

About two years ago, my friends wrote the game in XNA. XNA has not been installed on my PC at the time, but I was curious to see the game content, especially images from this game.
And to my great disappointment, all the contents had an unusual format XNB.

So I decided to analyze this file format and to write a simple converter.
First of all I mentioned that the XNB files size is too big.
I assumed that the image is stored in the XNB as a 32-bit uncompressed image. Unfortunately, I later discovered that XNB can store compressed images.

After some binary data analysis, I almost completely decoded the structure of XNB header.

XNB files may contain several different resources. Besides images whose content type is "Microsoft.Xna.Framework.Content.Texture2DReader" they can store Sprite Fonts, Lists, Vectors, Rectangles et al.

I convert XNB file containing only the uncompressed image to uncompressed 32-bit TGA file.

Here the source code:
#ifndef XNBFILE_H
#define XNBFILE_H

#include <stdio.h>
#include <windows.h>

class XnbFile
{
private:

    #define XNB_CONTENT_TEXTURE2D "Microsoft.Xna.Framework.Content.Texture2DReader"

    #define XNB_IDENT (('w' << 24) + ('B' << 16) + ('N' << 8) + 'X')

    struct ColorRGBA
    {
        unsigned char R; 
        unsigned char G; 
        unsigned char B; 
        unsigned char A;
    };
    
    #pragma pack (push, 1)

    struct XnbHead
    {
        int ident;
        WORD version;
    };    
    
    struct XnbAsset
    {    
        unsigned char text_len;
        char *text;
    };

    struct XnbInfo
    {
        unsigned int file_size;
        unsigned char num_assets;        
    };

    struct XnbImage
    {        
        unsigned char flagA[6]; // probably compression method
        unsigned int width;
        unsigned int height;
        unsigned char flagB[4]; // unknown
        unsigned char flagC[4]; // unknown
    };

    #pragma pack (pop, 1)

public:

    XnbFile() {}
    ~XnbFile() {}

    void LoadXnb(const char *name)
    {
        XnbHead header;
        XnbInfo info;
        XnbAsset asset;
        XnbImage img;        

        FILE *f = fopen(name, "rb");
        if (!f) return;

        fread(&header, 1, sizeof(header), f);

        if (header.ident != XNB_IDENT)
        {
            printf("ID of xnb file is incorrect!\n");
            return;
        }
        
        char *versions[4] = { "1.0", "2.0", "3.0", "3.1" };
        printf("The version of XNA is %s\n", versions[header.version - 1]);

        fread(&info, 1, sizeof(info), f);        
        fread(&asset.text_len, 1, 1, f);

        asset.text = new char[asset.text_len + 1];
        fread(asset.text, 1, asset.text_len, f);
        asset.text[asset.text_len] = '\0';

        fseek(f, 4, SEEK_CUR); 

        if(!strcmp(asset.text, XNB_CONTENT_TEXTURE2D))
        {
            fread(&img, 1, sizeof(img), f);

            ColorRGBA *data = new ColorRGBA[img.width * img.height];
            fread(data, 1, img.width * img.height * 4, f);

            char newname[256];
            strcpy(newname, name);
            strcat(newname, ".tga");

            SaveToTga(newname, (unsigned char*)data, img.width, img.height);
        }
        
        fclose(f);        
    }

    void SaveToTga(const char *name, const unsigned char *data, int w, int h) 
    {
        FILE *f = fopen(name,"wb");
        if (!f) return;

        unsigned char *buf;
        buf = new unsigned char[18 + w * h * 4]; // 18 bytes for header
        memset(buf, 0, 18);
        buf[2] = 2;
        buf[12] = w % 256;
        buf[13] = w / 256;
        buf[14] = h % 256;
        buf[15] = h / 256;
        buf[16] = 32;
        buf[17] = 0x28;
        memcpy(buf + 18, data, w * h * 4);
        fwrite(buf, 1, 18 + w * h * 4, f);

        delete buf;

        fclose(f);    
    }

};

#endif //XNBFILE_H

2 comments:

  1. Do you know if there's a way to modify the header of an XNB file to resize the image? I have a game that uses textures that are too big for my graphics card to render, so I'm trying to figure out a way to make them smaller. Thanks for any help.

    ReplyDelete
  2. There is no way to make them smaller only changing the header. Anyway, you should read all the data from XNB file to resave smaller texture.

    ReplyDelete