Anonymous

Changes

From Final Fantasy Inside

FF7/Battle/Battle Animation (PC)

462 bytes added, 15:58, 19 April 2007
no edit summary
== Battle Animation File Format==
File format discovered/decoded by me, L. Spiro.
=== Part I: Structures===
There are 4 basic structures we will use in decoding the file format.
The header of animation data has been considered to be composed of 3 DWORD’s, 3 WORD’s, and one BYTE, however this is not how the header is really intended to be, despite being aligned correctly.
Cloud’s battle animation file (rtda) has 94 (0x 5E) animations in it.
After this number begins each animation.
Each animation begins with a 12-byte header (3 DWORD’s) we will call “FF7FrameHeader”"FF7FrameHeader".To get from one animation to the next, start at offset 0x04 in the animation file and begin reading these headers. For each header, skip “FF7FrameHeader"FF7FrameHeader.dwChunkSize” dwChunkSize" bytes until you get to the index of the file you want to load. When skipping, remember to skip starting at the end of the “FF7FrameHeader” "FF7FrameHeader" header.
I mentioned a type of special animation data set that is in the header file.
These data sets, when filled with the “FF7FrameHeader” "FF7FrameHeader" header, will have a “dwChunkSize” "dwChunkSize" less than eleven, we skip them by jumping over the next 8 bytes that follow.
1.
<code><pre>
typedef struct FF7FrameHeader {
DWORD dwBones; // Bones in the model + 1. 0x00
DWORD dwFrames; // Frames in the animation. 0x04
DWORD dwChunkSize; // Size of the animation set. 0x08
} * PFF7FrameHeader; // Size = 12 bytes.
</pre></code>
2.
<code><pre>
typedef struct FF7FrameMiniHeader {
SHORT sBones; // Bones in the animation. 0x00
BYTE bKey; // A key flag used for decoding. 0x04
} * PFF7FrameMiniHeader; // Size = 5 bytes.
</pre></code>
 Most of these members are straight-forward, however there is a very special and VERY important member in the “FF7FrameMiniHeader” "FF7FrameMiniHeader" structure called “bKey”"bKey".
This is used for every rotation-decoding scheme (but one). It determines, essentially, the precision of the rotations and the deltas that follow in successive frames.
The value of “bKey” can only be 0, 2, or 4; the equation "(12 - bKey) " is used to determine the length of each raw (uncompressed) rotation.
After decompression, every rotation must be 12 bits, giving it a range from 0 to 4095.
But if “bKey” "bKey" is 4, for example, then that means uncompressed rotations are stored as 8 bits, which gives them a range from 0 to 255. How is this fixed? After the 8 bits are read, they are then shifted left (up) by “bKey”"bKey". This will place them at 12 bits, but with decreased accuracy.
This loss in accuracy is acceptable since rotations work as deltas and usually only change by a small amount.
Most large rotation deltas are things that are spinning, such as the blades on Aero Combatant. These cases are always a nice round number that can be handled with lower precision (in the case of Aero Combatant, it is 90 degrees even).
Now the code to skip to any animation, by index, where “iTarget” "iTarget" is the index. This code assumes you have already opened the animation file (hFile) and you have skipped pasted the first 4 bytes. 
<code><pre>
FF7FrameHeader fhHeader;
DWORD dwBytesRead;
for ( int I = 0; I < iTarget; I++ ) {
if ( !ReadFile( hFile, &fhHeader, sizeof( fhHeader ), &dwBytesRead, NULL ) ) {
CloseHandle( hFile ); return false; }
if ( fhHeader.dwChunkSize < 11 ) { // If this is a special
// chunk, skip it (it // is counted as part // of the total in the // file). if ( SetFilePointer( hFile, 8, NULL, FILE_CURRENT ) == INVALID_SET_FILE_POINTER ) {
CloseHandle( hFile );
return false;
}
continue; // Go on to the next // animation. } // Skip this animation set, whose size is determined by // fhHeader.dwChunkSize. if ( SetFilePointer( hFile, fhHeader.dwChunkSize, NULL, FILE_CURRENT ) == INVALID_SET_FILE_POINTER ) {
CloseHandle( hFile );
return false;
}
}
// Once we come to this point, we are at the very first byte of the
// animation we want to load. Let’s store it into a BYTE array.
if ( !ReadFile( hFile,
&fhHeader, sizeof( fhHeader ), &dwBytesRead, NULL ) ) {
CloseHandle( hFile ); return false;
}
BYTE * pbBuffer = new BYTE[fhHeader.dwChunkSize];
// Now pbBuffer holds the actual animation data, including the 5-byte
// “FF7FrameMiniHeader” header.
</pre></code>
We now have the animation we want loaded into a BYTE array (remember to delete it later).
3.
<code><pre>
typedef struct FF7ShortVec {
SHORT sX, sY, sZ; // Signed short versions. 0x00
FLOAT fX, fY, fZ; // Float version after math. 0x12
} * PFF7ShortRot; // Size = 30 bytes.
</pre></code>
Each rotation goes through 3 forms. Firstly, everything is stored as 2-byte SHORT’s. These SHORT’s are stored from 0 to 4096, where 0 = 0 degrees and 4096 = 360 degrees. This is the equation to convert one of these SHORT’s into degrees: (SHORT / 4096 * 360). Each frame is based off the previous frame, using the SHORT value as its basis.
Each SHORT is converted to an INT, which is the exact same as the SHORT version, except always positive.
Finally, the FLOAT gets filled with the final value, using the INT version as its base.
So, the sequence is:
 
