mirror of
https://github.com/WerWolv/ImHex-Patterns.git
synced 2026-03-27 23:37:04 -05:00
patterns/bplist: Added pattern file for Apple’s binary property list format (bplist) (#190)
* Added pattern file for Apple’s binary property list format (bplist) * renamed some stuff and improved error messages * added error handling for object size special case (0x0F)
This commit is contained in:
271
patterns/bplist.hexpat
Normal file
271
patterns/bplist.hexpat
Normal file
@@ -0,0 +1,271 @@
|
||||
#include <std/math.pat>
|
||||
#include <std/core.pat>
|
||||
#include <type/magic.pat>
|
||||
#include <type/time.pat>
|
||||
|
||||
using CFBinaryPlistObject;
|
||||
|
||||
enum Marker : u8 {
|
||||
Null = 0x00,
|
||||
False = 0x08,
|
||||
True = 0x09,
|
||||
Fill = 0x0F,
|
||||
Int = 0x10,
|
||||
Real = 0x20,
|
||||
Date = 0x30,
|
||||
Data = 0x40,
|
||||
ASCIIString = 0x50,
|
||||
Unicode16String = 0x60,
|
||||
UNK_0x70 = 0x70,
|
||||
UID = 0x80,
|
||||
UNK_0x90 = 0x90,
|
||||
Array = 0xA0,
|
||||
UNK_0xB0 = 0xB0,
|
||||
Set = 0xC0,
|
||||
Dict = 0xD0,
|
||||
UNK_0xE0 = 0xE0,
|
||||
UNK_0xF0 = 0xF0
|
||||
};
|
||||
|
||||
fn get_marker_name(u8 marker) {
|
||||
if (marker == Marker::Null){// null 0000 0000
|
||||
return "Null ";
|
||||
}else if (marker == Marker::False){ //bool 0000 1000 // false
|
||||
return "False";
|
||||
}else if (marker == Marker::True){//bool 0000 1001 // true
|
||||
return "True";
|
||||
}else if (marker == Marker::Fill){ //fill 0000 1111 // fill byte
|
||||
return "Fill";
|
||||
}else if (marker & 0xF0 == Marker::Int){ //int 0001 nnnn ... // # of bytes is 2^nnnn, big-endian bytes
|
||||
return "Int";
|
||||
}else if (marker & 0xF0 == Marker:: Real){ //real 0010 nnnn ... // # of bytes is 2^nnnn, big-endian bytes
|
||||
return "Real";
|
||||
}else if (marker == Marker::Date){ //date 0011 0011 ... // 8 byte float follows, big-endian bytes
|
||||
return "Date";
|
||||
}else if (marker & 0xF0 == Marker::Data){ //data 0100 nnnn [int] ... // nnnn is number of bytes unless 1111 then int count follows, followed by bytes
|
||||
return "Data";
|
||||
}else if (marker & 0xF0 == Marker::ASCIIString){ //string 0101 nnnn [int] ... // ASCII string, nnnn is # of chars, else 1111 then int count, then bytes
|
||||
return "ASCIIString";
|
||||
}else if (marker & 0xF0 == Marker::Unicode16String){ //string 0110 nnnn [int] ... // Unicode string, nnnn is # of chars, else 1111 then int count, then big-endian 2-byte
|
||||
return "Unicode16String";
|
||||
}else if (marker & 0xF0 == Marker::UNK_0x70){ //0111 xxxx // unused
|
||||
return "UNK_0x70";
|
||||
}else if (marker & 0xF0 == Marker::UID){ //uid 1000 nnnn ... // nnnn+1 is # of bytes
|
||||
return "UID";
|
||||
}else if (marker & 0xF0 == Marker::UNK_0x90){ // 1001 xxxx // unused
|
||||
return "UNK_0x90";
|
||||
}else if (marker & 0xF0 == Marker::Array){ //array 1010 nnnn [int] objref* // nnnn is count, unless '1111', then int count follows
|
||||
return "Array";
|
||||
}else if (marker & 0xF0 == Marker::UNK_0xB0){ //1011 xxxx // unused
|
||||
return "UNK_0xB0";
|
||||
}else if (marker & 0xF0 == Marker::Set){ //set 1100 nnnn [int] objref* // nnnn is count, unless '1111', then int count follows
|
||||
return "Set";
|
||||
}else if (marker & 0xF0 == Marker::Dict){ //dict 1101 nnnn [int] keyref* objref* // nnnn is count, unless '1111', then int count follows
|
||||
return "Dict";
|
||||
}else if (marker & 0xF0 == Marker::UNK_0xE0){ // 1110 xxxx // unused
|
||||
return "UNK_0xE0";
|
||||
}else if (marker & 0xF0 == Marker::UNK_0xF0){ //1111 xxxx // unused
|
||||
return "UNK_0xF0";
|
||||
}
|
||||
};
|
||||
|
||||
fn format_tag(u8 marker) {
|
||||
return std::format("{}", get_marker(marker));
|
||||
};
|
||||
|
||||
fn coredata_to_date (double val){
|
||||
return type::impl::format_time_t(978307200 + val);
|
||||
};
|
||||
|
||||
struct DictElement {
|
||||
CFBinaryPlistObject key @ offsetTable[parent.objReference.key_refs[std::core::array_index()]].offset;
|
||||
CFBinaryPlistObject value @ offsetTable[parent.objReference.value_refs[std::core::array_index()]].offset;
|
||||
};
|
||||
|
||||
struct ArrayElement {
|
||||
CFBinaryPlistObject value @ offsetTable[parent.objReference.value_refs[std::core::array_index()]].offset;
|
||||
};
|
||||
|
||||
struct ObjectReference{
|
||||
match(trailer.objectRefSize){
|
||||
(1): {
|
||||
be u8 key_refs[parent.ObjectLen.size];
|
||||
be u8 value_refs[parent.ObjectLen.size];
|
||||
}
|
||||
(2): {
|
||||
be u16 key_refs[parent.ObjectLen.size];
|
||||
be u16 value_refs[parent.ObjectLen.size];
|
||||
}
|
||||
(4): {
|
||||
be u32 key_refs[parent.ObjectLen.size];
|
||||
be u32 value_refs[parent.ObjectLen.size];
|
||||
}
|
||||
(8): {
|
||||
be u64 key_refs[parent.ObjectLen.size];
|
||||
be u64 value_refs[parent.ObjectLen.size];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ObjectReferenceArray{
|
||||
match(trailer.objectRefSize){
|
||||
(1): be u8 value_refs[parent.ObjectLen.size];
|
||||
(2): be u16 value_refs[parent.ObjectLen.size];
|
||||
(4): be u32 value_refs[parent.ObjectLen.size];
|
||||
(8): be u64 value_refs[parent.ObjectLen.size];
|
||||
}
|
||||
};
|
||||
|
||||
struct ObjectLen{
|
||||
if (parent.marker_lsb != 0x0F){
|
||||
u8 size = parent.marker_lsb [[export]];
|
||||
}else{
|
||||
CFBinaryPlistObject obj;
|
||||
if (obj.marker & 0xF0 != Marker::Int){
|
||||
std::error(std::format("Expects a 'Int' marker. Got 0x{:x} marker, with value {}", obj.marker, obj.value));
|
||||
}
|
||||
|
||||
u128 size = obj.value [[export]];
|
||||
}
|
||||
};
|
||||
|
||||
struct CFBinaryPlistOffset{
|
||||
match (trailer.offsetIntSize){
|
||||
(1): be u8 offset;
|
||||
(2): be u16 offset;
|
||||
(4): be u32 offset;
|
||||
(8): be u64 offset;
|
||||
}
|
||||
};
|
||||
|
||||
struct CFBinaryPlistObject{
|
||||
u8 marker [[format("get_marker_name")]];
|
||||
|
||||
u8 marker_msb = marker & 0xF0;
|
||||
u8 marker_lsb = marker & 0x0F;
|
||||
|
||||
match (marker_msb){
|
||||
(0x0): {
|
||||
match (marker_lsb){
|
||||
(Marker::Null): {
|
||||
u8 value = 0x00 [[export]];
|
||||
}
|
||||
(Marker::False): {
|
||||
bool value = false [[export]];
|
||||
}
|
||||
(Marker::True): {
|
||||
bool value = true [[export]];
|
||||
}
|
||||
(Marker::Fill): {
|
||||
//I think the correct implementation is to do nothing here. The marker will be used as padding (Fill) ???
|
||||
}
|
||||
(_): {
|
||||
std::error("Detected unknown marker {}.", marker_msb);
|
||||
}
|
||||
}
|
||||
}
|
||||
(Marker::Int): {
|
||||
be u8 size = std::math::pow(2, marker_lsb);
|
||||
// in format version '00', 1, 2, and 4-byte integers have to be interpreted as unsigned,
|
||||
// whereas 8-byte integers are signed (and 16-byte when available)
|
||||
// negative 1, 2, 4-byte integers are always emitted as 8 bytes in format '00'
|
||||
// integers are not required to be in the most compact possible representation, but only the last 64 bits are significant currently
|
||||
// Source: https://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c
|
||||
|
||||
match (size) {
|
||||
(1): be u8 value;
|
||||
(2): be u16 value;
|
||||
(4): be u32 value;
|
||||
(8): be s64 value;
|
||||
(16): be s128 value;
|
||||
(_): std::error(std::format("Invalid size detected for 'Int' marker. Got size: {}.", size));
|
||||
}
|
||||
}
|
||||
(Marker::Real): {
|
||||
be u8 size = std::math::pow(2, marker_lsb);
|
||||
match (size){
|
||||
(4): be float value;
|
||||
(8): be double value;
|
||||
(_): std::error(std::format("Invalid size detected for 'Real' marker. Got size: {}.", size));
|
||||
}
|
||||
}
|
||||
(Marker::Date): {
|
||||
be double value [[format("coredata_to_date")]];
|
||||
}
|
||||
(Marker::Data): {
|
||||
ObjectLen ObjectLen;
|
||||
u8 value[ObjectLen.size];
|
||||
}
|
||||
(Marker::ASCIIString): {
|
||||
ObjectLen ObjectLen;
|
||||
char value[ObjectLen.size];
|
||||
}
|
||||
(Marker::Unicode16String): {
|
||||
ObjectLen ObjectLen;
|
||||
be char16 value[ObjectLen.size];
|
||||
}
|
||||
(Marker::UID): {
|
||||
//Not 100% sure if this is correct for UID. Need more testing
|
||||
u8 size = marker_lsb+1;
|
||||
match (size) {
|
||||
(1): be u8 value;
|
||||
(2): be u16 value;
|
||||
(4): be u32 value;
|
||||
(8): be u64 value;
|
||||
(16): be u128 value;
|
||||
(_): std::error(std::format("Invalid size detected for 'UID' marker. Got size: {}.", size));
|
||||
}
|
||||
}
|
||||
(Marker::Set | Marker::Array): {
|
||||
ObjectLen ObjectLen;
|
||||
|
||||
ObjectReferenceArray objReference;
|
||||
ArrayElement value[ObjectLen.size];
|
||||
}
|
||||
(Marker::Dict): {
|
||||
ObjectLen ObjectLen;
|
||||
|
||||
ObjectReference objReference;
|
||||
DictElement value[ObjectLen.size];
|
||||
}
|
||||
(Marker::UNK_0x70 | Marker::UNK_0x90 | Marker::UNK_0xB0 | Marker::UNK_0xE0 | Marker::UNK_0xF0): {
|
||||
std::error(std::format("Got unused marker 0x{:x}", marker));
|
||||
}
|
||||
(_): {
|
||||
std::error(std::format("Got unknown marker 0x{:x}", marker));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct CFBinaryPlistHeader{
|
||||
type::Magic<"bplist"> magic;
|
||||
u16 version;
|
||||
if (version != 0x3030){
|
||||
std::error("Unsupported version detected. Only version 00 is supported (bplist00).");
|
||||
}
|
||||
};
|
||||
|
||||
struct CFBinaryPlistTrailer {
|
||||
u8 unused[5];
|
||||
u8 sortVersion;
|
||||
be u8 offsetIntSize;
|
||||
match (offsetIntSize){
|
||||
(1|2|4|8): {}
|
||||
(_): {std::error("Invalid offsetIntSize.");}
|
||||
}
|
||||
be u8 objectRefSize;
|
||||
match (objectRefSize){
|
||||
(1|2|4|8): {}
|
||||
(_): {std::error("Invalid objectRefSize.");}
|
||||
}
|
||||
be u64 numObjects;
|
||||
be u64 topObject;
|
||||
be u64 offsetTableOffset;
|
||||
};
|
||||
|
||||
|
||||
CFBinaryPlistHeader header @ 0x00;
|
||||
CFBinaryPlistTrailer trailer @ std::mem::size()-32;
|
||||
|
||||
CFBinaryPlistOffset offsetTable[trailer.numObjects] @ trailer.offsetTableOffset;
|
||||
CFBinaryPlistObject objectTable @ offsetTable[trailer.topObject].offset;
|
||||
Reference in New Issue
Block a user