diff --git a/Cargo.lock b/Cargo.lock index f850eba..2da45f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arboard" version = "3.6.1" @@ -101,9 +110,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64" @@ -134,9 +143,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block-buffer" @@ -149,9 +158,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" @@ -222,7 +237,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -248,9 +263,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "compact_str" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" dependencies = [ "castaway", "cfg-if", @@ -287,13 +302,19 @@ dependencies = [ "libc", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossterm" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "crossterm_winapi", "derive_more", "document-features", @@ -354,7 +375,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -365,7 +386,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -379,9 +400,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] [[package]] name = "derive_more" @@ -402,7 +420,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -442,7 +460,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2", ] @@ -465,6 +483,7 @@ dependencies = [ "directories-next", "encoding_rs", "evalexpr", + "goblin", "hex", "memchr", "mmap-io", @@ -479,9 +498,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "encoding_rs" @@ -539,6 +558,12 @@ dependencies = [ "regex", ] +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + [[package]] name = "filedescriptor" version = "0.8.3" @@ -568,12 +593,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -649,25 +668,25 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", "rand_core 0.10.1", - "wasip2", - "wasip3", ] [[package]] -name = "hashbrown" -version = "0.15.5" +name = "goblin" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "17582616a7718cca54cec18e534a76c7c4aec11a8b9a85695712f262fd15a4c8" dependencies = [ - "foldhash 0.1.5", + "log", + "plain", + "scroll", ] [[package]] @@ -678,7 +697,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.2.0", + "foldhash", ] [[package]] @@ -686,6 +705,11 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heck" @@ -699,12 +723,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -719,8 +737,6 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.1", - "serde", - "serde_core", ] [[package]] @@ -742,7 +758,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -768,13 +784,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -801,23 +816,23 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -828,7 +843,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -854,17 +869,17 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" -version = "0.16.4" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" dependencies = [ - "hashbrown 0.16.1", + "hashbrown 0.17.1", ] [[package]] @@ -879,9 +894,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memmap2" @@ -915,9 +930,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "log", @@ -927,9 +942,9 @@ dependencies = [ [[package]] name = "mmap-io" -version = "0.9.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34b926fd173419d630eb9d8ee3a485345237bf699d6f7ea6e5c1ee97cf1fa47" +checksum = "da7573a60d8c5fa36a94411413740fcf2ef1fc0b1f21700b57d4abbcd3bf7ff9" dependencies = [ "anyhow", "cfg-if", @@ -946,7 +961,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -965,9 +980,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" @@ -977,7 +992,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1013,7 +1028,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2", "objc2-core-graphics", "objc2-foundation", @@ -1025,7 +1040,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dispatch2", "objc2", ] @@ -1036,7 +1051,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dispatch2", "objc2", "objc2-core-foundation", @@ -1055,7 +1070,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", ] @@ -1066,7 +1081,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", ] @@ -1092,6 +1107,30 @@ dependencies = [ "num-traits", ] +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "libm", + "palette_derive", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1151,7 +1190,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1204,7 +1243,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1222,6 +1261,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "portable-atomic" version = "1.13.1" @@ -1234,16 +1279,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -1290,7 +1325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", - "getrandom 0.4.2", + "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -1308,31 +1343,35 @@ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "ratatui" -version = "0.30.0" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +checksum = "3274ba0a2c5e1bcad2a2005d20f4dc59dad26b2eb0940fb094500dba4099d57d" dependencies = [ "instability", "ratatui-core", "ratatui-crossterm", "ratatui-macros", + "ratatui-termina", "ratatui-termwiz", "ratatui-widgets", + "serde", ] [[package]] name = "ratatui-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "compact_str", - "hashbrown 0.16.1", - "indoc", + "critical-section", + "hashbrown 0.17.1", "itertools", "kasuari", "lru", + "palette", + "serde", "strum", "thiserror 2.0.18", "unicode-segmentation", @@ -1342,9 +1381,9 @@ dependencies = [ [[package]] name = "ratatui-crossterm" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +checksum = "567584a3b0e6a8203c23de40b4861497266725eb5363dbfd18a1edd603cca9f0" dependencies = [ "cfg-if", "crossterm", @@ -1354,19 +1393,30 @@ dependencies = [ [[package]] name = "ratatui-macros" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" +checksum = "ed7dc68daa7498a43e4d68e0eb078427e10c38fbcfbb1e42d955f1fa2140d814" dependencies = [ "ratatui-core", "ratatui-widgets", ] [[package]] -name = "ratatui-termwiz" +name = "ratatui-termina" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" +checksum = "c0bf912d9e66f057a759d92e386a280ea886b352ab757d6ac4d653c7ed2c43c2" +dependencies = [ + "instability", + "ratatui-core", + "termina", +] + +[[package]] +name = "ratatui-termwiz" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf03e0380b7744054d6cb74224fe3adf062a029754933f575ca1e3b4c2ce977" dependencies = [ "ratatui-core", "termwiz", @@ -1374,17 +1424,18 @@ dependencies = [ [[package]] name = "ratatui-widgets" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.16.1", + "bitflags 2.13.0", + "hashbrown 0.17.1", "indoc", "instability", "itertools", "line-clipping", "ratatui-core", + "serde", "strum", "time", "unicode-segmentation", @@ -1397,7 +1448,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -1413,9 +1464,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -1436,9 +1487,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rustc_version" @@ -1455,7 +1506,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -1480,6 +1531,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "semver" version = "1.0.28" @@ -1513,20 +1584,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", + "syn 2.0.118", ] [[package]] @@ -1600,9 +1658,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "static_assertions" @@ -1618,23 +1676,23 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1650,15 +1708,28 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "termina" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9048a889effe34a5cddee0af7f53285198b16dca3be510858d38dfdb3e62a04e" +dependencies = [ + "bitflags 2.13.0", + "parking_lot", + "rustix", + "signal-hook", + "windows-sys 0.61.2", +] + [[package]] name = "terminfo" version = "0.9.0" @@ -1688,7 +1759,7 @@ checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", "base64", - "bitflags 2.11.1", + "bitflags 2.13.0", "fancy-regex", "filedescriptor", "finl_unicode", @@ -1748,7 +1819,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1759,14 +1830,14 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "time" -version = "0.3.47" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" dependencies = [ "deranged", "libc", @@ -1779,9 +1850,9 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "toml" @@ -1835,9 +1906,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -1853,9 +1924,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-truncate" @@ -1874,12 +1945,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "utf8parse" version = "0.2.2" @@ -1888,12 +1953,12 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ "atomic", - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] @@ -1921,27 +1986,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" -dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" dependencies = [ "cfg-if", "once_cell", @@ -1952,9 +2008,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1962,60 +2018,26 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "wezterm-bidi" version = "0.2.3" @@ -2201,18 +2223,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" [[package]] name = "wit-bindgen" @@ -2220,85 +2233,6 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.1", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "x11rb" version = "0.13.2" @@ -2315,9 +2249,3 @@ name = "x11rb-protocol" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index fcab473..29e3783 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,10 @@ crossterm = "0.29.0" directories-next = "2.0.0" encoding_rs = "0.8.35" evalexpr = "13.1.*" +goblin = "0.10.5" hex = "0.4.3" memchr = "2.8.0" -mmap-io = "0.9.4" +mmap-io = "1.0.0" rand = "0.10.1" ratatui = "0.30.0" regex = "1.11.2" diff --git a/src/app.rs b/src/app.rs index 83524be..00677bd 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,6 +6,8 @@ use std::{ }; use arboard::Clipboard; +use goblin::Object; +use goblin::error; use mmap_io::{MemoryMappedFile, MmapMode}; use ratatui::{Frame, layout::Rect, widgets::ListState}; @@ -13,6 +15,7 @@ use crate::{ config::*, editor::*, global::calculator::Calculator, + header::header_view::{Elf, HeaderView, Pe}, hex::{hex_view::HexView, strings::FoundString}, input_history::InputHistory, reader::Reader, @@ -38,7 +41,7 @@ impl FileInfo { /// which also takes care of unloading it if memory constrained. pub fn get_buffer(&mut self) -> &[u8] { if let Some(mmap) = self.mmap.as_mut() { - return mmap.as_slice(0, self.size as u64).unwrap(); + return mmap.as_slice_bytes(0, self.size as u64).unwrap(); } &[] @@ -68,7 +71,9 @@ pub struct App { pub dialog_renderer: Option, pub editor_view: AppView, pub file_info: FileInfo, + pub header_view: HeaderView, pub hex_view: HexView, + pub last_error: Dz6Error, pub list_state: ListState, pub log_scroll_offset: (u16, u16), pub logs: Vec, @@ -79,7 +84,6 @@ pub struct App { pub string_regex: String, pub strings: Vec, pub text_view: TextView, - pub last_error: Dz6Error, } impl App { @@ -107,6 +111,10 @@ impl App { dialog_2nd_renderer: None, editor_view: AppView::Hex, file_info: FileInfo::default(), + header_view: HeaderView { + // elf_header_table_state: TableState::new().with_selected_cell(Some((0, 1))), + ..Default::default() + }, hex_view: HexView { editing_hex: true, highlights: HashSet::with_capacity(8), @@ -134,14 +142,55 @@ impl App { } /// this function tries to identify a file type; this is a boilerplate implementation. - fn id_file(&mut self) { + fn id_file(&mut self) -> error::Result<()> { let buffer = self.file_info.get_buffer(); - self.file_info.r#type = match buffer[0] { - 0x7f => "ELF", - 0xca | 0xcf => "Mach-O", - 0x4d => "PE", + + self.file_info.r#type = match Object::parse(&buffer)? { + Object::COFF(_coff) => "COFF", + Object::Elf(elf) => { + self.header_view.elf = Some(Elf { + header: elf.header.clone(), + phdrs: elf.program_headers.clone(), + sections: elf.section_headers.clone(), + symtab: elf.syms.to_vec(), + strtab: elf + .syms + .iter() + .map(|s| s.st_name) + .filter_map(|idx| elf.strtab.get_at(idx).map(|name| (idx, name.to_owned()))) + .collect(), + }); + "ELF" + } + Object::Mach(_mach) => "Mach-O", + Object::PE(pe) => { + let mut imports = Vec::new(); + for imp in pe.imports { + imports.push(crate::header::header_view::PEImport { + dll: imp.dll.to_string(), + name: imp.name.to_string(), + offset: imp.offset, + ordinal: imp.ordinal, + rva: imp.rva, + _size: imp.size, + }); + } + + self.header_view.pe = Some(Pe { + dos_header: pe.header.dos_header.clone(), + coff_header: pe.header.coff_header.clone(), + optional_header: pe.header.optional_header.clone(), + sections: pe.sections, + imports, + }); + + "PE" + } + Object::TE(_magic) => "TE", _ => "", - } + }; + + Ok(()) } /// load a file @@ -182,7 +231,7 @@ impl App { self.file_info.size = meta.len() as usize; if self.file_info.size > 0 { - self.id_file(); + _ = self.id_file(); } self.log(format!( @@ -199,6 +248,7 @@ impl App { if self.config.database { let _ = self.load_database(); } + Ok(()) } diff --git a/src/commands.rs b/src/commands.rs index fb6ed09..99030fd 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -244,6 +244,26 @@ pub fn parse_command(app: &mut App, cmdline: &str) { app.config.search_wrap = false; app.dialog_renderer = None; } + // view + "view" => { + if let Some(val) = value { + match val.as_str() { + "text" => { + app.editor_view = crate::editor::AppView::Text; + app.dialog_renderer = None; + } + "hex" => { + app.editor_view = crate::editor::AppView::Hex; + app.dialog_renderer = None; + } + "header" => { + app.editor_view = crate::editor::AppView::Header; + app.dialog_renderer = None; + } + _ => {} + } + } + } _ => { app.dialog_renderer = None; } diff --git a/src/draw.rs b/src/draw.rs index f7a0c80..035159f 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -3,7 +3,7 @@ use ratatui::{Frame, prelude::*, widgets::Paragraph}; use crate::{ app::App, editor::AppView, - global, + global, header, hex::{self, comment}, ruler, text, }; @@ -72,6 +72,8 @@ pub fn draw(frame: &mut Frame, app: &mut App) { // Draw status bar at the bottom global::status_bar::status_bar_draw(app, frame, vertical_layout[1]); + app.command_area = vertical_layout[2]; + let horizontal_layout = Layout::default() .direction(Direction::Horizontal) .constraints(vec![Constraint::Percentage(100)]) @@ -80,6 +82,31 @@ pub fn draw(frame: &mut Frame, app: &mut App) { text::draw::text_contents_draw(app, frame, horizontal_layout[0]); app.text_view.area_height = horizontal_layout[0].height; } + AppView::Header => { + let constraints = vec![ + Constraint::Percentage(100), // middle area (text content) + Constraint::Length(1), // status bar + Constraint::Length(1), // command bar + ]; + + let vertical_layout = Layout::default() + .direction(Direction::Vertical) + .constraints(constraints) + .split(frame.area()); + + // Draw status bar at the bottom + global::status_bar::status_bar_draw(app, frame, vertical_layout[1]); + + app.command_area = vertical_layout[2]; + + let horizontal_layout = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![Constraint::Percentage(100)]) + .split(vertical_layout[0]); + + header::draw::header_contents_draw(app, frame, horizontal_layout[0]); + app.text_view.area_height = horizontal_layout[0].height; + } } // The right event handler function is set by the keypress diff --git a/src/editor.rs b/src/editor.rs index a960e7e..8e6792e 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -2,13 +2,15 @@ pub enum AppView { Text, Hex, + Header, } impl AppView { pub fn next(&mut self) { match self { - AppView::Text => *self = AppView::Hex, AppView::Hex => *self = AppView::Text, + AppView::Text => *self = AppView::Header, + AppView::Header => *self = AppView::Hex, } } } diff --git a/src/events.rs b/src/events.rs index 35979b7..c39845d 100644 --- a/src/events.rs +++ b/src/events.rs @@ -2,11 +2,11 @@ use ratatui::crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; use std::io::Result; use crate::app::App; -use crate::commands; use crate::editor::{AppView, UIState}; use crate::global; use crate::hex; use crate::text; +use crate::{commands, header}; pub fn handle_dialog_error_events(app: &mut App, key: KeyEvent) -> Result { match key.code { @@ -29,6 +29,7 @@ pub fn handle_events(app: &mut App) -> Result { match app.editor_view { AppView::Hex => hex::events::hex_mode_events(app, key)?, AppView::Text => text::events::text_mode_events(app, key)?, + AppView::Header => header::events::header_view_events(app, key)?, } } UIState::DialogHelp => handle_dialog_error_events(app, key)?, diff --git a/src/global/events.rs b/src/global/events.rs index 32d9566..8f21015 100644 --- a/src/global/events.rs +++ b/src/global/events.rs @@ -1,11 +1,13 @@ use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use crate::{app::App, commands, editor::UIState, global}; +use crate::{app::App, beep, commands, editor::UIState, global}; use std::io::Result; pub fn handle_global_events(app: &mut App, key: KeyEvent) -> Result { match key.code { + // beep in NORMAL mode + KeyCode::Esc => beep!(), // switch views KeyCode::Enter => app.switch_editor_view(), // log window diff --git a/src/global/goto.rs b/src/global/goto.rs index 37e98c1..ec30a23 100644 --- a/src/global/goto.rs +++ b/src/global/goto.rs @@ -1,4 +1,5 @@ use crate::app::App; +use crate::editor::AppView; impl App { /// The goto() function handles moving page position and smooth transition between pages @@ -64,6 +65,11 @@ impl App { // Update offset self.hex_view.offset = offset; + // if goto is called from Header view + if self.editor_view == AppView::Header { + self.editor_view = AppView::Hex; + } + App::log(self, format!("goto: {:x}", offset)); } } diff --git a/src/header/draw.rs b/src/header/draw.rs new file mode 100644 index 0000000..067033e --- /dev/null +++ b/src/header/draw.rs @@ -0,0 +1,11 @@ +use ratatui::{Frame, layout::Rect}; + +use crate::{app::App, header::formats}; + +pub fn header_contents_draw(app: &mut App, frame: &mut Frame, area: Rect) { + if app.file_info.r#type == "PE" { + let _ = formats::pe::draw::pe_draw(app, frame, area); + } else if app.file_info.r#type == "ELF" { + let _ = formats::elf::draw::elf_draw(app, frame, area); + } +} diff --git a/src/header/events.rs b/src/header/events.rs new file mode 100644 index 0000000..e7dd37a --- /dev/null +++ b/src/header/events.rs @@ -0,0 +1,13 @@ +use std::io::Result; + +use ratatui::crossterm::event::KeyEvent; + +use crate::{app::App, header::formats}; + +pub fn header_view_events(app: &mut App, key: KeyEvent) -> Result { + match app.file_info.r#type { + "ELF" => formats::elf::events::view_header_elf_events(app, key), + "PE" => formats::pe::events::view_header_pe_events(app, key), + _ => Ok(false), + } +} diff --git a/src/header/formats/elf/draw.rs b/src/header/formats/elf/draw.rs new file mode 100644 index 0000000..479af53 --- /dev/null +++ b/src/header/formats/elf/draw.rs @@ -0,0 +1,339 @@ +use goblin::elf::{ + header::*, + program_header::pt_to_str, + section_header::sht_to_str, + sym::{bind_to_str, type_to_str, visibility_to_str}, +}; +use ratatui::{ + Frame, + layout::{Constraint, Layout, Rect}, + style::Style, + widgets::{Cell, Clear, Row, Table, Tabs}, +}; + +use crate::app::App; + +fn osabi_to_str(osabi: u8) -> &'static str { + // https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html + match osabi { + ELFOSABI_NONE => "NONE", + ELFOSABI_HPUX => "HP-UX", + ELFOSABI_NETBSD => "NetBSD", + ELFOSABI_LINUX => "Linux", + ELFOSABI_SOLARIS => "Sun Solaris", + ELFOSABI_AIX => "AIX", + ELFOSABI_IRIX => "IRIX", + ELFOSABI_FREEBSD => "FreeBSD", + ELFOSABI_TRU64 => "Compaq TRU64 UNIX", + ELFOSABI_MODESTO => "Novell Modesto", + ELFOSABI_OPENBSD => "OpenBSD", + 13 => "OpenVMS", + 14 => "HP Non-Stop Kernel", + _ => "UNKNOWN_OSABI", + } +} + +fn draw_elf_header(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(elf) = &app.header_view.elf { + let mut rows = Vec::new(); + + let e_ident = elf.header.e_ident; + + rows.push(Row::new(vec![ + Cell::new("Class"), + Cell::new(format!("{:08X} ({})", e_ident[4], class_to_str(e_ident[4]))), + ])); + + rows.push(Row::new(vec![ + Cell::new("Data"), + Cell::new(format!( + "{:08X} ({})", + e_ident[5], + match e_ident[5] { + 1 => "LSB/little endian", + 2 => "MSB/big endian", + _ => "Invalid data encoding", + } + )), + ])); + + rows.push(Row::new(vec![ + Cell::new("Version"), + Cell::new(format!( + "{:08X} ({})", + e_ident[6], + if e_ident[6] == 1 { + "current" + } else { + "invalid" + } + )), + ])); + + rows.push(Row::new(vec![ + Cell::new("OS/ABI"), + Cell::new(format!("{:08X} ({})", e_ident[7], osabi_to_str(e_ident[7]))), + ])); + + rows.push(Row::new(vec![ + Cell::new("Type"), + Cell::new(format!( + "{:08X} ({})", + elf.header.e_type, + et_to_str(elf.header.e_type) + )), + ])); + + rows.push(Row::new(vec![ + Cell::new("Machine"), + Cell::new(format!( + "{:08X} ({})", + elf.header.e_machine, + machine_to_str(elf.header.e_machine) + )), + ])); + + rows.push(Row::new(vec![ + Cell::new("Version"), + Cell::new(format!("{:08X}", elf.header.e_version)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Entrypoint"), + Cell::new(format!("{:08X}", elf.header.e_entry)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Program header offset"), + Cell::new(format!("{:08X}", elf.header.e_phoff)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Section header offset"), + Cell::new(format!("{:08X}", elf.header.e_shoff)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Flags"), + Cell::new(format!("{:08X}", elf.header.e_flags)), + ])); + + rows.push(Row::new(vec![ + Cell::new("(This) header size"), + Cell::new(format!("{:08X}", elf.header.e_ehsize)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Program header size"), + Cell::new(format!("{:08X}", elf.header.e_phentsize)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Number of program headers"), + Cell::new(format!("{:08X}", elf.header.e_phnum)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Section headers size"), + Cell::new(format!("{:08X}", elf.header.e_shentsize)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Number of section headers"), + Cell::new(format!("{:08X}", elf.header.e_shnum)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Section header string table index"), + Cell::new(format!("{:08X}", elf.header.e_shstrndx)), + ])); + + let widths = [Constraint::Min(8), Constraint::Fill(1), Constraint::Fill(1)]; + + let header_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_stateful_widget( + header_table, + area, + &mut app.header_view.elf_state.elf_header_table_state, + ); + } +} + +fn draw_program_header(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(elf) = &app.header_view.elf { + let mut rows = Vec::new(); + + let phdrs = &elf.phdrs; + + for phdr in phdrs { + rows.push(Row::new(vec![ + Cell::new(format!("{}", pt_to_str(phdr.p_type))), + Cell::new(format!("{:08X}", phdr.p_offset)), + Cell::new(format!("{:08X}", phdr.p_filesz)), + Cell::new(format!("{:08X}", phdr.p_vaddr)), + Cell::new(format!("{:08X}", phdr.p_memsz)), + Cell::new(format!("{:08X}", phdr.p_paddr)), + Cell::new(format!("{:X}", phdr.p_flags)), + Cell::new(format!("{:X}", phdr.p_align)), + ])); + } + + let widths = [Constraint::Ratio(1, 8); 8]; + + let header_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .header(Row::new(vec![ + "Type", "Offset", "FileSiz", "VirtAddr", "MemSiz", "PhysAddr", "Flags", "Align", + ])) + .style(Style::new().bold()) + .style(app.config.theme.main) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_stateful_widget( + header_table, + area, + &mut app.header_view.elf_state.program_header_table_state, + ); + } +} + +fn draw_section_header(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(elf) = &app.header_view.elf { + let mut rows = Vec::new(); + + let strtab = elf.sections.get(elf.header.e_shstrndx as usize); + let buf = app.file_info.get_buffer(); + + for (i, section) in elf.sections.iter().enumerate() { + let mut name_cell = Cell::default(); + if let Some(strtab) = strtab { + let bytes: Vec = buf + .iter() + .skip(strtab.sh_offset as usize + section.sh_name) + .take_while(|b| **b != 0) + .copied() + .collect(); + + let name = String::from_utf8(bytes).unwrap_or_default(); + name_cell = Cell::new(name); + } + + rows.push(Row::new(vec![ + Cell::new(format!("{:X}", i)), + name_cell, + Cell::new(format!("{:08X}", section.sh_name)), + Cell::new(format!("{}", sht_to_str(section.sh_type))), + Cell::new(format!("{:08X}", section.sh_flags)), + Cell::new(format!("{:08X}", section.sh_addr)), + Cell::new(format!("{:08X}", section.sh_offset)), + Cell::new(format!("{:08X}", section.sh_size)), + Cell::new(format!("{:08X}", section.sh_link)), + Cell::new(format!("{:08X}", section.sh_info)), + Cell::new(format!("{:08X}", section.sh_addralign)), + Cell::new(format!("{:08X}", section.sh_entsize)), + ])); + } + + let widths = [Constraint::Ratio(1, 8); 8]; + + let header_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .header(Row::new(vec![ + "Idx", "Name", "NameIdx", "Type", "Flags", "Addr", "Offset", "Size", "Link", + "Info", "Align", "EntSize", + ])) + .style(Style::new().bold()) + .style(app.config.theme.main) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_stateful_widget( + header_table, + area, + &mut app.header_view.elf_state.sections_table_state, + ); + } +} + +fn draw_symbols(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(elf) = &app.header_view.elf { + let mut rows = Vec::new(); + + for symbol in &elf.symtab { + let name = elf + .strtab + .get(&symbol.st_name) + .map(String::as_str) + .unwrap_or_default(); + + rows.push(Row::new(vec![ + Cell::new(name), + Cell::new(format!("{}", bind_to_str(symbol.st_bind()))), + Cell::new(format!("{}", type_to_str(symbol.st_type()))), + Cell::new(format!("{}", visibility_to_str(symbol.st_visibility()))), + Cell::new(format!("{:08X}", symbol.st_shndx)), + Cell::new(format!("{:08X}", symbol.st_value)), + Cell::new(format!("{:08X}", symbol.st_size)), + ])); + } + + let widths = [ + Constraint::Fill(1), // Name + Constraint::Length(6), // Bind + Constraint::Length(6), // Type + Constraint::Length(10), // Visibility + Constraint::Length(8), // SecHdrIdx + Constraint::Length(8), // Value + Constraint::Length(8), // Size + ]; + let symbol_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .header(Row::new(vec![ + "Name", + "Bind", + "Type", + "Visibility", + "SecHdrIdx", + "Value", + "Size", + ])) + .style(Style::new().bold()) + .style(app.config.theme.main) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_stateful_widget( + symbol_table, + area, + &mut app.header_view.elf_state.symbols_table_state, + ); + } +} + +pub fn elf_draw(app: &mut App, frame: &mut Frame, area: Rect) { + let tabs = Tabs::new(vec!["Header", "Phdrs", "Sections", "Symbols"]) + .style(app.config.theme.main) + .highlight_style(app.config.theme.highlight) + .divider("|") + .padding(" ", " ") + .select(app.header_view.tab_index); + + let layout = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]); + let [top, main] = area.layout(&layout); + + frame.render_widget(Clear, area); + frame.render_widget(tabs, top); + + match app.header_view.tab_index { + 0 => draw_elf_header(app, frame, main), + 1 => draw_program_header(app, frame, main), + 2 => draw_section_header(app, frame, main), + 3 => draw_symbols(app, frame, main), + _ => {} + } +} diff --git a/src/header/formats/elf/events.rs b/src/header/formats/elf/events.rs new file mode 100644 index 0000000..fc85314 --- /dev/null +++ b/src/header/formats/elf/events.rs @@ -0,0 +1,212 @@ +use std::io::Result; + +use ratatui::crossterm::event::{KeyCode, KeyEvent}; + +use crate::app::App; + +const NUMBER_OF_TABS: usize = 4; + +fn tab_next(app: &mut App) { + if app.header_view.tab_index < NUMBER_OF_TABS - 1 { + app.header_view.tab_index = app.header_view.tab_index.saturating_add(1); + } else { + app.header_view.tab_index = 0; + } +} + +fn tab_prev(app: &mut App) { + if app.header_view.tab_index == 0 { + app.header_view.tab_index = NUMBER_OF_TABS - 1; + } else { + app.header_view.tab_index -= 1; + } +} + +fn tab_elf_header_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .elf_state + .elf_header_table_state + .selected_cell() + .is_none() + { + app.header_view + .elf_state + .elf_header_table_state + .select_cell(Some((0, 1))); + } else { + app.header_view + .elf_state + .elf_header_table_state + .select_next(); + } + } + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.elf_state.elf_header_table_state.selected() { + if idx == 0 { + app.header_view + .elf_state + .elf_header_table_state + .select(None); + } else { + app.header_view + .elf_state + .elf_header_table_state + .select_previous(); + } + } + } + KeyCode::Char('G') => { + // goto + } + _ => {} + } + Ok(false) +} + +fn tab_program_headers_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .elf_state + .program_header_table_state + .selected_cell() + .is_none() + { + app.header_view + .elf_state + .program_header_table_state + .select_cell(Some((0, 1))); + } else { + app.header_view + .elf_state + .program_header_table_state + .select_next(); + } + } + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app + .header_view + .elf_state + .program_header_table_state + .selected() + { + if idx == 0 { + app.header_view + .elf_state + .program_header_table_state + .select(None); + } else { + app.header_view + .elf_state + .program_header_table_state + .select_previous(); + } + } + } + KeyCode::Char('G') => { + // goto + } + _ => {} + } + Ok(false) +} + +fn tab_sections_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .elf_state + .sections_table_state + .selected_cell() + .is_none() + { + app.header_view + .elf_state + .sections_table_state + .select_cell(Some((0, 1))); + } else { + app.header_view.elf_state.sections_table_state.select_next(); + } + } + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.elf_state.sections_table_state.selected() { + if idx == 0 { + app.header_view.elf_state.sections_table_state.select(None); + } else { + app.header_view + .elf_state + .sections_table_state + .select_previous(); + } + } + } + KeyCode::Char('G') => { + // goto + } + _ => {} + } + Ok(false) +} + +fn tab_symbols_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .elf_state + .symbols_table_state + .selected_cell() + .is_none() + { + app.header_view + .elf_state + .symbols_table_state + .select_cell(Some((0, 0))); + } else { + app.header_view.elf_state.symbols_table_state.select_next(); + } + } + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.elf_state.symbols_table_state.selected() { + if idx == 0 { + app.header_view.elf_state.symbols_table_state.select(None); + } else { + app.header_view + .elf_state + .symbols_table_state + .select_previous(); + } + } + } + KeyCode::Char('G') => { + // goto + } + _ => {} + } + Ok(false) +} + +pub fn view_header_elf_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Tab | KeyCode::Right | KeyCode::Char('l') => { + tab_next(app); + } + KeyCode::BackTab | KeyCode::Left | KeyCode::Char('h') => { + tab_prev(app); + } + _ => (), + } + + match app.header_view.tab_index { + 0 => tab_elf_header_events(app, key), + 1 => tab_program_headers_events(app, key), + 2 => tab_sections_events(app, key), + 3 => tab_symbols_events(app, key), + _ => Ok(false), + } +} diff --git a/src/header/formats/elf/mod.rs b/src/header/formats/elf/mod.rs new file mode 100644 index 0000000..912b70d --- /dev/null +++ b/src/header/formats/elf/mod.rs @@ -0,0 +1,2 @@ +pub mod draw; +pub mod events; diff --git a/src/header/formats/mod.rs b/src/header/formats/mod.rs new file mode 100644 index 0000000..e4b79ca --- /dev/null +++ b/src/header/formats/mod.rs @@ -0,0 +1,2 @@ +pub mod elf; +pub mod pe; diff --git a/src/header/formats/pe/draw.rs b/src/header/formats/pe/draw.rs new file mode 100644 index 0000000..8c4db98 --- /dev/null +++ b/src/header/formats/pe/draw.rs @@ -0,0 +1,322 @@ +use ratatui::{ + Frame, + layout::{Constraint, Layout, Rect}, + style::Style, + widgets::{Cell, Clear, Row, Table, Tabs}, +}; + +use crate::app::App; + +fn draw_dos_header(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(pe) = &app.header_view.pe { + let mut rows = Vec::new(); + + rows.push(Row::new(vec![ + Cell::new("Signature"), + Cell::new(format!("{:X}", pe.dos_header.signature)), + ])); + rows.push(Row::new(vec![ + Cell::new("BytesOnLastPage"), + Cell::new(format!("{:X}", pe.dos_header.bytes_on_last_page)), + ])); + rows.push(Row::new(vec![ + Cell::new("PagesInFile"), + Cell::new(format!("{:X}", pe.dos_header.pages_in_file)), + ])); + rows.push(Row::new(vec![ + Cell::new("Relocations"), + Cell::new(format!("{:X}", pe.dos_header.relocations)), + ])); + rows.push(Row::new(vec![ + Cell::new("SizeOfHeaderInParagraphs"), + Cell::new(format!("{:X}", pe.dos_header.size_of_header_in_paragraphs)), + ])); + rows.push(Row::new(vec![ + Cell::new("MinimumExtraParagraphsNeeded"), + Cell::new(format!( + "{:X}", + pe.dos_header.minimum_extra_paragraphs_needed + )), + ])); + rows.push(Row::new(vec![ + Cell::new("MaximumExtraParagraphsNeeded"), + Cell::new(format!( + "{:X}", + pe.dos_header.maximum_extra_paragraphs_needed + )), + ])); + rows.push(Row::new(vec![ + Cell::new("InitialRelativeSS"), + Cell::new(format!("{:X}", pe.dos_header.initial_relative_ss)), + ])); + rows.push(Row::new(vec![ + Cell::new("InitialSP"), + Cell::new(format!("{:X}", pe.dos_header.initial_sp)), + ])); + rows.push(Row::new(vec![ + Cell::new("Checksum"), + Cell::new(format!("{:X}", pe.dos_header.checksum)), + ])); + rows.push(Row::new(vec![ + Cell::new("InitialIP"), + Cell::new(format!("{:X}", pe.dos_header.initial_ip)), + ])); + rows.push(Row::new(vec![ + Cell::new("InitialRelativeCS"), + Cell::new(format!("{:X}", pe.dos_header.initial_relative_cs)), + ])); + rows.push(Row::new(vec![ + Cell::new("FileAddressOfRelocationTable"), + Cell::new(format!( + "{:X}", + pe.dos_header.file_address_of_relocation_table + )), + ])); + rows.push(Row::new(vec![ + Cell::new("OverlayNumber"), + Cell::new(format!("{:X}", pe.dos_header.overlay_number)), + ])); + rows.push(Row::new(vec![ + Cell::new("Reserved"), + Cell::new(format!("{:?}", pe.dos_header.reserved)), + ])); + rows.push(Row::new(vec![ + Cell::new("OemId"), + Cell::new(format!("{:X}", pe.dos_header.oem_id)), + ])); + rows.push(Row::new(vec![ + Cell::new("OemInfo"), + Cell::new(format!("{:X}", pe.dos_header.oem_info)), + ])); + rows.push(Row::new(vec![ + Cell::new("Reserved2"), + Cell::new(format!("{:?}", pe.dos_header.reserved2)), + ])); + rows.push(Row::new(vec![ + Cell::new("PEHeaderOffset"), + Cell::new(format!("{:X}", pe.dos_header.pe_pointer)), + ])); + + let widths = [Constraint::Min(16), Constraint::Fill(1)]; + + let header_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_stateful_widget( + header_table, + area, + &mut app.header_view.pe_state.dos_header_table_state, + ); + } +} + +fn draw_pe_header(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(pe) = &app.header_view.pe { + let mut rows = Vec::new(); + + let machine = pe.coff_header.machine; + rows.push(Row::new(vec![ + Cell::new("Machine"), + Cell::new(format!( + "{:X} ({})", + machine, + get_pe_machine_string(machine) + )), + ])); + rows.push(Row::new(vec![ + Cell::new("Characteristics"), + Cell::new(format!("{:X}", pe.coff_header.characteristics)), + ])); + rows.push(Row::new(vec![ + Cell::new("Compilation Timestamp"), + Cell::new(format!("{:X}", pe.coff_header.time_date_stamp)), + ])); + rows.push(Row::new(vec![ + Cell::new("Number of sections"), + Cell::new( + pe.optional_header + .unwrap() + .windows_fields + .number_of_rva_and_sizes + .to_string(), + ), + ])); + + if let Some(opt) = &pe.optional_header { + rows.push(Row::new(vec![ + Cell::new("Entrypoint"), + Cell::new(format!("{:X}", opt.standard_fields.address_of_entry_point)), + ])); + rows.push(Row::new(vec![ + Cell::new("Magic"), + Cell::new(format!("{:X}", opt.standard_fields.magic)), + ])); + rows.push(Row::new(vec![ + Cell::new("Minor linker version"), + Cell::new(opt.standard_fields.minor_linker_version.to_string()), + ])); + rows.push(Row::new(vec![ + Cell::new("Major linker version"), + Cell::new(opt.standard_fields.major_linker_version.to_string()), + ])); + } + + let widths = [Constraint::Min(16), Constraint::Fill(1)]; + + let header_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_widget(header_table, area); + } +} + +fn draw_sections(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(pe) = &app.header_view.pe { + let mut rows = Vec::new(); + + for sec in &pe.sections { + let mut cells = Vec::new(); + cells.push(Cell::new(sec.name().unwrap_or(""))); + cells.push(Cell::new(format!("{:08X}", sec.virtual_address))); + cells.push(Cell::new(format!("{:08X}", sec.virtual_size))); + cells.push(Cell::new(format!("{:08X}", sec.pointer_to_raw_data))); + cells.push(Cell::new(format!("{:08X}", sec.size_of_raw_data))); + cells.push(Cell::new(format!("{:08X}", sec.characteristics))); + rows.push(Row::new(cells)); + } + + let widths = [Constraint::Ratio(1, 6); 6]; + + let section_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .header( + Row::new(vec![ + "Name", + "VirtualAddress", + "VirtualSize", + "PtrToRawData", + "SizeOfRawData", + "Characteristics", + ]) + .style(Style::new().bold()), + ) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_stateful_widget( + section_table, + area, + &mut app.header_view.pe_state.sections_table_state, + ); + } +} + +fn draw_imports(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(pe) = &app.header_view.pe { + let mut rows = Vec::new(); + + for imp in &pe.imports { + let mut cells = Vec::new(); + cells.push(Cell::new(imp.dll.as_str())); + cells.push(Cell::new(imp.name.as_str())); + cells.push(Cell::new(format!("{:08X}", imp.offset))); + cells.push(Cell::new(format!("{:08X}", imp.ordinal))); + cells.push(Cell::new(format!("{:08X}", imp.rva))); + rows.push(Row::new(cells)); + } + + let widths = [Constraint::Ratio(1, 5); 5]; + + let imports_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .header( + Row::new(vec!["DLL", "Name", "Offset", "Ordinal", "RVA"]) + .style(Style::new().bold()), + ) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_stateful_widget( + imports_table, + area, + &mut app.header_view.pe_state.imports_table_sate, + ); + } +} + +fn draw_overlay(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(pe) = &app.header_view.pe { + let overlay_start = pe + .sections + .iter() + .map(|sec| sec.pointer_to_raw_data.saturating_add(sec.size_of_raw_data) as usize) + .max() + .unwrap_or(pe.dos_header.pe_pointer as usize); + + let overlay_size = app.file_info.size.saturating_sub(overlay_start); + + let mut rows = Vec::new(); + + rows.push(Row::new(vec![ + Cell::new("OverlayStart"), + Cell::new(format!("{:08X}", overlay_start)), + ])); + rows.push(Row::new(vec![ + Cell::new("OverlaySize"), + Cell::new(format!("{:08X}", overlay_size)), + ])); + + let widths = [Constraint::Min(16), Constraint::Fill(1)]; + + let overlay_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_widget(overlay_table, area); + } +} + +pub fn pe_draw(app: &mut App, frame: &mut Frame, area: Rect) { + let tabs = Tabs::new(vec![ + "DOS Header", + "PE Header", + "Sections", + "Imports", + "Overlay", + ]) + .style(app.config.theme.main) + .highlight_style(app.config.theme.highlight) + .divider("|") + .padding(" ", " ") + .select(app.header_view.tab_index); + + let layout = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]); + let [top, main] = area.layout(&layout); + + frame.render_widget(Clear, area); + frame.render_widget(tabs, top); + + match app.header_view.tab_index { + 0 => draw_dos_header(app, frame, main), + 1 => draw_pe_header(app, frame, main), + 2 => draw_sections(app, frame, main), + 3 => draw_imports(app, frame, main), + 4 => draw_overlay(app, frame, main), + _ => {} + } +} + +fn get_pe_machine_string(mach: u16) -> &'static str { + match mach { + goblin::pe::header::COFF_MACHINE_ARM => "ARM", + goblin::pe::header::COFF_MACHINE_ARM64 => "AARCH64", + goblin::pe::header::COFF_MACHINE_X86 => "x86", + goblin::pe::header::COFF_MACHINE_X86_64 => "x86-64", + _ => "", + } +} diff --git a/src/header/formats/pe/events.rs b/src/header/formats/pe/events.rs new file mode 100644 index 0000000..d53b347 --- /dev/null +++ b/src/header/formats/pe/events.rs @@ -0,0 +1,198 @@ +use std::io::Result; + +use ratatui::crossterm::event::{KeyCode, KeyEvent}; + +use crate::app::App; + +const NUMBER_OF_TABS: usize = 5; + +fn tab_next(app: &mut App) { + if app.header_view.tab_index < NUMBER_OF_TABS - 1 { + app.header_view.tab_index = app.header_view.tab_index.saturating_add(1); + } else { + app.header_view.tab_index = 0; + } +} + +fn tab_prev(app: &mut App) { + if app.header_view.tab_index == 0 { + app.header_view.tab_index = NUMBER_OF_TABS - 1; + } else { + app.header_view.tab_index -= 1; + } +} + +fn tab_dos_header_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .pe_state + .dos_header_table_state + .selected_cell() + .is_none() + { + app.header_view + .pe_state + .dos_header_table_state + .select_cell(Some((0, 1))); + } else { + app.header_view + .pe_state + .dos_header_table_state + .select_next(); + } + } + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.pe_state.dos_header_table_state.selected() { + if idx == 0 { + app.header_view.pe_state.dos_header_table_state.select(None); + } else { + app.header_view + .pe_state + .dos_header_table_state + .select_previous(); + } + } + } + KeyCode::Char('G') => { + // goto + } + _ => {} + } + Ok(false) +} + +fn tab_pe_header_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .pe_state + .pe_header_table_state + .selected_cell() + .is_none() + { + app.header_view + .pe_state + .pe_header_table_state + .select_cell(Some((0, 1))); + } else { + app.header_view.pe_state.pe_header_table_state.select_next(); + } + } + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.pe_state.pe_header_table_state.selected() { + if idx == 0 { + app.header_view.pe_state.pe_header_table_state.select(None); + } else { + app.header_view + .pe_state + .pe_header_table_state + .select_previous(); + } + } + } + KeyCode::Char('G') => { + // goto + } + _ => {} + } + Ok(false) +} + +fn tab_sections_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .pe_state + .sections_table_state + .selected_cell() + .is_none() + { + app.header_view + .pe_state + .sections_table_state + .select_cell(Some((0, 1))); + } else { + app.header_view.pe_state.sections_table_state.select_next(); + } + } + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.pe_state.sections_table_state.selected() { + if idx == 0 { + app.header_view.pe_state.sections_table_state.select(None); + } else { + app.header_view + .pe_state + .sections_table_state + .select_previous(); + } + } + } + KeyCode::Char('G') => { + // goto + } + _ => {} + } + Ok(false) +} + +fn tab_imports_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .pe_state + .imports_table_sate + .selected_cell() + .is_none() + { + app.header_view + .pe_state + .imports_table_sate + .select_cell(Some((0, 1))); + } else { + app.header_view.pe_state.imports_table_sate.select_next(); + } + } + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.pe_state.imports_table_sate.selected() { + if idx == 0 { + app.header_view.pe_state.imports_table_sate.select(None); + } else { + app.header_view + .pe_state + .imports_table_sate + .select_previous(); + } + } + } + KeyCode::Char('G') => { + // goto + } + _ => {} + } + Ok(false) +} + +pub fn view_header_pe_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Tab | KeyCode::Right | KeyCode::Char('l') => { + tab_next(app); + } + KeyCode::BackTab | KeyCode::Left | KeyCode::Char('h') => { + tab_prev(app); + } + _ => (), + } + + match app.header_view.tab_index { + 0 => tab_dos_header_events(app, key), + 1 => tab_pe_header_events(app, key), + 2 => tab_sections_events(app, key), + 3 => tab_imports_events(app, key), + _ => Ok(false), + } +} diff --git a/src/header/formats/pe/mod.rs b/src/header/formats/pe/mod.rs new file mode 100644 index 0000000..912b70d --- /dev/null +++ b/src/header/formats/pe/mod.rs @@ -0,0 +1,2 @@ +pub mod draw; +pub mod events; diff --git a/src/header/header_view.rs b/src/header/header_view.rs new file mode 100644 index 0000000..e4aeb2e --- /dev/null +++ b/src/header/header_view.rs @@ -0,0 +1,56 @@ +use std::collections::HashMap; + +use ratatui::widgets::TableState; + +#[derive(Debug, Clone)] +pub struct PEImport { + pub dll: String, + pub name: String, + pub offset: usize, + pub ordinal: u16, + pub rva: usize, + pub _size: usize, +} + +#[derive(Debug, Clone)] +pub struct Pe { + pub dos_header: goblin::pe::header::DosHeader, + pub coff_header: goblin::pe::header::CoffHeader, + pub optional_header: Option, + pub sections: Vec, + pub imports: Vec, +} + +#[derive(Debug, Clone)] +pub struct Elf { + pub header: goblin::elf::Header, + pub phdrs: goblin::elf::ProgramHeaders, + pub sections: goblin::elf::SectionHeaders, + pub symtab: Vec, + pub strtab: HashMap, +} + +#[derive(Debug, Default)] +pub struct PeState { + pub dos_header_table_state: TableState, + pub pe_header_table_state: TableState, + pub sections_table_state: TableState, + pub imports_table_sate: TableState, +} + +#[derive(Debug, Default)] +pub struct ElfState { + pub elf_header_table_state: TableState, + pub program_header_table_state: TableState, + pub sections_table_state: TableState, + pub symbols_table_state: TableState, +} + +#[derive(Default, Debug)] +pub struct HeaderView { + pub pe: Option, + pub elf: Option, + pub elf_state: ElfState, + pub pe_state: PeState, + pub tab_index: usize, +} diff --git a/src/header/mod.rs b/src/header/mod.rs new file mode 100644 index 0000000..4696537 --- /dev/null +++ b/src/header/mod.rs @@ -0,0 +1,4 @@ +pub mod draw; +pub mod events; +pub mod formats; +pub mod header_view; diff --git a/src/main.rs b/src/main.rs index 7bdf04d..160d322 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod draw; mod editor; mod events; mod global; +mod header; mod hex; mod initfile; mod input_history;