Actions

SCHG

Difference between revisions of "Sonic Generations"

From Sonic Retro

(QuickBMS)
(CRI Packed File Maker section has been replaced with a brand new CPKREDIR section, which probably needs revision. I've also added a small section on SonicGLvl, which needs expansion.)
Line 2: Line 2:
 
This is the '''[[Sonic Community Hacking Guide]]''' for the 2011 US/European PC version of '''[[Sonic Generations]]'''.
 
This is the '''[[Sonic Community Hacking Guide]]''' for the 2011 US/European PC version of '''[[Sonic Generations]]'''.
  
=Hacking Tools=
+
=Hacking Programs=
 
==QuickBMS==
 
==QuickBMS==
* [http://www.mediafire.com/?3o3sbjtvtg8vz68 QuickBMS] is used to extract files from Sonic Generations.
+
* [http://www.mediafire.com/?3o3sbjtvtg8vz68 QuickBMS] is used to extract CPK files from Sonic Generations.
* Primarily it is used to unpack .CPK files with [http://aluigi.altervista.org/papers/bms/cpk.bms this script] (right click => save as).
+
* It is used to unpack .CPK files with [http://aluigi.altervista.org/papers/bms/cpk.bms this script] (right click => save as).
* However, it can also be used to unpack .ar.** files with [http://aluigi.altervista.org/papers/bms/arcsys.bms this script]. For editing purposes, it is best not to use QuickBMS to hack .ar.** files.
+
 
 +
==CPKREDIR==
 +
* [http://www32.zippyshare.com/v/56917710/file.html CPKREDIR] intercepts CPK file read operations by Sonic Generations and redirects them to external files. This makes it possible to have the game load modified data files without the need for unpacking and repacking those large CPK archives every time.
 +
* It comes with Dario FF's Classic Sonic Adventures mod, which allows users to play as Classic Sonic in Green Hill Act 2 and vice versa. You will need [http://www.mediafire.com/?fq333b0k5686tap this file] to complete many stages, because Classic Sonic cannot go through Rainbow Rings or Trick Panels.
 +
* CPKREDIR also comes with the functionality of being able to load several mods and combine them. This opens up many possibilities such as Classic Sonic in Wave Ocean, which is available through [http://www.youtube.com/user/Melpontro/videos Melpontro's channel].
  
==Cri Packed File Maker==
 
* [http://199.91.153.4/d6tp2am64uqg/jwygidgmvdw/crifilesystem.7z Crifilesystem] has a tool used specifically for hacking Sonic Generations; CriPackedFileMaker.exe
 
* While QuickBMS is the best tool for ''extracting'' .CPK files, CRI Packed File Maker is the best tool for ''repacking'' (or making, rather) .CPK files.
 
* Do not pack .cpk files with compression; it is unnecessary and takes extra time out of your life.
 
* Data alignment is usually set to 64. Presumably this is unnecessary, but for precautionary purposes, it's best to just leave data alignment at 64.
 
 
==Generations Archive Editor==
 
==Generations Archive Editor==
 
* Community member [[MainMemory]] has adapted his tool [[SADXsndSharp]] to work with Generations' .ar.** files, available [[Generations Archive Editor|here]].
 
* Community member [[MainMemory]] has adapted his tool [[SADXsndSharp]] to work with Generations' .ar.** files, available [[Generations Archive Editor|here]].
* Generally this can be considered an easier way to edit .ar.** files, since individual files can be brought out for editing and put back in without having to make new .ar.** files.
+
 
 +
==SonicGLVL==
 +
* Community member Dario FF has created a level editing and importing program called SonicGLVL, available [http://code.google.com/p/sonic-glvl/downloads/list here]. This allows users to create and remove objects, as well as import Ogre .scene files into Sonic Generations.
 +
* SonicGLVL also has another function: model converting. This allows users to convert .mesh files to .model files, ready for importing into Sonic Generations.
  
 
=File Specifications=
 
=File Specifications=

Revision as of 00:35, 10 June 2012

SCHG: Sonic Generations
Main Article

Objects
Collision Properties

File Index
BB

BB2
BB3

How-To
Import Levels

Create Breakable Objects
Create Splines

This is the Sonic Community Hacking Guide for the 2011 US/European PC version of Sonic Generations.

Hacking Programs

QuickBMS

  • QuickBMS is used to extract CPK files from Sonic Generations.
  • It is used to unpack .CPK files with this script (right click => save as).

CPKREDIR

  • CPKREDIR intercepts CPK file read operations by Sonic Generations and redirects them to external files. This makes it possible to have the game load modified data files without the need for unpacking and repacking those large CPK archives every time.
  • It comes with Dario FF's Classic Sonic Adventures mod, which allows users to play as Classic Sonic in Green Hill Act 2 and vice versa. You will need this file to complete many stages, because Classic Sonic cannot go through Rainbow Rings or Trick Panels.
  • CPKREDIR also comes with the functionality of being able to load several mods and combine them. This opens up many possibilities such as Classic Sonic in Wave Ocean, which is available through Melpontro's channel.

Generations Archive Editor

SonicGLVL

  • Community member Dario FF has created a level editing and importing program called SonicGLVL, available here. This allows users to create and remove objects, as well as import Ogre .scene files into Sonic Generations.
  • SonicGLVL also has another function: model converting. This allows users to convert .mesh files to .model files, ready for importing into Sonic Generations.

File Specifications

Over the past few months, several files have had their formats cracked thanks to various members in and outside of the Sonic Retro community. Here are some of the cracked format specifications:

.instanceinfo

// instanceinfo files
// All offsets not specified as absolute needs to be added 0x18(dec 24) to reach the right referenced address


struct header {
  uint32_be file_size; 
  uint32_be root_node_type; (likely 5)
  uint32_be sub_offset_to_offset_table;
  uint32_be offset_to_root; (absolute)
  uint32_be offset_to_offset_table; (absolute)
  uint32_be offset_to_eof; (absolute)
}


struct root_node {
  uint32_be offset_model_filename;
  uint32_be offset_matrix;
  uint32_be offset_filename;
  uint32_be instance_total; // Unknown what's this total used for?
  uint32_be offset_filename_end;
}



struct instance_matrix {
  float32_be m[16]; // 4x4 matrix, has position, scale, and rotation info
}



struct filename {
  char name[]; // Use as many characters are needed, must be null terminated, 
               // add extra padding to align the end to a 0x4 address
}


// Write after filename

// Code names for mesh loaders: alpha, beta, gamma

struct face_loader {
  uint32_be loader_offset; // offset to this struct + 4, points to the next value

  uint32_be alpha_mesh_loader_total;
  uint32_be alpha_mesh_loader_offset; // If loader total is 0, use the biggest address out of the 3 offsets

  uint32_be beta_mesh_loader_total;
  uint32_be beta_mesh_loader_offset;  // Same case as with alpha

  uint32_be gamma_mesh_loader_total;
  uint32_be gamma_mesh_loader_offset; // Same case as with alpha
}


@alpha_mesh_loader_offset

// Follow each of these offsets to reach yet another loader
uint32_be alpha_mesh_offset[alpha_mesh_loader_total];


struct alpha_mesh_loader {
   uint32_be alpha_mesh_total;
   uint32_be alpha_mesh_offset; // Leads to sub_alpha_mesh_offset
}

// Follow these offsets in the table to reach a sub_alpha_mesh structure
uint32_be sub_alpha_mesh_offset[alpha_mesh_total];

struct sub_alpha_mesh {
  uint32_be id_total;
  uint32_be id_offset;
  uint32_be face_count;
  uint32_be face_data_start_offset;
  uint32_be id_data[id_total];
  // Read new faces as triangle strips, each 65535 value means a new strip starts
  uint16_be face_data[face_count];
  
  uint16_be optional_padding; // Add if face_count is an odd value so the structure aligns to 0x4
} 

// *  Repeat the above structures for beta and gamma mesh loaders  *

struct final_offset_table {
   uint32_be offset_count;
   uint32_be offset_table[offset_count];
   uint32_be padding=0; // Marks end of file
}

Offset table contents:
[Addresses to any declared offset in any structure whatsoever in order of writing]

.material

// Material files

// Any offsets not declared as absolute must be added 0x18(dec 24) to reach the proper referenced address

struct header {
   uint32_be file_size;
   uint32_be root_node_type; // Most of the time 3 for Generations
                             // 1 for Unleashed

   uint32_be offset_final_table;
   uint32_be root_node_offset; // Always 24
   uint32_be offset_final_table_abs; // (Absolute)
   uint32_be padding=0;
}


struct root_node {
   uint32_be offset_type_string;
   uint32_be offset_type_string_2;

   uint32_be texset_offset;
   uint32_be texture_offset;
   
   uint32_be unknown_value; // Probably 2 short ints, or 1 byte multipler/scale uv components
 
   uint8 total_material_definitions;
   uint8 unknown_ints[2]={0,0};
   uint8 total_texture_info; // Used for texset and texture tables
   
   uint32_be offset_to_material_offset_table;
   uint32_be unknown_value_2=0;
   uint32_be unknown_value_3=0;

   char type_string_1[]; // Null terminated, no padding needed
   char type_string_2[]; // Null terminated, add padding to reach 0x4 address at the end

   
   uint32_be material_definition_offset_table[total_material_definitions];
}

// Write this structure total_material_definitions times, fill with 0s and null strings
// the marked values if not used

struct material_definition {
   uint16_be map_size_width;
   uint16_be map_size_height;
   uint32_be offset_mat_name;
   uint32_be offset_rgba;
   
   char mat_name[]; // Null terminated, add padding to reach 0x4 address at the end
                    // Write 4 null bytes in case there's no name
   
   float32_be rgba_values[4]; // 4th value is 0 most of the time
                              // Fill with 0s if not used
}


// Read at texset_offset, only 1 structure
uint32_be texset_name_offset_table[total_texture_info];


// Read on each offset a null terminated string
// last string adds padding to reach a 0x4 address
char texset_name[total_texture_info][];




uint32_be texture_offset_table[total_texture_info];


struct texture_info {
   uint32_be offset_tex_name;
   uint32_be unknown_total=0;
   uint32_be offset_tex_type_name;
   char tex_name[]; // Null terminated, no padding needed
   char tex_type_name[]; // Null terminated, add padding to reach a 0x4 address at the end
}


// Final offset table

struct final_offset_table {
  uint32_be offset_total;
  uint32_be offset_table[offset_total];
}

// Since the first EOF offset in the header is 0, there's no need to write an extra 0 value at the end
// of the offset table

Offset table contents: 
[Addresses to any declared offset in any structure whatsoever in order of writing the file(non-absolute)]

.pfi

struct header {

  uint32_be file_size;
  uint32_be root_node_type; // most cases for pfis is 0
  uint32_be struct_table_offset; // Add 0x18(dec 24) to reach it
  uint32_be root_node_offset; // always 0x18(dec 24)
  uint32_be struct_table_offset_2; // Same as before but already has an additional 0x18
  uint32_be end_of_file_offset; // file_size - 4

}

// Write the root node after the main header. Only 1 root node is needed

struct root_node {

  uint32_be ar_package_total;  // Ammount of AR files the PFD has.
  uint32_be ar_offset_table_address;  // Add 0x18(dec 24) to it to reach the table

}

struct ar_offset_table {

  // For writing this one, just fill it with blank if you want
  // then fill it after you're done with the rest

  // Each address is relative to the start of the file plus 0x18(as with any other pointers)
  // The address points to each of the package info structs below

  uint32_be ar_package_offset[ar_package_total];

}

// Write the following structure ar_package_total times
// And fill the offsets of the previous table with them

struct ar_package_info {

  uint32_be package_name_offset; // Pointer to the name of the AR file
  uint32_be package_pfd_offset;  // Absolute offset of the ar file inside the PFD(not needed to add 0x18)
  uint32_be package_size; // Absolute size of the AR file, in bytes
  char name[]; // Size is as long as the ar filename, adds padding if it doesn't align to 0x4 at the end.
}



// There's another extra offsets table of structures written at the end
// After writing all of the above, you have to write the following

struct final_offset_table {
  uint32_be offset_total; // Considering PFIs, this would be ar_package_total*2 + 1
  uint32_be offset_table[offset_total];
  uint32_be padding; // Just an extra 0 to fill the end of file
}

// This final offset table is filled with the following
// All of them use the pointer math I mentioned before(add 0x18(dec 24) to all of them)


Offset table contents:
[Addresses to any declared offset in any structure whatsoever in order of writing]

terrain-block.tbst

// Terrain Block files

struct header {
   uint32_be file_size;
   uint32_be root_node_type; // Most of the time 1
   uint32_be offset_final_table;
   uint32_be root_node_offset; // Always 24
   uint32_be offset_final_table_abs; // (Absolute)
   uint32_be offset_eof; (Absolute)
}


struct root_node {
   uint32_be instance_total;
   uint32_be instance_table_offset;
   uint32_be unknown_total; // Equals instance_total - 1?
}

// Offset to each Instanceinfo structure
uint32_be instance_offset_table[instance_total];

struct instance_info {
   uint32_be block_type; // If it's 1, follow the below struct
                         // If it's 0, it's still in research
   uint32_be terrain_instance_index;
   uint32_be terrain_instance_sub_index;
   uint32_be offset_to_bounding_sphere_center;
   float32_be bounding_sphere_center[3]; // XYZ coordinates of the bounding sphere
                                         // You can read it on the terrain file using the 
                                         // index and sub index mentioned above
   float32_be bounding_sphere_radius;
}



// Final offset table

struct final_offset_table {
  uint32_be offset_total;
  uint32_be offset_table[offset_total];
  uint32_be extra_padding_eof=0;
}

Offset table contents:
[Addresses to any declared offset in any structure whatsoever in order of writing]

.terrain-group

// Terrain Group file

struct header {
   uint32_be file_size;
   uint32_be root_node_type; // Most of the time 1
   uint32_be offset_final_table;
   uint32_be root_node_offset; // Always 24
   uint32_be offset_final_table_abs; // (Absolute)
   uint32_be offset_eof; (Absolute)
}

struct root_node {
   uint32_be instanceinfo_total;
   uint32_be instanceinfo_offset_table; // Address to a table
   uint32_be terrainmodel_total;
   uint32_be terrainmodel_offset_table; // Address to a table
}

uint32_be instanceinfo_offsets[instanceinfo_total];


struct instance_info {
   uint32_be filename_total;
   uint32_be offset_to_filename_offset;
   uint32_be offset_to_bounding_sphere;
   uint32_be offset_to_filename[filename_total];
   char filename[filename_total][]; // Null-terminated, add padding to reach a 0x4 multiple at the end of each string
   float32_be bounding_sphere_center[3]; // XYZ Coordinates
   float32_be bounding_sphere_radius;
}


uint32_be terrainmodel_offsets[terrainmodel_total];


struct terrainmodel_info {
   char filename[]; // Null-terminated, add padding to reach a 0x4 multiple
}


// All filenames don't have the extension

// Final offset table

struct final_offset_table {
  uint32_be offset_total;
  uint32_be offset_table[offset_total];
  uint32_be extra_padding_eof=0;
}

Offset table contents:
[Addresses to any declared offset in any structure whatsoever in order of writing]

==========
EOF

.terrain

// Terrain.terrain files

// Any offsets not declared as absolute must be added 0x18(dec 24) to reach the proper referenced address

struct header {
   uint32_be file_size;
   uint32_be root_node_type; // Most of the time 1
   uint32_be offset_final_table;
   uint32_be root_node_offset; // Always 24
   uint32_be offset_final_table_abs; // (Absolute)
   uint32_be offset_eof; (Absolute)
}


struct root_node {
   uint32_be group_total;
   uint32_be group_table_offset;
}


// Offset to each group_info structure
uint32_be group_offset_table[group_total];

struct group_info {
   uint32_be offset_to_center_position;
   uint32_be offset_to_filename;
   uint32_be extracted_folder_size_total; // Calculate this by adding the size of each file
                                          // in the uncompressed tg folder

   uint32_be spheres_total; // Matches up to the amount of unique instances, as in, the ones that don't start
                            // with the same prefix name
   uint32_be offset_to_sphere_offset_table;
   int32_be unknown_ff_value=-1;

   float32_be center_position_x;
   float32_be center_position_y;
   float32_be center_position_z;
   float32_be center_position_radius;

   char filename[]; // Null terminated, add padding to reach a 0x4 address at the end

   // Offset to each group_instance_info_sphere structure
   uint32_be sphere_offset_table[spheres_total];
}


// Write this structure after it spheres_total times

struct group_instance_info_sphere {
   float32_be center_x;
   float32_be center_y;
   float32_be center_z;
   float32_be center_radius;
}



// Final offset table

struct final_offset_table {
  uint32_be offset_total;
  uint32_be offset_table[offset_total];
  uint32_be extra_padding_eof=0;
}

Offset table contents: 
[Addresses to any declared offset in any structure whatsoever in order of writing the file(non-absolute)]

.light-list

// Any offsets not declared as absolute must be added 0x18(dec 24) to reach the proper referenced address

struct header {
   uint32_be file_size;
   uint32_be root_node_type; // Most of the time 1
   uint32_be offset_final_table;
   uint32_be root_node_offset; // Always 24
   uint32_be offset_final_table_abs; // (Absolute)
   uint32_be offset_eof; (Absolute)
}


struct root_node {
   uint32_be light_total;
   uint32_be light_table_offset;
}

// Address to each name written
uint32_be offset_light_table[light_total];

char light_name[light_total][]; // Null terminated strings big as needed
                                // Add padding to each string to reach a 0x4 multiple address



// Final offset table

struct final_offset_table {
  uint32_be name_stuff;
  uint32_be offset_table[offset_total];
  uint32_be extra_padding_eof=0;
}

Offset table contents: 
[Addresses to any declared offset in any structure whatsoever in order of writing the file(non-absolute)]

.light

struct header {
   uint32_be file_size;
   uint32_be root_node_type; // Most of the time 1
   uint32_be offset_final_table;
   uint32_be root_node_offset; // Always 24
   uint32_be offset_final_table_abs; // (Absolute)
   uint32_be padding=0;
}


struct root_node {
   // Read one of the following structures depending on light_type
   uint32_be light_type;
}

// light_type = 0 - Directional Light
// light_type = 1 - Omni Light

struct directional_light {
   float32_be position[3]; // Direction in the case of directional lights
   float32_be rgb[3];
}

struct omni_light {
   float32_be position[3]; // Position of omni/point lights
   float32_be rgb[3];
   uint32_be unknown_total_1;
   uint32_be unknown_total_2;
   uint32_be unknown_total_3;
   float32_be unknown_float_1;  // It's assumed these two are distance and falloff distance
   float32_be unknown_float_2;
}


// Final offset table

struct final_offset_table {
  // No offsets in this structure
  uint32_be total=0;
}

light-field.lft

// Very WIP
struct header {
   uint32_be file_size;
   uint32_be root_node_type; // Most of the time 1
   uint32_be offset_final_table;
   uint32_be root_node_offset; // Always 24
   uint32_be offset_final_table_abs; // (Absolute)
   uint32_be offset_eof; (Absolute)
}


struct root_node {
   // XYZ coordinates of the world AABB
   float32_be aabb_x1;
   float32_be aabb_x2;
   float32_be aabb_y1;
   float32_be aabb_y2;
   float32_be aabb_z1;
   float32_be aabb_z2;

   uint32_be id_block_total;
   uint32_be id_block_table_offset;

   uint32_be color_block_total;
   uint32_be color_block_table_offset;

   uint32_be index_total;
   uint32_be index_table_offset;
}


struct id_block {
   // This value goes from 0 to 3 in most files tested
   // Given that GIA Levels are on that range, it might be related
   uint32_be type;

   // Value seems to be in the [0..index_total-8] range for most tested files
   // So it's most likely an index to those indices?
   uint32_be value;
}

id_block id_block_table[id_block_total];


// The purpose of the color block table is to build a palette to use
// for light field cubes. Saves up on space by reusing the same blocks

struct color_block {
   // Map [0, 255] to [0.0, 1.0] to get each color(R, G, B) channel.
   // Corresponds to the Box borders color value for light fields
   byte rgb[8][3];

   // Either FF or 00.
   byte flag;
}

color_block id_block_table[color_block_total];

// Points to indices of the palette table
uint32_be index_table[index_total];


// Final offset table

struct final_offset_table {
  uint32_be offset_total;
  uint32_be offset_table[offset_total];
  uint32_be extra_padding_eof=0;
}

Offset table contents:
[Addresses to any declared offset in any structure whatsoever in order of writing]

.gi-texture-group-info

// gi-texture.gi-texture-group-info files

struct header {
   uint32_be file_size;
   uint32_be root_node_type; // Most of the time 2
   uint32_be offset_final_table;
   uint32_be root_node_offset; // Always 24
   uint32_be offset_final_table_abs; // (Absolute)
   uint32_be offset_eof; (Absolute)
}


struct root_node {
   uint32_be name_total; // Matches instance count
   uint32_be name_offset_table_offset;
   uint32_be sphere_offset_table_offset;

   uint32_be gia_total;
   uint32_be gia_offset_table_offset;

   uint32_be low_gia_total;
   uint32_be low_gia_index_table_offset;
}

// Points to name structures
uint32_be name_offset_table[name_total];

struct name {
   // Null terminated string
   // Add padding to reach a 0x4 address
   // The name is the same as the instance referenced
   char instance_name[]; 
}

// Points to instance sphere structures
uint32_be sphere_offset_table[name_total];

struct instance_sphere {
   float32_be position[3];
   float32_be radius;
}


// Points to GIA Info structures
uint32_be gia_info_offset_table[gia_total];

struct gia_info {
   // Quality Level of the GIA. Range: 0-3.
   uint32_be gia_level;
   uint32_be instance_count;
   uint32_be index_table_offset;
   uint32_be bounding_sphere_offset;

   // Total size of the files in the GIA folder of this file
   uint32_be folder_size;
   
   // If the quality level of the GIA is 0, these indexes
   // directly reference the above tables to retrieve the names/bounding spheres

   // If the level is above 0, it will use the indexes of the GIA files that have the instances in one level before
   // For example, a GIA file of Level 1 will reference many files of GIA level 0 to define its own instances
   // Searching recursively should retrieve all the instances you need for calculating the bounding sphere
   uint32_be index_table[instance_count];

   // General bounding sphere, it's the result from all the bounding spheres of the instances
   // Refer to the above comment for an explanation of how to retrieve them
   float32_be bounding_sphere_center[3];
   float32_be bounding_sphere_radius;
}


// Indexes of the GIA files which are in Stage.pfd instead of Stage-add.pfd. These are the low quality GIA files.
uint32_be low_gia_index_table[low_gia_total];


// Final offset table

struct final_offset_table {
  uint32_be offset_total;
  uint32_be offset_table[offset_total];
  uint32_be extra_padding_eof=0;
}

Offset table contents:
[Addresses to any declared offset in any structure whatsoever in order of writing]

atlasinfo

struct atlasinfo_header {
    char padding=0;
    unsigned int8 textures_ammount; // How many DDS files it needs to load
    char add_padding=0;
};

// Read this for textures_ammount times after reading sub_texture_info
struct texture_header {
    unsigned int8 texture_name_size; // How many chars the texture name has
    char texture_name[texture_name_size]; // DDS texture name (without the .dds extension), non-null terminated
    unsigned int8 sub_texture_size; // How many subtextures are in this file
    char padding=0;
};

// Read the next structure structure for sub_texture_size times

struct sub_texture_info {
    unsigned int8 subtexture_name_size; // How many chars the subtexture name has
    char subtexture_name[subtexture_name_size]; // non-null terminated string, internal name of the texture this gia data belongs to in the terrain(or perhaps the object)

    // Multipliers for the UV coords
    unsigned int8 u_mult;
    unsigned int8 v_mult;

   // Absolute value of the UV coordinate this texture is
  // Should be multiplied by u_mult, and v_mult
    unsigned int8 u_scale;
    unsigned int8 v_scale;
};

visibility-tree.vt

// Visibility Tree file

struct header {
   uint32_be file_size;
   uint32_be root_node_type; // Most of the time 0
   uint32_be offset_final_table;
   uint32_be root_node_offset; // Always 24
   uint32_be offset_final_table_abs; // (Absolute)
   uint32_be offset_eof; (Absolute)
}


struct root_node {
   uint32_be offset_to_id_definition_loader_header;
   uint32_be vblock_total;
   uint32_be offset_to_vblock_offset_table;
   uint32_be offset_to_sub_id_offset_table;

   // Uses same aabb, but only has one ID definition object.
   uint32_be offset_to_id_definition_loader_footer;
}

// Only one is used
id_definition_loader id_definition_loader_header;

struct id_definition_loader {
   uint32_be offset_aabb;
   uint32_be id_definition_total;
   uint32_be offset_to_id_definition_table;
   uint32_be unknown_total=0;

   // Read as X1 X2 Y1 Y2 Z1 Z2
   float32_be aabb[6];
}



id_definition id_definition_table[id_definition_total];

struct id_definition {
   // Type 0 1 and 2 unknown, but value points to an index on the same table
   // If no node references its own ID, it might mean it's the root of the linked list
   // If the type is 3, value will either point to a vblock or be FF FF FF FF.
   uint32_be type;
   uint32_be value;
}



uint32_be vblock_offset_table[vblock_total];

struct vblock {
   uint32_be bit_total;
   uint32_be bit_table_offset;

   // Presumably binary flags
   bit binary_block[bit_total];

   // Add the necessary bits to align the structure to a 0x4 address
   bit padding[]=0;
}



uint32_be subid_offset_table[vblock_total];

struct subid {
   uint32_be id_total;
   uint32_be id_table_offset; // Points to ids
   uint32_be ids[id_total];
}

// Again, just use one for the footer
id_definition_loader id_definition_loader_footer;



// Final offset table

struct final_offset_table {
  uint32_be offset_total;
  uint32_be offset_table[offset_total];
  uint32_be extra_padding_eof=0;
}

Offset table contents:
[Addresses to any declared offset in any structure whatsoever in order of writing]
Sonic Community Hacking Guide
General
SonED2 Manual | Subroutine Equivalency List
Game-Specific
Sonic the Hedgehog (16-bit) | Sonic the Hedgehog (8-bit) | Sonic CD (prototype 510) | Sonic CD | Sonic CD (PC) | Sonic CD (2011) | Sonic 2 (Simon Wai prototype) | Sonic 2 (16-bit) | Sonic 2 (Master System) | Sonic 3 | Sonic 3 & Knuckles | Chaotix | Sonic Jam | Sonic Jam 6 | Sonic Adventure | Sonic Adventure DX: Director's Cut | Sonic Adventure DX: PC | Sonic Adventure (2010) | Sonic Adventure 2 | Sonic Adventure 2: Battle | Sonic Adventure 2 (PC) | Sonic Heroes | Sonic Riders | Sonic the Hedgehog (2006) | Sonic & Sega All-Stars Racing | Sonic Unleashed (Xbox 360/PS3) | Sonic Colours | Sonic Generations | Sonic Forces
Technical information
Sonic Eraser | Sonic 2 (Nick Arcade prototype) | Sonic CD (prototype; 1992-12-04) | Dr. Robotnik's Mean Bean Machine | Sonic Triple Trouble | Tails Adventures | Sonic Crackers | Sonic 3D: Flickies' Island | Sonic & Knuckles Collection | Sonic R | Sonic Shuffle | Sonic Advance | Sonic Advance 3 | Sonic Battle | Shadow the Hedgehog | Sonic Rush | Sonic Classic Collection | Sonic Free Riders | Sonic Lost World
Legacy Guides
The Nemesis Hacking Guides The Esrael Hacking Guides
ROM: Sonic 1 | Sonic 2 | Sonic 2 Beta | Sonic 3

Savestate: Sonic 1 | Sonic 2 Beta/Final | Sonic 3

Sonic 1 (English / Portuguese) | Sonic 2 Beta (English / Portuguese) | Sonic 2 and Knuckles (English / Portuguese)
Move to Sega Retro
Number Systems (or scrap) | Assembly Hacking Guide | 68000 Instruction Set | 68000 ASM-to-Hex Code Reference | SMPS Music Hacking Guide | Mega Drive technical information