Vagrant Story/SEQ files

From Data Crystal
< Vagrant Story
Revision as of 02:42, 24 January 2024 by Xkeeper (talk | contribs) (Xkeeper moved page Vagrant Story:SEQ files to Vagrant Story/SEQ files: normalize subpages and titles)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

This is a sub-page of Vagrant Story.

Seq (Sequence) Files are animation files that are used to animate the games 3D models. The file format is not yet understood. Much of what follows was decyphered by Valendian and another reverse engineer whose shall remain anonymous until I have their permission to reveal their identity.

It seems that each SeqFrameHeader defines a different skeletal/bone pose. Animations are achieved by interpolating between these poses. But there is more done to the joints with the Opcodes, which still has to be uncovered. Note that, for example in a jump, the different poses are hardcoded. So it seems that not all animation info is stored in the SEQ files.

Every joint is assigned a rotation, but translation vectors, except for the complete model, have not been found yet.

typedef struct tSeqHeader {
    ubyte NumFrames;
    ubyte padding;
    uhalf NumJoints;
    word  FileSize;
} SEQ_HEADER;
// Hdr.NumFrames   # number of frames of animation
// Hdr.NumJoints   # number of joints in the target skeleton (must match that of the shp file)
// Hdr.FileSize    # size of file data (minus this header) in bytes aligned to a 4 byte boundary
// the number of sequences must be calculated at runtime. its not actually needed but nice to know

typedef struct tSeqPointerTable {    // seq Pointer Table
    word  PtrSection3;               // [1][2][3][4]
    word  PtrSection2;               // [1][2][3]
} SEQ_PTRTBL;
// SeqPointerTable.PtrSection3   # pointer to the frame data section
// SeqPointerTable.PtrSection2   # pointer to the next sequence section

typedef struct tSeqFrameHeader {     // seq Frame Header
   uhalf Unknown1;                  // [1]
   ubyte idOtherPose;               // [1]
   ubyte Unknown3;                  // [1][2]
   uhalf PtrNextFrame;              // [1]
   uhalf PtrThisFrame;              // [1][2]
   uhalf PtrUnknownFrame;           // [1][2]
   uhalf JointPtr0[Hdr.NumJoints];  // [1][2]
   uhalf JointPtr1[Hdr.NumJoints];  // [1]
} SEQ_FRAMEHDR[NumSequences];
// FrameHdr.Unknown1                   #
// FrameHdr.idOtherPose                # see below
// FrameHdr.Unknown3                   #
// FrameHdr.PtrNextFrame               #
// FrameHdr.PtrThisFrame               #
// FrameHdr.PtrUnknownFrame            #
// FrameHdr.JointPtr0[Hdr.NumJoints]   # points to rotation and animation data for each joint per frame
// FrameHdr.JointPtr1[Hdr.NumJoints]   # points to animation data for each joint per frame

// if FrameHdr.idOtherPose is not 0xFF = -1, then it's an id of a
// different pose, and the FrameHdr.JointPtr0/1 of the OTHER pose is
// used instead!

ubyte Section2[Hdr.NumFrames];       // [1]
// specifies the sequence that follows on from this one (or looping)

typedef struct tSeqFrameDataHeader { // seq Frame Data Header
    ubyte Unknown0;                  // [1][2] # (unaligned half's are read as two bytes)
    ubyte Unknown1;                  // [1][2] # (unaligned half's are read as two bytes)
    ubyte Unknown2;                  // [1][2] # (unaligned half's are read as two bytes)
} SEQ_FrameDataHDR;
// FrameDatahdr.Unknown0   #
// FrameDatahdr.Unknown1   #
// FrameDatahdr.Unknown2   #

// if FrameHdr.idOtherPose is 0xFF, this is the first thing that
// appears at any offset given by FrameHdr.JointPtr[0]
// use this instead of SEQ_FrameDataHDR

