Actions

SCHG

Sonic Generations

From Sonic Retro

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 US/European PC version of Sonic Generations.

Hacking programs released

QuickBMS

QuickBMS is used to extract CPK files (*.cpk) from the disk and sound folders of Sonic Generations', with the help of this script (right click and choose Save as). It is recommended that you have a folder containing clean versions of the files in the CPK archive, so that you do not have to unpack every single time you want to revert a file back to its original state.

CPKREDIR

CPKREDIR by Korama, a Tech Member of Sonic Retro, is a DLL that intercepts attempts by Sonic Generations to read files within a CPK archive; it then redirects the calls and instead returns the requested data from external files specified by the user. 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.

CPKREDIR also, by virtue of allowing the user to choose which external files are loaded when Generations requests resources of different types, allows the user to combine different modifications. This opens up many possibilities, e.g. Classic Sonic in Wave Ocean as shown on Melpontro's YouTube channel.

Installing CPKREDIR requires that the user apply a patch (included in the download) to their Sonic Generations executable so that CPKREDIR's DLL replaces all references to imagehlp.dll, which was an ideal candidate to be CPKREDIR's trojan horse because the game only imports a single function from it. Simply renaming the CPKREDIR DLL without having patched the main executable would not work because imagehlp.dll is a known DLL.

CPKREDIR already includes one modification: Dario FF's Classic Sonic Adventures, which swaps Modern and Classic Sonic within their respective stages, thereby enabling users to play as Classic Sonic in all Act 2 stages and as Modern Sonic in all Act 1 stages (with certain limitations). Players should additionally install the files in this zip, because otherwise the game crashes if Classic Sonic goes through rainbow rings or trick panels.

CriPack Browser

A tool by Unleashed, is designed to explore (and extract assets from) CPKs without extracting the whole archive.

SonicGMI

SonicGMI(Sonic Generations Mod Installer) is a GUI front-end application to CPKREDIR, designed so end-users can easily sort through their list of installed mods, activate and deactivate them, change the priority loading order, managing save files and various other settings, and even patching/unpatching the exe without the need of Hex-editing.

SonicGMI comes packaged with the latest version of CPKREDIR, so it's recommended to just download it instead if you don't want to mess around with the configuration files manually, and want to boot up mods easily.

Generations Archive Editor

MainMemory, also a Tech Member of Sonic Retro, has adapted his SADXsndSharp tool to work with Generations' .ar.** files and its stage .pfd files. The program provides a GUI interface for adding, removing, and extracting files; and a command-line interface for extracting and packing files.

See its dedicated page: Generations Archive Editor.

SonicGLVL

Dario FF, another Tech Member, has created a level editing and importing program called SonicGLVL, available for download here. This allows users to create and remove objects, as well as to import Ogre .scene files into Sonic Generations to use as level geometry.

SonicGLVL also has another function: model converting. This allows users to convert Ogre .mesh files to Sonic Generations' .model files, ready for importing into the game. It can also convert the game's .model files into COLLADA's .dae format. The same exporting function is also available for Terrain geometry.

SonicGLVL also allows the snapping of objects to the nearest spline, which makes placing objects to paths such as 2D sections and quickstep sections much easier. Along with this, there is also a feature that allows the snapping of objects to the polygon under the mouse.


Hacking programs in development

LibGens is an open source file formats library for reading and modifying Hedgehog Engine file formats. The tools below can be found in this repository.

Hedgehog Converter

A terrain importer for individual stages. Relies on Assimp for 3D File Format support.

GIAtlas Converter

A Global Illumination Atlas (GIA) importer for individual stages. Allows to generate the texture distribution and replacing these textures on two separate modes. The process of rendering the Global Illumination textures is up to the user.

Havok Converter

A Havok collision importer for individual stages. Negates the need of using the Havok Content Tools for creating custom stages. Relies on Assimp for 3D File Format support. More types of Havok files such as Animations and Skeletons are planned to be supported in this tool.

SonicGLVL

