From 297f611feda66bf642d634f140c664afcb1914fb Mon Sep 17 00:00:00 2001 From: Nightowl Date: Tue, 9 Sep 2025 21:39:59 +0100 Subject: [PATCH] patterns: Add terminfo pattern (#437) * patterns/terminfo: Add pattern for compiled term info entry files. This adds support for the compiled (legacy and extended) term info entry files that are used for determining terminal capabilities. * Add .bin extension to the terminfo test file. --- README.md | 1 + patterns/terminfo.hexpat | 348 +++++++++++++++++++ tests/patterns/test_data/terminfo.hexpat.bin | Bin 0 -> 4071 bytes 3 files changed, 349 insertions(+) create mode 100644 patterns/terminfo.hexpat create mode 100644 tests/patterns/test_data/terminfo.hexpat.bin 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 0000000000000000000000000000000000000000..2a9f42b40ea5317efeba69a432dce6eb43714e95 GIT binary patch literal 4071 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 literal 0 HcmV?d00001