From fdc4a87389d6efa0a85868b82d9e093b488dbcae Mon Sep 17 00:00:00 2001 From: Known Rabbit Date: Tue, 14 Jan 2025 04:20:36 +0800 Subject: [PATCH] patterns: Add bcss (BeyondCompare SnapShot) file (#338) * patterns: add bcss (BeyondCompare SnapShot) file * Add entry to readme * Change table entries in alphabetical order * Support both bcss file and uncompressed content * Remove misleading cases, add warning message * Add test cases to bcss.hexpat * ifdef out ImHex-only functionality --------- Co-authored-by: Nik --- README.md | 1 + patterns/bcss.hexpat | 168 ++++++++++++++++++ .../patterns/test_data/bcss.hexpat/test.bcss | Bin 0 -> 227 bytes .../test_data/bcss.hexpat/test.bcss_content | Bin 0 -> 362 bytes 4 files changed, 169 insertions(+) create mode 100644 patterns/bcss.hexpat create mode 100644 tests/patterns/test_data/bcss.hexpat/test.bcss create mode 100644 tests/patterns/test_data/bcss.hexpat/test.bcss_content diff --git a/README.md b/README.md index 9f4cbfed..f0f24243 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | ARIA2 | | [`patterns/aria2.hexpat`](patterns/aria2.hexpat) | ARIA2 Download Manager Control files | | ARM VTOR | | [`patterns/arm_cm_vtor.hexpat`](patterns/arm_cm_vtor.hexpat) | ARM Cortex M Vector Table Layout | | Bastion | | [`patterns/bastion/*`](https://gitlab.com/EvelynTSMG/imhex-bastion-pats) | Various [Bastion](https://en.wikipedia.org/wiki/Bastion_(video_game)) files | +| BeyondCompare BCSS | | [`patterns/bcss.hexpat`](patterns/bcss.hexpat) | BeyondCompare Snapshot (BCSS) file | | Bencode | `application/x-bittorrent` | [`patterns/bencode.hexpat`](patterns/bencode.hexpat) | Bencode encoding, used by Torrent files | | Prusa BGCODE | | [`patterns/bgcode.hexpat`](patterns/bgcode.hexpat) | PrusaSlicer Binary G-Code files | | BLEND | | [`patterns/blend.hexpat`](patterns/blend.hexpat) | Blender Project file | diff --git a/patterns/bcss.hexpat b/patterns/bcss.hexpat new file mode 100644 index 00000000..b2a13836 --- /dev/null +++ b/patterns/bcss.hexpat @@ -0,0 +1,168 @@ +#pragma author ttimasdf +#pragma description BeyondCompare Snapshot (BCSS) file + +#pragma magic [42 43 53 53] @ 0x00 + +#pragma array_limit 4294967295 +#pragma pattern_limit 4294967295 + + +import std.io; +import std.mem; +import std.array; +import std.string; +import type.magic; + +#ifdef __IMHEX__ +import hex.dec; +import hex.core; +#endif + + +const u8 max_path_size = 1000; +str current_path[max_path_size]; +u8 current_path_level = 0; + +enum EntryType : u8 { + DIRECTORY = 0x01, + FILE = 0x02, + SYMLINK = 0x03, + // NULL = 0x00, + DIR_END = 0xFF, +}; + +struct BCSSEntry { + EntryType type; + match (type) { + (EntryType::DIRECTORY) : { + // FileName name; + std::string::SizedString name; + if (name.size != 0) { + u8 unknown[12]; + on_dir_enter(name.data); // std::string::to_string(name) + } else { + // some buggy edge cases + u8 unknown[6]; + std::warning(std::format("invalid empty entry current_lvl={} current_pos=0x{:02x}", current_path_level, $)); + } + } + (EntryType::FILE) : { + std::string::SizedString name; + if (name.size != 0) { + u8 unknown[20]; + #ifdef __IMHEX__ + hex::core::add_virtual_file(get_vfs_path(name), this); // std::string::to_string(name) + #endif + } else { + // some buggy edge cases + u8 unknown[6]; + std::warning(std::format("invalid empty entry current_lvl={} current_pos=0x{:02x}", current_path_level, $)); + } + //try { + // u8 unknown[20]; + //} catch { + // u8 unknown[0]; + //} + } + (EntryType::SYMLINK) : { + std::string::SizedString name; + u8 unknown[23]; + std::string::SizedString target; + #ifdef __IMHEX__ + hex::core::add_virtual_file(get_vfs_path(name + " [s]"), this); // std::string::to_string(name) + #endif + } + (EntryType::DIR_END) : { + on_dir_exit(); + } + // (EntryType::NULL) : { + // // some buggy edge cases + // u8 unknown[7]; + // std::warning(std::format("invalid empty entry current_lvl={} current_pos=0x{:02x}", current_path_level, $)); + // } + (_): { + std::error(std::format("unknown EntryType idx={} current_pos=0x{:02x}", std::core::array_index(), $)); + } + } +}[[format_read("fmt_BCSSEntry")]]; + +fn on_dir_enter(str folder_name) { + // std::print("on_dir_enter folder={} current_lvl={}", folder_name, current_path_level); + if (std::string::length(folder_name) > 0) { + current_path[current_path_level] = folder_name; + current_path_level += 1; + } else { + std::warning(std::format("invalid folder name {} current_lvl={} current_pos=0x{:02x}", folder_name, current_path_level, $)); + } +}; + +fn on_dir_exit() { + if (current_path_level > 0) { + current_path_level -= 1; + } else if (!std::mem::eof()) { + std::warning(std::format("on_dir_exit current_lvl already == 0 current_pos=0x{:02x}", $)); + } + // std::print("on_dir_exit current_lvl={}", current_path_level); +}; + +fn get_vfs_path(str file_name) { + str vfs_path = ""; + if (current_path_level > 0) { + vfs_path = current_path[0]; + for(u8 i = 1, i < current_path_level, i += 1) { + //hash_hex = hash_hex + std::format("{:02X}",bytes[i]); + vfs_path = vfs_path + "/" + current_path[i]; + } + return vfs_path + "/" + file_name; + } else { + return file_name; + } +}; + + +fn fmt_BCSSEntry(BCSSEntry e) { + try { + match (e.type) { + (EntryType::DIRECTORY | EntryType::FILE) : { + return std::format("{}: {}", (e.type == EntryType::DIRECTORY ? "Dir" : "File"), e.name.data); + } + (EntryType::SYMLINK) : { + return std::format("Symlink: {} -> {}", e.name.data, e.target.data); + } + (EntryType::DIR_END) : { + return "Directory End"; + } + } + } catch { + return "[FmtErr]"; + } +}; + +struct BCSSFile { + if (std::mem::read_unsigned(0, 4) == 0x53534342) { + type::Magic<"BCSS"> magic; + u8 unknown[14]; + std::string::SizedString root_path; + + u8 zlib_content[std::mem::size()-$]; + + // manually add zlib header which is essential for hex::dec::zlib_decompress + const str zlib_header = "\x78\x9c"; + std::mem::Section zlib_compressed = std::mem::create_section("zlib_compressed"); + std::mem::copy_value_to_section(zlib_header, zlib_compressed, 0); + std::mem::copy_value_to_section(zlib_content, zlib_compressed, 2); + u8 zlib[std::mem::get_section_size(zlib_compressed)] @ 0x00 in zlib_compressed; + + #ifdef __IMHEX__ + std::mem::Section zlib_decompressed = std::mem::create_section("zlib_decompressed"); + hex::dec::zlib_decompress(zlib, zlib_decompressed, 15); + u8 decompressed_content[std::mem::get_section_size(zlib_decompressed)] @ 0x00 in zlib_decompressed; + hex::core::add_virtual_file("bcss_content", decompressed_content); + std::warning("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n! BCSS file content is compressed !\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\nopen `bcss_content` file from the Virtual Filesystem tab and run this pattern on it."); + #endif + } else { + BCSSEntry entries[while(!std::mem::eof())]; + } +}; + +BCSSFile bcss_file @ 0x00; diff --git a/tests/patterns/test_data/bcss.hexpat/test.bcss b/tests/patterns/test_data/bcss.hexpat/test.bcss new file mode 100644 index 0000000000000000000000000000000000000000..9873088811ec8b862758a162d0bc3c18dcde7bff GIT binary patch literal 227 zcmV<903829LsL@$0RaGT91k*9W7`1>01f~{I$U&bZ)|g1XLEIAZ)0RDGtx8FGn9DV zvgP)NTZ{q>3=B-H>BXff`3wijKIm`0#i#%jde6iF227k_DZSK+RAlKB&QTC)0kE`h zeoARhs$M~HiXpP9V1EXzs&Y|Otx1KdlJd+e%g;{LbEB4wM;~<(;i!O7in_ietd$#6W^42JY?u%9mGC+!fSq!@Y{{cFSYwFPKWY+)y literal 0 HcmV?d00001 diff --git a/tests/patterns/test_data/bcss.hexpat/test.bcss_content b/tests/patterns/test_data/bcss.hexpat/test.bcss_content new file mode 100644 index 0000000000000000000000000000000000000000..3f9010e2eb87ec824d1ddf0f0f0c6b44121079bc GIT binary patch literal 362 zcmZQ%D>KqF)iacM-m>NPhg*yS3=9lRtm(z2DftWs%0B3CzQw2j6nf7DB!Ga46D*~d zT9Jw@eZn~kA}s)x_RUWz%}LcOC{8g%Ru$|IR>Z)BrYaXj)tXeODk;yrvi$5+UDuq{ zvc!_q6zBZh+{C;Tps^_GcZb7Fg{p_R5KR?dBt#W62gqw6_rrXwzygs0k;)9r^3GN< uCHeU|#W7%WVj#f-;efRLXJ&UUEh