From ae15e983778f60101b2ea715ba74e29eb6b84005 Mon Sep 17 00:00:00 2001 From: SpectralBlade Date: Sat, 20 Jun 2026 14:35:30 +0200 Subject: [PATCH 1/4] Add CSNAP pattern --- README.md | 2 +- patterns/csnap.hexpat | 141 ++++++++++++++++++++ tests/patterns/test_data/csnap.hexpat.csnap | 141 ++++++++++++++++++++ 3 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 patterns/csnap.hexpat create mode 100644 tests/patterns/test_data/csnap.hexpat.csnap diff --git a/README.md b/README.md index 8d6bc376..602fc00e 100644 --- a/README.md +++ b/README.md @@ -262,7 +262,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | CBM BASIC | | [`commodore_basic.hexpat`](patterns/commodore_basic.hexpat) | Commodore BASIC | | Atari XEX | | [`xex.hexpat`](patterns/xex.hexpat) | Atari 8-bit binary format | | Terminfo | `application/x-terminfo` and `application/x-terminfo2` | [`patterns/terminfo.hexpat`](patterns/terminfo.hexpat) | Compiled *(legacy and extended)* term info entry | - +| CSNAP | [`csnap.hexpat`](patterns/csnap.hexpat) | ClipboardEdit Clipboard Snapshot format | ### Scripts diff --git a/patterns/csnap.hexpat b/patterns/csnap.hexpat new file mode 100644 index 00000000..fba1e525 --- /dev/null +++ b/patterns/csnap.hexpat @@ -0,0 +1,141 @@ +import std.io; + +fn id_to_format(u32 format) { + if (format == 0x008E) return "CF_DSPENHMETAFILE"; + if (format == 0x0083) return "CF_DSPMETAFILEPICT"; + if (format == 14) return "CF_ENHMETAFILE"; + if (format == 3) return "CF_METAFILEPICT"; + if (format == 2) return "CF_BITMAP"; + if (format == 0x0082) return "CF_DSPBITMAP"; + if (format == 9) return "CF_PALETTE"; + if (format == 8) return "CF_DIB"; + if (format == 17) return "CF_DIBV5"; + if (format == 0x0081) return "CF_DSPTEXT"; + if (format == 7) return "CF_OEMTEXT"; + if (format == 1) return "CF_TEXT"; + if (format == 13) return "CF_UNICODETEXT"; + if (format == 15) return "CF_HDROP"; + if (format == 5) return "CF_DIF"; + if (format == 16) return "CF_LOCALE"; + if (format == 0x0080) return "CF_OWNERDISPLAY"; + if (format == 10) return "CF_PENDATA"; + if (format == 11) return "CF_RIFF"; + if (format == 4) return "CF_SYLK"; + if (format == 6) return "CF_TIFF"; + if (format == 12) return "CF_WAVE"; + + return std::format("0x{:X} ({})", format, format); +}; + +struct ClipboardEntry +{ + u32 format [[format("id_to_format")]]; + u32 length; + u8 data[length]; +}; + +const u64 FILETIME_EPOCH_DIFF = 116444736000000000; + +fn filetime_to_unix(s64 ft) +{ + return (ft - FILETIME_EPOCH_DIFF) / 10000000; +}; + +fn is_leap(s32 year) +{ + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); +}; + +fn days_in_month(s32 month, s32 year) +{ + if (month == 1) return 31; + if (month == 2) return is_leap(year) ? 29 : 28; + if (month == 3) return 31; + if (month == 4) return 30; + if (month == 5) return 31; + if (month == 6) return 30; + if (month == 7) return 31; + if (month == 8) return 31; + if (month == 9) return 30; + if (month == 10) return 31; + if (month == 11) return 30; + if (month == 12) return 31; + return 30; +}; + +fn unix_to_datetime(s64 unix) +{ + s64 seconds = unix; + s32 year = 1970; + + // compute year + while (true) + { + s64 seconds_in_year = is_leap(year) ? 31622400 : 31536000; + + if (seconds >= seconds_in_year) + { + seconds -= seconds_in_year; + year += 1; + } + else + { + break; + } + } + + // compute month + s32 month = 1; + + while (true) + { + s64 dim = days_in_month(month, year) * 86400; + + if (seconds >= dim) + { + seconds -= dim; + month += 1; + } + else + { + break; + } + } + + // day + s32 day = (seconds / 86400) + 1; + seconds = seconds % 86400; + + // time + s32 hour = seconds / 3600; + seconds = seconds % 3600; + + s32 minute = seconds / 60; + s32 second = seconds % 60; + + return std::format( + "{:04}-{:02}-{:02} {:02}:{:02}:{:02}", + year, month, day, hour, minute, second + ); +}; + +fn filetime_to_string(s64 ft) +{ + return unix_to_datetime(filetime_to_unix(ft)); +}; + +struct ClipboardSnapshot +{ + u32 magic; + u32 version; + u32 count; + + ClipboardEntry entries[count]; + + s64 filetimeTakenUtc [[format("filetime_to_string")]]; +}; + +ClipboardSnapshot snapshot @ 0x00; + +if (snapshot.magic != 0x0F1B0A6D) + std::error("Invalid Clipboard Snapshot file"); \ No newline at end of file diff --git a/tests/patterns/test_data/csnap.hexpat.csnap b/tests/patterns/test_data/csnap.hexpat.csnap new file mode 100644 index 00000000..fba1e525 --- /dev/null +++ b/tests/patterns/test_data/csnap.hexpat.csnap @@ -0,0 +1,141 @@ +import std.io; + +fn id_to_format(u32 format) { + if (format == 0x008E) return "CF_DSPENHMETAFILE"; + if (format == 0x0083) return "CF_DSPMETAFILEPICT"; + if (format == 14) return "CF_ENHMETAFILE"; + if (format == 3) return "CF_METAFILEPICT"; + if (format == 2) return "CF_BITMAP"; + if (format == 0x0082) return "CF_DSPBITMAP"; + if (format == 9) return "CF_PALETTE"; + if (format == 8) return "CF_DIB"; + if (format == 17) return "CF_DIBV5"; + if (format == 0x0081) return "CF_DSPTEXT"; + if (format == 7) return "CF_OEMTEXT"; + if (format == 1) return "CF_TEXT"; + if (format == 13) return "CF_UNICODETEXT"; + if (format == 15) return "CF_HDROP"; + if (format == 5) return "CF_DIF"; + if (format == 16) return "CF_LOCALE"; + if (format == 0x0080) return "CF_OWNERDISPLAY"; + if (format == 10) return "CF_PENDATA"; + if (format == 11) return "CF_RIFF"; + if (format == 4) return "CF_SYLK"; + if (format == 6) return "CF_TIFF"; + if (format == 12) return "CF_WAVE"; + + return std::format("0x{:X} ({})", format, format); +}; + +struct ClipboardEntry +{ + u32 format [[format("id_to_format")]]; + u32 length; + u8 data[length]; +}; + +const u64 FILETIME_EPOCH_DIFF = 116444736000000000; + +fn filetime_to_unix(s64 ft) +{ + return (ft - FILETIME_EPOCH_DIFF) / 10000000; +}; + +fn is_leap(s32 year) +{ + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); +}; + +fn days_in_month(s32 month, s32 year) +{ + if (month == 1) return 31; + if (month == 2) return is_leap(year) ? 29 : 28; + if (month == 3) return 31; + if (month == 4) return 30; + if (month == 5) return 31; + if (month == 6) return 30; + if (month == 7) return 31; + if (month == 8) return 31; + if (month == 9) return 30; + if (month == 10) return 31; + if (month == 11) return 30; + if (month == 12) return 31; + return 30; +}; + +fn unix_to_datetime(s64 unix) +{ + s64 seconds = unix; + s32 year = 1970; + + // compute year + while (true) + { + s64 seconds_in_year = is_leap(year) ? 31622400 : 31536000; + + if (seconds >= seconds_in_year) + { + seconds -= seconds_in_year; + year += 1; + } + else + { + break; + } + } + + // compute month + s32 month = 1; + + while (true) + { + s64 dim = days_in_month(month, year) * 86400; + + if (seconds >= dim) + { + seconds -= dim; + month += 1; + } + else + { + break; + } + } + + // day + s32 day = (seconds / 86400) + 1; + seconds = seconds % 86400; + + // time + s32 hour = seconds / 3600; + seconds = seconds % 3600; + + s32 minute = seconds / 60; + s32 second = seconds % 60; + + return std::format( + "{:04}-{:02}-{:02} {:02}:{:02}:{:02}", + year, month, day, hour, minute, second + ); +}; + +fn filetime_to_string(s64 ft) +{ + return unix_to_datetime(filetime_to_unix(ft)); +}; + +struct ClipboardSnapshot +{ + u32 magic; + u32 version; + u32 count; + + ClipboardEntry entries[count]; + + s64 filetimeTakenUtc [[format("filetime_to_string")]]; +}; + +ClipboardSnapshot snapshot @ 0x00; + +if (snapshot.magic != 0x0F1B0A6D) + std::error("Invalid Clipboard Snapshot file"); \ No newline at end of file From 2ab121835c8de626b8f07c833f8846b303910a21 Mon Sep 17 00:00:00 2001 From: SpectralBlade Date: Wed, 24 Jun 2026 15:07:52 +0200 Subject: [PATCH 2/4] Fix pattern and add valid test file --- patterns/csnap.hexpat | 95 ++------------------ tests/patterns/test_data/csnap.hexpat.csnap | Bin 3244 -> 192 bytes 2 files changed, 6 insertions(+), 89 deletions(-) diff --git a/patterns/csnap.hexpat b/patterns/csnap.hexpat index fba1e525..d458382d 100644 --- a/patterns/csnap.hexpat +++ b/patterns/csnap.hexpat @@ -1,4 +1,6 @@ import std.io; +import std.time; +import type.time; fn id_to_format(u32 format) { if (format == 0x008E) return "CF_DSPENHMETAFILE"; @@ -34,94 +36,9 @@ struct ClipboardEntry u8 data[length]; }; -const u64 FILETIME_EPOCH_DIFF = 116444736000000000; - -fn filetime_to_unix(s64 ft) -{ - return (ft - FILETIME_EPOCH_DIFF) / 10000000; -}; - -fn is_leap(s32 year) -{ - return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); -}; - -fn days_in_month(s32 month, s32 year) -{ - if (month == 1) return 31; - if (month == 2) return is_leap(year) ? 29 : 28; - if (month == 3) return 31; - if (month == 4) return 30; - if (month == 5) return 31; - if (month == 6) return 30; - if (month == 7) return 31; - if (month == 8) return 31; - if (month == 9) return 30; - if (month == 10) return 31; - if (month == 11) return 30; - if (month == 12) return 31; - return 30; -}; - -fn unix_to_datetime(s64 unix) -{ - s64 seconds = unix; - s32 year = 1970; - - // compute year - while (true) - { - s64 seconds_in_year = is_leap(year) ? 31622400 : 31536000; - - if (seconds >= seconds_in_year) - { - seconds -= seconds_in_year; - year += 1; - } - else - { - break; - } - } - - // compute month - s32 month = 1; - - while (true) - { - s64 dim = days_in_month(month, year) * 86400; - - if (seconds >= dim) - { - seconds -= dim; - month += 1; - } - else - { - break; - } - } - - // day - s32 day = (seconds / 86400) + 1; - seconds = seconds % 86400; - - // time - s32 hour = seconds / 3600; - seconds = seconds % 3600; - - s32 minute = seconds / 60; - s32 second = seconds % 60; - - return std::format( - "{:04}-{:02}-{:02} {:02}:{:02}:{:02}", - year, month, day, hour, minute, second - ); -}; - -fn filetime_to_string(s64 ft) -{ - return unix_to_datetime(filetime_to_unix(ft)); +fn to_utc(auto v) { + type::time64_t unix_time = std::time::filetime_to_unix(v); + return type::impl::format_time_t(unix_time); }; struct ClipboardSnapshot @@ -132,7 +49,7 @@ struct ClipboardSnapshot ClipboardEntry entries[count]; - s64 filetimeTakenUtc [[format("filetime_to_string")]]; + type::FILETIME filetimeTakenUtc [[format("to_utc")]]; }; ClipboardSnapshot snapshot @ 0x00; diff --git a/tests/patterns/test_data/csnap.hexpat.csnap b/tests/patterns/test_data/csnap.hexpat.csnap index fba1e5255a425e3c459da47579531a60e45beb3e..30bf38837e1c4eb7dd1cb2144a9275d6d9586bb0 100644 GIT binary patch literal 192 zcma)!(Fub<5JX235CTDe@|B+@wd5lO*ub6&cc9+kZnc4QkpdjS0)%}t!?4VJdo}>t zE9Fk!q(el4K@L?W$H>N+dQ9vK0x}dU26l#pxzHn9-v`OQ`Jhzv7wKX$;i>p&F8!2B W$hr<p4Qy|jra|Gl%jUayxd z&3QOHIA(U{+qrx*L&EhYP6(uA{x*y+OQl5w;e1Ns=^{?nJ~6iS8t_*Wb|t`1xB!Do zpwj{C(Xv{O2}wYr755+-A}=V{IChGO#4E$Rg? zqSqAgE7!Z}j4z$tRO$BZ^>S5-!nk-M2 zqCNV2>)iF-$#~fPsvi?2YP#;+f!<-2-jlDx4_Y`iEbkbaty1)J_mc>o zb!67D*!k^t*6d~Lao7H`2V=K4%ayF}#b5KXw8sIFl4Lt0&|8I@@3EiEorok)B|aMD zuz>q}fms|UW%JuNJ`bqsDu^D)GW*_wxljCiE<+`H*DQ`w0^4Q-=p^yn8)xc_N4@JQ zR(t>*uN#p$c|}$Q_|m zF`cdge`BQR_*38~hhS6&C(vL+5njDwN+>`e_y^~t3BP}XA+%6Phi8^kgrECQ=`@U{ z>o_7yb_si{z~Oy#{IVkoAbC}{<($orB>{x@%yW1TwF_uNt);P%9jk?u6s=ntYuT7u zYsp-#wVXd%YdIXX$8ySQ!3+JWHMa}N)P+`PCt;rviC#zPie;p1qpU+NGX^r|QK6s< zWnvm2vF9lgn4a(~iZWAHUz{T#>(OczX7PHnB>|%oll@vEyTQOV3`{22u_s;41#rqG zMz&7QTv7YF-K^Cb$iOz#?K9Rq*>z&Q3kTnIgs%dQ-$?c3t@N9)IDKZp!=pO%!(4iU zF-q}1cRyIALH|DFW?k3*aNvm`O*GYFc`tPkjAO!h+N=HVK%uQnossPKk|*U)NW z-i{{=5&lDhP)kNalfwP{|FeRj?($fRHP#{2VBP6 z1QCT<6)2H-TtX&KPqNe8dL2fT)Ka4gO<|3d;|PW06L!qZHG7N6X(r3Nw$<35;0v9l2KD#&Jf^=4ukZbso-j50kM^s?Mcu7 z5kw!!tgy9<&hjYv?xu4thZk-_n)wU=vVRCrhps6hean1jTkz2u*jHA!+2?)2etZN; Y5+_F4jeh#8a4vsYKn_oxDVvvn0I%+VBme*a From 37248fde4d608be6b11c78c3411028bb6bf98e61 Mon Sep 17 00:00:00 2001 From: SpectralBlade Date: Wed, 24 Jun 2026 15:57:47 +0200 Subject: [PATCH 3/4] Update CSNAP pattern --- patterns/csnap.hexpat | 60 +++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/patterns/csnap.hexpat b/patterns/csnap.hexpat index d458382d..5b09159b 100644 --- a/patterns/csnap.hexpat +++ b/patterns/csnap.hexpat @@ -1,32 +1,42 @@ import std.io; +import std.core; import std.time; import type.time; +import std.string; -fn id_to_format(u32 format) { - if (format == 0x008E) return "CF_DSPENHMETAFILE"; - if (format == 0x0083) return "CF_DSPMETAFILEPICT"; - if (format == 14) return "CF_ENHMETAFILE"; - if (format == 3) return "CF_METAFILEPICT"; - if (format == 2) return "CF_BITMAP"; - if (format == 0x0082) return "CF_DSPBITMAP"; - if (format == 9) return "CF_PALETTE"; - if (format == 8) return "CF_DIB"; - if (format == 17) return "CF_DIBV5"; - if (format == 0x0081) return "CF_DSPTEXT"; - if (format == 7) return "CF_OEMTEXT"; - if (format == 1) return "CF_TEXT"; - if (format == 13) return "CF_UNICODETEXT"; - if (format == 15) return "CF_HDROP"; - if (format == 5) return "CF_DIF"; - if (format == 16) return "CF_LOCALE"; - if (format == 0x0080) return "CF_OWNERDISPLAY"; - if (format == 10) return "CF_PENDATA"; - if (format == 11) return "CF_RIFF"; - if (format == 4) return "CF_SYLK"; - if (format == 6) return "CF_TIFF"; - if (format == 12) return "CF_WAVE"; - - return std::format("0x{:X} ({})", format, format); +enum ID : u32 { + CF_TEXT = 1, + CF_BITMAP, + CF_METAFILEPICT, + CF_SYLK, + CF_DIF, + CF_TIFF, + CF_OEMTEXT, + CF_DIB, + CF_PALETTE, + CF_PENDATA, + CF_RIFF, + CF_WAVE, + CF_UNICODETEXT, + CF_ENHMETAFILE, + CF_HDROP, + CF_LOCALE, + CF_DIBV5, + CF_OWNERDISPLAY = 0x80, + CF_DSPTEXT, + CF_DSPBITMAP, + CF_DSPMETAFILEPICT, + CF_DSPENHMETAFILE = 0x8E, +}; + +fn id_to_format(auto v) { + ID id = v; + str result = std::format("{}",id); + u32 resultLength = std::string::length(result)-4; + result = std::string::substr(result,4,resultLength); + if (result == "???") + return std::format("{:#X} {}",v,v); + return result; }; struct ClipboardEntry From d033d36e5894cdbfbde1ead908540417f4f0d8a6 Mon Sep 17 00:00:00 2001 From: SpectralBlade Date: Wed, 24 Jun 2026 16:04:33 +0200 Subject: [PATCH 4/4] Add pragmas to CSNAP pattern --- patterns/csnap.hexpat | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/patterns/csnap.hexpat b/patterns/csnap.hexpat index 5b09159b..e5d30d81 100644 --- a/patterns/csnap.hexpat +++ b/patterns/csnap.hexpat @@ -1,3 +1,9 @@ +#pragma endian little +#pragma magic 0x0F1B0A6D @ 0x00 +#pragma author FireBlade +#pragma description ClipboardEdit Clipboard Snapshot format + + import std.io; import std.core; import std.time;