Two versions of SonicGLVL can be found on the LibGens repository. SonicGLVL 0.9 is an experimental branch which is mostly functional for layout editing, but is no longer being actively developed on. The Qt branch currently lacks a lot of features, but is the prime candidate for any new developments. Since terrain, collision and global illumination editing has been transferred to individual programs, it's very likely that SonicGLVL will remain focused as a level layout and material editor.

SonicAudioTools

Multiple tools for working with the audio formats used in the modern Sonic Team games and other games that use Criware audio.

Binary releases and source code can be found in the SonicAudioTools repository.


File specifications

Over the past few months, several files have had their formats cracked, thanks to various members both within and beyond 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;

   // Depending on the block type, these two variables mean either of two things:
   // 0: Unknown - Under research
   // 1: id_a = Terrain Group Index; id_b = Terrain Group Instance Sub-index
   uint32_be id_a;
   uint32_be id_b;
   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;
   uint32_be subset_id;

   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;
}


// ID Blocks define a BSP tree, starting from the first node as the root
// Type 0, 1, 2 define a split in the X, Y, Z axis. The current size of the ID Block is defined starting
// from the root, with the World AABB
// The value is an index to another ID Block and its next one (id_block_table[value] and id_block_table[value+1]), 
// defining the two blocks that are part of the split

// Type 3 simply means the ID Block has no children, and the value points to index_table[value...value+7]
// grabbing the 8 indices as the 8 corners of the current ID Block.
// The position of each corner is identified by the relative index of each point. start and end are the vectors of 
// the current AABB
// 0: (start.x, start.y, start.z);
// 1: (start.x, start.y, end.z);
// 2: (start.x, end.y, start.z);
// 3: (start.x, end.y, end.z);
// 4: (end.x, start.y, start.z);
// 5: (end.x, start.y, end.z);
// 6: (end.x, end.y, start.z);
// 7: (end.x, end.y, end.z);
//
// The same ordering goes for the corners of the Color Blocks internally, but it's used for shading purposes instead
// rather than positioning in the AABB

struct id_block {
   uint32_be type;
   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. Color Blocks can, and should be reused
// to save up space

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. Use unknown so far
   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_amount; // How many DDS files it needs to load
    char add_padding=0;
};

// Read this for textures_amount 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)

    // Real width of the texture in pixels = total_width/(2^(width - 1))
    // Real height of the texture in pixels = total_height/(2^(height - 1))
    unsigned int8 width;
    unsigned int8 height;
    
    // Map [0..255] to [0.0..1.0], to get the absolute coordinate of the sub-texture(top-left corner)
    // Since textures only works in powers of two, this should give you perfect precision
    unsigned int8 x;
    unsigned int8 y;
};

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]

.fco Text files

Entries appear to be proceeded by a name in plain text such as "sonic" or "Artwork_039" followed by 1 to 3 @ symbols for padding.
Then there is an unknown four byte value, followed by the text, which is four bytes per letter.
Text seems to end with 00 00 00 04, however editing text to use this earlier doesn't appear to end it prematurely?
After that are 0x4C/76 more bytes of unknown data. 

Text seems to go relatively in the same order as the font image file for the text.