First frame…
*Read X bits and store as a signed SHORT. *Convert the SHORT to the INT field, adding 0x1000 if negative. *Convert to FLOAT using (INT / 4096 * 360). Apply this FLOAT to your model.
Next frame…
*Read X bits, and add them to the SHORT value from last frame.*Convert the SHORT to the INT field, adding 0x1000 if negative. *Convert to FLOAT using (INT / 4096 * 360). Apply this FLOAT to your model.
Repeat…
To load an entire frame’s work of bones, we need this structure:
4.
<code><pre>
typedef struct FF7FrameBuffer {
DWORD dwBones;
FF7ShortVec svPosOffset;
FF7ShortVec * psvRots;
FF7FrameBuffer() {
}
} * PFF7FrameBuffer;
</pre></code>
This structure will allocate enough memory for one frame of rotations. Simply call “FF7FrameBuffer.SetBones” with the number of bones in your animation.
=== Part II: Functions and Format===
First, we need a way to read bits from the BYTE array we have stored.
This is a basic bit-reading function. It reads “dwTotalBits” from “pbBuffer” starting at the “dwStartBit”’th bit.
1.
<code><pre>
INT GetBitsFixed( BYTE * pbBuffer, DWORD &dwStartBit,
DWORD dwTotalBits ) {
return iReturn;
}
</pre></code>
Now that we can read the bits in the buffer we have made, it’s time to know what we’re doing!
Remember that we stored our animation buffer with a 5-byte “FF7FrameMiniHeader” at the beginning of it? We need this header now!
<code><pre>
PFF7FrameMiniHeader pfmhMiniHeader = (PFF7FrameMiniHeader)pbBuffer;
</pre></code>
After this cast, “pfmhMiniHeader->bKey” will contain a number, either 0, 2, or 4.
To get these bits, we first need to make a pointer point to the correct location. “pbBuffer” points 5 bytes before this data, so let’s make a pointer that points to this data directly.
<code><pre>
BYTE * pbAnimBuffer = &pbBuffer[5];
</pre></code>
When we use “GetBitsFixed()” to get the bits.
<code><pre>
DWORD dwBitStart = 0; // The bits at which to begin reading in the
// stream.
SHORT sY = GetBitsFixed( pbAnimBuffer, dwBitStart, 16 );
SHORT sZ = GetBitsFixed( pbAnimBuffer, dwBitStart, 16 );
</pre></code>
After doing this, we have each of the three offsets, 0, -466, and 0.
For each bone, there are 3 rotations. So, for each bone, we do this:
<code><pre>
SHORT sRotX = GetBitsFixed( pbAnimBuffer, dwBitStart,
12 - pfmhMiniHeader->bKey );
sRotY <<= pfmhMiniHeader->bKey;
sRotZ <<= pfmhMiniHeader->bKey;
</pre></code>
The first rotation is always 0, 0, 0. This is the root rotation and is not actually counted as part of the bone network of the character.
2.
<code><pre>
SHORT GetDynamicFrameOffsetBits( BYTE * pBuffer, DWORD &dwBitStart ) {
DWORD dwFirstByte, dwConsumedBits, dwBitsRemainingToNextByte, dwTemp;
mov ecx, pBuffer
add ecx, dwFirstByte // Go to the first byte that
// has the bit where we // want to begin.
xor edx, edx
mov dl, byte ptr ds:[ecx]
mov ecx, dwBitStart //
mov edx, [ecx] //
add edx, 8 //
mov eax, dwBitStart //
mov [eax], edx // Increase dwBitStart by 0x8 (8).
mov edx, dwBitStart //
mov [edx], ecx // Increase dwBitStart by 0x11
// (17).
End :
return sReturn;
}
</pre></code>
After the first frame, we know that the positional offsets immediately follow.
So to get the positional deltas for the next frame, we would do this:
<code><pre>
SHORT sDeltaX = GetDynamicFrameOffsetBits( pbAnimBuffer, dwBitStart );
SHORT sDeltaY = GetDynamicFrameOffsetBits( pbAnimBuffer, dwBitStart );
SHORT sDeltaZ = GetDynamicFrameOffsetBits( pbAnimBuffer, dwBitStart );
</pre></code>
Now we have the change from the previous frame. In our “FF7ShortVec” structure, these are the SHORT values. To get the position of this frame, we add these offsets to the last frame’s position.
If “I” is this frame and “I-1” is the last frame, we could do something like this:
<code><pre>
FF7FrameBuffer[I].svPosOffset.sX = FF7FrameBuffer[I-1].svPosOffset.sX + sX;
FF7FrameBuffer[I].svPosOffset.sY = FF7FrameBuffer[I-1].svPosOffset.sY + sY;
FF7FrameBuffer[I].svPosOffset.sZ = FF7FrameBuffer[I-1].svPosOffset.sZ + sZ;
</pre></code>
Now all that is left is to decode the rotations.
3.
<code><pre>
SHORT GetEncryptedRotationBits( BYTE * pBuffer, DWORD &dwBitStart,
INT iKeyBits ) {
INT iBits = GetBitsFixed( pBuffer, dwBitStart, 1 );
__asm mov eax, iBits // If the first bit is 0, return 0
// and continue. It is not necessary // to mov iBits into EAX, but I do it // anyway.
__asm test eax, eax
__asm jnz SecondTest
__asm mov ecx, dwNumBits
__asm mov dwType, ecx // dwType = ecx = dwNumBits = eax =
// (iBits & 7). // When we get to the case, all of // these values are the same.
__asm cmp dwType, 7
__asm ja ReturnZero // Is dwType above 7? If so, return 0.
// This can never actually happen.
// Otherwise, use it in a switch case.
case 0 : {
__asm or eax, 0xFFFFFFFF // After this, EAX will
// always be -1.
__asm mov ecx, iKeyBits
__asm shl eax, cl // Shift left by // precision. // (-1 << iKeyBits)
__asm mov sReturn, ax // Return that number.
__asm jmp End
case 6 : {
// Get a number of bits equal to the case switch (1,
// 2, 3, 4, 5, or 6).
iTemp = GetBitsFixed( pBuffer, dwBitStart,
dwNumBits );
__asm mov eax, iTemp
__asm cmp iTemp, 0
// If greater than or equal to 0…
__asm mov ecx, dwNumBits // dwNumBits = (iBits &
// 7) from before. __asm sub ecx, 1 // dwNumBits - 1.
__asm mov eax, 1
__asm shl eax, cl // (1 << (dwNumBits – // 1)).
__asm mov ecx, iTemp
__asm add ecx, eax // iTemp += (1 <<
// (dwNumBits - 1)).
__asm mov iTemp, ecx
__asm jmp AfterTests
IfLessThanZero :
__asm mov ecx, dwNumBits // dwNumBits = (iBits &
// 7) from before. __asm sub ecx, 1 // Decrease it by 1.
__asm mov edx, 1
__asm shl edx, cl // Shift “1” left by // (dwNumBits - 1).
__asm mov eax, iTemp // iTemp still has the
// bits we read // from before.
__asm sub eax, edx // iTemp - (1 <<
// (dwNumBits - 1))
__asm mov iTemp, eax
// Now, whatever we set on iTemp, we need to shift it
// up by the precision value.
AfterTests :
__asm mov eax, iTemp
// Uncompressed bits. Use standard decoding.
iTemp = GetBitsFixed( pBuffer, dwBitStart,
12 - iKeyBits );
__asm mov ecx, iKeyBits
__asm shl eax, cl // iTemp <<= iKeyBits.
return sReturn;
}
</pre></code>
     === Part III: Putting it All Together===
To make life easy, let’s use one function to load an entire frame at a time.
This function will load an entire frame into a “FF7FrameBuffer” structure.
1.
<code><pre>
DWORD LoadFrames( PFF7FrameBuffer pfbFrameBuffer,
INT iBones,
delete [] baData;
 </pre></code>
“GetValueFromStream” is the C/C++ version of my “GetDynamicFrameOffsetBits” and his “GetCompressedDeltaFromStream” is the C++ version of my “GetEncryptedRotationBits”.
<code><pre>
short GetValueFromStream( BYTE *pStreamBytes,
DWORD *pdwStreamBitOffset )
*/
</pre></code>
Anonymous user