Actions

SCHG

Difference between revisions of "Sonic Forces"

From Sonic Retro

m (Text replacement - " =([A-Za-z]*)= " to " ==$1== ")
 
(6 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
{{SCHG SF}}
 
{{SCHG SF}}
  
This is the '''[[Sonic Community Hacking Guide]]''' for the PC version of '''''[[Sonic Forces]]'''''.
+
This is the '''[[Sonic Community Hacking Guide]]''' for the PC version of '''''[[Sonic Forces]]'''''.<br />
 
 
 
Sonic Forces you to use this stuff to fix his game.
 
Sonic Forces you to use this stuff to fix his game.
  
=File/Format specifications=
+
==Tools==
==BINA==
+
==Hedge Mod Manager==
'''NOTE:''' This section is highly based on my page on ''Sonic Colors'' formats: [[SCHG:Sonic_Colors]]
+
[[Hedge Mod Manager]] is a mod/code loader and manager for various Hedgehog Engine-based games, most noticeably ''[[Sonic Forces]]''. It was written in C# by Radfordhound and thesupersonic16, and is [https://github.com/thesupersonic16/HedgeModManager fully open-source]/[https://github.com/thesupersonic16/HedgeModManager/blob/master/LICENSE.md under the MIT License]. You can download the latest version [https://github.com/thesupersonic16/HedgeModManager/releases/latest here].
 
 
The BINA format is a generic container format used by Sonic Team in titles such as ''[[Sonic the Hedgehog (2006 game)]]'', ''[[Sonic and the Secret Rings]]'', ''[[Sonic and the Black Knight]]'', ''[[Sonic Colors]]'', ''[[Sonic Lost World]]'', and now in ''[[Sonic Forces]]''.
 
 
 
Generally speaking, any file that follows the BINA Format is structured as follows:
 
* BINA Header
 
* Data (Entirely based on the type of data the file contains)
 
* BINA String Table
 
* BINA Offset Table (aka BINA Final Table or BINA Footer)
 
 
 
Each of BINA-specific components are detailed below:
 
 
 
===Header===
 
<pre>
 
struct Header
 
{
 
    char[4] BINASignature = "BINA";
 
    char[3] VersionNumber = "210";
 
    char EndianFlag; // "B" if the file is big-endian, "L" if the file is little-endian.
 
    uint FileSize;
 
    ushort Unknown1;
 
    ushort Unknown2;
 
 
 
    char[4] DATASignature = "DATA";
 
    uint DataLength = (FileSize - 16); // How long the file is from the beginning of DATASignature until the end.
 
    uint StringTableOffset; // The non-absolute offset to the BINA String Table explained below.
 
    uint StringTableLength; // The length of the BINA String Table explained below.
 
 
 
    uint OffsetTableLength; // The length of the BINA Offset Table explained below.
 
    ushort AdditionalDataLength = 0x18;
 
    ushort Padding = 0; // Just two nulls to pad-out AdditionalDataLength to 4 bytes.
 
    byte[AdditionalDataLength] AdditionalData; // Seems to always just contain nulls
 
}
 
</pre>
 
 
 
===String Table===
 
Literally just an array of null-terminated strings (which are referenced via offsets by other values in the file).
 
The only other thing noteworthy about them is that the last value in the array '''must''' be padded to a 0x4 offset.
 
 
 
===Offset Table===
 
This is the most complicated part of the BINA format by far, so brace yourselves!! <br />
 
The BINA Offset Table, in concept, is actually very simple. It's literally just an array that lists the position of every offset in the file (not counting the offsets in the header, such as the offset to the string table).
 
 
 
However, in execution, BINA offset tables are quite complicated, as they use a few clever techniques to ensure the offset tables are as small as possible.
 
 
 
As an example, let's use some values (in hex) from an actual BINA offset table, namely the first 4 bytes in w5a01_obj_area01.gedit's offset table:
 
<pre>
 
44 48 42 42
 
</pre>
 
 
 
 
 
The first value in this table (44 in hex) is represented as the following in binary:
 
<pre>
 
0100 0100
 
</pre>
 
 
 
 
 
So basically, here's how this works.
 
The first two bits represent how long this offset is, corresponding to one of the values in the table below:
 
<pre>
 
00 = This offset is 0 bits long, you've reached the end of the offset table!
 
01 = This offset is 6 bits long.
 
10 = This offset is 14 bits long.
 
11 = This offset is 30 bits long.
 
</pre>
 
 
 
 
 
In this case, since the first two bits are 01, the offset is 6 bits long, so we read the next 6 bits (00 0100) to get the value for the offset. Simple, right?
 
 
 
Now here's where things get a little tricky.
 
 
 
You see, these values aren't simply the positions of each offset. Rather, these values represent the distance between this offset and the last offset we've currently read (or the length of the BINA Header, aka 64 bytes, if we haven't actually read an offset yet).
 
 
 
Beyond that, due to how binary works, the last two bits in a value can only possibly be used to represent a value from 0-3, which wouldn't be very helpful here, as each offset, at a minimum, must be 4 bytes long (in Forces they're actually typically 8 bytes long but shh let's save that for later)!
 
 
 
So, to better take advantage of space, all of the values have actually been bit-shifted by two to the right (01 0100 is stored in the file like 00 0101, for example). Therefore, you also need to bit-shift these values back to the left by 2 to counter-act this.
 
 
 
====Example====
 
 
 
Phew, alright, we covered a lot just then! Still with us? Good! <br />
 
Let's go over how you'd read each offset in the above example table.
 
 
 
The first value in our example offset table is 44 in hex and 0100 0100 in binary.
 
Since the first two bits are 01, it means we need to read the next 6 bits (00 0100) to get our value.
 
But remember, these values are actually all shifted by 2 to the right, so we need to shift it to the left by 2 to get the actual value: 0001 0000 (or 16).
 
 
 
Now we just add that to the position of the last offset we read, except in this case we haven't actually read an offset yet, so instead we use the length of the BINA header (again, 64 bytes). 64 + 16 = 80. So, the position of our first offset is 80! Now we just have to repeat this process for each offset in the table.
 
 
 
The next offset in this table is 0x48 (0100 1000). The first two bits are 01, so we use the next 6 bits (00 1000) as the value for the offset.
 
Left-shift it by two as described above, and we get 0010 0000 (32). Add that to the position of the last offset we read (80), and presto, we get the position of our second offset (112)!
 
 
 
I think you get it now, so I'll stop there. Hopefully the BINA Offset Table now makes sense!
 
 
 
==GEdit==
 
The GEdit format is used for defining "set data" (stage object layouts).
 
You can find each stage's gedit files in "wars_patch.cpk" (for some reason not in wars_0 or wars_1), inside the PAC file that has the stage's ID in its name under the "gedit" folder.
 
 
 
The GEdit format is a BINA format, therefore it contains a BINA Header, String Table, and Offset Table as described in the above section on the BINA format. The format-specific "Data" portion of the GEdit format works as follows:
 
 
 
<pre>
 
struct Header
 
{
 
    ulong Padding1 = 0;
 
    ulong Padding2 = 0;
 
   
 
    ulong ObjectOffsetTableOffset;
 
    ulong ObjectCount;
 
 
 
    ulong ObjectCount2 = ObjectCount; // Not sure why the object count is in here twice? Lol
 
    ulong Padding3 = 0;
 
 
 
    ulong[ObjectCount] ObjectOffsetTable; // An array of offsets to each object entry
 
    ObjectEntry[ObjectCount] Objects;
 
}
 
 
 
class ObjectEntry
 
{
 
    ulong Padding1 = 0;
 
    ulong ObjectTypeOffset; // Points to the object's type in the BINA String Table.
 
 
 
    ulong ObjectNameOffset; // Points to the object's name in the BINA String Table.
 
    ushort ObjectID;
 
    ushort Unknown1;
 
    ushort ParentObjectID; // Set to 0 if the object has no parent
 
    ushort ParentUnknown1; // Set to 0 if the object has no parent
 
 
 
    float[3] Position;
 
    float[3] Rotation; // In radians, not degrees.
 
 
 
    // These seem to have no effect in-game, so they might just be
 
    // values used by the dev team's level editor.
 
    float[3] Position2;
 
    float[3] Rotation2; // Also in radians
 
   
 
    // See the following section for more information on tags
 
    ulong TagsOffsetTableOffset;
 
    ulong TagCount; // ?
 
    ulong UnknownCount1 = TagCount; // Seems to always be the same as TagCount?
 
    ulong Padding3 = 0;
 
  
    // Parameters are hard-coded for each object type. Since there are too
+
==SFPac==
    // many object-specific parameters to list on one page, see our Forces
+
SFPac is an open-source Python script written by Skyth which can be used to extract/repack archives in the Forces variation of the .PAC format. You can download the latest version [https://gist.github.com/blueskythlikesclouds/887d227301dd3c0ea3c62ab6984388cc here].
    // object templates on the HedgeLib repository for type-specific parameter
 
    // specificatons. (https://goo.gl/e1tYXT)
 
    ulong ParametersOffset;
 
   
 
    ulong[TagCount] TagsOffsetTable; // An array of offsets to each tag
 
    Tag[TagCount] Tags;
 
}
 
</pre>
 
  
===Tags===
+
Simply drop .PAC files onto it to extract them, or drop a folder onto it to repack.
Tags are a special type of data that can be assigned to an object.
 
  
They act very similarly to the object's parameters, except tags actually have types that are included in the set data, as well as a pre-defined structure that can be used to read them even if their type is not known.
+
==ModelConverter==
 +
ModelConverter is a command-line tool written by Skyth in C++ that allows you to convert FBX/DAE files to .model and .material files in the Forces variation of the format (I.E. with the new header). You can download it [https://drive.google.com/file/d/122Q3iefdAcGp1vRNI0Ng_KQztFK_quSd/view?usp=drivesdk here].
  
<pre>
+
Simply drop an FBX or DAE file onto it to convert it to Forces .model and .material formats.
class Tag
 
{
 
    ulong Padding1 = 0;
 
    ulong TypeOffset; // Points to the tag's type in the BINA String Table
 
    ulong DataLength;
 
    ulong DataOffset;
 
  
    // The actual data in this array has to be parsed differently based on the tag's type.
+
Additionally however, you can append "tags" to the end of your model/mesh/material names before exporting to FBX/DAE to set various options.
    // See the following section for currently-known tags.
 
    byte[DataLength] Data;
 
}
 
</pre>
 
  
 +
* '''@LYR(layer)'''    Specifies what layer the given material will operate on.
 +
* '''@NAME(meshName)'''    Specifies the name of the given mesh. Used for mouth-swapping, for example.
 +
* '''@PRP(propertyName, value)'''    Specifies a property that will be added to the header of the given mesh.
  
While the data itself can be read due to this structure even if the type of tag being read is unknown, actually parsing it unfortunately still requires type-specific code or templates. Here are the specifications for every currently-known type:
+
Properties are a component of the new header used in Lost World and Forces. Some examples of properties include:
  
====RangeSpawning====
+
* '''ShadowCa'''    Specifies whether or not the given mesh should cast a shadow.
<pre>
+
* '''ShadowRe'''    Specifies whether or not the given mesh should receive a shadow.
class RangeSpawning
 
{
 
    float RangeIn; // How close the player needs to get to the object before the object will spawn
 
    float RangeOut; // How far the player needs to move away from the object once it has spawned before the object will de-spawn.
 
}
 
</pre>
 
  
=Filesystem=
+
For example, if you wish to make a mesh called "MyMesh" cast a shadow, you'd simply append "@PRP(ShadowCa, true)" to the end of its name, so that the mesh's name is "MyMesh@PRP(ShadowCa, true)" upon exporting the FBX/DAE file.
All files are located in their directories found in the steam game directory.
 
  
*''build\main\projects\exec'' - Data regarding the executables and modules.
+
==HedgeEdit==
*''image\x64\disk'' - Packed cpk archives which contains most of the game data are located here including DLC content downloaded from the Steam app's database.
+
[[HedgeEdit]] is an [https://github.com/Radfordhound/HedgeLib/tree/master/HedgeEdit open-source] level editor similar to [[SonicGlvl]], but with support for various 3D Sonic games in the series, rather than being primarily focused on ''[[Sonic Generations]]''. It was written in C# by Radfordhound using [[HedgeLib]], and is [https://github.com/Radfordhound/HedgeLib/blob/master/HedgeEdit/License.txt under the MIT License]. You can download the latest version [https://ci.appveyor.com/project/Radfordhound/hedgelib/build/artifacts here].
*''savedata\'' - Save data are stored in this folder everytime the game saves.
 
  
=Tools=
+
==References==
*Mods and codes can be managed and run by [[Hedge Mod Manager]] by Radfordhound and thesupersonic16.
+
<references />
  
 
{{SCHGuides}}
 
{{SCHGuides}}
 
[[Category:Sonic Community Hacking Guide]]
 
[[Category:Sonic Community Hacking Guide]]

Latest revision as of 16:52, 24 March 2020

Sonic Community Hacking Guide
Sonic Forces
File Index
wars_0

wars_1
wars_patch

BINA Formats
GEdit

This is the Sonic Community Hacking Guide for the PC version of Sonic Forces.
Sonic Forces you to use this stuff to fix his game.

Tools

Hedge Mod Manager

Hedge Mod Manager is a mod/code loader and manager for various Hedgehog Engine-based games, most noticeably Sonic Forces. It was written in C# by Radfordhound and thesupersonic16, and is fully open-source/under the MIT License. You can download the latest version here.

SFPac

SFPac is an open-source Python script written by Skyth which can be used to extract/repack archives in the Forces variation of the .PAC format. You can download the latest version here.

Simply drop .PAC files onto it to extract them, or drop a folder onto it to repack.

ModelConverter

ModelConverter is a command-line tool written by Skyth in C++ that allows you to convert FBX/DAE files to .model and .material files in the Forces variation of the format (I.E. with the new header). You can download it here.

Simply drop an FBX or DAE file onto it to convert it to Forces .model and .material formats.

Additionally however, you can append "tags" to the end of your model/mesh/material names before exporting to FBX/DAE to set various options.

  • @LYR(layer) Specifies what layer the given material will operate on.
  • @NAME(meshName) Specifies the name of the given mesh. Used for mouth-swapping, for example.
  • @PRP(propertyName, value) Specifies a property that will be added to the header of the given mesh.

Properties are a component of the new header used in Lost World and Forces. Some examples of properties include:

  • ShadowCa Specifies whether or not the given mesh should cast a shadow.
  • ShadowRe Specifies whether or not the given mesh should receive a shadow.

For example, if you wish to make a mesh called "MyMesh" cast a shadow, you'd simply append "@PRP(ShadowCa, true)" to the end of its name, so that the mesh's name is "MyMesh@PRP(ShadowCa, true)" upon exporting the FBX/DAE file.

HedgeEdit

HedgeEdit is an open-source level editor similar to SonicGlvl, but with support for various 3D Sonic games in the series, rather than being primarily focused on Sonic Generations. It was written in C# by Radfordhound using HedgeLib, and is under the MIT License. You can download the latest version here.

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