Files
ImHex-Patterns/patterns/adtfdat.hexpat
2025-03-22 13:52:59 +01:00

177 lines
4.5 KiB
Rust

#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;