typedef struct tSeqFrameDataRotation { // seq Frame Data Rotation
    ubyte x1;
    ubyte x2;
    ubyte y1;
    ubyte y2
    ubyte z1;
    ubyte z2;
} SEQ_FrameDataRotation;
// rotation is represented by euler angles
// stored in big endian!
// angles are computed like this:
// x = ((x1 << 8) | x2) << 1; // rotation around x axis
// 0x0000 is 0 degrees, 0x0400 is 90 degrees, 0x0800 is 180 degrees and 0x0c00 is -90 degrees
// the bone is first rotated around the x axis, then y, then z

typedef struct tSeqFrameOpcode {
    ubyte Opcode;
    ubyte Param1;
    ubyte Param2;
    ubyte Param3;
    ubyte Param4;
    uhalf Param5;
} SEQ_FrameOpcode;
// FrameOpcode.Opcode   # the 3 most significant bits flag the existence of parameters
// FrameOpcode.Param1   # present if  ((FrameOpcode.Opcode & 0x80) == 0x80)
// FrameOpcode.Param2   # present if  ((FrameOpcode.Opcode & 0x40) == 0x40)
// FrameOpcode.Param3   # present if  ((FrameOpcode.Opcode & 0x20) == 0x20)
// FrameOpcode.Param4   # present if (((FrameOpcode.Opcode & 0xE0) == 0x00)
//                                &&  ((FrameOpcode.Opcode & 0x03) != 0x03))
// FrameOpcode.Param5   # present if  ((FrameOpcode.Opcode & 0xE0) == 0x00)

typedef struct tSeqSequenceEnd { // appears to control looping animations
    uhalf Unknown1;                  // [1][2] # (unaligned half's are read as two bytes)
    uhalf Unknown2;                  // [1][2] # (unaligned half's are read as two bytes)
    uhalf Unknown3;                  // [1][2] # (unaligned half's are read as two bytes)
} SEQ_SequenceEnd;
// SequenceEnd.Unknown1   # sometimes first byte is read as an opcode
// SequenceEnd.Unknown2   #
// SequenceEnd.Unknown3   #

// putting it all together the file has the following layout
runtime word NumSequences = (PtrTbl.PtrSection2 - &FrameHdr[0]) / (0x0A + 4*Hdr.NumJoints);
SEQ_HEADER   Hdr;
SEQ_PTRTBL   PtrTbl;
SEQ_FRAMEHDR FrameHdr[NumSequences];
ubyte        Section2[Hdr.NumFrames];

// probably not for each sequence
for (NumSequences) {
   SEQ_FrameDataHDR FrameDataHdr;
   for (?) {
   	SEQ_FrameOpcode Opcode;
   }
}

On Opcodes and Operations

Let op be an opcode. We will construct the bytes of the corresponding operation v = (op, ?, ...)

if op = 0, then v = (op), the animation ends.

if any of the three MSBs of op is 1, we write op = xyzabcde where x,y,z,a,b,c,d,e are the respective bits.

if abcde = 11111, we have an additional byte t (for timing, need to confirm purpose) otherwise, we define t = abcde, also for timing/number of frames, but without an additional byte. so we have v = (op, t?, ?, ...)

in both cases we have rotation angles, which can be constructed like this:

if x = 1, we have an additional byte rx (rotation speed around x axis). if y = 1, we have an additional byte ry (rotation speed around y axis). if z = 1, we have an additional byte rz (rotation speed around z axis).

so if xyz = 111, v = (op, t?, rx, ry, rz) if xyz = 101, v = (op, t?, rx, rz) and so on.

all in all, we have v = (op, t?, rx?, ry?, rz?), where the bits marked with ? are present depending on the above conditions.

now on the computation of the actual rotation:

it seems that an animation may only have a fixed number of frames, it is assumed that this could be 30. this can be seen by adding the operations (fe, 0x80, 0, 0), (fe, 0, 0x80, 0) to a bone animation, for example at RAM 0x801275fe (US version). the second operation has no effect!

each frame, the rx,ry,rz values are added to the rotation vector of the bone.

however, if any of the rx, ry, rz are NOT present, then these rotations seem to change towards zero. need confirmation here.

now let all of the three MSBs of op be 0. TODO