From cee3a5de77fc19152880fa1799cf28d77a7e3fe1 Mon Sep 17 00:00:00 2001 From: MrClock <48679831+MrClock8163@users.noreply.github.com> Date: Sun, 11 Jan 2026 19:10:23 +0100 Subject: [PATCH] patterns: Add pattern and magic for Arma 3 PAA (#475) * patterns/paa: Added pattern for Arma 3 PAA * patterns/paa: Small correction * magic/arma3: Added magic file for Arma 3 formats Only the PAA format for now --- README.md | 2 + magic/arma3_magic | 15 +++ patterns/a3_paa.hexpat | 120 +++++++++++++++++++++ tests/patterns/test_data/a3_paa.hexpat.paa | Bin 0 -> 22016 bytes 4 files changed, 137 insertions(+) create mode 100644 magic/arma3_magic create mode 100644 patterns/a3_paa.hexpat create mode 100644 tests/patterns/test_data/a3_paa.hexpat.paa diff --git a/README.md b/README.md index a53c40b..c2c709b 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ 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 | | 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 | @@ -266,6 +267,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | Name | Path | Description | |------|------|-------------| +| Arma 3 | [`magic/arma3_magic`](magic/arma3_magic) | Identifies Arma 3 binary formats | | Nintendo Switch | [`magic/nintendo_switch_magic`](magic/nintendo_switch_magic) | Identifies common file types used on the Nintendo Switch | | Portable Executable | [`magic/portable_executable_magic`](magic/portable_executable_magic) | Identifies PE files used on Windows diff --git a/magic/arma3_magic b/magic/arma3_magic new file mode 100644 index 0000000..6df2c80 --- /dev/null +++ b/magic/arma3_magic @@ -0,0 +1,15 @@ +# A libmagic database containing definitions for files used by the Arma 3 game by Bohemia Interactive + +# Arma 3 PAA image +0x02 string GGAT Arma 3 PAA image file +!:mime image/x.a3-paa +!:ext paa +>0 leshort 0xff01 DXT1 compression +>0 leshort 0xff02 DXT2 compression +>0 leshort 0xff03 DXT3 compression +>0 leshort 0xff04 DXT4 compression +>0 leshort 0xff05 DXT5 compression +>0 leshort 0x4444 RGBA4 format +>0 leshort 0x1555 RGBA5 format +>0 leshort 0x8888 RGBA8 format +>0 leshort 0x8080 Grayscale format diff --git a/patterns/a3_paa.hexpat b/patterns/a3_paa.hexpat new file mode 100644 index 0000000..02ad904 --- /dev/null +++ b/patterns/a3_paa.hexpat @@ -0,0 +1,120 @@ +#pragma author MrClock +#pragma description Arma 3 PAA image format + +#pragma endian little + +#pragma MIME image/x.a3-paa + +import type.color; +import std.mem; +import std.sys; + +struct Color { + u8 b; + u8 g; + u8 r; + if (alpha) u8 a; +} [[sealed, format("type::impl::format_color"), color(std::format("{0:02X}{1:02X}{2:02X}", r, g, b))]]; + +using BGR8 = Color; +using BGRA8 = Color; + +enum PixelFormat: u16 { + DXT1 = 0xFF01, + DXT2 = 0xFF02, + DXT3 = 0xFF03, + DXT4 = 0xFF04, + DXT5 = 0xFF05, + RGBA4 = 0x4444, + RGBA5 = 0x1555, + RGBA8 = 0x8888, + GRAY = 0x8080 +}; + +enum AlphaMode: u32 { + NONE = 0, + INTERPOLATED = 1, + BINARY = 2 +}; + +enum Swizzle: u8 { + ALPHA = 0, + RED = 1, + GREEN = 2, + BLUE = 3, + INVERTED_ALPHA = 4, + INVERTED_RED = 5, + INVERTED_GREEN = 6, + INVERTED_BLUE = 7, + BLANK_WHITE = 8, + BLANK_BLACK = 9 +}; + +struct Tagg { + char signature[8]; + u32 length; + + match (signature) { + ("GGATCGVA"): BGRA8 color; + ("GGATCXAM"): BGRA8 color; + ("GGATGALF"): AlphaMode alpha; + ("GGATSFFO"): u32 offsets[16]; + ("GGATZIWS"): Swizzle copies[4]; + (_): u8 data[length]; + } +} [[format("format_tagg_name")]]; + +struct Palette { + u16 length; + BGR8 colors[length]; +}; + +struct Mipmap { + u16 width_and_lzo [[format("format_width_lzo")]]; + u16 height; + + u16 width = width_and_lzo & 0x7FFF; + + if (width == 0 && height == 0) { + break; + } + + u24 size; + u8 data[size]; +} [[format("format_resolution")]]; + +struct PAA { + PixelFormat format; + Tagg taggs[while(std::mem::read_string($, 4) == "GGAT")]; + Palette palette; + Mipmap mipmaps[while(true)]; + u16 EOF; + + std::assert_warn(EOF == 0, "Invalid EOF sentinel"); +}; + +fn format_resolution(ref auto mip) { + return std::format("{0:d} x {1:d}", mip.width, mip.height); +}; + +fn format_width_lzo(u16 value) { + u16 width = value & 0x7FFF; + if (value & 0x8000) { + return std::format("{0:d} (+LZO)", width); + } else { + return std::format("{0:d}", width); + } +}; + +fn format_tagg_name(Tagg data) { + match (data.signature) { + ("GGATCGVA"): return "Average color"; + ("GGATCXAM"): return "Max color"; + ("GGATGALF"): return "Alpha flag"; + ("GGATZIWS"): return "Swizzle"; + ("GGATSFFO"): return "Mipmap offsets"; + (_): return "Unknown"; + } +}; + +PAA file @ 0x0000; diff --git a/tests/patterns/test_data/a3_paa.hexpat.paa b/tests/patterns/test_data/a3_paa.hexpat.paa new file mode 100644 index 0000000000000000000000000000000000000000..373a502d4146cbf1468adf73df5e19385510a968 GIT binary patch literal 22016 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 literal 0 HcmV?d00001