Skip to content

Commit

Permalink
(SonicStorybook) Motion Table Port
Browse files Browse the repository at this point in the history
  • Loading branch information
Knuxfan24 committed Aug 29, 2024
1 parent 0f31ca3 commit 971b9fa
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 0 deletions.
292 changes: 292 additions & 0 deletions KnuxLib/Engines/SonicStorybook/MotionTable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
namespace KnuxLib.Engines.SonicStorybook
{
// TODO: Figure out the unknown values.
public class MotionTable : FileBase
{
// Generic VS stuff to allow creating an object that instantly loads a file.
public MotionTable() { }
public MotionTable(string filepath, bool export = false)
{
// Set this format's JSON file extension (usually in the form of engine.format.json).
string jsonExtension = ".sonicstorybook.motion.json";

// Check if the input file is this format's JSON.
if (Helpers.GetExtension(filepath) == jsonExtension)
{
// Deserialise the input JSON.
Data = JsonDeserialise<Motion[]>(filepath);

// If the export flag is set, then save this format.
if (export)
Save($@"{Helpers.GetExtension(filepath, true)}.bin");
}

// Check if the input file isn't this format's JSON.
else
{
// Load this file.
Load(filepath);

// If the export flag is set, then export this format.
if (export)
JsonSerialise($@"{Helpers.GetExtension(filepath, true)}{jsonExtension}", Data);
}
}

// Classes for this format.
public class Motion
{
/// <summary>
/// The index that other motion entries use to reference this one.
/// </summary>
public uint AnimationIndex { get; set; }

/// <summary>
/// The Ninja Animation file associated with this motion.
/// </summary>
public string AssociatedNinjaMotion { get; set; } = "";

/// <summary>
/// The index of the motion to use as a follow up when this one ends.
/// </summary>
public uint FollowUpAnimationIndex { get; set; }

/// <summary>
/// An unknown floating point value.
/// TODO: What is this? Something to do with frames? Loop start frame?
/// </summary>
public float UnknownFloat_1 { get; set; }

/// <summary>
/// An unknown floating point value.
/// TODO: What is this? Something to do with frames? Loop end frame?
/// </summary>
public float UnknownFloat_2 { get; set; }

/// <summary>
/// An unknown short value.
/// TODO: What is this?
/// </summary>
public ushort UnknownUShort_1 { get; set; }

/// <summary>
/// An unknown short value.
/// TODO: What is this?
/// </summary>
public ushort UnknownUShort_2 { get; set; }

/// <summary>
/// A value to multiple the framerate specified in this motion's GNM file by.
/// </summary>
public float FramerateMultiplier { get; set; }

/// <summary>
/// An unknown floating point value.
/// TODO: What is this?
/// </summary>
public float UnknownFloat_3 { get; set; }

/// <summary>
/// Displays this motion's index and ninja file in the debugger.
/// </summary>
public override string ToString() => $"Index {AnimationIndex}: {AssociatedNinjaMotion}";

/// <summary>
/// Initialises this motion with default data.
/// </summary>
public Motion() { }

/// <summary>
/// Initialises this motion with the provided data.
/// </summary>
public Motion(uint animationIndex, string associatedNinjaMotion, uint followUpAnimationIndex, float unknownFloat_1, float unknownFloat_2, ushort unknownUShort_1, ushort unknownUShort_2, float framerateMultiplier, float unknownFloat_3)
{
AnimationIndex = animationIndex;
AssociatedNinjaMotion = associatedNinjaMotion;
FollowUpAnimationIndex = followUpAnimationIndex;
UnknownFloat_1 = unknownFloat_1;
UnknownFloat_2 = unknownFloat_2;
UnknownUShort_1 = unknownUShort_1;
UnknownUShort_2 = unknownUShort_2;
FramerateMultiplier = framerateMultiplier;
UnknownFloat_3 = unknownFloat_3;
}

/// <summary>
/// Initialises this motion by reading its data from a BinaryReader.
/// </summary>
public Motion(ExtendedBinaryReader reader, uint stringTableOffset) => Read(reader, stringTableOffset);

/// <summary>
/// Reads the data for this motion.
/// </summary>
public void Read(ExtendedBinaryReader reader, uint stringTableOffset)
{
// Read this motion's internal index.
AnimationIndex = reader.ReadUInt32();

// Read the offset into the name table for this motion.
uint nameOffset = reader.ReadUInt32();

// Save our position so we can jump back after reading the motion name.
long position = reader.BaseStream.Position;

// Jump to this motion's entry in the string table.
reader.JumpTo(stringTableOffset + nameOffset);

// Read this motion's Ninja file reference.
AssociatedNinjaMotion = reader.ReadNullTerminatedString();

// Jump back for the rest of the motion.
reader.JumpTo(position);

// Skip an unknown value of 0xFFFFFFFF.
reader.CheckValue(-1);

// Read this motion's follow up animation index.
FollowUpAnimationIndex = reader.ReadUInt32();

// Read this motion's first unknown floating point value.
UnknownFloat_1 = reader.ReadSingle();

// Read this motion's second unknown floating point value.
UnknownFloat_2 = reader.ReadSingle();

// Read this motion's first unknown short value.
UnknownUShort_1 = reader.ReadUInt16();

// Read this motion's second unknown short value.
UnknownUShort_2 = reader.ReadUInt16();

// Read this motion's framerate multiplier.
FramerateMultiplier = reader.ReadSingle();

// Read this motion's third unknown floating point value.
UnknownFloat_3 = reader.ReadSingle();
}

/// <summary>
/// Writes the data for this motion.
/// </summary>
public int Write(ExtendedBinaryWriter writer, int totalStringLength)
{
// Write this motion's internal animation index.
writer.Write(AnimationIndex);

// Write the current value of totalStringLength.
writer.Write(totalStringLength);

// Add the length of this motion's Ninja file name (including the null terminator) to the totalStringLength.
totalStringLength += AssociatedNinjaMotion.Length + 1;

// Write an unknown value of 0xFFFFFFFF.
writer.Write(0xFFFFFFFF);

// Write this motion's follow up animation index.
writer.Write(FollowUpAnimationIndex);

// Write this motion's first unknown floating point value.
writer.Write(UnknownFloat_1);

// Write this motion's second unknown floating point value.
writer.Write(UnknownFloat_2);

// Write this motion's first unknown short value.
writer.Write(UnknownUShort_1);

// Write this motion's second unknown short value.
writer.Write(UnknownUShort_2);

// Write this motion's framerate modifier.
writer.Write(FramerateMultiplier);

// Write this motion's third unknown floating point value.
writer.Write(UnknownFloat_3);

// Return the updated string length.
return totalStringLength;
}
}

// Actual data presented to the end user.
public Motion[] Data = [];

/// <summary>
/// Loads and parses this format's file.
/// </summary>
/// <param name="filepath">The path to the file to load and parse.</param>
public void Load(string filepath)
{
// Load this file into a BinaryReader.
ExtendedBinaryReader reader = new(File.OpenRead(filepath));

// Read this file's size.
uint fileSize = reader.ReadUInt32();

// Read the motion count for this file.
Data = new Motion[reader.ReadUInt32()];

// Read the offset to this file's motion table.
uint motionTableOffset = reader.ReadUInt32();

// Read the offset to this file's string table.
uint stringTableOffset = reader.ReadUInt32();

// Jump to the motion table.
reader.JumpTo(motionTableOffset);

// Loop through and read each motion.
for (int motionIndex = 0; motionIndex < Data.Length; motionIndex++)
Data[motionIndex] = new(reader, stringTableOffset);

// Close our BinaryReader.
reader.Close();
}

/// <summary>
/// Saves this format's file.
/// </summary>
/// <param name="filepath">The path to save to.</param>
public void Save(string filepath)
{
// Set up a value to store the string table's current length.
int totalStringLength = 0;

// Create this file through a BinaryWriter.
ExtendedBinaryWriter writer = new(File.Create(filepath));

// Write a placeholder for this file's size.
writer.Write("SIZE");

// Write this file's motion count.
writer.Write(Data.Length);

// Add an offset for this file's motion table.
writer.AddOffset("MotionTable");

// Add an offset for this file's string table.
writer.AddOffset("StringTable");

// Fill in the offset for this file's motion table.
writer.FillInOffset("MotionTable");

// Loop through and write each motion entry.
for (int motionIndex = 0; motionIndex < Data.Length; motionIndex++)
totalStringLength = Data[motionIndex].Write(writer, totalStringLength);

// Fill in the offset for this file's string table.
writer.FillInOffset("StringTable");

// Loop through and write each motion entry's Ninja file name.
for (int motionIndex = 0; motionIndex < Data.Length; motionIndex++)
writer.WriteNullTerminatedString(Data[motionIndex].AssociatedNinjaMotion);

// Write the file size.
writer.BaseStream.Position = 0x00;
writer.Write((uint)writer.BaseStream.Length);

// Close our BinaryWriter.
writer.Close();
}
}
}
2 changes: 2 additions & 0 deletions KnuxTools/FormatPrints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ public static void SonicStorybook()
Console.WriteLine("Sonic Storybook Engine:");
Console.WriteLine("Light Field (.bin)");
Helpers.ColourConsole(" Version Flag - storybook_lightfield", true, ConsoleColor.Yellow);
Helpers.ColourConsole("Motion Table (.bin)");
Helpers.ColourConsole(" Version Flag - storybook_motion", true, ConsoleColor.Yellow);
Console.WriteLine("ONE Archive (.one) - Extracts to a directory of the same name as the input archive and creates an archive from an input\r\ndirectory.");
Helpers.ColourConsole(" Version Flag - storybook", true, ConsoleColor.Yellow);
Console.WriteLine("Stage Entity Table Object Table (.bin)");
Expand Down
3 changes: 3 additions & 0 deletions KnuxTools/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ private static void HandleFile(string arg)
{ "spacechannel_caption\t(Space Channel Engine Caption Table)", false },
{ "spacechannel_caption_jpn\t(Space Channel Engine Caption Table (Japanese))", false },
{ "storybook_lightfield\t(Sonic Storybook Engine Light Field)", false },
{ "storybook_motion\t\t(Sonic Storybook Engine Motion Table)", false },
{ "storybook_setitems_sr\t(Sonic Storybook Engine Stage Entity Table Object Table File (Secret Rings))", false },
{ "storybook_setitems_bk\t(Sonic Storybook Engine Stage Entity Table Object Table File (Black Knight))", false }
});
Expand All @@ -237,12 +238,14 @@ private static void HandleFile(string arg)
case "spacechannel_caption": _ = new KnuxLib.Engines.SpaceChannel.CaptionTable(arg, false, true); break;
case "spacechannel_caption_jpn": _ = new KnuxLib.Engines.SpaceChannel.CaptionTable(arg, true, true); break;
case "storybook_lightfield": _ = new KnuxLib.Engines.SonicStorybook.LightField(arg, true); break;
case "storybook_motion": _ = new KnuxLib.Engines.SonicStorybook.MotionTable(arg, true); break;
case "storybook_setitems_sr": _ = new KnuxLib.Engines.SonicStorybook.StageEntityTableItems(arg, KnuxLib.Engines.SonicStorybook.StageEntityTableItems.FormatVersion.SecretRings, true); break;
case "storybook_setitems_bk": _ = new KnuxLib.Engines.SonicStorybook.StageEntityTableItems(arg, KnuxLib.Engines.SonicStorybook.StageEntityTableItems.FormatVersion.BlackKnight, true); break;
default: Helpers.InvalidFormatVersion("Generic Binary"); return;
}

break;
case ".sonicstorybook.motion.json": _ = new KnuxLib.Engines.SonicStorybook.MotionTable(arg, true); break;
case ".sonicstorybook.lightfield.json": _ = new KnuxLib.Engines.SonicStorybook.LightField(arg, true); break;
case ".sonicstorybook.setitems.json":
// Check for a format version.
Expand Down

0 comments on commit 971b9fa

Please sign in to comment.