diff --git a/README.md b/README.md index c2c709b..ccd8378 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,9 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | ARC | | [`patterns/arc.hexpat`](patterns/arc.hexpat) | Minecraft Legacy Console Edition ARC files | | ARIA2 | | [`patterns/aria2.hexpat`](patterns/aria2.hexpat) | ARIA2 Download Manager Control files | | ARM VTOR | | [`patterns/arm_cm_vtor.hexpat`](patterns/arm_cm_vtor.hexpat) | ARM Cortex M Vector Table Layout | -| Arma 3 PAA | | [`patterns/a3_paa.hexpat`](patterns/a3_paa.hexpat) | Arma 3 PAA texture file | +| Arma 3 PAA | `image/x.a3-paa` | [`patterns/a3_paa.hexpat`](patterns/a3_paa.hexpat) | Arma 3 PAA texture file | +| Arma 3 RTM | `application/x.a3-rtm` | [`patterns/a3_rtm.hexpat`](patterns/a3_rtm.hexpat) | Arma 3 RTM animation file (plain) | +| Arma 3 RTM (binarized) | `application/x.a3-bmtr` | [`patterns/a3_bmtr.hexpat`](patterns/a3_bmtr.hexpat) | Arma 3 RTM animation file (binarized) | | Assassin's Creed: Unity | | [`patterns/AC Unity`](patterns/Assassin's Creed: Unity) | Assassin's Creed: Unity archive files -- .forge & .data (compressed and decompressed) -- | | Bastion | | [`patterns/bastion/*`](https://gitlab.com/EvelynTSMG/imhex-bastion-pats) | Various [Bastion](https://en.wikipedia.org/wiki/Bastion_(video_game)) files | | BeyondCompare BCSS | | [`patterns/bcss.hexpat`](patterns/bcss.hexpat) | BeyondCompare Snapshot (BCSS) file | diff --git a/magic/arma3_magic b/magic/arma3_magic index 6df2c80..7da0b4c 100644 --- a/magic/arma3_magic +++ b/magic/arma3_magic @@ -13,3 +13,13 @@ >0 leshort 0x1555 RGBA5 format >0 leshort 0x8888 RGBA8 format >0 leshort 0x8080 Grayscale format + +# Arma 3 RTM animation +0 string RTM_ Arma 3 RTM animation file (plain) +!:mime application/x.a3-rtm +!:ext rtm + +# Arma 3 binarized RTM animation +0 string BMTR Arma 3 binarized RTM animation file +!:mime application/x.a3-bmtr +!:ext rtm diff --git a/patterns/a3_bmtr.hexpat b/patterns/a3_bmtr.hexpat new file mode 100644 index 0000000..0896157 --- /dev/null +++ b/patterns/a3_bmtr.hexpat @@ -0,0 +1,129 @@ +#pragma author MrClock +#pragma description Arma 3 RTM animation format (binarized) + +#pragma endian little + +#pragma MIME application/x.a3-bmtr + +fn get_data_description() { + return "Binarized RTM (BMTR) animation files are the PBO packed versions of plain RTMs.\n Binarized files are optimized for use by the game engine, and they are not editable.\nBone transformations are stored as relative quaternion-vector pairs.\nData blocks are conditionally LZO1X compressed (these are not supported by this pattern)."; +}; + +import std.string; +import std.sys; +import type.float16; + +using asciiz = std::string::NullString [[format("formatAsciiz")]]; +using half = type::float16 ; +struct s16float { + s16 data; +} [[sealed,static,transform("transforms16float"), format("transforms16float")]]; + +struct Property { + padding[4]; + asciiz name; + float phase; + asciiz value; +} [[format("formatProperty")]]; + +struct Vector { + DataType x [[comment("+Left/-Right")]]; + DataType y [[comment("+Up/-Down (UNUSED)")]]; + DataType z [[comment("+Forward/-Backward")]]; +} [[static]]; + +struct Quaternion { + s16float x; + s16float y; + s16float z; + s16float w; +} [[static,format("formatQuaternion")]]; + +struct Transform { + Quaternion orientation; + Vector position [[format("formatVectorHalf")]]; +} [[static]]; + +struct Frame { + u32 count_bones; + bool compressed = count_bones * sizeof(Transform) >= 1024; + if (parent.version > 4) { + u8 lzo_flag; + compressed = lzo_flag > 0; + } + + if (compressed) { + std::error("Transformations are LZO compressed and compressed length is unknown"); + } else { + Transform transforms[count_bones]; + } +}; + +struct BMTR { + char signature[4]; + u32 version; + padding[1]; + Vector motion [[format("formatVectorFloat")]]; + u32 count_frames; + padding[4]; + u32 count_bones; + u32 count_bones_again; + + std::assert_warn(count_bones == count_bones_again, "Mismatch between bone counts"); + + asciiz bones[count_bones]; + + if (version >= 4) { + padding[4]; + u32 count_properties; + Property properties[count_properties]; + } + + u32 count_phases; + std::assert_warn(count_frames == count_phases, "Frame and phase counts do not match"); + + bool compressed = count_phases * sizeof(float) >= 1024; + if (version > 4) { + u8 lzo_flag; + compressed = lzo_flag > 0; + } + + if (compressed) { + std::error("Phases are LZO compressed and compressed length is unknown"); + } else { + float phases[count_phases]; + } + + Frame frames[count_frames]; +}; + +fn transforms16float(ref s16float value) { + return float(value.data) / 16384; +}; + +fn formatAsciiz(ref asciiz value) { + return std::format("\"{:s}\"", value); +}; + +fn formatProperty(ref Property prop) { + return std::format("\"{0:s}\" = \"{1:s}\" @ {2:.4f}", prop.name, prop.value, prop.phase); +}; + +fn formatVectorHalf(ref Vector vec) { + return std::format( + "[{0}, {1}, {2}]", + type::impl::format_float16(vec.x), + type::impl::format_float16(vec.y), + type::impl::format_float16(vec.z) + ); +}; + +fn formatVectorFloat(ref Vector vec) { + return std::format("[{0:.2f}, {1:.2f}, {2:.2f}]", vec.x, vec.y, vec.z); +}; + +fn formatQuaternion(ref Quaternion q) { + return std::format("[{0:.2f}, {1:.2f}, {2:.2f}, {3:.2f}]", q.x, q.y, q.z, q.w); +}; + +BMTR file @ 0x0000; diff --git a/patterns/a3_rtm.hexpat b/patterns/a3_rtm.hexpat new file mode 100644 index 0000000..4f299b6 --- /dev/null +++ b/patterns/a3_rtm.hexpat @@ -0,0 +1,93 @@ +#pragma author MrClock +#pragma description Arma 3 RTM animation format (plain) + +#pragma endian little + +#pragma MIME application/x.a3-rtm + +fn get_data_description() { + return "Plain RTM animation files are used in animation authoring for Arma 3.\nThey can be created and edited in Object Builder.\nBone transformations are stored as absolute transformation matrices.\nPlain RTMs must be converted to their \"binarized\" versions by an appropriate PBO packing tool for use in game."; +}; + +import std.mem; +import std.sys; +import std.string; +import std.io; + +using lascii = std::string::SizedString [[format("formatLascii")]]; + +struct Property { + float phase; + lascii name; + lascii value; +} [[format("formatProperty")]]; + +struct Bone { + char name[32]; +} [[sealed,static,transform("transformBone"),format("formatBone")]]; + +struct Transform { + Bone bone; + float matrix[12] [[comment("4x4 transformation matrix (with last row omitted)")]]; +} [[static,format("formatTransform")]]; + +struct Frame { + float phase; + Transform transforms[parent.count_bones]; +} [[static,format("formatFrame")]]; + +struct Vector { + float x [[comment("+Left/-Right")]]; + float y [[comment("+Up/-Down (UNUSED)")]]; + float z [[comment("+Forward/-Backward")]]; +} [[static,format("formatVector")]]; + +struct RTM { + if (std::mem::read_string($, 8) == "RTM_MDAT") { + char properties_signature[8]; + padding[4]; + u32 count_properties; + Property properties[count_properties]; + } + + std::assert(std::mem::read_string($, 8) == "RTM_0101", "Missing animation data"); + + char animation_signature[8]; + Vector motion; + u32 count_frames; + u32 count_bones; + Bone bones[count_bones]; + Frame frames[count_frames]; + + std::assert_warn(std::mem::eof(), "Data ended before EOF"); +}; + +fn formatLascii(ref lascii value) { + return std::format("\"{:s}\"", value); +}; + +fn formatProperty(ref Property prop) { + return std::format("\"{0:s}\" = \"{1:s}\" @ {2:.4f}", prop.name, prop.value, prop.phase); +}; + +fn transformBone(ref Bone value) { + return std::string::to_string(value.name); +}; + +fn formatBone(ref Bone value) { + return std::format("\"{0:s}\"", value); +}; + +fn formatTransform(ref Transform transform) { + return std::format("\"{0:s}\" transform", transform.bone); +}; + +fn formatFrame(ref Frame frame) { + return std::format("frame @ {0:.4f}", frame.phase); +}; + +fn formatVector(ref Vector vec) { + return std::format("[{0:.2f}, {1:.2f}, {2:.2f}]", vec.x, vec.y, vec.z); +}; + +RTM file @ 0x0000; diff --git a/tests/patterns/test_data/a3_bmtr.hexpat.rtm b/tests/patterns/test_data/a3_bmtr.hexpat.rtm new file mode 100644 index 0000000..1f829c2 Binary files /dev/null and b/tests/patterns/test_data/a3_bmtr.hexpat.rtm differ diff --git a/tests/patterns/test_data/a3_rtm.hexpat.rtm b/tests/patterns/test_data/a3_rtm.hexpat.rtm new file mode 100644 index 0000000..ce5374b Binary files /dev/null and b/tests/patterns/test_data/a3_rtm.hexpat.rtm differ