From 9f92c38ecf7ad0c8b624e481697fe93d722787bf Mon Sep 17 00:00:00 2001 From: mheimlich Date: Sat, 22 Mar 2025 13:52:59 +0100 Subject: [PATCH] patterns: Add ADTFDAT pattern (#368) --- README.md | 1 + patterns/adtfdat.hexpat | 176 ++++++++++++++++++ .../patterns/test_data/adtfdat.hexpat.adtfdat | Bin 0 -> 33247 bytes 3 files changed, 177 insertions(+) create mode 100644 patterns/adtfdat.hexpat create mode 100644 tests/patterns/test_data/adtfdat.hexpat.adtfdat diff --git a/README.md b/README.md index a198900..a43ec41 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi |------|------|------|-------------| | 3DS | | [`patterns/3ds.hexpat`](patterns/3ds.hexpat) | Autodesk 3DS Max Model file | | 7Z | | [`patterns/7z.hexpat`](patterns/7z.hexpat) | 7z File Format | +| ADTFDAT | | [`patterns/adtfdat.hexpat`](patterns/adtfdat.hexpat) | [ADTFDAT files](https://digitalwerk.gitlab.io/solutions/adtf_content/adtf_base/adtf_file_library) | | ADTS | `audio/x-hx-aac-adts` | [`patterns/adts.hexpat`](patterns/adts.hexpat) | ADTS/AAC audio files | | AFE2 | | [`patterns/afe2.hexpat`](patterns/afe2.hexpat) | Nintendo Switch Atmosphère CFW Fatal Error log | | ANI | `application/x-navi-animation` | [`patterns/ani.hexpat`](patterns/ani.hexpat) | Windows Animated Cursor file | diff --git a/patterns/adtfdat.hexpat b/patterns/adtfdat.hexpat new file mode 100644 index 0000000..e83c4cd --- /dev/null +++ b/patterns/adtfdat.hexpat @@ -0,0 +1,176 @@ +#pragma description ADTFDAT File + +#pragma magic [ 49 46 48 44 ] @ 0x00 +#pragma endian little + +import std.io; +import std.mem; +import type.time; + +enum FileVersion : u32 { + with_history_end_offset = 0x00000301, + v500 = 0x00000500 +}; + +bitfield ChunkFlags { + key : 1; + info : 1; + marker : 1; + type : 1; + trigger : 1; + reserved: 11; +}; + +struct Extension +{ + char identifier[384]; + u16 stream_id; + u8 reserved1[2]; + u32 user_id; + u32 type_id; + u32 version_id; + u64 data_pos; + u64 data_size; + u8 reserved[96]; + u8 payload[data_size] @ data_pos; +}; + +struct ChunkHeader { + u64 time_stamp [[format("format_timestamp_with_offset")]];; + u32 ref_master_table_index; + u32 offset_to_last; + u32 size; + u16 stream_id; + ChunkFlags flags; + u64 stream_index; +}; + +struct SampleInfo { + u8 memory_layout_version; + u32 size; + u8 payload[size]; +}; + +struct Sample { + s64 timestamp [[format("format_timestamp")]];; + s32 flags; + u64 buffer_size; + u8 payload[buffer_size]; + if (flags & 0x100) { + SampleInfo sample_info; + } + if (flags & 0x200) { + u32 substream_id; + } else { + u32 substream_id = 0 [[export]]; + } +}; + +struct Chunk { + auto start_address = $; + + ChunkHeader header; + + if (header.size < 32 || start_address + header.size > std::mem::size()) { + std::warning(std::format("Invalid header size {} in chunk {} at offset 0x{:X}, last valid chunk at 0x{:X}.", header.size, chunk_index, start_address, last_valid_chunk_offset)); + break; + } + + last_valid_chunk_offset = start_address; + + u8 payload[header.size - 32]; + + if (!skip_valid_chunks) { + if (!header.flags.info && !header.flags.marker && !header.flags.type && !header.flags.trigger) { + try { + Sample sample @ addressof(payload); + } catch { + std::warning(std::format("Invalid sample payload at chunk {} at 0x{:X}", chunk_index, start_address)); + } + } + } + + chunk_index += 1; + + // Padding with 0xEE to the next chunk header + std::mem::AlignTo<16>; + + if (skip_valid_chunks) { + continue; + } +}; + +struct Header { + u32 file_id; + u32 version_id; + u32 flags; + u32 extension_count; + u64 extension_offset; + u64 data_offset; + u64 data_size; + u64 chunk_count; + u64 max_chunk_size; + u64 duration; + type::time64_t filetime; + u8 header_byte_order; + u64 time_offset [[format("format_timestamp")]]; + u8 patch_number; + u64 first_chunk_offset; + u64 continuous_offset; + u64 ring_buffer_end_offset; + u8 reserved[30]; + char description[1912]; +}; + +struct IndexedFile { + Header header; + Extension extensions[header.extension_count] @ header.extension_offset; + + if (header.version_id >= FileVersion::with_history_end_offset && + header.continuous_offset != header.data_offset) { + try { + Chunk ring_front[while($ < header.continuous_offset)] @ header.first_chunk_offset; + Chunk ring_back[while($ < header.ring_buffer_end_offset)] @ header.data_offset; + Chunk continueous[header.chunk_count - chunk_index] @ header.continuous_offset; + } catch { + std::warning("Too many chunks. Performing check only."); + skip_valid_chunks = true; + chunk_index = 0; + Chunk ring_front[while($ < header.continuous_offset)] @ header.first_chunk_offset; + Chunk ring_back[while($ < header.ring_buffer_end_offset)] @ header.data_offset; + Chunk continueous[while(chunk_index < header.chunk_count)] @ header.continuous_offset; + } + } else { + try { + Chunk chunks[header.chunk_count] @ header.data_offset; + } catch { + std::warning("Too many chunks. Performing check only."); + skip_valid_chunks = true; + chunk_index = 0; + Chunk chunks[while(chunk_index < header.chunk_count)] @ header.data_offset; + } + } +}; + +fn format_timestamp(u64 data) { + double seconds = 0; + if (file.header.version_id < FileVersion::v500) { + // microseconds + seconds = data / double(1000000); + } else { + // nanoseconds + seconds = data / double(1000000000); + } + + std::time::Time time64 = std::time::to_utc(seconds); + return std::time::format(time64); +}; + +fn format_timestamp_with_offset(u64 data) { + return format_timestamp(data + file.header.time_offset); +}; + +bool skip_valid_chunks = false; +u64 last_valid_chunk_offset = 0; +u64 chunk_index = 0; +IndexedFile file @ 0x00; diff --git a/tests/patterns/test_data/adtfdat.hexpat.adtfdat b/tests/patterns/test_data/adtfdat.hexpat.adtfdat new file mode 100644 index 0000000000000000000000000000000000000000..4e45fcd45d8cecb9bbe255c6430dd91f9d965449 GIT binary patch literal 33247 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