diff --git a/README.md b/README.md index 29a71bd..e20e8b7 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | ZSTD | `application/zstd` | [`patterns/zstd.hexpat`](patterns/zstd.hexpat) | Zstandard compressed data format | | MOD | `3d-model/mod` | [`patterns/DMC3HD-Mod.hexpat`](patterns/dmc3_hd_mod.hexpat) | 3D Model files used in Devil May Cry 3 HD Collection | | CBM BASIC | | [`commodore_basic.hexpat`](patterns/commodore_basic.hexpat) | Commodore BASIC | +| Terminfo | `application/x-terminfo` and `application/x-terminfo2` | [`patterns/terminfo.hexpat`](patterns/terminfo.hexpat) | Compiled *(legacy and extended)* term info entry | ### Scripts diff --git a/patterns/terminfo.hexpat b/patterns/terminfo.hexpat new file mode 100644 index 0000000..63bc520 --- /dev/null +++ b/patterns/terminfo.hexpat @@ -0,0 +1,348 @@ +/* info */ +#pragma author Nightowl286 +#pragma description Compiled term info entry + +#pragma endian little + +#pragma magic [1e 02] @ 0x00 +#pragma MIME application/x-terminfo2 + +#pragma magic [1a 01] @ 0x00 +#pragma MIME application/x-terminfo + +/* + Format has been primarily taken from: + https://www.man7.org/linux/man-pages/man5/term.5.html + + Technically you can't count on the binary formats being compatible + on different unix versions, however *(as I've been told)* those versions + are pretty much dead nowadays so this format is common enough to work + in like the majority of the cases. +*/ + +/* imports */ +import std.array; +import std.mem; +import std.string; +import std.ctype; + + +/* utility */ +fn is_char(char ch) { + char value = std::mem::read_unsigned($, 1); + return value == ch; +}; + +fn is_not_char(char ch) { + char value = std::mem::read_unsigned($, 1); + return value != ch; +}; + + +/* types */ +enum number_format : s16 { + legacy = 0o432, + extended = 0o1036 +} +[[format("number_format_to_string")]]; + +struct boolean_capability { s8 value; } +[[ + sealed, + format("boolean_capability_format"), + comment("Indicates the presence of a boolean capability.\n\n(An empty value indicates that the capability is missing).") +]]; + +struct numerical_capability { + if (parent.header.format == number_format::extended) + s32 value; + else + s16 value; +} +[[ + sealed, + format("numerical_capability_format"), + comment("Indicates the presence of a numerical capability.\n\n(An empty value indicates that the capability is missing).") +]]; + +struct string_table_value_offset { s16 value; } +[[ + sealed, + format("string_table_value_offset_format"), + comment("Indicates the offset of a string capability value in the string table.\n\n(An empty value indicates that the capability is missing).") +]]; + +struct string_table_name_offset { s16 value; } +[[ + sealed, + format("string_table_name_offset_format"), + comment("Indicates the offset of a name string in the string table.\n\n(This value is relative to the name portion of the string table).") +]]; + +struct terminal_name { + char name[while(is_not_char('|') && is_not_char(0x00))]; + padding[1]; + + if (is_char('|')) $ += 1; + if (is_char(0)) break; +} +[[ + sealed, + comment("Represents a terminal name/alias.") +]]; + +struct table_string { + char value[while(is_not_char(0x00))]; + if (is_char(0)) + $ += 1; + +} +[[ + sealed, + format("string_table_value_format"), + comment("A null-terminated string in the string table.") +]]; + +struct string_table { + std::ByteSizedArray array [[inline]]; +}; + +struct legacy_header { + number_format format + [[comment("The number format of the terminfo entry.")]]; + + s16 name_size + [[ + name("name size"), + comment("The size of the terminal names in bytes.") + ]]; + + s16 boolean_count + [[ + name("boolean capability count"), + comment("The amount of known boolean capabilities in the file.") + ]]; + + s16 numerical_count + [[ + name("numerical capability count"), + comment("The amount of known numerical capabilities in the file.") + ]]; + + s16 string_count + [[ + name("string capability count"), + comment("The amount of known string capabilities in the file.") + ]]; + + s16 string_table_size + [[ + name("string table size"), + comment("The size of the string table (in bytes).") + ]]; +}; + +struct extended_header { + s16 boolean_count + [[ + name("boolean capability count"), + comment("The amount of extended boolean capabilities in the file.") + ]]; + + s16 numerical_count + [[ + name("numerical capability count"), + comment("The amount of extended numerical capabilities in the file.") + ]]; + + s16 string_count + [[ + name("string capability count"), + comment("The amount of extended string capabilities in the file.") + ]]; + + s16 table_item_count + [[ + name("table item count"), + comment("The amount of items in the string table.") + ]]; + + s16 string_table_size + [[ + name("string table size"), + comment("The size of the extended string table (in bytes).") + ]]; +}; + +struct extended_data { + extended_header header + [[comment("The metadata about the extended section of the file.")]]; + + boolean_capability booleans[header.boolean_count] + [[ + name("boolean capabilities"), + comment("The extended boolean capabilities.") + ]]; + + if ($ % 2 != 0) $ += 1; // align to even boundary before numerical capabilities are read; + + numerical_capability numerical[header.numerical_count] + [[ + name("numerical capabilities"), + comment("The extended numerical capabilities.") + ]]; + + string_table_value_offset string_offsets[header.string_count] + [[ + name("string table value offsets"), + comment("The string table offsets for the extended string capability values.") + ]]; + + string_table_name_offset name_offsets[header.table_item_count - header.string_count] + [[ + name("string table name offsets"), + comment("The string table offsets for all of the extended capability names.\n\n(These values are relative to the name portion of the string table).") + ]]; + + string_table string_table + [[ + name("string table"), + comment("Contains all of the string values used in the extended section of the file.") + ]]; +}; + +struct file { + legacy_header header + [[comment("The metadata about the file.")]]; + + terminal_name terminal_names[1000] + [[ + format_entries("terminal_name_format"), + name("terminal names"), + comment("The names and aliases that the terminfo file is describing.") + ]]; + + boolean_capability booleans[header.boolean_count] + [[ + name("boolean capabilities"), + comment("The known boolean capabilities.") + ]]; + + if ($ % 2 != 0) $ += 1; // align to even boundary before numerical capabilities are read; + + numerical_capability numerical[header.numerical_count] + [[ + name("numerical capabilities"), + comment("The known numerical capabilities.") + ]]; + + string_table_value_offset string_offsets[header.string_count] + [[ + name("string table value offsets"), + comment("The string table offsets for the known string capability values.") + ]]; + + string_table string_table + [[ + name("string table"), + comment("Contains all of the string values used in the (legacy storage format section of) file.") + ]]; + + if (std::mem::eof() == false) { + extended_data extended + [[comment("Contains the information from extended storage format section.")]]; + } + +} [[inline]]; + + +/* formatting */ +fn terminal_name_format(terminal_name name) { + return '"' + name.name + '"'; +}; + +fn boolean_capability_format(boolean_capability value) { + match (value.value) { + (-2): return "cancelled"; + (-1 | 0): return ""; + (1): return "present"; + (_): return value.value; + } + return value; +}; + +fn numerical_capability_format(numerical_capability value) { + match (value.value) { + (-2): return "cancelled"; + (-1): return ""; + (_): return value.value; + } + return value; +}; + +fn string_table_value_offset_format(string_table_value_offset value) { + match (value.value) { + (-2): return "cancelled"; + (-1): return ""; + (_): return value.value; + } + return value; +}; + +fn string_table_name_offset_format(string_table_name_offset value) { + return value.value; +}; + +fn string_table_value_format(ref table_string value) { + str view = "\""; + + for (s16 i = 0, i < sizeof(value.value), i += 1) { + char ch = value.value[i]; + if (ch == '\\') view += "\\\\"; + else if (ch == '"') view += "\\\""; + else if (std::ctype::isgraph(ch)) view += ch; + else view += to_hex_ch(ch); + } + + view += "\""; + return view; +}; + +fn to_hex_ch(char ch) { + if (ch == '\x1b') return "\\e"; + if (ch == ' ') return " "; + if (ch == '\r') return "\\r"; + if (ch == '\n') return "\\n"; + if (ch == '\a') return "\\a"; + if (ch == '\b') return "\\b"; + if (ch == '\t') return "\\t"; + if (ch == 11) return "\\v"; + if (ch == '\f') return "\\f"; + + s8 second = ch % 16; + s8 first = ch / 16; + + return "\\x" + to_hex_digit(first) + to_hex_digit(second); +}; + +fn to_hex_digit(u8 num) { + if (num == 10) return 'A'; + if (num == 11) return 'B'; + if (num == 12) return 'C'; + if (num == 13) return 'D'; + if (num == 14) return 'E'; + if (num == 15) return 'F'; + + return std::string::to_string(num); +}; + +fn number_format_to_string(number_format format) { + if (format == number_format::extended) return "extended"; + if (format == number_format::legacy) return "legacy"; + + return "unknown (" + std::string::to_string(s16(format)) + ")"; +}; + + +/* actual data */ +file file @ 0x00; \ No newline at end of file diff --git a/tests/patterns/test_data/terminfo.hexpat.bin b/tests/patterns/test_data/terminfo.hexpat.bin new file mode 100644 index 0000000..2a9f42b Binary files /dev/null and b/tests/patterns/test_data/terminfo.hexpat.bin differ