summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRubens Brandao <git@rubens.io>2024-08-06 11:01:23 -0300
committerMason Reed <mason@vector35.com>2024-08-27 17:19:25 -0400
commit7e1027d904e66b0cadb277883cdfa7b13dcb209f (patch)
tree4451fe8134e7518d43b1e04c1446f56d3bb2091e
parentfeca74615ab3e10e7e256630946d830d07414d08 (diff)
Implement IHEX, SREC and TI-TXT views
All bundled under the arch_bintxt/arch_bintext view plugin
-rw-r--r--view/bintxt/CMakeLists.txt88
-rw-r--r--view/bintxt/Cargo.lock398
-rw-r--r--view/bintxt/Cargo.toml19
-rw-r--r--view/bintxt/src/ihex.rs237
-rw-r--r--view/bintxt/src/lib.rs131
-rw-r--r--view/bintxt/src/srec.rs329
-rw-r--r--view/bintxt/src/titxt.rs267
7 files changed, 1469 insertions, 0 deletions
diff --git a/view/bintxt/CMakeLists.txt b/view/bintxt/CMakeLists.txt
new file mode 100644
index 00000000..bc7edda3
--- /dev/null
+++ b/view/bintxt/CMakeLists.txt
@@ -0,0 +1,88 @@
+cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
+
+project(view_bintext)
+
+file(GLOB PLUGIN_SOURCES CONFIGURE_DEPENDS
+ ${PROJECT_SOURCE_DIR}/Cargo.toml
+ ${PROJECT_SOURCE_DIR}/src/*.rs)
+
+file(GLOB_RECURSE API_SOURCES CONFIGURE_DEPENDS
+ ${PROJECT_SOURCE_DIR}/../../binaryninjacore.h
+ ${PROJECT_SOURCE_DIR}/../../rust/Cargo.toml
+ ${PROJECT_SOURCE_DIR}/../../rust/src/*.rs)
+
+if(CMAKE_BUILD_TYPE MATCHES Debug)
+ set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug)
+ set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target)
+else()
+ set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release)
+ set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --release)
+ set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}view_bintxt.pdb)
+endif()
+
+set(OUTPUT_FILE ${CMAKE_STATIC_LIBRARY_PREFIX}view_bintxt${CMAKE_SHARED_LIBRARY_SUFFIX})
+set(PLUGIN_PATH ${TARGET_DIR}/${OUTPUT_FILE})
+
+add_custom_target(view_bintext ALL DEPENDS ${PLUGIN_PATH})
+add_dependencies(view_bintext binaryninjaapi)
+
+find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin)
+if(CARGO_API_VERSION)
+ set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_API_VERSION} cargo build)
+else()
+ set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_STABLE_VERSION} cargo build)
+endif()
+
+if(APPLE)
+ if(UNIVERSAL)
+ if(CMAKE_BUILD_TYPE MATCHES Debug)
+ set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE})
+ set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE})
+ else()
+ set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE})
+ set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE})
+ endif()
+
+ add_custom_command(
+ OUTPUT ${PLUGIN_PATH}
+ COMMAND ${CMAKE_COMMAND} -E env
+ MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR}
+ ${RUSTUP_COMMAND} --target=aarch64-apple-darwin ${CARGO_OPTS}
+ COMMAND ${CMAKE_COMMAND} -E env
+ MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR}
+ ${RUSTUP_COMMAND} --target=x86_64-apple-darwin ${CARGO_OPTS}
+ COMMAND mkdir -p ${TARGET_DIR}
+ COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${PLUGIN_PATH}
+ COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES})
+ else()
+ if(CMAKE_BUILD_TYPE MATCHES Debug)
+ set(LIB_PATH ${PROJECT_BINARY_DIR}/target/debug/${OUTPUT_FILE})
+ else()
+ set(LIB_PATH ${PROJECT_BINARY_DIR}/target/release/${OUTPUT_FILE})
+ endif()
+
+ add_custom_command(
+ OUTPUT ${PLUGIN_PATH}
+ COMMAND ${CMAKE_COMMAND} -E env MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS}
+ COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES})
+ endif()
+elseif(WIN32)
+ add_custom_command(
+ OUTPUT ${PLUGIN_PATH}
+ COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS}
+ COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR}
+ COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${BN_CORE_PLUGIN_DIR}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES})
+else()
+ add_custom_command(
+ OUTPUT ${PLUGIN_PATH}
+ COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS}
+ COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES})
+endif()
diff --git a/view/bintxt/Cargo.lock b/view/bintxt/Cargo.lock
new file mode 100644
index 00000000..8791e59d
--- /dev/null
+++ b/view/bintxt/Cargo.lock
@@ -0,0 +1,398 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "binaryninja"
+version = "0.1.0"
+dependencies = [
+ "binaryninjacore-sys",
+ "lazy_static",
+ "libc",
+ "log",
+]
+
+[[package]]
+name = "binaryninjacore-sys"
+version = "0.1.0"
+dependencies = [
+ "bindgen",
+]
+
+[[package]]
+name = "bindgen"
+version = "0.69.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clang-sys"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "either"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "ihex"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "365a784774bb381e8c19edb91190a90d7f2625e057b55de2bc0f6b57bc779ff2"
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.155"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "libloading"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
+dependencies = [
+ "cfg-if",
+ "windows-targets",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+
+[[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "prettyplease"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "srec"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17c3a0538ec242e3cd333cdcdc8b720faa2fa0a9d7f444cf1ff63e7d3303adfb"
+
+[[package]]
+name = "syn"
+version = "2.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "view_bintxt"
+version = "0.1.0"
+dependencies = [
+ "binaryninja",
+ "ihex",
+ "log",
+ "srec",
+]
+
+[[package]]
+name = "which"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/view/bintxt/Cargo.toml b/view/bintxt/Cargo.toml
new file mode 100644
index 00000000..0ec7a9c1
--- /dev/null
+++ b/view/bintxt/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "view_bintxt"
+version = "0.1.0"
+authors = ["Rubens Brandao <git@rubens.io>"]
+edition = "2021"
+
+[dependencies]
+binaryninja = { path = "../../rust" }
+ihex = "3.0.0"
+log = "*"
+srec = "0.2.0"
+
+[lib]
+crate-type = ["cdylib"]
+
+[profile.release]
+panic = "abort"
+lto = true
+debug = 1
diff --git a/view/bintxt/src/ihex.rs b/view/bintxt/src/ihex.rs
new file mode 100644
index 00000000..5b210ff4
--- /dev/null
+++ b/view/bintxt/src/ihex.rs
@@ -0,0 +1,237 @@
+use binaryninja::binaryview::*;
+use binaryninja::custombinaryview::*;
+use binaryninja::rc::Ref;
+use binaryninja::segment::SegmentBuilder;
+use ihex::Record;
+
+use crate::segment_after_address;
+use crate::segment_from_address;
+use crate::sort_and_merge_segments;
+use crate::MergedSegment;
+use crate::UnmergedSegment;
+
+fn parse_ihex(string: &str) -> Result<(Vec<u8>, IHexViewData)> {
+ let mut reader = ihex::Reader::new(&string);
+ let mut unmerged_data: Vec<UnmergedSegment> = vec![];
+ let mut start = None;
+ let mut offset = None;
+ while let Some(record) = reader.next() {
+ let record = record.map_err(|e| {
+ log::error!("Unable to parse record: {e}");
+ ()
+ })?;
+ match record {
+ ihex::Record::EndOfFile => break,
+ ihex::Record::Data {
+ offset: data_offset,
+ value,
+ } => {
+ let address = match offset {
+ Some(IHexOffset::Segment(offset)) => {
+ u64::from(offset) * 16 + u64::from(data_offset)
+ }
+ Some(IHexOffset::Linear(offset)) => {
+ u64::from(offset) << 16 | u64::from(data_offset)
+ }
+ None => data_offset.into(),
+ };
+ // check if the block is just extending the previous one
+ match unmerged_data.last_mut() {
+ // check if the block is just extending the previous one, merge both
+ Some(last) if last.end() == address => last.data.extend(value),
+ // otherwise just add a new block
+ _ => unmerged_data.push(UnmergedSegment {
+ address,
+ data: value,
+ }),
+ }
+ }
+ ihex::Record::StartSegmentAddress { cs, ip } => {
+ if start.is_some() {
+ log::error!("Multiple Start Address defined");
+ return Err(());
+ }
+ start = Some(IHexStart::Segment { _cs: cs, ip });
+ }
+ ihex::Record::StartLinearAddress(offset) => {
+ if start.is_some() {
+ log::error!("Multiple Start Address defined");
+ return Err(());
+ }
+ start = Some(IHexStart::Linear(offset));
+ }
+ ihex::Record::ExtendedSegmentAddress(segment_offset) => {
+ offset = Some(IHexOffset::Segment(segment_offset))
+ }
+ ihex::Record::ExtendedLinearAddress(linear_offset) => {
+ offset = Some(IHexOffset::Linear(linear_offset))
+ }
+ }
+ }
+ // can't have other record after the EoF record
+ if reader.next().is_some() {
+ log::error!("Found record after EoF record");
+ return Err(());
+ }
+
+ let merged = sort_and_merge_segments(unmerged_data)?;
+ Ok((
+ merged.data,
+ IHexViewData {
+ segments: merged.segments,
+ start,
+ },
+ ))
+}
+
+pub struct IHexViewConstructor {
+ pub core: BinaryViewType,
+}
+
+impl AsRef<BinaryViewType> for IHexViewConstructor {
+ fn as_ref(&self) -> &BinaryViewType {
+ &self.core
+ }
+}
+
+impl CustomBinaryViewType for IHexViewConstructor {
+ fn create_custom_view<'builder>(
+ &self,
+ parent: &BinaryView,
+ builder: CustomViewBuilder<'builder, Self>,
+ ) -> Result<CustomView<'builder>> {
+ let bytes = parent.len();
+ let mut buf = vec![0; bytes];
+ let bytes_read = parent.read(&mut buf, 0);
+ if bytes_read != bytes {
+ log::error!("IHex file is too small");
+ return Err(());
+ }
+ let string = String::from_utf8(buf).map_err(|_| {
+ log::error!("File contains invalid UTF8 characters");
+ })?;
+
+ let (data, segments) = parse_ihex(&string)?;
+
+ let parent_bin = BinaryView::from_data(&parent.file(), &data)?;
+ builder.create::<IHexView>(&parent_bin, segments)
+ }
+}
+
+impl BinaryViewTypeBase for IHexViewConstructor {
+ fn is_valid_for(&self, data: &BinaryView) -> bool {
+ // TODO check filename and identify the type if necessary
+ //let filename = data.file().filename();
+ //let filename = std::path::Path::new(filename.as_str());
+ //let filetype = filename
+ // .extension()
+ // .map(|ext| match ext.to_string_lossy().as_ref() {
+ // // General-purpose:
+ // "hex" | "mcs" | "int" | "ihex" | "ihe" | "ihx" => true,
+ // // Platform-specific:
+ // "h80" | "h86" | "a43" | "a90" => Some(true),
+ // // Binary or Intel hex:
+ // "obj" | "obl" | "obh" | "rom" | "eep" => true,
+ // // TODO: Split, banked, or paged:
+ // //.hxl–.hxh,[8] .h00–.h15, .p00–.pff[9]
+ // _ => false,
+ // });
+
+ // The biggest possible record is data record with 255 bytes: 268 bytes
+ let mut first_bytes = [0u8; 384];
+ let read_bytes = data.read(&mut first_bytes, 0);
+ let data = String::from_utf8_lossy(&first_bytes[0..read_bytes]);
+ let Some(line) = data.lines().next() else {
+ return false;
+ };
+ Record::from_record_string(line).is_ok()
+ }
+
+ fn is_deprecated(&self) -> bool {
+ false
+ }
+}
+
+pub struct IHexView {
+ core: Ref<BinaryView>,
+ segments: Vec<MergedSegment>,
+ start: Option<IHexStart>,
+}
+
+pub struct IHexViewData {
+ segments: Vec<MergedSegment>,
+ start: Option<IHexStart>,
+}
+
+#[derive(Clone, Copy, Debug)]
+enum IHexStart {
+ Segment { _cs: u16, ip: u16 },
+ Linear(u32),
+}
+
+#[derive(Clone, Copy, Debug)]
+enum IHexOffset {
+ Segment(u16),
+ Linear(u16),
+}
+
+impl AsRef<BinaryView> for IHexView {
+ fn as_ref(&self) -> &BinaryView {
+ &self.core
+ }
+}
+
+unsafe impl CustomBinaryView for IHexView {
+ type Args = IHexViewData;
+
+ fn new(handle: &BinaryView, _args: &Self::Args) -> Result<Self> {
+ Ok(Self {
+ core: handle.to_owned(),
+ // NOTE dummy values, final values are added on init
+ start: None,
+ segments: vec![],
+ })
+ }
+
+ fn init(&mut self, IHexViewData { start, segments }: Self::Args) -> Result<()> {
+ self.start = start;
+ self.segments = segments;
+
+ for segment in self.segments.iter() {
+ self.add_segment(
+ SegmentBuilder::from(*segment)
+ .executable(true)
+ .readable(true)
+ .contains_data(true)
+ .contains_code(true),
+ );
+ }
+ Ok(())
+ }
+}
+
+impl BinaryViewBase for IHexView {
+ fn entry_point(&self) -> u64 {
+ match self.start {
+ Some(IHexStart::Linear(addr)) => addr.into(),
+ Some(IHexStart::Segment { _cs, ip }) => ip.into(),
+ None => self.start(),
+ }
+ }
+
+ fn default_endianness(&self) -> binaryninja::Endianness {
+ binaryninja::Endianness::LittleEndian
+ }
+
+ fn address_size(&self) -> usize {
+ 4
+ }
+
+ fn offset_valid(&self, offset: u64) -> bool {
+ segment_from_address(&self.segments, offset).is_some()
+ }
+
+ fn next_valid_offset_after(&self, offset: u64) -> u64 {
+ segment_after_address(&self.segments, offset)
+ }
+}
diff --git a/view/bintxt/src/lib.rs b/view/bintxt/src/lib.rs
new file mode 100644
index 00000000..96cd1835
--- /dev/null
+++ b/view/bintxt/src/lib.rs
@@ -0,0 +1,131 @@
+mod ihex;
+mod srec;
+mod titxt;
+
+use binaryninja::segment::SegmentBuilder;
+use ihex::*;
+use srec::*;
+use titxt::*;
+
+use std::ops::Range;
+
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "C" fn CorePluginInit() -> bool {
+ binaryninja::logger::init(log::LevelFilter::Error).expect("Unable to initialize logger");
+
+ binaryninja::custombinaryview::register_view_type(c"ti-txt", c"TI-TXT", |core| {
+ TiTxtViewConstructor { core }
+ });
+
+ binaryninja::custombinaryview::register_view_type(c"srec", c"Motorola S-record", |core| {
+ SRecViewConstructor { core }
+ });
+
+ binaryninja::custombinaryview::register_view_type(c"ihex", c"Intel HEX", |core| {
+ IHexViewConstructor { core }
+ });
+
+ true
+}
+
+struct UnmergedSegment {
+ address: u64,
+ data: Vec<u8>,
+}
+
+impl UnmergedSegment {
+ fn end(&self) -> u64 {
+ self.address + u64::try_from(self.data.len()).unwrap()
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct MergedSegment {
+ address: u64,
+ len: u64,
+ data_offset: u64,
+}
+
+impl MergedSegment {
+ fn address_range(&self) -> Range<u64> {
+ self.address..self.end()
+ }
+
+ fn data_range(&self) -> Range<u64> {
+ self.data_offset..self.data_offset + self.len
+ }
+
+ fn end(&self) -> u64 {
+ self.address + self.len
+ }
+}
+
+impl From<MergedSegment> for SegmentBuilder {
+ fn from(segment: MergedSegment) -> Self {
+ SegmentBuilder::new(segment.address_range()).parent_backing(segment.data_range())
+ }
+}
+
+struct MergedSegments {
+ data: Vec<u8>,
+ segments: Vec<MergedSegment>,
+}
+
+fn sort_and_merge_segments(mut unmerged_data: Vec<UnmergedSegment>) -> Result<MergedSegments, ()> {
+ // sort segments by address and len, so we can detect overlaps
+ unmerged_data.sort_unstable_by_key(|segment| (segment.address, segment.data.len()));
+
+ let mut data: Vec<u8> =
+ Vec::with_capacity(unmerged_data.iter().map(|sector| sector.data.len()).sum());
+ let mut segments: Vec<MergedSegment> = Vec::with_capacity(unmerged_data.len());
+ for segment in unmerged_data.into_iter() {
+ // add the data to the data poll
+ let data_offset = u64::try_from(data.len()).unwrap();
+ let segment_len = u64::try_from(segment.data.len()).unwrap();
+ data.extend(segment.data);
+ match segments.last_mut() {
+ // if have a last segment and the current chunk just extend it, merge both
+ Some(last) if segment.address == last.end() => last.len += segment_len,
+ // the same sector overlap, then the data was defined multiple times.
+ Some(last) if segment.address < last.end() => {
+ log::error!("Chunks of data overlap");
+ return Err(());
+ }
+ // otherwise just create a new segment
+ _ => segments.push(MergedSegment {
+ address: segment.address,
+ len: segment_len,
+ data_offset,
+ }),
+ }
+ }
+
+ Ok(MergedSegments { data, segments })
+}
+
+fn segment_from_address(segments: &[MergedSegment], offset: u64) -> Option<&MergedSegment> {
+ segments
+ .binary_search_by(|segment| {
+ let range = segment.address_range();
+ if range.contains(&offset) {
+ return core::cmp::Ordering::Equal;
+ }
+ offset.cmp(&range.start)
+ })
+ .ok()
+ .map(|idx| &segments[idx])
+}
+
+fn segment_after_address(segments: &[MergedSegment], offset: u64) -> u64 {
+ let sector = segments.iter().find_map(|sector| {
+ if sector.address >= offset {
+ Some(sector.address)
+ } else if sector.end() < offset {
+ Some(offset)
+ } else {
+ None
+ }
+ });
+ sector.unwrap_or(offset).into()
+}
diff --git a/view/bintxt/src/srec.rs b/view/bintxt/src/srec.rs
new file mode 100644
index 00000000..d11e5fd5
--- /dev/null
+++ b/view/bintxt/src/srec.rs
@@ -0,0 +1,329 @@
+use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt};
+use binaryninja::custombinaryview::{
+ BinaryViewType, BinaryViewTypeBase, CustomBinaryView, CustomBinaryViewType, CustomView,
+ CustomViewBuilder,
+};
+use binaryninja::rc::Ref;
+use binaryninja::segment::SegmentBuilder;
+use srec::Record;
+
+use crate::{
+ segment_after_address, segment_from_address, sort_and_merge_segments, MergedSegment,
+ UnmergedSegment,
+};
+
+struct SRecParser<I> {
+ reader: I,
+ segments: Vec<UnmergedSegment>,
+ sector_counter: u32,
+ start: u32,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum RecordLen {
+ R16,
+ R24,
+ R32,
+}
+
+impl<I: Iterator<Item = Result<Record, srec::ReaderError>>> SRecParser<I> {
+ fn parse(&mut self) -> Result<(), ()> {
+ // first sector need to be the header
+ let header = self.reader.next();
+ let header = header
+ .ok_or_else(|| {
+ log::error!("Missing header record");
+ })?
+ .map_err(|e| {
+ log::error!("Unable to parse header record: {e}");
+ })?;
+
+ let Record::S0(_header_info) = header else {
+ log::error!("Invalid first record");
+ return Err(());
+ };
+
+ // parse the first data record
+ let second_record = self
+ .reader
+ .next()
+ .ok_or_else(|| {
+ log::error!("Missing first data record");
+ })?
+ .map_err(|e| {
+ log::error!("Unable to parse first data record: {e}");
+ })?;
+ let (addr_len, first_data_addr, first_data) = match second_record {
+ Record::S0(_)
+ | Record::S5(_)
+ | Record::S6(_)
+ | Record::S7(_)
+ | Record::S8(_)
+ | Record::S9(_) => {
+ log::error!("Invalid second record type");
+ return Err(());
+ }
+ Record::S1(s) => (RecordLen::R16, u32::from(s.address).into(), s.data),
+ Record::S2(s) => (RecordLen::R24, u32::from(s.address).into(), s.data),
+ Record::S3(s) => (RecordLen::R32, u32::from(s.address).into(), s.data),
+ };
+ self.sector_counter = 1;
+ self.segments.push(UnmergedSegment {
+ address: first_data_addr,
+ data: first_data,
+ });
+
+ // parse zero or more data after the first until hit a Count (S5, S6)
+ // or Start Address (S7, S8, S9) Record
+ let next = loop {
+ let record = self
+ .reader
+ .next()
+ .ok_or_else(|| {
+ log::error!("Final record can't be data");
+ })?
+ .map_err(|e| {
+ log::error!("Unable to parse record: {e}");
+ })?;
+ match record {
+ Record::S0(_) => {
+ log::error!("Multiple header records");
+ return Err(());
+ }
+ Record::S1(s) if addr_len == RecordLen::R16 => {
+ self.add_record_data(u32::from(s.address).into(), s.data)
+ }
+ Record::S2(s) if addr_len == RecordLen::R24 => {
+ self.add_record_data(u32::from(s.address).into(), s.data)
+ }
+ Record::S3(s) if addr_len == RecordLen::R32 => {
+ self.add_record_data(u32::from(s.address).into(), s.data)
+ }
+ Record::S1(_) | Record::S2(_) | Record::S3(_) => {
+ log::error!("Data record with invalid len");
+ return Err(());
+ }
+ other @ (Record::S5(_)
+ | Record::S6(_)
+ | Record::S7(_)
+ | Record::S8(_)
+ | Record::S9(_)) => break other,
+ }
+ };
+
+ // S5/S6 is an opional last-but-one record
+ let last = match next {
+ Record::S0(_) | Record::S1(_) | Record::S2(_) | Record::S3(_) => {
+ unreachable!()
+ }
+ Record::S5(s) => {
+ self.check_count_record(s.into())?;
+ self.reader.next()
+ }
+ Record::S6(s) => {
+ self.check_count_record(s.into())?;
+ self.reader.next()
+ }
+ s @ (Record::S7(_) | Record::S8(_) | Record::S9(_)) => Some(Ok(s)),
+ };
+
+ // the last record need to be S7 | S8 | S9
+ let last = last
+ .ok_or_else(|| {
+ log::error!("Missing last record");
+ })?
+ .map_err(|e| {
+ log::error!("Unable to parse last record: {e}");
+ })?;
+ match last {
+ Record::S7(s) => self.start = s.into(),
+ Record::S8(s) => self.start = s.into(),
+ Record::S9(s) => self.start = s.into(),
+ _ => {
+ log::error!("Invalid last record type");
+ return Err(());
+ }
+ }
+
+ // can't have any record after S7 | S8 | S9
+ if let Some(_s) = self.reader.next() {
+ log::error!("Records found after the last");
+ return Err(());
+ }
+
+ Ok(())
+ }
+
+ fn add_record_data(&mut self, address: u64, data: Vec<u8>) {
+ self.sector_counter += 1;
+ match self.segments.last_mut() {
+ // check if the block is just extending the previous one, merge both
+ Some(last) if last.end() == address => {
+ last.data.extend(data);
+ }
+ // otherwise just add a new block
+ _ => {
+ self.segments.push(UnmergedSegment { address, data });
+ }
+ }
+ }
+
+ fn check_count_record(&self, value: u32) -> Result<(), ()> {
+ if value != self.sector_counter {
+ log::error!("Invalid number of records");
+ Err(())
+ } else {
+ Ok(())
+ }
+ }
+}
+
+fn parse_srec(data: &str) -> Result<(Vec<u8>, SRecViewData), ()> {
+ let mut parser = SRecParser {
+ reader: srec::reader::read_records(&data),
+ segments: vec![],
+ sector_counter: 0,
+ start: 0,
+ };
+ parser.parse()?;
+
+ let segments = sort_and_merge_segments(parser.segments)?;
+ Ok((
+ segments.data,
+ SRecViewData {
+ segments: segments.segments,
+ start: parser.start,
+ },
+ ))
+}
+
+pub struct SRecViewConstructor {
+ pub core: BinaryViewType,
+}
+
+impl AsRef<BinaryViewType> for SRecViewConstructor {
+ fn as_ref(&self) -> &BinaryViewType {
+ &self.core
+ }
+}
+impl CustomBinaryViewType for SRecViewConstructor {
+ fn create_custom_view<'builder>(
+ &self,
+ parent: &BinaryView,
+ builder: CustomViewBuilder<'builder, Self>,
+ ) -> Result<CustomView<'builder>, ()> {
+ let bytes = parent.len();
+ let mut buf = vec![0; bytes];
+ let bytes_read = parent.read(&mut buf, 0);
+ if bytes_read != bytes {
+ log::error!("IHex file is too small");
+ return Err(());
+ }
+ let string = String::from_utf8(buf).map_err(|_| {
+ log::error!("File contains invalid UTF8 characters");
+ })?;
+ let (data, regs) = parse_srec(&string)?;
+
+ let parent_bin = BinaryView::from_data(&parent.file(), &data)?;
+ builder.create::<SRecView>(&parent_bin, regs)
+ }
+}
+
+impl BinaryViewTypeBase for SRecViewConstructor {
+ fn is_valid_for(&self, data: &BinaryView) -> bool {
+ // TODO check filename and identify the type if necessary
+ //let filename = data.file().filename();
+ //let filename = std::path::Path::new(filename.as_str());
+ //let filetype = filename
+ // .extension()
+ // .map(|ext| match ext.to_string_lossy().as_ref() {
+ // "s19" | "s28" | "s37" | "s" | "s1" | "s2" | "s3" | "sx" | "srec" | "exo"
+ // | "mot" | "mxt" => true,
+ // _ => false,
+ // });
+
+ // The biggest possible record line is 269
+ const READ_LEN: usize = 384;
+ let mut first_bytes = [0u8; READ_LEN];
+ let read_bytes = data.read(&mut first_bytes, 0);
+ let line = String::from_utf8_lossy(&first_bytes[0..read_bytes]);
+ if !line.is_ascii() {
+ return false;
+ }
+ let first_record: Result<Record, _> = line.parse();
+ // first record need to be and S0
+ matches!(first_record, Ok(Record::S0(_)))
+ }
+
+ fn is_deprecated(&self) -> bool {
+ false
+ }
+}
+
+pub struct SRecView {
+ core: Ref<BinaryView>,
+ segments: Vec<MergedSegment>,
+ start: u32,
+}
+
+pub struct SRecViewData {
+ segments: Vec<MergedSegment>,
+ start: u32,
+}
+
+impl AsRef<BinaryView> for SRecView {
+ fn as_ref(&self) -> &BinaryView {
+ &self.core
+ }
+}
+
+unsafe impl CustomBinaryView for SRecView {
+ type Args = SRecViewData;
+
+ fn new(handle: &BinaryView, _args: &Self::Args) -> Result<Self, ()> {
+ Ok(Self {
+ core: handle.to_owned(),
+ // NOTE dummy values, final values are added on init
+ start: 0,
+ segments: vec![],
+ })
+ }
+
+ fn init(&mut self, SRecViewData { start, segments }: Self::Args) -> Result<(), ()> {
+ self.start = start;
+ self.segments = segments;
+
+ for segment in self.segments.iter() {
+ self.add_segment(
+ SegmentBuilder::from(*segment)
+ .executable(true)
+ .readable(true)
+ .contains_data(true)
+ .contains_code(true),
+ );
+ }
+ Ok(())
+ }
+}
+
+impl BinaryViewBase for SRecView {
+ fn entry_point(&self) -> u64 {
+ self.start.into()
+ }
+
+ fn default_endianness(&self) -> binaryninja::Endianness {
+ binaryninja::Endianness::LittleEndian
+ }
+
+ fn address_size(&self) -> usize {
+ 4
+ }
+
+ fn offset_valid(&self, offset: u64) -> bool {
+ segment_from_address(&self.segments, offset).is_some()
+ }
+
+ fn next_valid_offset_after(&self, offset: u64) -> u64 {
+ segment_after_address(&self.segments, offset)
+ }
+}
diff --git a/view/bintxt/src/titxt.rs b/view/bintxt/src/titxt.rs
new file mode 100644
index 00000000..6cb006ab
--- /dev/null
+++ b/view/bintxt/src/titxt.rs
@@ -0,0 +1,267 @@
+use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt};
+use binaryninja::custombinaryview::{
+ BinaryViewType, BinaryViewTypeBase, CustomBinaryView, CustomBinaryViewType, CustomView,
+ CustomViewBuilder,
+};
+use binaryninja::rc::Ref;
+use binaryninja::segment::SegmentBuilder;
+
+use crate::{
+ segment_after_address, segment_from_address, sort_and_merge_segments, MergedSegment,
+ MergedSegments, UnmergedSegment,
+};
+
+fn hex_to_byte(char: u8) -> Option<u8> {
+ match char {
+ b'0'..=b'9' => Some(char - b'0'),
+ b'a'..=b'f' => Some(0xa + (char - b'a')),
+ b'A'..=b'F' => Some(0xA + (char - b'A')),
+ _ => None,
+ }
+}
+
+fn take_address(input: &[u8]) -> Result<(u16, &[u8]), ()> {
+ let [b'@', b1, b2, b3, b4, rest @ ..] = input else {
+ return Err(());
+ };
+ let b1 = hex_to_byte(*b1).map(u16::from).ok_or(())?;
+ let b2 = hex_to_byte(*b2).map(u16::from).ok_or(())?;
+ let b3 = hex_to_byte(*b3).map(u16::from).ok_or(())?;
+ let b4 = hex_to_byte(*b4).map(u16::from).ok_or(())?;
+ let value = b1 << 12 | b2 << 8 | b3 << 4 | b4;
+ Ok((value, rest))
+}
+
+fn take_byte(input: &[u8]) -> Option<(u8, &[u8])> {
+ let [b1, b2, rest @ ..] = input else {
+ return None;
+ };
+ let b1 = hex_to_byte(*b1)?;
+ let b2 = hex_to_byte(*b2)?;
+ let value = b1 << 4 | b2;
+ Some((value, rest))
+}
+
+fn take_newline(input: &[u8]) -> Option<&[u8]> {
+ match input {
+ [b'\r', b'\n', rest @ ..] => Some(rest),
+ [b'\n', rest @ ..] => Some(rest),
+ _ => None,
+ }
+}
+
+// take 0 or more newlines
+fn take_newlines(mut input: &[u8]) -> (usize, &[u8]) {
+ let mut counter = 0;
+ while let Some(rest) = take_newline(input) {
+ input = rest;
+ counter += 1;
+ }
+ (counter, input)
+}
+
+fn take_byte_line(input: &[u8]) -> Result<(Vec<u8>, &[u8]), ()> {
+ // a line can be 16 bytes long, or less in case it's the last line
+ let mut current_input = input;
+ let mut bytes = Vec::with_capacity(16);
+
+ // line need to have at least one line
+ for _i in 0..16 {
+ let (byte, rest) =
+ take_byte(current_input).ok_or_else(|| log::error!("Unable to parse byte"))?;
+ current_input = rest;
+ bytes.push(byte);
+
+ match current_input {
+ // space with newline ends this line
+ [b' ', b'\r', b'\n', rest @ ..] | [b' ', b'\n', rest @ ..] => return Ok((bytes, rest)),
+ // bytes are separated by space
+ [b' ', rest @ ..] => current_input = rest,
+ // newline ends this line
+ [b'\r', b'\n', rest @ ..] | [b'\n', rest @ ..] => return Ok((bytes, rest)),
+ // other chars are forbidden
+ _ => {
+ log::error!("Invalid character on data bytes");
+ return Err(());
+ }
+ }
+ }
+ // can't have more then 16 bytes
+ log::error!("Bytes line is too long");
+ Err(())
+}
+
+fn parse_sections(input: &[u8]) -> Result<(UnmergedSegment, &[u8]), ()> {
+ // get the address part of the section
+ let (address, input) =
+ take_address(input).map_err(|_| log::error!("Unable to parse Address"))?;
+ let mut input =
+ take_newline(input).ok_or_else(|| log::error!("Unable to find Address delimiter"))?;
+
+ // get one or more bytes
+ let mut data = vec![];
+ let rest = loop {
+ let (line, rest) = take_byte_line(input)?;
+ let line_len = line.len();
+ data.extend(line);
+ input = rest;
+
+ // allow sections to be separated by multiple spaces
+ let (new_lines, rest) = take_newlines(input);
+ input = rest;
+
+ match input {
+ // if after a line we find a 'q', then we found the end of file
+ b"q" | b"q\n" | b"q\r\n" => break &[][..],
+
+ // end of file, but with data after
+ [b'q', ..] => {
+ log::error!("Found data after the end section");
+ return Err(());
+ }
+
+ // found the next sector
+ [b'@', ..] => break input,
+
+ // NOTE: only the last line is allowed to be less then 16 bytes,
+ // if less then 16 it's the end of a sector, so we need
+ // find a new sector or end of file
+ // NOTE: bytes can't be separated by multiple lines, only sections can
+ _ if line_len != 16 || new_lines > 0 => {
+ log::error!("Unable to find end of section");
+ return Err(());
+ }
+
+ // line is followed by other line of bytes
+ _ => {}
+ }
+ };
+
+ let segment = UnmergedSegment {
+ address: address.into(),
+ data,
+ };
+ Ok((segment, rest))
+}
+
+fn parse_ti_txt(data: &[u8]) -> Result<MergedSegments, ()> {
+ let mut current_data = data;
+ let mut unmerged_segments = vec![];
+ while !current_data.is_empty() {
+ let (section, rest) = parse_sections(current_data)?;
+ current_data = rest;
+
+ unmerged_segments.push(section);
+ }
+ sort_and_merge_segments(unmerged_segments)
+}
+
+pub struct TiTxtViewConstructor {
+ pub core: BinaryViewType,
+}
+
+impl AsRef<BinaryViewType> for TiTxtViewConstructor {
+ fn as_ref(&self) -> &BinaryViewType {
+ &self.core
+ }
+}
+
+impl CustomBinaryViewType for TiTxtViewConstructor {
+ fn create_custom_view<'builder>(
+ &self,
+ parent: &BinaryView,
+ builder: CustomViewBuilder<'builder, Self>,
+ ) -> Result<CustomView<'builder>, ()> {
+ let bytes = parent.len();
+ let mut buf = vec![0; bytes];
+ let bytes_read = parent.read(&mut buf, 0);
+ if bytes_read != bytes {
+ log::error!("IHex file is too small");
+ return Err(());
+ }
+ let sectors = parse_ti_txt(&buf)?;
+
+ let parent_bin = BinaryView::from_data(&parent.file(), &sectors.data)?;
+ builder.create::<TiTxtView>(&parent_bin, sectors.segments)
+ }
+}
+
+impl BinaryViewTypeBase for TiTxtViewConstructor {
+ fn is_valid_for(&self, data: &BinaryView) -> bool {
+ let mut first_bytes = [0u8; 16];
+ let read_bytes = data.read(&mut first_bytes, 0);
+
+ let [b'@', b1, b2, b3, b4, b'\r' | b'\n', ..] = first_bytes[0..read_bytes] else {
+ return false;
+ };
+ b1.is_ascii_hexdigit()
+ && b2.is_ascii_hexdigit()
+ && b3.is_ascii_hexdigit()
+ && b4.is_ascii_hexdigit()
+ }
+
+ fn is_deprecated(&self) -> bool {
+ false
+ }
+}
+
+pub struct TiTxtView {
+ core: Ref<BinaryView>,
+ segments: Vec<MergedSegment>,
+}
+
+impl AsRef<BinaryView> for TiTxtView {
+ fn as_ref(&self) -> &BinaryView {
+ &self.core
+ }
+}
+
+unsafe impl CustomBinaryView for TiTxtView {
+ type Args = Vec<MergedSegment>;
+
+ fn new(handle: &BinaryView, _args: &Self::Args) -> Result<Self, ()> {
+ Ok(Self {
+ core: handle.to_owned(),
+ // NOTE dummy value, final values are added on init
+ segments: vec![],
+ })
+ }
+
+ fn init(&mut self, segments: Self::Args) -> Result<(), ()> {
+ self.segments = segments;
+
+ for segment in self.segments.iter() {
+ self.add_segment(
+ SegmentBuilder::from(*segment)
+ .executable(true)
+ .readable(true)
+ .contains_data(true)
+ .contains_code(true),
+ );
+ }
+ Ok(())
+ }
+}
+
+impl BinaryViewBase for TiTxtView {
+ fn entry_point(&self) -> u64 {
+ // NOTE: TI TXT don't have any entry point information
+ self.segments.first().map(|s| s.address.into()).unwrap_or(0)
+ }
+
+ fn default_endianness(&self) -> binaryninja::Endianness {
+ binaryninja::Endianness::LittleEndian
+ }
+
+ fn address_size(&self) -> usize {
+ 4
+ }
+
+ fn offset_valid(&self, offset: u64) -> bool {
+ segment_from_address(&self.segments, offset).is_some()
+ }
+
+ fn next_valid_offset_after(&self, offset: u64) -> u64 {
+ segment_after_address(&self.segments, offset)
+ }
+}