I believe I've cracked pretty much all of it just need to determine the format of the keyframe values and we are done.
Explaination of converting the Animation's compressed scale, rotation & position can be found in my other post here.
I feel I've almost figured out the details for all of these formats but I may need some input for the last stretch. Huge credits go to Ekey for his PAZUnpacker_CBT2 (without which I'd have nothing to do!), and to chrrox for their Noesis script (which is pretty much the foundation of what I've got). There are still a few things that aren't quite complete, primarily with the PAA files which I believe have some form of compression which I talk about in their section any input here would be awesome.
For those not familiar with the files:
- PAC - This file represents the model format (chrrox mapped most of this out in their script)
- PAB - This file represents the skeleton format which various models depend on (chrrox mapped this out in their script)
- PAA - This file represents the animation format, one file per animation
Below are some of the common structures in each of these files, listing them here will save some space and typing. They are pretty self explainatory. If anyone knows more about the unknown data in the FileHeader I'd be interested to know what it is, it hasn't seemed to be required so I haven't really looked at it.
Code: Select all
typedef struct FileHeader
{
char fileId[4];
unsigned_int16 fileVersion;
unsigned_int8 unknown[10];
} FileHeader;
typedef struct Matrix4x4
{
float data[4][4];
} Matrix4x4;
typedef struct Vector3
{
float x;
float y;
float z;
} Vector3;
typedef struct Quaternion
{
float x;
float y;
float z;
float w;
} Quaternion;
typedef struct CompressedVector3
{
int16 x;
int16 y;
int16 z;
} CompressedVector3;
typedef struct CompressedQuaternion
{
int16 x;
int16 y;
int16 z;
int16 w;
} CompressedQuaternion;
This file starts with the standard FileHeader structure with the main data starting at 0x00000010 which lists the number of bones in the file and is followed by the array of bones.
I believe the unknown data is just a padding or chunk end or something, either way it doesn't seem to be required.
Once again, credit to chrrox who's Noesis script is the framework for this.
Code: Select all
typedef struct Bone
{
unsigned_int32 boneHash;
unsigned_int8 boneNameLength;
char boneName[boneNameLength];
unsigned_int32 boneParent;
Matrix4x4 boneMatrix;
Matrix4x4 boneMatrixInverse;
Matrix4x4 boneLocalMatrix;
Matrix4x4 boneLocalMatrixInverse;
Vector3 boneScale;
Quaternion boneRotation;
Vector3 bonePosition;
unsigned_int8 unknown[2];
} Bone;
struct BoneFileFormat
{
FileHeader fileHeader;
unsigned_int16 boneCount;
Bone bones[boneCount];
};
Moving up in complexity slightly, this file has the mesh data. Each mesh has a texture name attached to it, I believe this is a base name so textures with other semantics (like normal map, ambient occlusion, specular power, etc) can be automatically added by searching for their relevant semantic id which is appended to the base texture name. I haven't really looked into determining what PAB file is supposed to be used for these yet, if it's not in the file then it may be a matter of searching the directory (and parent directories with a depth restriction) to find a PAB file which has the bone hashes that the PAC file contains. I've only included the version 259 of this, I haven't seen where the 513 version chrrox also uses is used (but there are a lot of models so easily possible I've missed it!)
Once again, credit to chrrox who's Noesis script is the framework for this.
Code: Select all
typedef struct VertexBuffer
{
Vector3 position;
unsigned_int8 normals[4]; // Commented out by chrrox
unsigned_int16 uv[2];
unsigned_int8 colour[4]; // Commented out by chrrox
unsigned_int8 boneIndices[4];
unsigned_int8 boneWeights[4];
} VertexBuffer;
typedef struct LOD
{
unsigned_int16 vertexCount;
VertexBuffer vertexBuffer[vertexCount];
unsigned_int32 faceCount;
unsigned_int16 faceIndices[faceCount];
} LOD;
typedef struct Mesh
{
unsigned_int8 textureNameLength;
char textureName[textureNameLength];
unsigned_int16 flag;
LOD lodArray[3];
} Mesh;
struct ModelFile
{
FileHeader fileHeader;
unsigned_int8 boneCount;
unsigned_int32 boneHashArray[boneCount];
unsigned_int8 usedBoneCount;
int8 usedBoneArray[usedBoneCount];
unsigned_int32 totalVertices;
unsigned_int32 totalFaces;
unsigned_int16 meshCount;
Mesh meshes[meshCount];
};
This file was a pain, it's easy knowing what data needs to be in an animation file yet people find so many different ways to do so. I believe there is some form of compression being used within this file for the scale, rotation and position (and possibly key frame / frame time).
The file starts the same as normal with the FileHeader followed by a count which for this file represents the number of bones in the animation, following this is 8 bytes of data (first 4 I believe are a float for the total animation time) and from here the bone animation data starts.
The animation data appears to compress all of the floats (e.g. time frame, scale, rotation and position components) into half's. I'm not so good at decryption so maybe someone can help out, at the least we can logically infer from the scale values that "E8 03" should equal 1.0F. Once again I such ad decompression so I'd love some input here.
With this knowledge the format for the animation file is as follows:
Code: Select all
typedef struct ScaleData
{
half keyframe;
CompressedVector3 keyframeScale; // Multiply values by 0.001F to get float value
} ScaleData;
typedef struct RotationData
{
half keyframe;
CompressedQuaternion keyframeRotation; // Multiply values by max value of their sign to get float value
// value == 0 : (0.0F)
// value < 0 : (value * 32768.0F)
// value > 0 : (value * 32767.0F)
} RotationData;
typedef struct PositionData
{
half keyframe;
CompressedVector3 keyframePosition; // Multiply values by 0.1F to get float value
} PositionData;
typedef struct AnimBoneData
{
unsigned_int32 boneHash;
unsigned_int16 scaleKeyframeCount;
ScaleData scaleData[scaleKeyframeCount];
unsigned_int16 rotationKeyframeCount;
RotationData rotationData[rotationKeyframeCount];
unsigned_int16 positionKeyframeCount;
PositionData positionData[positionKeyframeCount];
} BoneAnimationData;
struct Animation
{
FileHeader fileHeader;
unsigned_int16 boneCount;
float animationDuration; // ?
unsigned_int8 unknown[4]; // ?
AnimBoneData boneData[boneCount];
};