diff --git a/README.md b/README.md index 5641dd6..216be75 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Hex patterns, include patterns and magic files for the use with the ImHex Hex Ed | BSON | `application/bson` | [`patterns/bson.hexpat`](patterns/bson.hexpat) | BSON (Binary JSON) format | | msgpack | `application/x-msgpack` | [`patterns/msgpack.hexpat`](patterns/msgpack.hexpat) | MessagePack binary serialization format | | MiniDump | `application/x-dmp` | [`patterns/minidump.hexpat`](patterns/minidump.hexpat) | Windows MiniDump files | +| ID3 | `audio/mpeg` | [`patterns/id3tag.hexpat`](patterns/id3tag.hexpat) | ID3 tags in MP3 files | ### Scripts diff --git a/patterns/id3.hexpat b/patterns/id3.hexpat new file mode 100644 index 0000000..4129395 --- /dev/null +++ b/patterns/id3.hexpat @@ -0,0 +1,156 @@ +#pragma MIME audio/mpeg + +#include + +namespace v1 { + + struct Tag { + char header[3]; + char title[30]; + char artist[30]; + char album[30]; + char year[4]; + char comment[30]; + u8 zero; + u8 track; + u8 genre; + } [[static]]; + +} + +namespace v2 { + + struct SyncSafeInt { + u8 bytes[4]; + } [[sealed, static, format("v2::ssi_value"), transform("v2::ssi_value")]]; + + fn ssi_value(ref SyncSafeInt n) { + u32 result = 0; + for (u8 i = 0, i < 4, i = i + 1) { + u8 byteval = n.bytes[i] & 0x7F; + u8 shift = 7 * (4 - i - 1); + result = result | (byteval << shift); + } + return result; + }; + + struct TagVersion { + u8 major; + u8 revision; + }; + + bitfield TagHeaderFlags { + unsynchronized : 1; + extended : 1; + experimental : 1; + footer : 1; + padding : 4; + }; + + struct TagHeader { + char identifier[3]; + TagVersion version; + TagHeaderFlags flags; + SyncSafeInt size; + } [[static]]; + + bitfield ExtendedFlag { + padding : 1; + update : 1; + crcpresent : 1; + restrictions : 1; + padding : 4; + }; + + struct TagExtendedHeader { + SyncSafeInt size; + u8 nflagbytes; + ExtendedFlag flags[nflagbytes]; + u8 data[size]; + }; + + struct TagFooter { + char identifier[3]; + TagVersion version; + TagHeaderFlags flags; + SyncSafeInt size; + } [[static]]; + + bitfield FrameFlags { + padding : 1; + tagalterpreservation : 1; + filealterpreservation : 1; + readonly : 1; + padding : 5; + groupid : 1; + padding : 2; + compressed : 1; + encrypted : 1; + unzynchronized : 1; + datalengthindicator : 1; + }; + + enum TextEncoding : u8 { + ISO88591 = 0x00, + UTF16BOM = 0x01, + UTF16BE = 0x02, + UTF8 = 0x03, + }; + + struct StringData { + TextEncoding encoding; + if (encoding == TextEncoding::UTF16BOM) { + u16 bom; + char16 data[(parent.size - 3) / 2]; + } else if (encoding == TextEncoding::UTF16BE) + be char16 data[(parent.size - 1) / 2]; + else + char data[parent.size - 1]; + }; + + // Hack to work around the fact, that chars may not be compared + union FrameId { + char cdata[4]; + u8 ndata[4]; + } [[sealed, static, format("v2::impl::format_frame_id")]]; + + namespace impl { + + fn format_frame_id(ref FrameId id) { + return id.cdata; + }; + + } + + struct Frame { + FrameId id; + SyncSafeInt size; + FrameFlags flags; + if (id.ndata[0] == 'T') + StringData data; + else + u8 data[size]; + }; + + fn has_next_frame(u32 size) { + bool hasnext = $ < size; + if (hasnext) { + hasnext = std::mem::read_unsigned($, 1) != 0; + $ = $ - 1; + } + return hasnext; + }; + + struct Tag { + TagHeader header; + if (header.flags.extended) + TagExtendedHeader extendedheader; + Frame frames[while(v2::has_next_frame(header.size))]; + if (header.flags.footer) + TagFooter footer; + }; + +} + +v2::Tag tag @ 0; +v1::Tag oldtag @ std::mem::size() - 128;