00 00 00 00 = New Line
00 00 00 64 = A button
00 00 00 65 = B button
00 00 00 66 = Y button
00 00 00 67 = X button
00 00 00 68 = LB button
00 00 00 69 = RB button
00 00 00 6A = LT button
00 00 00 6B = RT button
00 00 00 6C = Start button
00 00 00 6D = Back button
00 00 00 6E = Left stick
00 00 00 6F = Right stick
00 00 00 82 = Space
00 00 00 83 = Longer Space
00 00 00 84 = 0
00 00 00 85 = 1
00 00 00 86 = 2
00 00 00 87 = 3
00 00 00 88 = 4
00 00 00 89 = 5
00 00 00 8A = 6
00 00 00 8B = 7
00 00 00 8C = 8
00 00 00 8D = 9
00 00 00 8E = A
00 00 00 8F = B
00 00 00 90 = C
00 00 00 91 = D
00 00 00 92 = E
00 00 00 93 = F
00 00 00 94 = G
00 00 00 95 = H
00 00 00 96 = I
00 00 00 97 = J
00 00 00 98 = K
00 00 00 99 = L
00 00 00 9A = M
00 00 00 9B = N
00 00 00 9C = O
00 00 00 9D = P
00 00 00 9E = Q
00 00 00 9F = R
00 00 00 A0 = S
00 00 00 A1 = T
00 00 00 A2 = U
00 00 00 A3 = V
00 00 00 A4 = W
00 00 00 A5 = X
00 00 00 A6 = Y
00 00 00 A7 = Z
00 00 00 A8 = a
00 00 00 A9 = b
00 00 00 AA = c
00 00 00 AB = d
00 00 00 AC = e
00 00 00 AD = f
00 00 00 AE = g
00 00 00 AF = h
00 00 00 B0 = i
00 00 00 B1 = j
00 00 00 B2 = k
00 00 00 B3 = l
00 00 00 B4 = m
00 00 00 B5 = n
00 00 00 B6 = o
00 00 00 B7 = p
00 00 00 B8 = q
00 00 00 B9 = r
00 00 00 BA = s
00 00 00 BB = t
00 00 00 BC = u
00 00 00 BD = v
00 00 00 BE = w
00 00 00 BF = x
00 00 00 C0 = u
00 00 00 C1 = z
00 00 00 C2 = !
00 00 00 C3 = ?
00 00 00 C4 = ,
00 00 00 C5 = .
00 00 00 C6 = :
00 00 00 C7 = l
00 00 00 C8 = '
00 00 00 C9 = "
00 00 00 CA = /
00 00 00 CB = -
00 00 00 CC = +
00 00 00 CD = *
00 00 00 CE = #
00 00 00 CF = $
00 00 00 D0 = %
00 00 00 D1 = &
00 00 00 D2 = (
00 00 00 D3 = )
00 00 00 D4 = =
00 00 00 D5 = [
00 00 00 D6 = ]
00 00 00 D7 = <
00 00 00 D8 = >
00 00 00 D9 = @
00 00 00 DA = {
00 00 00 DB = }
00 00 01 7D = ?
00 00 01 79 = .
00 00 03 04 = А
00 00 03 05 = Б
00 00 03 06 = В
00 00 03 07 = Г
00 00 03 08 = Д
00 00 03 09 = Е
00 00 03 0A = Ё
00 00 03 0B = Ж
00 00 03 0C = З
00 00 03 0D = И
00 00 03 0E = Й
00 00 03 0F = К
00 00 03 10 = Л
00 00 03 11 = М
00 00 03 12 = Н
00 00 03 13 = О
00 00 03 14 = П
00 00 03 15 = Р
00 00 03 16 = С
00 00 03 17 = Т
00 00 03 18 = У
00 00 03 19 = Ф
00 00 03 1A = Х
00 00 03 1B = Ц
00 00 03 1C = Ч
00 00 03 1D = Ш
00 00 03 1E = Щ
00 00 03 1F = Ъ
00 00 03 20 = Ы
00 00 03 21 = Ь
00 00 03 22 = Э
00 00 03 23 = Ю
00 00 03 24 = Я
00 00 03 25 = а
00 00 03 26 = б
00 00 03 27 = в
00 00 03 28 = г
00 00 03 29 = д
00 00 03 2A = е
00 00 03 2B = ё
00 00 03 2C = ж
00 00 03 2D = з
00 00 03 2E = и
00 00 03 2F = й
00 00 03 30 = к
00 00 03 31 = л
00 00 03 32 = м
00 00 03 33 = н
00 00 03 34 = о
00 00 03 35 = п
00 00 03 36 = р
00 00 03 37 = с
00 00 03 38 = т
00 00 03 39 = у
00 00 03 3A = ф
00 00 03 3B = х
00 00 03 3C = ц
00 00 03 3D = ч
00 00 03 3E = ш
00 00 03 3F = щ
00 00 03 40 = ъ
00 00 03 41 = ы
00 00 03 42 = ь
00 00 03 43 = э
00 00 03 44 = ю
00 00 03 45 = я

After that are various other unicode characters, things like é and Japanese characters, still in the order of the font image.

References


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