-
Notifications
You must be signed in to change notification settings - Fork 12
Motion Formats (mot files)
Motion files can be decoded using Bayonetta mot.bt, Bayonetta 2 mot.bt, Nier Automata mot.bt and Transformers Devastation mot.bt.
Bayonetta motion files encode skeletal animation data for a given model or model family, meaning models that share a common skeleton. Each animation is defined for a set number of frames.
Nine values per bone and per frame can be encoded (a tenth seems to be possible but is never found) each is associated to an index (6 is the unused index). These values are:
index | value |
---|---|
0 | translation along axis 0 |
1 | translation along axis 1 |
2 | translation along axis 2 |
3 | rotation around axis 0 |
4 | rotation around axis 1 |
5 | rotation around axis 2 |
7 | scaling along axis 0 |
8 | scaling along axis 1 |
9 | scaling along axis 2 |
When a triplet is not present its default value is used. Translations have parent relative offset as default value. Rotations have 0.0 as default values. Scaling values have a default value of 1.0.
It has to be noted that bone indexes found in motion files are absolute bone index and need to be converted for each model with a table given in the model file. Bone -1 is parent bone, which is usefull if the morion displaces a character for example.
Values can be encoded in a number of ways ranging from one float value per frame to key-frame interpolated data using half floats.
Motion files use a special type of half floats that will be referenced as pghalf (thanks Alquazar): they are IEEE floats with 1 bit sign, 6 bit exponent, 9 bit significand and a bias of 47. Using ruby they can be easily decoded thanks to the float-formats gem.
require 'float-formats'
Flt::IEEE.binary :IEEE_binary16_pg, significand: 9, exponent: 6, bias: 47
Flt::IEEE.binary :IEEE_binary16_pg_BE, significand: 9, exponent: 6, bias: 47, endianness: :big_endian
# Reading from binary data
f = Flt::IEEE_binary16_pg::from_bytes("\x00\x5e")
val = f.to(Float) # val == 1.0
g = Flt::IEEE_binary16_pg_BE::from_bytes("\x5e\x00")
val2 = g.to(Float) # val2 == 1.0
# Writing to binary data
h = Flt::IEEE_binary16_pg::new(1.0)
s = h.to_bytes # s == "\x00\x5e"
A c++ decoder can also be found here: Bayonetta noesis plugin. A faster c decoder/encoder can be found here libbin pghalf decoder.
Formats are slightly different between Bayonetta and Bayonetta 2 so each one will be presented individually.
Every motion file starts with a 16 byte header:
offset | size | c type | description |
---|---|---|---|
0x00 | 4 | char[4] | "mot\x00" |
0x04 | 2 | uint16_t | seems to be flags |
0x06 | 2 | int16_t | frame count |
0x08 | 4 | uint32_t | records offset |
0x0C | 4 | uint32_t | record count |
Records of 12 bytes follow at the offset specified in the header and with the given count. They are tightly packed.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | int16_t | bone index |
0x02 | 1 | char | value index |
0x03 | 1 | char | record type |
0x04 | 2 | int16_t | value count |
0x06 | 2 | int16_t | always -1 |
0x08 | 4 | float or uint32_t | an union with a float value for type 0x00 or an offset for other types |
Each different record type corresponds to a different data encoding. Data are located at the offset given in the record (except for type 0x00). Only some of them are known:
The value is constant over the animation and given by the record float value.
The value for each frame is given in a float. Missing values should repeat the last one. Floats are packed.
offset | size | c type | description |
---|---|---|---|
0x00 | 4 | float | value |
Hermit interpolated values using float data at key-frames.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint16_t | absolute frame index |
0x02 | 2 | uint16_t | padding |
0x04 | 4 | float | value (p) |
0x08 | 4 | float | incoming derivative value (m0) |
0x0C | 4 | float | outgoing derivative value (m1 |
Values between key-frames i
and i+1
, at frame index
are computed using:
float p0 = keys[i].p
float m0 = keys[i].m1
float p1 = keys[i+1].p
float m1 = keys[i+1].m0
float t = (float)(index - keys[i].index)/(keys[i+1].index - keys[i].index)
float val = (2*t^3 - 3*t^2 + 1)*p0 + (t^3 - 2*t^2 + t)*m0 + (-2*t^3 + 3*t^2)*p1 + (t^3 - t^2)*m1;
Quantized float values. One per frame. Missing values should repeat the last one.
Values are computed using the following formulas:
value: p + dp * cp
offset | size | c type | description |
---|---|---|---|
0x00 | 4 | float | value (p) |
0x04 | 4 | float | value delta (dp) |
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint16_t | value quantum (cp) |
Hermit interpolated values using quantized float data at key-frames.
Values at a key-frame are computed using the following formulas:
value: p + dp * cp
incoming derivative: m0 + dm0 * cm0
outgoing derivative: m1 + dm1 * cm1
Values between key-frames i
and i+1
, at frame index
are computed using:
float p0 = value.p + value.dp * keys[i].cp
float m0 = value.m1 + value.dm1 * keys[i].cm1
float p1 = value.p + value.dp * keys[i+1].cp
float m1 = value.m0 + value.dm0 * keys[i+1].cm0
float t = (float)(index - keys[i].index)/(keys[i+1].index - keys[i].index)
float val = (2*t^3 - 3*t^2 + 1)*p0 + (t^3 - 2*t^2 + t)*m0 + (-2*t^3 + 3*t^2)*p1 + (t^3 - t^2)*m1;
The record data starts with a 24 bytes header giving quantized values bounds.
offset | size | c type | description |
---|---|---|---|
0x00 | 4 | float | value (p) |
0x04 | 4 | float | value delta (dp) |
0x08 | 4 | float | incoming derivative value (m0) |
0x0C | 4 | float | incoming derivative value delta (dm0) |
0x10 | 4 | float | outgoing derivative value (m1) |
0x14 | 4 | float | outgoing derivative value delta (dm1) |
Key-frames have a 8 byte size, they are densely packed.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint16_t | absolute frame index |
0x02 | 2 | uint16_t | value quantum (cp) |
0x04 | 2 | uint16_t | incoming derivative quantum (cm0) |
0x06 | 2 | uint16_t | outgoing derivative quantum (cm1) |
Same as 0x04 with reduced precision and relative frame index encoding
12 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | pghalf | value (p) |
0x02 | 2 | pghalf | value delta (dp) |
0x04 | 2 | pghalf | incoming derivative value (m0) |
0x06 | 2 | pghalf | incoming derivative value delta (dm0) |
0x08 | 2 | pghalf | outgoing derivative value (m1) |
0x10 | 2 | pghalf | outgoing derivative value delta (dm1) |
4 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 1 | uint8_t | relative frame index |
0x01 | 1 | uint8_t | value quantum (cp) |
0x02 | 1 | uint8_t | incoming derivative quantum (cm0) |
0x03 | 1 | uint8_t | outgoing derivative quantum (cm1) |
Same as 0x06 but with absolute frame index
12 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | pghalf | value (p) |
0x02 | 2 | pghalf | value delta (dp) |
0x04 | 2 | pghalf | incoming derivative value (m0) |
0x06 | 2 | pghalf | incoming derivative value delta (dm0) |
0x08 | 2 | pghalf | outgoing derivative value (m1) |
0x10 | 2 | pghalf | outgoing derivative value delta (dm1) |
6 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint16_t | absolute frame index |
0x02 | 1 | uint8_t | dummy, probably for alignment |
0x03 | 1 | uint8_t | value quantum (cp) |
0x04 | 1 | uint8_t | incoming derivative quantum (cm0) |
0x05 | 1 | uint8_t | outgoing derivative quantum (cm1) |
Bayonetta 2 format is very close to Bayonetta format. Offset in records are given relatively to the record start rather than relatively to the file start.
Every motion file starts with a 16 byte header:
offset | size | c type | description |
---|---|---|---|
0x00 | 4 | char[4] | "mot\x00" |
0x04 | 4 | uint32_t | unknown |
0x08 | 2 | uint16_t | seems to be flags |
0x0A | 2 | int16_t | frame count |
0x0C | 4 | uint32_t | records offset |
0x10 | 4 | uint32_t | record count |
0x14 | 4 | uint32_t | unknown |
0x18 | 12 | char[12] | motion name |
Records of 12 bytes follow at the offset specified in the header and with the given count. They are tightly packed.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | int16_t | bone index |
0x02 | 1 | char | value index |
0x03 | 1 | char | record type |
0x04 | 2 | int16_t | value count |
0x06 | 2 | int16_t | always -1 |
0x08 | 4 | float or uint32_t | an union with a float value for type 0x00 or an offset for other types |
Each different record type corresponds to a different data encoding. Data are located at the offset given in the record plus the offset of the start of the record (except for type 0x00). Most of them seem to b known:
The value is constant over the animation and given by the record float value.
The value for each frame is given in a float. Missing values should repeat the last one. Floats are packed.
offset | size | c type | description |
---|---|---|---|
0x00 | 4 | float | value |
The value for each frame is quantized in a 16 bit integer. If some are missing the last is repeated.
8 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 4 | float | value (p) |
0x04 | 4 | float | value delta (dp) |
Densely packed uint16_t.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint16_t | value quantum (cp) |
Same as 0x02 with reduced precision
4 byte size
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | pghalf | value (p) |
0x02 | 2 | pghalf | value delta (dp) |
Densely packed uint8_t.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint8_t | value quantum (cp) |
Hermit interpolated values. Missing ranges before or after should repeat the first or last value.
Values at a key-frame are computed using the following formulas:
value: p
incoming derivative: m0
outgoing derivative: m1
Values between key-frames i
and i+1
, at frame index
are computed using:
float p0 = values[i].p
float m0 = values[i].m1
float p1 = value[i+1].p
float m1 = value[i+1].m0
float t = (float)(index - values[i].index)/(vlaues[i+1].index - values[i].index)
float val = (2*t^3 - 3*t^2 + 1)*p0 + (t^3 - 2*t^2 + t)*m0 + (-2*t^3 + 3*t^2)*p1 + (t^3 - t^2)*m1;
Densely packed
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint16_t | absolute frame index |
0x02 | 2 | uint16_t | dummy, probably for alignment |
0x04 | 4 | float | value (p) |
0x08 | 4 | float | incoming derivative (m0) |
0x0C | 4 | float | outgoing derivative (m1) |
Hermit interpolated values using quantized float data at key-frames. Missing ranges before or after should repeat the first or last value.
Values at a key-frame are computed using the following formulas:
value: p + dp * cp
incoming derivative: m0 + dm0 * cm0
outgoing derivative: m1 + dm1 * cm1
Values between key-frames i
and i+1
, at frame index
are computed using:
float p0 = value.p + value.dp * keys[i].cp
float m0 = value.m1 + value.dm1 * keys[i].cm1
float p1 = value.p + value.dp * keys[i+1].cp
float m1 = value.m0 + value.dm0 * keys[i+1].cm0
float t = (float)(index - keys[i].index)/(keys[i+1].index - keys[i].index)
float val = (2*t^3 - 3*t^2 + 1)*p0 + (t^3 - 2*t^2 + t)*m0 + (-2*t^3 + 3*t^2)*p1 + (t^3 - t^2)*m1;
The record data starts with a 24 bytes header giving quantized values bounds.
offset | size | c type | description |
---|---|---|---|
0x00 | 4 | float | value (p) |
0x04 | 4 | float | value delta (dp) |
0x08 | 4 | float | incoming derivative value (m0) |
0x0C | 4 | float | incoming derivative value delta (dm0) |
0x10 | 4 | float | outgoing derivative value (m1) |
0x14 | 4 | float | outgoing derivative value delta (dm1) |
Key-frames have a 8 byte size, they are densely packed.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint16_t | absolute frame index |
0x02 | 2 | uint16_t | value quantum (cp) |
0x04 | 2 | uint16_t | incoming derivative quantum (cm0) |
0x06 | 2 | uint16_t | outgoing derivative quantum (cm1) |
Same as 0x05 with reduced precision.
12 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | pghalf | value (p) |
0x02 | 2 | pghalf | value delta (dp) |
0x04 | 2 | pghalf | incoming derivative value (m0) |
0x06 | 2 | pghalf | incoming derivative value delta (dm0) |
0x08 | 2 | pghalf | outgoing derivative value (m1) |
0x10 | 2 | pghalf | outgoing derivative value delta (dm1) |
4 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 1 | uint8_t | absolute frame index |
0x01 | 1 | uint8_t | value quantum (cp) |
0x02 | 1 | uint8_t | incoming derivative quantum (cm0) |
0x03 | 1 | uint8_t | outgoing derivative quantum (cm1) |
Same as 0x06 with relative frame index.
12 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | pghalf | value (p) |
0x02 | 2 | pghalf | value delta (dp) |
0x04 | 2 | pghalf | incoming derivative value (m0) |
0x06 | 2 | pghalf | incoming derivative value delta (dm0) |
0x08 | 2 | pghalf | outgoing derivative value (m1) |
0x10 | 2 | pghalf | outgoing derivative value delta (dm1) |
4 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 1 | uint8_t | relative frame index |
0x01 | 1 | uint8_t | value quantum (cp) |
0x02 | 1 | uint8_t | incoming derivative quantum (cm0) |
0x03 | 1 | uint8_t | outgoing derivative quantum (cm1) |
Same as 0x07 but with absolute frame index (at least one relative frame index would have been > 255).
12 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | pghalf | value (p) |
0x02 | 2 | pghalf | value delta (dp) |
0x04 | 2 | pghalf | incoming derivative value (m0) |
0x06 | 2 | pghalf | incoming derivative value delta (dm0) |
0x08 | 2 | pghalf | outgoing derivative value (m1) |
0x10 | 2 | pghalf | outgoing derivative value delta (dm1) |
5 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint16_t | absolute frame index |
0x02 | 1 | uint8_t | value quantum (cp) |
0x03 | 1 | uint8_t | incoming derivative quantum (cm0) |
0x04 | 1 | uint8_t | outgoing derivative quantum (cm1) |
Nier Automata format is identical to Bayonetta 2 beside two changes:
- one index is stored in a big endian way even on a small endian architecture
Every motion file starts with a 16 byte header:
offset | size | c type | description |
---|---|---|---|
0x00 | 4 | char[4] | "mot\x00" |
0x04 | 4 | uint32_t | unknown |
0x08 | 2 | uint16_t | seems to be flags |
0x0A | 2 | int16_t | frame count |
0x0C | 4 | uint32_t | records offset |
0x10 | 4 | uint32_t | record count |
0x14 | 4 | uint32_t | unknown |
0x18 | 12 | char[12] | motion name |
Records of 12 bytes follow at the offset specified in the header and with the given count. They are tightly packed.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | int16_t | bone index |
0x02 | 1 | char | value index |
0x03 | 1 | char | record type |
0x04 | 2 | int16_t | value count |
0x06 | 2 | int16_t | always -1 |
0x08 | 4 | float or uint32_t | an union with a float value for type 0x00 or an offset for other types |
Each different record type corresponds to a different data encoding. Data are located at the offset given in the record plus the offset of the start of the record (except for type 0x00). Most of them seem to b known:
The value is constant over the animation and given by the record float value.
The value for each frame is given in a float. Missing values should repeat the last one. Floats are packed.
offset | size | c type | description |
---|---|---|---|
0x00 | 4 | float | value |
The value for each frame is quantized in a 16 bit integer. If some are missing the last is repeated.
8 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 4 | float | value (p) |
0x04 | 4 | float | value delta (dp) |
Densely packed uint16_t.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint16_t | value quantum (cp) |
Same as 0x02 with reduced precision
4 byte size
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | pghalf | value (p) |
0x02 | 2 | pghalf | value delta (dp) |
Densely packed uint8_t.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint8_t | value quantum (cp) |
Hermit interpolated values. Missing ranges before or after should repeat the first or last value.
Values at a key-frame are computed using the following formulas:
value: p
incoming derivative: m0
outgoing derivative: m1
Values between key-frames i
and i+1
, at frame index
are computed using:
float p0 = values[i].p
float m0 = values[i].m1
float p1 = value[i+1].p
float m1 = value[i+1].m0
float t = (float)(index - values[i].index)/(vlaues[i+1].index - values[i].index)
float val = (2*t^3 - 3*t^2 + 1)*p0 + (t^3 - 2*t^2 + t)*m0 + (-2*t^3 + 3*t^2)*p1 + (t^3 - t^2)*m1;
Densely packed
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint16_t | absolute frame index |
0x02 | 2 | uint16_t | dummy, probably for alignment |
0x04 | 4 | float | value (p) |
0x08 | 4 | float | incoming derivative (m0) |
0x0C | 4 | float | outgoing derivative (m1) |
Hermit interpolated values using quantized float data at key-frames. Missing ranges before or after should repeat the first or last value.
Values at a key-frame are computed using the following formulas:
value: p + dp * cp
incoming derivative: m0 + dm0 * cm0
outgoing derivative: m1 + dm1 * cm1
Values between key-frames i
and i+1
, at frame index
are computed using:
float p0 = value.p + value.dp * keys[i].cp
float m0 = value.m1 + value.dm1 * keys[i].cm1
float p1 = value.p + value.dp * keys[i+1].cp
float m1 = value.m0 + value.dm0 * keys[i+1].cm0
float t = (float)(index - keys[i].index)/(keys[i+1].index - keys[i].index)
float val = (2*t^3 - 3*t^2 + 1)*p0 + (t^3 - 2*t^2 + t)*m0 + (-2*t^3 + 3*t^2)*p1 + (t^3 - t^2)*m1;
The record data starts with a 24 bytes header giving quantized values bounds.
offset | size | c type | description |
---|---|---|---|
0x00 | 4 | float | value (p) |
0x04 | 4 | float | value delta (dp) |
0x08 | 4 | float | incoming derivative value (m0) |
0x0C | 4 | float | incoming derivative value delta (dm0) |
0x10 | 4 | float | outgoing derivative value (m1) |
0x14 | 4 | float | outgoing derivative value delta (dm1) |
Key-frames have a 8 byte size, they are densely packed.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint16_t | absolute frame index |
0x02 | 2 | uint16_t | value quantum (cp) |
0x04 | 2 | uint16_t | incoming derivative quantum (cm0) |
0x06 | 2 | uint16_t | outgoing derivative quantum (cm1) |
Same as 0x05 with reduced precision.
12 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | pghalf | value (p) |
0x02 | 2 | pghalf | value delta (dp) |
0x04 | 2 | pghalf | incoming derivative value (m0) |
0x06 | 2 | pghalf | incoming derivative value delta (dm0) |
0x08 | 2 | pghalf | outgoing derivative value (m1) |
0x10 | 2 | pghalf | outgoing derivative value delta (dm1) |
4 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 1 | uint8_t | absolute frame index |
0x01 | 1 | uint8_t | value quantum (cp) |
0x02 | 1 | uint8_t | incoming derivative quantum (cm0) |
0x03 | 1 | uint8_t | outgoing derivative quantum (cm1) |
Same as 0x06 with relative frame index.
12 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | pghalf | value (p) |
0x02 | 2 | pghalf | value delta (dp) |
0x04 | 2 | pghalf | incoming derivative value (m0) |
0x06 | 2 | pghalf | incoming derivative value delta (dm0) |
0x08 | 2 | pghalf | outgoing derivative value (m1) |
0x10 | 2 | pghalf | outgoing derivative value delta (dm1) |
4 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 1 | uint8_t | relative frame index |
0x01 | 1 | uint8_t | value quantum (cp) |
0x02 | 1 | uint8_t | incoming derivative quantum (cm0) |
0x03 | 1 | uint8_t | outgoing derivative quantum (cm1) |
Same as 0x07 but with absolute frame index (at least one relative frame index would have been > 255).
12 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | pghalf | value (p) |
0x02 | 2 | pghalf | value delta (dp) |
0x04 | 2 | pghalf | incoming derivative value (m0) |
0x06 | 2 | pghalf | incoming derivative value delta (dm0) |
0x08 | 2 | pghalf | outgoing derivative value (m1) |
0x10 | 2 | pghalf | outgoing derivative value delta (dm1) |
5 byte size.
offset | size | c type | description |
---|---|---|---|
0x00 | 2 | uint16_t | absolute frame index (big endian order) |
0x02 | 1 | uint8_t | value quantum (cp) |
0x03 | 1 | uint8_t | incoming derivative quantum (cm0) |
0x04 | 1 | uint8_t | outgoing derivative quantum (cm1) |