Skip to content

Instantly share code, notes, and snippets.

@Radfordhound
Created June 28, 2020 19:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Radfordhound/5650ef12209da2709a6bf9b705bdd79c to your computer and use it in GitHub Desktop.
Save Radfordhound/5650ef12209da2709a6bf9b705bdd79c to your computer and use it in GitHub Desktop.
Sega NN Binary Chunk File Specification
/*
Sega NN Binary Chunk File Specification
Version: 0.1 (WIP)
By: Radfordhound
Thanks to:
- ItsEasyActually For sharing his Sega NN findings with me, including
a list of platform IDs and NN library names used in
the creation of this specification.
- Sega For releasing a version of Sonic 4 Episode 1 for
Windows Phone 7 for some unholy reason which appears
to have been generated via some sort of C++ to MSIL
converter, and thus, can effortlessly be decompiled
into C# code that happens to include complete symbols
for everything - including struct names/layouts and
even enum values. Thanks guys, you're the best!! <3
*/
// ===============================================================================
// Introduction and General Structure
// ===============================================================================
// The Sega NN Binary Chunk Format is a file format developed by Sega somewhere
// around 2002-2004. It originally appeared in Sega Superstars (2004), (to our
// knowledge) and is heavily utilized throughout many Sega games released since
// (such as Sonic '06, Sonic and the Secret Rings/Black Knight, Bleach: Shattered Blade,
// Super Monkey Ball: Banana Blitz, and Sonic 4 Episode 1/2 - to name a few).
// The Sega NN Binary Chunk Format is heavily based on the Sega Ninja Chunk Model Binary
// Format, which was originally developed for the Sega Dreamcast and utilized in many
// Dreamcast games (it was even included as part of the Dreamcast "Katana" SDK).
// The exact meaning of the "NN" is currently unknown. However, given that it's
// so heavily based off of the Ninja format, it's almost certainly an acronym
// for something like "Ninja Next".
// Sega NN Binary Chunk files, as the name might suggest, are comprised of a
// series of "chunks" each of which features a header which goes as follows:
struct NNS_BINCNK_HEADER
{
/*
This chunk's identifier. The data which follows this
header is dependant on the value of this ID.
*/
char id[4];
/*
The size of this chunk, *NOT* counting the size of this header.
Depending on the version of the format, this size either
includes padding, or doesn't (I haven't nailed down exactly
which versions include padding and which don't yet).
*/
uint32_t size;
};
// The chunk's ID states what type of chunk this is and what type of data it contains.
// NN chunk IDs always consist of exactly 4 characters.
// The first character is usually "N" for "Ninja",
// the second character is usually the "platform ID" (a character
// which specifies the platform this file is intended to be used on),
// and the last two characters are usually the chunk type.
// For example, in the "NXIF" chunk ID, the "N" stands for "Ninja",
// the "X" stands for "Xbox", and the "IF" stands for "InFo", meaning
// this is a chunk which contains information on the rest of the file.
// We currently know of the following platform IDs:
/*
ID Meaning Intended platform(s) NN Library version
X "Xbox" Xbox/Xbox 360 DirectX G1.1
S "Sony" Playstation 2 PlayStation 2
G "Gamecube" Gamecube/Wii GAMECUBE
C "Cell" Playstation 3 PS3
I "IOS" iOS/Android/Windows Phone [Unknown]
E [Unknown] Xbox 360 DirectX G2.0 on XBOX360
Z [Other] Other platforms, such as PC DirectX G2.0
TODO: Is this information all correct? Are there any platform IDs that we missed?
*/
// Each Sega NN file begins with one of the following "file header chunks":
/*
Chunk IDs:
N[X]IF (Ninja [Platform] InFo)
NIFL (NInja FiLe header)
*/
struct NNS_BINCNK_FILEHEADER : NNS_BINCNK_HEADER
{
/* The number of "data chunks" in the file, starting at [dataOffset]. */
uint32_t dataChunkCount;
/* Pointer to the first "data chunk" in the file. */
NNS_BINCNK_DATAHEADER* dataOffset;
/* The complete size of all of the "data chunks" in the file combined, including padding. */
uint32_t dataSize;
/* Pointer to the NOF0 chunk. */
NNS_BINCNK_NOF0HEADER* NOF0Offset;
/*
Size of the NOF0 chunk, including its header and, optionally, the
padding at the end of the chunk. Seems .[x]ncp files don't count
the padding, but everything else does??
*/
uint32_t NOF0Size;
/*
1 in most NN files, 0 in some newer variants
(I think they just started using this as padding).
*/
uint32_t version;
};
// Then, starting at [dataOffset], comes an array of [dataChunkCount] "data chunks", which are as follows:
struct NNS_BINCNK_DATAHEADER : NNS_BINCNK_HEADER
{
/* Offset to the data contained within this chunk. */
void* mainDataOffset;
/* Always 0 from what I've seen. */
uint32_t version;
/*
After this comes the actual data pointed to by [mainDataOffset].
The actual contents/format of this data is based on this chunk's ID.
Look for "Chunk IDs:" comments above structs throughout this specification.
*/
};
// After all of the data chunks, at [NOF0Offset], comes the "offsets chunk", which is as follows:
/*
Chunk IDs:
NOF0 (Ninja OFfsets list 0) The 0 appears to simply be padding.
*/
struct NNS_BINCNK_NOF0HEADER : NNS_BINCNK_HEADER
{
/* The number of offsets listed in the array contained within this chunk. */
uint32_t offsetCount;
/* Always 0. */
uint32_t padding;
/*
After this comes an array of [offsetCount] uint32_ts, which represent
the positions of every pointer ("offset") in the file, relative to
[dataOffset] in the NNS_BINCNK_FILEHEADER chunk.
*/
};
// This chunk is sometimes followed by the optional "file name chunk", which is as follows:
/*
Chunk IDs:
NFN0 (Ninja File Name 0) The 0 appears to simply be padding.
*/
struct NNS_BINCNK_NFN0HEADER : NNS_BINCNK_HEADER
{
/* Always 0. */
uint32_t padding1;
/* Always 0. */
uint32_t padding2;
/*
After this comes the file's name in the form of a null-terminated
string, which is then padded to an offset divisible by 16.
*/
};
// Finally, at the end of the file comes the "end chunk", which is as follows:
/*
Chunk IDs:
NEND (Ninja END)
*/
struct NNS_BINCNK_NENDHEADER : NNS_BINCNK_HEADER
{
/* Always 0. */
uint32_t padding1;
/* Always 0. */
uint32_t padding2;
};
// This chunk simply exists to indicate that this is, in fact, the end of the file.
// ===============================================================================
// Generic structures used in various chunks
// ===============================================================================
struct NNS_VECTOR2D
{
float x;
float y;
};
struct NNS_VECTOR
{
float x;
float y;
float z;
};
struct NNS_TEXCOORD
{
float u;
float v;
};
struct NNS_RGB
{
float r;
float g;
float b;
};
struct NNS_ROTATE_A16
{
int16_t x;
int16_t y;
int16_t z;
};
struct NNS_ROTATE_A32
{
int32_t x;
int32_t y;
int32_t z;
};
struct NNS_QUATERNION
{
float x;
float y;
float z;
float w;
};
// ===============================================================================
// Format-specific structures contained within "data chunks"
// ===============================================================================
/*
Chunk IDs:
N[X]MO (Ninja [Platform] MOtion)
N[X]MC (Ninja [Platform] Motion Camera)
N[X]ML (Ninja [Platform] Motion Light)
N[X]MM (Ninja [Platform] Motion Morph)
N[X]MA (Ninja [Platform] Motion mAterial)
*/
struct NNS_MOTION
{
/**
Various flags representing this motion's type, as
well as various other properties; see NNE_MOTIONTYPE.
*/
uint32_t type;
/* The frame this motion starts playing at. */
float startFrame;
/* The frame this motion stops playing at. */
float endFrame;
/* The number of NNS_SUBMOTION structs in the array pointed to by subMotions. */
uint32_t subMotionCount;
/* Pointer to an array of NNS_SUBMOTION structs. */
NNS_SUBMOTION* subMotions;
/* How many frames of the motion are shown each second. */
float framerate;
/* Reserved value that was never used; always 0. */
uint32_t reserved0;
/* Reserved value that was never used; always 0. */
uint32_t reserved1;
};
enum NNE_MOTIONTYPE
{
/* Masks */
NND_MOTIONTYPE_CATEGORY_MASK = 31,
NND_MOTIONTYPE_REPEAT_MASK = 0x1F0040U,
/* Flags */
NND_MOTIONTYPE_VERSION2 = 0x10000000U,
/* Repeat types */
NND_MOTIONTYPE_TRIGGER = 64,
NND_MOTIONTYPE_NOREPEAT = 0x10000U,
NND_MOTIONTYPE_CONSTREPEAT = 0x20000U,
NND_MOTIONTYPE_REPEAT = 0x40000U,
NND_MOTIONTYPE_MIRROR = 0x80000U,
NND_MOTIONTYPE_OFFSET = 0x100000U,
/* Motion types */
NND_MOTIONTYPE_NODE = 1,
NND_MOTIONTYPE_CAMERA = 2,
NND_MOTIONTYPE_LIGHT = 4,
NND_MOTIONTYPE_MORPH = 8,
NND_MOTIONTYPE_MATERIAL = 16
};
struct NNS_SUBMOTION
{
/**
Various flags representing this submotion's type, as
well as various other properties; see NNE_SMOTTYPE.
*/
uint32_t type;
/**
Various flags representing how this submotion
is interpolated; see NNE_SMOTIPTYPE.
*/
uint32_t ipType;
/* The index of the thing (bone, material, etc.) being animated. */
int32_t id; // TODO: Actually, this seems like two shorts?
/* The frame this submotion starts playing at. */
float startFrame;
/* The frame this submotion stops playing at. */
float endFrame;
/** The first frame that has a keyframe assigned to it within this submotion. */
float startKeyframe;
/** The last frame that has a keyframe assigned to it within this submotion. */
float endKeyframe;
/* The number of keyframes in the array pointed to by subMotions. */
uint32_t keyframeCount;
/* The size of each individual key within the keyframes array. */
uint32_t keySize;
/*
Pointer to an array of keyframes. The type of the
individual keyframes varies based on [type].
Refer to the structs prefixed with "NNS_MOTION_KEY_".
*/
void* keyframes;
};
enum NNE_SMOTTYPE
{
/* Masks */
NND_SMOTTYPE_FRAME_MASK = 3,
NND_SMOTTYPE_ANGLE_MASK = 28,
NND_SMOTTYPE_TRANSLATION_MASK = 0x700U,
NND_SMOTTYPE_DIFFUSE_MASK = 0xE00U,
NND_SMOTTYPE_ROTATION_MASK = 0x7800U,
NND_SMOTTYPE_SPECULAR_MASK = 0xE000U,
NND_SMOTTYPE_SCALING_MASK = 0x38000U,
NND_SMOTTYPE_USER_MASK = 0xC0000U,
NND_SMOTTYPE_LIGHT_COLOR_MASK = 0xE00000U,
NND_SMOTTYPE_OFFSET_MASK = 0x1800000U,
NND_SMOTTYPE_TARGET_MASK = 0x1C0000U,
NND_SMOTTYPE_AMBIENT_MASK = 0x1C0000U,
NND_SMOTTYPE_UPTARGET_MASK = 0x1C00000U,
NND_SMOTTYPE_TEXTURE_MASK = 0x1E00000U,
NND_SMOTTYPE_UPVECTOR_MASK = 0xE000000U,
NND_SMOTTYPE_VALUETYPE_MASK = 0xFFFFFF00U,
/* Frame types */
NND_SMOTTYPE_FRAME_FLOAT = 1,
NND_SMOTTYPE_FRAME_SINT16 = 2,
/* Angle types */
NND_SMOTTYPE_ANGLE_RADIAN = 4,
NND_SMOTTYPE_ANGLE_ANGLE32 = 8,
NND_SMOTTYPE_ANGLE_ANGLE16 = 16,
/* Node types */
NND_SMOTTYPE_TRANSLATION_X = 0x100U,
NND_SMOTTYPE_TRANSLATION_Y = 0x200U,
NND_SMOTTYPE_TRANSLATION_Z = 0x400U,
NND_SMOTTYPE_TRANSLATION_XYZ = (NND_SMOTTYPE_TRANSLATION_X |
NND_SMOTTYPE_TRANSLATION_Y | NND_SMOTTYPE_TRANSLATION_Z),
NND_SMOTTYPE_ROTATION_X = 0x800U,
NND_SMOTTYPE_ROTATION_Y = 0x1000U,
NND_SMOTTYPE_ROTATION_Z = 0x2000U,
NND_SMOTTYPE_ROTATION_XYZ = (NND_SMOTTYPE_ROTATION_X |
NND_SMOTTYPE_ROTATION_Y | NND_SMOTTYPE_ROTATION_Z),
NND_SMOTTYPE_QUATERNION = 0x4000U,
NND_SMOTTYPE_SCALING_X = 0x8000U,
NND_SMOTTYPE_SCALING_Y = 0x10000U,
NND_SMOTTYPE_SCALING_Z = 0x20000U,
NND_SMOTTYPE_SCALING_XYZ = (NND_SMOTTYPE_SCALING_X |
NND_SMOTTYPE_SCALING_Y | NND_SMOTTYPE_SCALING_Z),
NND_SMOTTYPE_USER_UINT32 = 0x40000U,
NND_SMOTTYPE_USER_FLOAT = 0x80000U,
NND_SMOTTYPE_NODEHIDE = 0x100000U,
/* Camera types */
NND_SMOTTYPE_TARGET_X = 0x40000U,
NND_SMOTTYPE_TARGET_Y = 0x80000U,
NND_SMOTTYPE_TARGET_Z = 0x100000U,
NND_SMOTTYPE_TARGET_XYZ = (NND_SMOTTYPE_TARGET_X |
NND_SMOTTYPE_TARGET_Y | NND_SMOTTYPE_TARGET_Z),
NND_SMOTTYPE_ROLL = 0x200000U,
NND_SMOTTYPE_UPTARGET_X = 0x400000U,
NND_SMOTTYPE_UPTARGET_Y = 0x800000U,
NND_SMOTTYPE_UPTARGET_Z = 0x1000000U,
NND_SMOTTYPE_UPTARGET_XYZ = (NND_SMOTTYPE_UPTARGET_X |
NND_SMOTTYPE_UPTARGET_Y | NND_SMOTTYPE_UPTARGET_Z),
NND_SMOTTYPE_UPVECTOR_X = 0x2000000U,
NND_SMOTTYPE_UPVECTOR_Y = 0x4000000U,
NND_SMOTTYPE_UPVECTOR_Z = 0x8000000U,
NND_SMOTTYPE_UPVECTOR_XYZ = (NND_SMOTTYPE_UPVECTOR_X |
NND_SMOTTYPE_UPVECTOR_Y | NND_SMOTTYPE_UPVECTOR_Z),
NND_SMOTTYPE_FOVY = 0x10000000U,
NND_SMOTTYPE_ZNEAR = 0x20000000U,
NND_SMOTTYPE_ZFAR = 0x40000000U,
NND_SMOTTYPE_ASPECT = 0x80000000U,
/* Light types */
NND_SMOTTYPE_LIGHT_COLOR_R = 0x200000U,
NND_SMOTTYPE_LIGHT_COLOR_G = 0x400000U,
NND_SMOTTYPE_LIGHT_COLOR_B = 0x800000U,
NND_SMOTTYPE_LIGHT_COLOR_RGB = (NND_SMOTTYPE_LIGHT_COLOR_R |
NND_SMOTTYPE_LIGHT_COLOR_G | NND_SMOTTYPE_LIGHT_COLOR_B),
NND_SMOTTYPE_LIGHT_ALPHA = 0x1000000U,
NND_SMOTTYPE_LIGHT_INTENSITY = 0x2000000U,
NND_SMOTTYPE_FALLOFF_START = 0x4000000U,
NND_SMOTTYPE_FALLOFF_END = 0x8000000U,
NND_SMOTTYPE_INNER_ANGLE = 0x10000000U,
NND_SMOTTYPE_OUTER_ANGLE = 0x20000000U,
NND_SMOTTYPE_INNER_RANGE = 0x40000000U,
NND_SMOTTYPE_OUTER_RANGE = 0x80000000U,
/* Morph types */
NND_SMOTTYPE_MORPH_WEIGHT = 0x1000000U,
/* Material types */
NND_SMOTTYPE_HIDE = 0x100U,
NND_SMOTTYPE_DIFFUSE_R = 0x200U,
NND_SMOTTYPE_DIFFUSE_G = 0x400U,
NND_SMOTTYPE_DIFFUSE_B = 0x800U,
NND_SMOTTYPE_DIFFUSE_RGB = (NND_SMOTTYPE_DIFFUSE_R |
NND_SMOTTYPE_DIFFUSE_G | NND_SMOTTYPE_DIFFUSE_B),
NND_SMOTTYPE_ALPHA = 0x1000U,
NND_SMOTTYPE_SPECULAR_R = 0x2000U,
NND_SMOTTYPE_SPECULAR_G = 0x4000U,
NND_SMOTTYPE_SPECULAR_B = 0x8000U,
NND_SMOTTYPE_SPECULAR_RGB = (NND_SMOTTYPE_SPECULAR_R |
NND_SMOTTYPE_SPECULAR_G | NND_SMOTTYPE_SPECULAR_B),
NND_SMOTTYPE_SPECULAR_LEVEL = 0x10000U,
NND_SMOTTYPE_SPECULAR_GLOSS = 0x20000U,
NND_SMOTTYPE_AMBIENT_R = 0x40000U,
NND_SMOTTYPE_AMBIENT_G = 0x80000U,
NND_SMOTTYPE_AMBIENT_B = 0x100000U,
NND_SMOTTYPE_AMBIENT_RGB = (NND_SMOTTYPE_AMBIENT_R |
NND_SMOTTYPE_AMBIENT_G | NND_SMOTTYPE_AMBIENT_B),
NND_SMOTTYPE_TEXTURE_INDEX = 0x200000U,
NND_SMOTTYPE_TEXTURE_BLEND = 0x400000U,
NND_SMOTTYPE_OFFSET_U = 0x800000U,
NND_SMOTTYPE_OFFSET_V = 0x1000000U,
NND_SMOTTYPE_OFFSET_UV = (NND_SMOTTYPE_OFFSET_U | NND_SMOTTYPE_OFFSET_V),
NND_SMOTTYPE_MATCLBK_USER = 0x2000000U
};
enum NNE_SMOTIPTYPE
{
/* Masks */
NND_SMOTIPTYPE_IP_MASK = 0xE77U,
NND_SMOTIPTYPE_REPEAT_MASK = 0x1F0000U,
/* Repeat types */
NND_SMOTIPTYPE_NOREPEAT = 0x10000U,
NND_SMOTIPTYPE_CONSTREPEAT = 0x20000U,
NND_SMOTIPTYPE_REPEAT = 0x40000U,
NND_SMOTIPTYPE_MIRROR = 0x80000U,
NND_SMOTIPTYPE_OFFSET = 0x100000U,
/* Interpolation types */
NND_SMOTIPTYPE_SPLINE = 1,
NND_SMOTIPTYPE_LINEAR = 2,
NND_SMOTIPTYPE_CONSTANT = 4,
NND_SMOTIPTYPE_BEZIER = 16,
NND_SMOTIPTYPE_SI_SPLINE = 32,
NND_SMOTIPTYPE_TRIGGER = 64,
NND_SMOTIPTYPE_QUAT_LERP = 512,
NND_SMOTIPTYPE_QUAT_SLERP = 1024,
NND_SMOTIPTYPE_QUAT_SQUAD = 2048
};
struct NNS_MOTION_BEZIER_HANDLE
{
NNS_VECTOR2D in;
NNS_VECTOR2D out;
};
struct NNS_MOTION_SI_SPLINE_HANDLE
{
NNS_VECTOR2D in;
NNS_VECTOR2D out;
};
struct NNS_MOTION_KEY_FLOAT
{
float frame;
float value;
};
struct NNS_MOTION_KEY_BEZIER
{
float frame;
float value;
NNS_MOTION_BEZIER_HANDLE bhandle;
};
struct NNS_MOTION_KEY_SI_SPLINE
{
float frame;
float value;
NNS_MOTION_SI_SPLINE_HANDLE shandle;
};
struct NNS_MOTION_KEY_TEXCOORD
{
float frame;
NNS_TEXCOORD value;
};
struct NNS_MOTION_KEY_VECTOR
{
float frame;
NNS_VECTOR value;
};
struct NNS_MOTION_KEY_RGB
{
float frame;
NNS_RGB value;
};
struct NNS_MOTION_KEY_QUATERNION
{
float frame;
NNS_QUATERNION value;
};
struct NNS_MOTION_KEY_SINT32
{
float frame;
int32_t value;
};
struct NNS_MOTION_KEY_BEZIER_SINT32
{
float frame;
int32_t value;
NNS_MOTION_BEZIER_HANDLE bhandle;
};
struct NNS_MOTION_KEY_SI_SPLINE_SINT32
{
float frame;
int32_t value;
NNS_MOTION_SI_SPLINE_HANDLE shandle;
};
struct NNS_MOTION_KEY_UINT32
{
float frame;
uint32_t value;
};
struct NNS_MOTION_KEY_ROTATE_A32
{
float frame;
NNS_ROTATE_A32 value;
};
struct NNS_MOTION_KEY_SINT16
{
int16_t frame;
int16_t value;
};
struct NNS_MOTION_KEY_SI_SPLINE_SINT16
{
int16_t frame;
int16_t value;
NNS_MOTION_SI_SPLINE_HANDLE shandle;
};
struct NNS_MOTION_KEY_ROTATE_A16
{
int16_t frame;
NNS_ROTATE_A16 value;
};
/*
Chunk IDs:
N[X]TL (Ninja [Platform] Texture List)
*/
struct NNS_TEXFILELIST
{
uint32_t texFileCount;
NNS_TEXFILE* texFiles;
};
enum NNE_TEXFTYPE
{
/* Masks */
NND_TEXFTYPE_TEXTYPE_MASK = 255,
/* Flags */
NND_TEXFTYPE_NO_FILENAME = 256,
NND_TEXFTYPE_NO_FILTER = 512,
NND_TEXFTYPE_LISTGLBIDX = 1024,
NND_TEXFTYPE_LISTBANK = 2048,
/* Types */
NND_TEXFTYPE_GVRTEX = 0,
NND_TEXFTYPE_SVRTEX = 1,
NND_TEXFTYPE_XVRTEX = 2
};
enum NNE_MIN
{
NND_MIN_NEAREST = 0,
NND_MIN_LINEAR = 1,
NND_MIN_NEAREST_MIPMAP_NEAREST = 2,
NND_MIN_NEAREST_MIPMAP_LINEAR = 3,
NND_MIN_LINEAR_MIPMAP_NEAREST = 4,
NND_MIN_LINEAR_MIPMAP_LINEAR = 5,
NND_MIN_ANISOTROPIC = 6,
NND_MIN_ANISOTROPIC2 = 6,
NND_MIN_ANISOTROPIC_MIPMAP_NEAREST = 7,
NND_MIN_ANISOTROPIC2_MIPMAP_NEAREST = 7,
NND_MIN_ANISOTROPIC_MIPMAP_LINEAR = 8,
NND_MIN_ANISOTROPIC2_MIPMAP_LINEAR = 8,
NND_MIN_ANISOTROPIC4 = 9,
NND_MIN_ANISOTROPIC4_MIPMAP_NEAREST = 10,
NND_MIN_ANISOTROPIC4_MIPMAP_LINEAR = 11,
NND_MIN_ANISOTROPIC8 = 12,
NND_MIN_ANISOTROPIC8_MIPMAP_NEAREST = 13,
NND_MIN_ANISOTROPIC8_MIPMAP_LINEAR = 14
};
enum NNE_MAG
{
NND_MAG_NEAREST = 0,
NND_MAG_LINEAR = 1,
NND_MAG_ANISOTROPIC = 2
};
struct NNS_TEXFILE
{
/* See NNE_TEXFTYPE. */
uint32_t type;
/* The name of the file the texture is stored in. Seems it can also be null?? */
char* fileName;
/* See NNE_MIN. */
uint16_t minFilter;
/* See NNE_MAG. */
uint16_t magFilter;
/* Set to 0 if NND_TEXFTYPE_LISTGLBIDX flag is not set in [type]. */
uint32_t globalIndex;
/* Set to 0 if NND_TEXFTYPE_LISTBANK flag is not set in [type]. */
uint32_t bank;
};
// TODO: Add information on the rest of the Sega NN chunks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment