Skip to content

Monkey's APE - APEv2 support? #380

@vrdriver

Description

@vrdriver

Hey all, I had a go with making a pattern for APEv2 but didn't have any luck, but it's really beyond me.

The funny this is that the APE tag can go at the end for just about any file.

This is the spec:
https://wiki.hydrogenaud.io/index.php?title=APEv2_specification

and there is one for fq:
https://github.com/wader/fq/blob/master/format/ape/apev2.go



#pragma description "APEv2 Tags"
#pragma MIME "audio/mpeg"

namespace APEv2 {
    struct TagHeader {
        char preamble[8];    // "APETAGEX"
        u32 version;         // Usually 2000 (0x000007D0)
        u32 tag_size;        // Total size including header/footer
        u32 item_count;      // Number of items
        u32 flags;           // Tag flags
        u8 reserved[8];      // Always zero
    };

    bitfield ItemFlags {
        unused0   : 6;       // Bits 0-5
        binary    : 1;       // Bit 6: Binary item flag
        unused1   : 25;      // Bits 7-31
    };

    // Function to find length of null-terminated key
    fn get_key_length(u32 start) {
        u32 pos = start;
        while (true) {
            u8 current_byte = std::read(pos);
            if (current_byte == 0) {
                break;
            }
            pos += 1;
        }
        return pos - start;
    };

    struct TagItem {
        u32 item_size;       // Size of value data
        ItemFlags flags;     // Item flags (includes binary flag)
        u32 key_start = $;   // Capture starting position
        u32 key_length = get_key_length(key_start); // Precompute key length
        char key[key_length]; // Null-terminated key
        u8 key_terminator;   // Null byte after key
        if (flags.binary) {
            u32 filename_start = $;
            u32 filename_length = get_key_length(filename_start); // Null-terminated filename
            char filename[filename_length];
            u8 filename_terminator; // Null byte after filename
            u32 remaining_size = item_size - (key_length + 1 + filename_length + 1);
            u8 value[remaining_size]; // Remaining bytes as raw value
        } else {
            char value[item_size]; // UTF-8 value
        }
    };

    struct TagFooter : TagHeader {};

    // Function to check for next item
    fn has_next_item(u32 tag_start, u32 tag_size) {
        bool has_next = $ < (tag_start + tag_size - sizeof(TagFooter));
        if (has_next) {
            has_next = std::read($) != 0; // Check for padding/end
        }
        return has_next;
    };

    struct Tag {
        // Start with footer at end of file
        TagFooter footer @ std::end - sizeof(TagFooter);
        if (footer.preamble == "APETAGEX" && footer.version == 2000) {
            u32 tag_start = std::end - footer.tag_size;
            TagHeader header @ tag_start;
            // Note: No check for item_count > 1000 as imHex doesn't support error throwing
            $ = tag_start + sizeof(TagHeader);
            TagItem items[while(has_next_item(tag_start, footer.tag_size))];
        }
    };
}

APEv2::Tag apev2_tag @ 0;

Cheers,
Steve

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions