diff --git a/Cargo.lock b/Cargo.lock index 32f22e0..8910039 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,55 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "anyhow" version = "1.0.86" @@ -29,6 +78,52 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "crc32fast" version = "1.4.2" @@ -48,17 +143,30 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "idb-rs" version = "0.1.0" dependencies = [ "anyhow", "bincode", + "clap", "flate2", "serde", "serde_repr", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -117,6 +225,12 @@ dependencies = [ "syn", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.76" @@ -133,3 +247,82 @@ name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[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/Cargo.toml b/Cargo.toml index e1bfbe4..26b88d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,12 @@ license-file = "LICENSE" [dependencies] anyhow = "1.0.86" +clap = { version = "4.5.*", features = ["derive"] } bincode = "1.3.3" flate2 = "1.0.31" serde = { version = "1.0", features = ["derive"] } serde_repr = "0.1.19" + +[[bin]] +name = "idb-tools" +path = "src/tools/tools.rs" diff --git a/resources/Readme.md b/resources/Readme.md deleted file mode 100644 index c9411d7..0000000 --- a/resources/Readme.md +++ /dev/null @@ -1,2 +0,0 @@ -Put here the `*.idb` `*.i64` in `idbs` folder and `*.til` files in `tils` folder. -Those files will be used to test the parser by the `cargo test` command. diff --git a/src/test.rs b/src/test.rs deleted file mode 100644 index 9db0fee..0000000 --- a/src/test.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::ffi::OsStr; -use std::fs::File; -use std::io::{BufReader, BufWriter, Seek}; -use std::path::{Path, PathBuf}; - -use anyhow::ensure; - -use crate::{IDBParser, IDBSectionCompression, TILSection}; - -#[test] -fn parse_idbs() { - let files = find_all("resources/idbs".as_ref(), &["idb".as_ref(), "i64".as_ref()]).unwrap(); - for filename in files { - println!("{}", filename.to_str().unwrap()); - let file = BufReader::new(File::open(&filename).unwrap()); - let mut parser = IDBParser::new(file).unwrap(); - let til = parser.read_til_section(parser.til_section().unwrap()); - - // if success, parse next file - let error = match til { - Ok(_til) => continue, - Err(e) => e, - }; - - //otherwise create a decompress version of the file for more testing - let mut output = BufWriter::new(std::fs::File::create("/tmp/lasterror.til").unwrap()); - parser - .decompress_til_section(parser.til_section().unwrap(), &mut output) - .unwrap(); - panic!("{error:?}") - } -} - -#[test] -fn parse_tils() { - let files = find_all("resources/tils".as_ref(), &["til".as_ref()]).unwrap(); - let results = files - .into_iter() - .map(|x| parse_til_file(&x).map_err(|e| (x, e))) - .collect::>(); - let Err((file, error)) = results else { - // if success, finish the test - return; - }; - println!("Unable to parse {}", file.to_str().unwrap()); - //otherwise create a decompress version of the file for more testing - let mut input = BufReader::new(std::fs::File::open(&file).unwrap()); - let mut output = BufWriter::new(std::fs::File::create("/tmp/lasterror.til").unwrap()); - TILSection::decompress_inner(&mut input, &mut output).unwrap(); - panic!( - "Unable to parse file `{}`: {error:?}", - file.to_str().unwrap() - ); -} - -fn parse_til_file(file: &Path) -> anyhow::Result<()> { - println!("TIL file: {}", file.to_str().unwrap()); - // makes sure it don't read out-of-bounds - let mut input = BufReader::new(std::fs::File::open(file).unwrap()); - // TODO make a SmartReader - match TILSection::read(&mut input, IDBSectionCompression::None) { - Ok(_til) => { - let current = input.seek(std::io::SeekFrom::Current(0))?; - let end = input.seek(std::io::SeekFrom::End(0))?; - ensure!( - current == end, - "unable to consume the entire TIL file, {current} != {end}" - ); - Ok(()) - } - Err(e) => Err(e), - } -} - -fn find_all(path: &Path, exts: &[&OsStr]) -> anyhow::Result> { - fn inner_find_all(path: &Path, exts: &[&OsStr], buf: &mut Vec) -> anyhow::Result<()> { - for entry in std::fs::read_dir(path).unwrap().map(Result::unwrap) { - let entry_type = entry.metadata().unwrap().file_type(); - if entry_type.is_dir() { - inner_find_all(&entry.path(), exts, buf)?; - continue; - } - - if !entry_type.is_file() { - continue; - } - - let filename = entry.file_name(); - let Some(ext) = Path::new(&filename).extension() else { - continue; - }; - - if exts.contains(&ext) { - buf.push(entry.path()) - } - } - Ok(()) - } - let mut result = vec![]; - inner_find_all(path, exts, &mut result)?; - Ok(result) -} diff --git a/src/til/mod.rs b/src/til/mod.rs deleted file mode 100644 index 24244cf..0000000 --- a/src/til/mod.rs +++ /dev/null @@ -1,1809 +0,0 @@ -/// The u8 values used to describes the type information records in IDA. -/// -/// The recommended way of using type info is to use the [tinfo_t] class. -/// The type information is internally kept as an array of bytes terminated by 0. -/// -/// Items in brackets [] are optional and sometimes are omitted. -/// ::type_t... means a sequence of ::type_t bytes which defines a type. -/// -/// NOTE: to work with the types of instructions or data in the database, -/// use `get_tinfo()`/`set_tinfo()` and similar functions. -#[allow(unused)] -mod flag; - -use std::io::{BufRead, BufReader, Read}; -use std::num::NonZeroU8; - -use anyhow::{anyhow, ensure, Context, Result}; -use serde::{Deserialize, Serialize}; - -use crate::{read_c_string, read_c_string_vec, read_string_len_u8, IDBSectionCompression}; - -// TODO migrate this to flags -const TIL_SECTION_MAGIC: &[u8; 6] = b"IDATIL"; - -#[derive(Debug, Clone)] -pub struct TILSection { - pub format: u32, - /// short file name (without path and extension) - pub title: String, - /// human readable til description - pub description: String, - pub id: u8, - /// information about the target compiler - pub cm: u8, - pub def_align: u8, - pub symbols: Vec, - pub type_ordinal_numbers: Option, - pub types: Vec, - pub size_i: NonZeroU8, - pub size_b: NonZeroU8, - pub sizes: Option, - pub size_long_double: Option, - pub macros: Option>, - pub is_universal: bool, -} - -#[derive(Debug, Clone, Copy)] -pub struct TILSizes { - pub size_short: NonZeroU8, - pub size_long: NonZeroU8, - pub size_long_long: NonZeroU8, -} - -#[derive(Debug, Clone)] -pub(crate) struct TILSectionHeader { - format: u32, - flags: TILSectionFlag, - title: String, - description: String, - id: u8, - cm: u8, - size_enum: u8, - size_i: NonZeroU8, - size_b: NonZeroU8, - def_align: u8, - size_s_l_ll: Option, - size_long_double: Option, -} - -#[derive(Debug, Clone, Copy, Deserialize, Serialize)] -struct TILSectionHeader1 { - signature: [u8; 6], - format: u32, - flags: TILSectionFlag, -} - -#[derive(Debug, Clone, Copy, Deserialize, Serialize)] -struct TILSectionHeader2 { - id: u8, - cm: u8, - size_i: u8, - size_b: u8, - size_enum: u8, - def_align: u8, -} - -impl TILSection { - pub fn parse(mut input: I) -> Result { - Self::read_inner(&mut input) - } - - pub(crate) fn read(input: &mut I, compress: IDBSectionCompression) -> Result { - match compress { - IDBSectionCompression::None => Self::read_inner(input), - IDBSectionCompression::Zlib => { - let mut input = BufReader::new(flate2::read::ZlibDecoder::new(input)); - Self::read_inner(&mut input) - } - } - } - - fn read_inner(input: &mut I) -> Result { - let header = Self::read_header(&mut *input)?; - let symbols = Self::read_bucket(&mut *input, &header)?; - let type_ordinal_numbers = header - .flags - .has_ordinal() - .then(|| bincode::deserialize_from(&mut *input)) - .transpose()?; - let types = Self::read_bucket(&mut *input, &header)?; - let macros = header - .flags - .has_macro_table() - .then(|| Self::read_macros(&mut *input, &header)) - .transpose()?; - - // TODO verify that is always false? - let _mod = header.flags.is_mod(); - let _uni = header.flags.is_universal(); - let _ord = header.flags.has_ordinal(); - let _ali = header.flags.has_type_aliases(); - let _stm = header.flags.has_extra_stream(); - - Ok(TILSection { - format: header.format, - title: header.title, - description: header.description, - id: header.id, - cm: header.cm, - def_align: header.def_align, - size_long_double: header.size_long_double, - is_universal: header.flags.is_universal(), - size_b: header.size_b, - size_i: header.size_i, - sizes: header.size_s_l_ll, - symbols, - type_ordinal_numbers, - types, - macros, - }) - } - - fn read_header(input: &mut I) -> Result { - let header1: TILSectionHeader1 = bincode::deserialize_from(&mut *input)?; - ensure!( - header1.signature == *TIL_SECTION_MAGIC, - "Invalid TIL Signature" - ); - - let title = read_string_len_u8(&mut *input)?; - let description = read_string_len_u8(&mut *input)?; - - let header2: TILSectionHeader2 = bincode::deserialize_from(&mut *input)?; - let size_s_l_ll = header1 - .flags - .have_size_short_long_longlong() - .then(|| bincode::deserialize_from(&mut *input)) - .transpose()? - .map(|(s, l, ll): (u8, u8, u8)| -> anyhow::Result<_> { - Ok(TILSizes { - size_short: s.try_into()?, - size_long: l.try_into()?, - size_long_long: ll.try_into()?, - }) - }) - .transpose()?; - let size_long_double = header1 - .flags - .has_size_long_double() - .then(|| bincode::deserialize_from::<_, u8>(&mut *input)) - .transpose()? - .map(|size| size.try_into()) - .transpose()?; - Ok(TILSectionHeader { - format: header1.format, - flags: header1.flags, - title, - description, - id: header2.id, - size_enum: header2.size_enum, - size_i: header2.size_i.try_into()?, - size_b: header2.size_b.try_into()?, - cm: header2.cm, - def_align: header2.def_align, - size_s_l_ll, - size_long_double, - }) - } - - #[cfg(test)] - pub(crate) fn decompress( - input: &mut I, - output: &mut O, - compress: IDBSectionCompression, - ) -> Result<()> { - match compress { - IDBSectionCompression::Zlib => { - let mut input = BufReader::new(flate2::read::ZlibDecoder::new(input)); - Self::decompress_inner(&mut input, output) - } - IDBSectionCompression::None => Self::decompress_inner(input, output), - } - } - - #[cfg(test)] - pub(crate) fn decompress_inner( - input: &mut I, - output: &mut O, - ) -> Result<()> { - let mut header = Self::read_header(&mut *input)?; - let og_flags = header.flags; - // disable the zip flag - header.flags.set_zip(false); - let header1 = TILSectionHeader1 { - signature: *TIL_SECTION_MAGIC, - format: header.format, - flags: header.flags, - }; - let header2 = TILSectionHeader2 { - id: header.id, - cm: header.cm, - size_i: header.size_i.get(), - size_b: header.size_b.get(), - size_enum: header.size_enum, - def_align: header.def_align, - }; - bincode::serialize_into(&mut *output, &header1)?; - crate::write_string_len_u8(&mut *output, &header.title)?; - crate::write_string_len_u8(&mut *output, &header.description)?; - bincode::serialize_into(&mut *output, &header2)?; - header - .size_s_l_ll - .map(|value| { - bincode::serialize_into( - &mut *output, - &( - value.size_short.get(), - value.size_long.get(), - value.size_long_long.get(), - ), - ) - }) - .transpose()?; - header - .size_long_double - .map(|value| bincode::serialize_into(&mut *output, &value)) - .transpose()?; - - // if not zipped, just copy the rest of the data, there is no posible zip - // block inside a bucket - if !og_flags.is_zip() { - std::io::copy(&mut *input, output)?; - return Ok(()); - } - - // symbols - Self::decompress_bucket(&mut *input, &mut *output)?; - let _type_ordinal_numbers: Option = header - .flags - .has_ordinal() - .then(|| -> Result { - let result: u32 = bincode::deserialize_from(&mut *input)?; - bincode::serialize_into(&mut *output, &result)?; - Ok(result) - }) - .transpose()?; - // types - Self::decompress_bucket(&mut *input, &mut *output)?; - // macros - header - .flags - .has_macro_table() - .then(|| Self::decompress_bucket(&mut *input, &mut *output)) - .transpose()?; - - Ok(()) - } -} - -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct TILSectionFlag(u32); -impl TILSectionFlag { - pub fn is_zip(&self) -> bool { - self.0 & flag::TIL_ZIP != 0 - } - pub fn set_zip(&mut self, value: bool) { - if value { - self.0 |= flag::TIL_ZIP - } else { - self.0 &= !flag::TIL_ZIP - } - } - pub fn has_macro_table(&self) -> bool { - self.0 & flag::TIL_MAC != 0 - } - /// extended sizeof info (short, long, longlong) - pub fn have_size_short_long_longlong(&self) -> bool { - self.0 & flag::TIL_ESI != 0 - } - /// universal til for any compiler - pub fn is_universal(&self) -> bool { - self.0 & flag::TIL_UNI != 0 - } - /// type ordinal numbers are present - pub fn has_ordinal(&self) -> bool { - self.0 & flag::TIL_ORD != 0 - } - /// type aliases are present - pub fn has_type_aliases(&self) -> bool { - self.0 & flag::TIL_ALI != 0 - } - /// til has been modified, should be saved - pub fn is_mod(&self) -> bool { - self.0 & flag::TIL_MOD != 0 - } - /// til has extra streams - pub fn has_extra_stream(&self) -> bool { - self.0 & flag::TIL_STM != 0 - } - /// sizeof(long double) - pub fn has_size_long_double(&self) -> bool { - self.0 & flag::TIL_SLD != 0 - } -} - -#[derive(Debug, Deserialize, Serialize)] -struct TILBucketRaw { - ndefs: u32, - len: u32, -} - -impl TILSection { - fn read_bucket_header(input: &mut I) -> Result<(u32, u32)> { - let ndefs = bincode::deserialize_from(&mut *input)?; - let len = bincode::deserialize_from(&mut *input)?; - Ok((ndefs, len)) - } - - fn read_bucket_zip_header(input: &mut I) -> Result<(u32, u32, u32)> { - let (ndefs, len) = Self::read_bucket_header(&mut *input)?; - let compressed_len = bincode::deserialize_from(&mut *input)?; - Ok((ndefs, len, compressed_len)) - } - - fn read_bucket( - input: &mut I, - header: &TILSectionHeader, - ) -> Result> { - if header.flags.is_zip() { - Self::read_bucket_zip(&mut *input, header) - } else { - Self::read_bucket_normal(&mut *input, header) - } - } - - fn read_bucket_normal( - input: &mut I, - header: &TILSectionHeader, - ) -> Result> { - let (ndefs, len) = Self::read_bucket_header(&mut *input)?; - let mut input = input.take(len.into()); - let type_info = (0..ndefs) - .map(|_| TILTypeInfo::read(&mut input, header)) - .collect::>()?; - ensure!( - input.limit() == 0, - "TypeBucket total data is smaller then expected" - ); - Ok(type_info) - } - - fn read_bucket_zip( - input: &mut I, - header: &TILSectionHeader, - ) -> Result> { - let (ndefs, len, compressed_len) = Self::read_bucket_zip_header(&mut *input)?; - // make sure the decompressor don't read out-of-bounds - let mut compressed_input = input.take(compressed_len.into()); - let inflate = BufReader::new(flate2::read::ZlibDecoder::new(&mut compressed_input)); - // make sure only the defined size is decompressed - let mut decompressed_input = inflate.take(len.into()); - let type_info = (0..ndefs.try_into().unwrap()) - .map(|_| TILTypeInfo::read(&mut decompressed_input, header)) - .collect::, _>>()?; - // make sure the input was fully consumed - ensure!( - decompressed_input.limit() == 0, - "TypeBucket data is smaller then expected" - ); - ensure!( - compressed_input.limit() == 0, - "TypeBucket compressed data is smaller then expected" - ); - Ok(type_info) - } - - fn read_macros(input: &mut I, header: &TILSectionHeader) -> Result> { - if header.flags.is_zip() { - Self::read_macros_zip(&mut *input) - } else { - Self::read_macros_normal(&mut *input) - } - } - - fn read_macros_normal(input: &mut I) -> Result> { - let (ndefs, len) = Self::read_bucket_header(&mut *input)?; - let mut input = input.take(len.into()); - let type_info = (0..ndefs) - .map(|_| TILMacro::read(&mut input)) - .collect::>()?; - ensure!( - input.limit() == 0, - "TypeBucket macro total data is smaller then expected" - ); - Ok(type_info) - } - - fn read_macros_zip(input: &mut I) -> Result> { - let (ndefs, len, compressed_len) = Self::read_bucket_zip_header(&mut *input)?; - // make sure the decompressor don't read out-of-bounds - let mut compressed_input = input.take(compressed_len.into()); - let inflate = BufReader::new(flate2::read::ZlibDecoder::new(&mut compressed_input)); - // make sure only the defined size is decompressed - let mut decompressed_input = inflate.take(len.into()); - let type_info = (0..ndefs.try_into().unwrap()) - .map(|_| TILMacro::read(&mut decompressed_input)) - .collect::, _>>()?; - // make sure the input was fully consumed - ensure!( - decompressed_input.limit() == 0, - "TypeBucket macros data is smaller then expected" - ); - ensure!( - compressed_input.limit() == 0, - "TypeBucket macros compressed data is smaller then expected" - ); - Ok(type_info) - } - - #[cfg(test)] - fn decompress_bucket( - input: &mut I, - output: &mut O, - ) -> Result<()> { - let (ndefs, len, compressed_len) = Self::read_bucket_zip_header(&mut *input)?; - bincode::serialize_into(&mut *output, &TILBucketRaw { len, ndefs })?; - // write the decompressed data - let mut compressed_input = input.take(compressed_len.into()); - let inflate = flate2::read::ZlibDecoder::new(&mut compressed_input); - let mut decompressed_input = inflate.take(len.into()); - std::io::copy(&mut decompressed_input, output)?; - ensure!( - decompressed_input.limit() == 0, - "TypeBucket data is smaller then expected" - ); - ensure!( - compressed_input.limit() == 0, - "TypeBucket compressed data is smaller then expected" - ); - Ok(()) - } -} - -#[derive(Debug, Clone)] -pub struct TILTypeInfo { - _flags: u32, - pub name: String, - pub ordinal: u64, - pub tinfo: Type, - _cmt: String, - _fieldcmts: String, - _sclass: u8, -} - -impl TILTypeInfo { - pub(crate) fn read(input: &mut I, til: &TILSectionHeader) -> Result { - let flags: u32 = bincode::deserialize_from(&mut *input)?; - let name = read_c_string(&mut *input)?; - let is_u64 = (flags >> 31) != 0; - let ordinal = match (til.format, is_u64) { - // formats below 0x12 doesn't have 64 bits ord - (0..=0x11, _) | (_, false) => bincode::deserialize_from::<_, u32>(&mut *input)?.into(), - (_, true) => bincode::deserialize_from(&mut *input)?, - }; - let tinfo_raw = TypeRaw::read(&mut *input, til).context("parsing `TILTypeInfo::tiinfo`")?; - let _info = read_c_string(&mut *input)?; - let cmt = read_c_string(&mut *input)?; - let fields = read_c_string_vec(&mut *input)?; - let fieldcmts = read_c_string(&mut *input)?; - let sclass: u8 = bincode::deserialize_from(&mut *input)?; - - let tinfo = Type::new(til, tinfo_raw, Some(fields))?; - - Ok(Self { - _flags: flags, - name, - ordinal, - tinfo, - _cmt: cmt, - _fieldcmts: fieldcmts, - _sclass: sclass, - }) - } -} - -#[derive(Debug, Clone)] -pub enum Type { - Basic(Basic), - Pointer(Pointer), - Function(Function), - Array(Array), - Typedef(Typedef), - Struct(Struct), - Union(Union), - Enum(Enum), - Bitfield(Bitfield), -} -impl Type { - fn new( - til: &TILSectionHeader, - tinfo_raw: TypeRaw, - fields: Option>, - ) -> Result { - match tinfo_raw { - TypeRaw::Basic(x) => { - if let Some(fields) = fields { - ensure!(fields.is_empty(), "Unset with fields"); - } - Ok(Type::Basic(x)) - } - TypeRaw::Bitfield(x) => { - if matches!(fields, Some(f) if !f.is_empty()) { - return Err(anyhow!("fields in a Bitfield")); - } - Ok(Type::Bitfield(x)) - } - TypeRaw::Typedef(x) => { - if matches!(fields, Some(f) if !f.is_empty()) { - return Err(anyhow!("fields in a Typedef")); - } - Ok(Type::Typedef(x)) - } - TypeRaw::Pointer(x) => Pointer::new(til, x, fields).map(Type::Pointer), - TypeRaw::Function(x) => Function::new(til, x, fields).map(Type::Function), - TypeRaw::Array(x) => Array::new(til, x, fields).map(Type::Array), - TypeRaw::Struct(x) => Struct::new(til, x, fields).map(Type::Struct), - TypeRaw::Union(x) => Union::new(til, x, fields).map(Type::Union), - TypeRaw::Enum(x) => Enum::new(til, x, fields).map(Type::Enum), - } - } -} - -#[derive(Debug, Clone)] -enum TypeRaw { - Basic(Basic), - Pointer(PointerRaw), - Function(FunctionRaw), - Array(ArrayRaw), - Typedef(Typedef), - Struct(StructRaw), - Union(UnionRaw), - Enum(EnumRaw), - Bitfield(Bitfield), -} - -impl TypeRaw { - pub fn read(input: &mut I, til: &TILSectionHeader) -> Result { - let metadata: u8 = bincode::deserialize_from(&mut *input)?; - let type_base = metadata & flag::tf_mask::TYPE_BASE_MASK; - let type_flags = metadata & flag::tf_mask::TYPE_FLAGS_MASK; - match (type_base, type_flags) { - (flag::BT_RESERVED, _) => Err(anyhow!("Reserved Basic Type")), - (..=flag::tf_last_basic::BT_LAST_BASIC, _) => { - Basic::new(til, type_base, type_flags).map(TypeRaw::Basic) - } - (flag::tf_ptr::BT_PTR, _) => PointerRaw::read(input, til, type_flags) - .context("Type::Pointer") - .map(TypeRaw::Pointer), - - (flag::tf_func::BT_FUNC, _) => FunctionRaw::read(input, til, type_flags) - .context("Type::Function") - .map(TypeRaw::Function), - - (flag::tf_array::BT_ARRAY, _) => ArrayRaw::read(input, til, type_flags) - .context("Type::Array") - .map(TypeRaw::Array), - - (flag::tf_complex::BT_BITFIELD, _) => Ok(TypeRaw::Bitfield( - Bitfield::read(input, metadata).context("Type::Bitfield")?, - )), - - (flag::tf_complex::BT_COMPLEX, flag::tf_complex::BTMT_TYPEDEF) => Typedef::read(input) - .context("Type::Typedef") - .map(TypeRaw::Typedef), - - (flag::tf_complex::BT_COMPLEX, flag::tf_complex::BTMT_UNION) => { - UnionRaw::read(input, til) - .context("Type::Union") - .map(TypeRaw::Union) - } - - (flag::tf_complex::BT_COMPLEX, flag::tf_complex::BTMT_STRUCT) => { - StructRaw::read(input, til) - .context("Type::Struct") - .map(TypeRaw::Struct) - } - - (flag::tf_complex::BT_COMPLEX, flag::tf_complex::BTMT_ENUM) => { - EnumRaw::read(input, til) - .context("Type::Enum") - .map(TypeRaw::Enum) - } - _ => todo!(), - } - //if metadata.get_base_type_flag().is_typeid_last() - // || metadata.get_base_type_flag().is_reserved() - //{ - // return Ok(TypeRaw::Basic(metadata)); - //} else if metadata.get_base_type_flag().is_pointer() { - // Ok(TypeRaw::Pointer( - // PointerRaw::read(input, metadata, header).context("Type::Pointer")?, - // )) - //} else if metadata.get_base_type_flag().is_function() { - // Ok(TypeRaw::Function( - // FunctionRaw::read(input, &metadata, header).context("Type::Function")?, - // )) - //} else if metadata.get_base_type_flag().is_array() { - // Ok(TypeRaw::Array( - // ArrayRaw::read(input, metadata, header).context("Type::Array")?, - // )) - //} else if metadata.get_full_type_flag().is_typedef() { - // Ok(TypeRaw::Typedef( - // Typedef::read(input).context("Type::Typedef")?, - // )) - //} else if metadata.get_full_type_flag().is_union() { - // Ok(TypeRaw::Union( - // UnionRaw::read(input, header).context("Type::Union")?, - // )) - //} else if metadata.get_full_type_flag().is_struct() { - // Ok(TypeRaw::Struct( - // StructRaw::read(input, header).context("Type::Struct")?, - // )) - //} else if metadata.get_full_type_flag().is_enum() { - // Ok(TypeRaw::Enum( - // EnumRaw::read(input, header).context("Type::Enum")?, - // )) - //} else if metadata.get_base_type_flag().is_bitfield() { - // Ok(TypeRaw::Bitfield( - // Bitfield::read(input, metadata).context("Type::Bitfield")?, - // )) - //} else { - // todo!(); - // //Ok(Type::Unknown(read_c_string_raw(input)?)) - //} - } - - pub fn read_ref(input: &mut I, header: &TILSectionHeader) -> Result { - let mut bytes = read_dt_bytes(&mut *input)?; - - if !bytes.starts_with(b"=") { - let dt = serialize_dt(bytes.len().try_into().unwrap())?; - bytes = [b'='].into_iter().chain(dt).chain(bytes).collect(); - } - - let mut bytes = &bytes[..]; - let result = TypeRaw::read(&mut bytes, header)?; - if !bytes.is_empty() { - return Err(anyhow!("Unable to fully parser Type ref")); - } - Ok(result) - } -} - -#[derive(Debug, Clone, Copy)] -pub enum Basic { - Void, - // NOTE Unknown with 0 bytes is NOT the same as Void - Unknown { - bytes: u8, - }, - - Bool { - bytes: NonZeroU8, - }, - Char, - SegReg, - Int { - bytes: NonZeroU8, - is_signed: Option, - }, - Float { - bytes: NonZeroU8, - }, -} - -impl Basic { - fn new(til: &TILSectionHeader, bt: u8, btmt: u8) -> Result { - const fn bytes(bytes: u8) -> NonZeroU8 { - if bytes == 0 { - unreachable!() - } - unsafe { NonZeroU8::new_unchecked(bytes) } - } - - use flag::{tf_bool::*, tf_float::*, tf_int::*, tf_unk::*}; - match bt { - BT_VOID => { - let bytes = match btmt { - // special case, void - BTMT_SIZE0 => return Ok(Self::Void), - BTMT_SIZE12 => 1, - BTMT_SIZE48 => 4, - BTMT_SIZE128 => 16, - _ => unreachable!(), - }; - Ok(Self::Unknown { bytes }) - } - BT_UNK => { - let bytes = match btmt { - BTMT_SIZE0 => return Err(anyhow!("forbidden use of BT_UNK")), - BTMT_SIZE12 => 2, - BTMT_SIZE48 => 8, - BTMT_SIZE128 => 0, - _ => unreachable!(), - }; - Ok(Self::Unknown { bytes }) - } - - bt_int @ BT_INT8..=BT_INT => { - let is_signed = match btmt { - BTMT_UNKSIGN => None, - BTMT_SIGNED => Some(true), - BTMT_UNSIGNED => Some(false), - // special case for char - BTMT_CHAR => match bt_int { - BT_INT8 => return Ok(Self::Char), - BT_INT => return Ok(Self::SegReg), - _ => return Err(anyhow!("Reserved use of tf_int::BTMT_CHAR {:x}", btmt)), - }, - _ => unreachable!(), - }; - let bytes = match bt_int { - BT_INT8 => bytes(1), - BT_INT16 => bytes(2), - BT_INT32 => bytes(4), - BT_INT64 => bytes(8), - BT_INT128 => bytes(16), - BT_INT => til.size_i, - _ => unreachable!(), - }; - Ok(Self::Int { bytes, is_signed }) - } - - BT_BOOL => { - let bytes = match btmt { - BTMT_DEFBOOL => til.size_b, - BTMT_BOOL1 => bytes(1), - BTMT_BOOL4 => bytes(4), - // TODO get the inf_is_64bit field - //BTMT_BOOL2 if !inf_is_64bit => Some(bytes(2)), - //BTMT_BOOL8 if inf_is_64bit => Some(bytes(8)), - BTMT_BOOL8 => bytes(2), // delete this - _ => unreachable!(), - }; - Ok(Self::Bool { bytes }) - } - - BT_FLOAT => { - let bytes = match btmt { - BTMT_FLOAT => bytes(4), - BTMT_DOUBLE => bytes(8), - // TODO error if none? - BTMT_LNGDBL => til.size_long_double.unwrap_or(bytes(8)), - // TODO find the tbyte_size field - //BTMT_SPECFLT if til.tbyte_size() => Some(bytes), - BTMT_SPECFLT => bytes(2), - _ => unreachable!(), - }; - Ok(Self::Float { bytes }) - } - _ => Err(anyhow!("Unkown Unset Type {}", btmt)), - } - } -} - -#[derive(Debug, Clone)] -pub struct Pointer { - pub closure: Option, - pub tah: TAH, - pub typ: Box, -} - -impl Pointer { - fn new(til: &TILSectionHeader, raw: PointerRaw, fields: Option>) -> Result { - Ok(Self { - closure: raw.closure.map(|x| Closure::new(til, x)).transpose()?, - tah: raw.tah, - typ: Type::new(til, *raw.typ, fields).map(Box::new)?, - }) - } -} - -#[derive(Debug, Clone)] -pub enum Closure { - Closure(Box), - PointerBased(u8), -} - -impl Closure { - fn new(til: &TILSectionHeader, raw: ClosureRaw) -> Result { - match raw { - ClosureRaw::Closure(c) => Type::new(til, *c, None).map(Box::new).map(Self::Closure), - ClosureRaw::PointerBased(p) => Ok(Self::PointerBased(p)), - } - } -} - -#[derive(Debug, Clone)] -struct PointerRaw { - pub closure: Option, - pub tah: TAH, - pub typ: Box, -} - -#[derive(Debug, Clone)] -enum ClosureRaw { - Closure(Box), - PointerBased(u8), -} - -impl PointerRaw { - fn read(input: &mut I, header: &TILSectionHeader, metadata: u8) -> Result { - use flag::tf_ptr::*; - let closure = match metadata { - BTMT_DEFPTR => None, - BTMT_CLOSURE => Some(ClosureRaw::read(&mut *input, header)?), - // TODO find the meaning of this - BTMT_FAR => None, - BTMT_NEAR => None, - _ => unreachable!(), - }; - let tah = TAH::read(&mut *input)?; - let typ = TypeRaw::read(&mut *input, header)?; - Ok(Self { - closure, - tah, - typ: Box::new(typ), - }) - } -} - -impl ClosureRaw { - fn read(input: &mut I, header: &TILSectionHeader) -> Result { - let closure_type: u8 = bincode::deserialize_from(&mut *input)?; - if closure_type == 0xFF { - let closure = TypeRaw::read(&mut *input, header)?; - Ok(Self::Closure(Box::new(closure))) - } else { - let closure_ptr = bincode::deserialize_from(&mut *input)?; - Ok(Self::PointerBased(closure_ptr)) - } - } -} - -#[derive(Debug, Clone)] -pub struct Function { - pub ret: Box, - pub args: Vec<(Option, Type, Option)>, - pub retloc: Option, -} -impl Function { - fn new( - til: &TILSectionHeader, - value: FunctionRaw, - fields: Option>, - ) -> Result { - let args = associate_field_name_and_member(fields, value.args) - .context("Function")? - .map(|(n, (t, a))| Type::new(til, t, None).map(|t| (n, t, a))) - .collect::>()?; - Ok(Self { - ret: Type::new(til, *value.ret, None).map(Box::new)?, - args, - retloc: value.retloc, - }) - } -} - -#[derive(Debug, Clone)] -struct FunctionRaw { - pub ret: Box, - pub args: Vec<(TypeRaw, Option)>, - pub retloc: Option, -} - -#[derive(Debug, Clone)] -pub enum ArgLoc { - // TODO add those to flags - // ::ALOC_STACK - // ::ALOC_STATIC - // ::ALOC_REG1 - // ::ALOC_REG2 - // ::ALOC_RREL - // ::ALOC_DIST - // ::ALOC_CUSTOM - /// 0 - None - None, - /// 1 - stack offset - Stack(u32), - /// 2 - distributed (scattered) - Dist(Vec), - /// 3 - one register (and offset within it) - Reg1(u32), - /// 4 - register pair - Reg2(u32), - /// 5 - register relative - RRel { reg: u16, off: u32 }, - /// 6 - global address - Static(u32), - // 7..=0xf custom - // TODO is possible to know the custom impl len? -} - -#[derive(Debug, Clone)] -pub struct ArgLocDist { - pub info: u16, - pub off: u16, - pub size: u16, -} - -impl FunctionRaw { - fn read(input: &mut I, header: &TILSectionHeader, metadata: u8) -> Result { - // TODO what is that? - let mut flags = metadata << 2; - - let cc = Self::read_cc(&mut *input, &mut flags)?; - - let _tah = TAH::read(&mut *input)?; - let ret = TypeRaw::read(&mut *input, header)?; - // TODO double check ducumentation for [flag::tf_func::BT_FUN] - let have_retloc = cc.get_calling_convention().is_special_pe() - && !matches!(&ret, TypeRaw::Basic(Basic::Void)); - let retloc = have_retloc.then(|| ArgLoc::read(&mut *input)).transpose()?; - if cc.get_calling_convention().is_void_arg() { - return Ok(Self { - ret: Box::new(ret), - args: vec![], - retloc, - }); - } - - let n = read_dt(&mut *input)?; - let is_special_pe = cc.get_calling_convention().is_special_pe(); - let args = (0..n) - .map(|_| -> Result<_> { - let tmp = input.fill_buf()?.first().copied(); - if tmp == Some(0xFF) { - // TODO what is this? - let _tmp: u8 = bincode::deserialize_from(&mut *input)?; - let _flags = read_de(&mut *input)?; - } - let tinfo = TypeRaw::read(&mut *input, header)?; - let argloc = is_special_pe - .then(|| ArgLoc::read(&mut *input)) - .transpose()?; - - Ok((tinfo, argloc)) - }) - .collect::>()?; - - Ok(Self { - ret: Box::new(ret), - args, - retloc, - }) - } - - fn read_cc(input: &mut I, flags: &mut u8) -> Result { - let mut cm = TypeMetadata::read(&mut *input)?; - if !cm.get_calling_convention().is_spoiled() { - return Ok(cm); - } - // TODO find what to do with this spoiled and flags stuff - let mut _spoiled = vec![]; - loop { - // TODO create flags::CM_CC_MASK - let nspoiled = cm.0 & !0xf0; - if nspoiled == 0xF { - let b: u8 = bincode::deserialize_from(&mut *input)?; - *flags |= (b & 0x1F) << 1; - } else { - for _ in 0..nspoiled { - let b: u8 = bincode::deserialize_from(&mut *input)?; - let (size, reg) = if b & 0x80 != 0 { - let size: u8 = bincode::deserialize_from(&mut *input)?; - let reg = b & 0x7F; - (size, reg) - } else { - ensure!(b > 1, "Unable to solve register from a spoiled function"); - let size = (b >> 4) + 1; - let reg = (b & 0xF) - 1; - (size, reg) - }; - _spoiled.push((size, reg)); - } - *flags |= 1; - } - - cm = TypeMetadata::read(&mut *input)?; - if !cm.get_calling_convention().is_spoiled() { - return Ok(cm); - } - } - } -} - -impl ArgLoc { - fn read(input: &mut I) -> Result { - let t: u8 = bincode::deserialize_from(&mut *input)?; - if t != 0xFF { - let b = t & 0x7F; - match (t, b) { - (0..=0x80, 1..) => Ok(Self::Reg1((b - 1).into())), - (0..=0x80, 0) => Ok(Self::Stack(0)), - _ => { - let c: u8 = bincode::deserialize_from(&mut *input)?; - if c == 0 { - Ok(Self::None) - } else { - Ok(Self::Reg2(u32::from(b) | u32::from(c - 1) << 16)) - } - } - } - } else { - let typ = read_dt(&mut *input)?; - match typ & 0xF { - 0 => Ok(Self::None), - 1 => { - let sval = read_de(&mut *input)?; - Ok(Self::Stack(sval)) - } - 2 => { - let n = (typ >> 5) & 0x7; - let dist: Vec<_> = (0..n) - .map(|_| { - let info = read_dt(&mut *input)?; - let off = read_dt(&mut *input)?; - let size = read_dt(&mut *input)?; - Ok(ArgLocDist { info, off, size }) - }) - .collect::>()?; - Ok(Self::Dist(dist)) - } - 3 => { - let reg_info = read_dt(&mut *input)?; - // TODO read other dt? - Ok(Self::Reg1(reg_info.into())) - } - 4 => { - let reg_info = read_dt(&mut *input)?; - // TODO read other dt? - Ok(Self::Reg2(reg_info.into())) - } - 5 => { - let reg = read_dt(&mut *input)?; - let off = read_de(&mut *input)?; - Ok(Self::RRel { reg, off }) - } - 6 => { - let sval = read_de(&mut *input)?; - Ok(Self::Static(sval)) - } - 0x7..=0xF => todo!("Custom implementation for ArgLoc"), - _ => unreachable!(), - } - } - } -} - -#[derive(Clone, Debug)] -pub struct Array { - pub base: u8, - pub nelem: u16, - pub tah: TAH, - pub elem_type: Box, -} -impl Array { - fn new(til: &TILSectionHeader, value: ArrayRaw, fields: Option>) -> Result { - if matches!(&fields, Some(f) if !f.is_empty()) { - return Err(anyhow!("fields in a Array")); - } - Ok(Self { - base: value.base, - nelem: value.nelem, - tah: value.tah, - elem_type: Type::new(til, *value.elem_type, None).map(Box::new)?, - }) - } -} - -#[derive(Clone, Debug)] -struct ArrayRaw { - pub base: u8, - pub nelem: u16, - pub tah: TAH, - pub elem_type: Box, -} - -impl ArrayRaw { - fn read(input: &mut I, header: &TILSectionHeader, metadata: u8) -> Result { - use flag::tf_array::*; - let (base, nelem) = match metadata { - BTMT_NONBASED => { - // TODO if num_elem==0 then the array size is unknown - let nelem = read_dt(&mut *input)?; - (0, nelem) - } - // I think is only for zero, but ducumentation says anything other then BTMT_NONBASED - _ => { - let (base, nelem) = read_da(&mut *input)?; - (base, nelem.into()) - } - }; - let tah = TAH::read(&mut *input)?; - let elem_type = TypeRaw::read(&mut *input, header)?; - Ok(ArrayRaw { - base, - nelem, - tah, - elem_type: Box::new(elem_type), - }) - } -} - -#[derive(Clone, Debug)] -pub enum Typedef { - Ordinal(u32), - Name(String), -} - -impl Typedef { - fn read(input: &mut I) -> Result { - let buf = read_dt_bytes(&mut *input)?; - match &buf[..] { - [b'#', data @ ..] => { - let mut tmp = std::io::Cursor::new(data); - let de = read_de(&mut tmp)?; - if tmp.position() != data.len().try_into().unwrap() { - return Err(anyhow!("Typedef Ordinal with more data then expected")); - } - Ok(Typedef::Ordinal(de)) - } - _ => Ok(Typedef::Name(String::from_utf8(buf)?)), - } - } -} - -#[derive(Clone, Debug)] -pub enum Struct { - Ref { - ref_type: Box, - taudt_bits: SDACL, - }, - NonRef { - effective_alignment: u16, - taudt_bits: SDACL, - members: Vec, - }, -} -impl Struct { - fn new(til: &TILSectionHeader, value: StructRaw, fields: Option>) -> Result { - match value { - StructRaw::Ref { - ref_type, - taudt_bits, - } => { - if matches!(&fields, Some(f) if !f.is_empty()) { - return Err(anyhow!("fields in a Ref Struct")); - } - Ok(Struct::Ref { - ref_type: Type::new(til, *ref_type, None).map(Box::new)?, - taudt_bits, - }) - } - StructRaw::NonRef { - effective_alignment, - taudt_bits, - members, - } => { - let members = associate_field_name_and_member(fields, members) - .context("Struct")? - .map(|(n, m)| StructMember::new(til, n, m)) - .collect::>()?; - Ok(Struct::NonRef { - effective_alignment, - taudt_bits, - members, - }) - } - } - } -} - -#[derive(Clone, Debug)] -enum StructRaw { - Ref { - ref_type: Box, - taudt_bits: SDACL, - }, - NonRef { - effective_alignment: u16, - taudt_bits: SDACL, - members: Vec, - }, -} - -impl StructRaw { - fn read(input: &mut I, header: &TILSectionHeader) -> Result { - let Some(n) = read_dt_de(&mut *input)? else { - // simple reference - let ref_type = TypeRaw::read_ref(&mut *input, header)?; - let taudt_bits = SDACL::read(&mut *input)?; - return Ok(Self::Ref { - ref_type: Box::new(ref_type), - taudt_bits, - }); - }; - - let alpow = n & 7; - let mem_cnt = n >> 3; - let effective_alignment = if alpow == 0 { 0 } else { 1 << (alpow - 1) }; - let taudt_bits = SDACL::read(&mut *input)?; - let members = (0..mem_cnt) - .map(|_| StructMemberRaw::read(&mut *input, header)) - .collect::>()?; - Ok(Self::NonRef { - effective_alignment, - taudt_bits, - members, - }) - } -} - -#[derive(Clone, Debug)] -pub enum Union { - Ref { - ref_type: Box, - taudt_bits: SDACL, - }, - NonRef { - taudt_bits: SDACL, - effective_alignment: u16, - members: Vec<(Option, Type)>, - }, -} -impl Union { - fn new(til: &TILSectionHeader, value: UnionRaw, fields: Option>) -> Result { - match value { - UnionRaw::Ref { - ref_type, - taudt_bits, - } => { - if matches!(fields, Some(f) if !f.is_empty()) { - return Err(anyhow!("fields in a Ref Union")); - } - Ok(Union::Ref { - ref_type: Type::new(til, *ref_type, None).map(Box::new)?, - taudt_bits, - }) - } - UnionRaw::NonRef { - taudt_bits, - effective_alignment, - members, - } => { - let members = associate_field_name_and_member(fields, members) - .context("Union")? - .map(|(n, m)| Type::new(til, m, None).map(|m| (n, m))) - .collect::>()?; - Ok(Union::NonRef { - taudt_bits, - effective_alignment, - members, - }) - } - } - } -} - -// TODO struct and union are basically identical, the diff is that member in union don't have SDACL, -// merge both -#[derive(Clone, Debug)] -enum UnionRaw { - Ref { - ref_type: Box, - taudt_bits: SDACL, - }, - NonRef { - taudt_bits: SDACL, - effective_alignment: u16, - members: Vec, - }, -} - -impl UnionRaw { - fn read(input: &mut I, header: &TILSectionHeader) -> Result { - let Some(n) = read_dt_de(&mut *input)? else { - // is ref - let ref_type = TypeRaw::read_ref(&mut *input, header)?; - let taudt_bits = SDACL::read(&mut *input)?; - return Ok(Self::Ref { - ref_type: Box::new(ref_type), - taudt_bits, - }); - }; - let alpow = n & 7; - let mem_cnt = n >> 3; - let effective_alignment = if alpow == 0 { 0 } else { 1 << (alpow - 1) }; - let taudt_bits = SDACL::read(&mut *input)?; - let members = (0..mem_cnt) - .map(|_| TypeRaw::read(&mut *input, header)) - .collect::>()?; - Ok(Self::NonRef { - effective_alignment, - taudt_bits, - members, - }) - } -} - -#[derive(Clone, Debug)] -pub enum Enum { - Ref { - ref_type: Box, - taenum_bits: TypeAttribute, - }, - NonRef { - group_sizes: Vec, - taenum_bits: TypeAttribute, - bte: u8, - members: Vec<(Option, u64)>, - bytesize: u64, - }, -} -impl Enum { - fn new(til: &TILSectionHeader, value: EnumRaw, fields: Option>) -> Result { - match value { - EnumRaw::Ref { - ref_type, - taenum_bits, - } => { - if matches!(&fields, Some(f) if !f.is_empty()) { - return Err(anyhow!("fields in a Ref Enum")); - } - Ok(Enum::Ref { - ref_type: Type::new(til, *ref_type, None).map(Box::new)?, - taenum_bits, - }) - } - EnumRaw::NonRef { - group_sizes, - taenum_bits, - bte, - members, - bytesize, - } => { - let members = associate_field_name_and_member(fields, members) - .context("Enum")? - .collect(); - Ok(Enum::NonRef { - group_sizes, - taenum_bits, - bte, - members, - bytesize, - }) - } - } - } -} - -#[derive(Clone, Debug)] -enum EnumRaw { - Ref { - ref_type: Box, - taenum_bits: TypeAttribute, - }, - NonRef { - group_sizes: Vec, - taenum_bits: TypeAttribute, - bte: u8, - members: Vec, - bytesize: u64, - }, -} - -impl EnumRaw { - fn read(input: &mut I, header: &TILSectionHeader) -> Result { - let Some(n) = read_dt_de(&mut *input)? else { - // is ref - let ref_type = TypeRaw::read_ref(&mut *input, header)?; - let taenum_bits = SDACL::read(&mut *input)?.0; - return Ok(EnumRaw::Ref { - ref_type: Box::new(ref_type), - taenum_bits, - }); - }; - - let taenum_bits = TAH::read(&mut *input)?.0; - let bte = bincode::deserialize_from(&mut *input)?; - let mut cur: u64 = 0; - let emsize = bte & flag::tf_enum::BTE_SIZE_MASK; - let bytesize: u32 = match emsize { - 0 if header.size_enum != 0 => header.size_enum.into(), - 0 => return Err(anyhow!("BTE emsize is 0 without header")), - 5..=7 => return Err(anyhow!("BTE emsize with reserved values")), - _ => 1u32 << (emsize - 1), - }; - - let mask: u64 = if bytesize >= 16 { - // is saturating valid? - //u64::MAX - return Err(anyhow!("Bytes size is too big")); - } else { - u64::MAX >> (u64::BITS - (bytesize * 8)) - }; - - let mut group_sizes = vec![]; - let mut members = vec![]; - for _ in 0..n { - let lo: u64 = read_de(&mut *input)?.into(); - let is_64 = (taenum_bits.0 & 0x0020) != 0; - let step = if is_64 { - let hi: u64 = read_de(&mut *input)?.into(); - (lo | (hi << 32)) & mask - } else { - lo & mask - }; - // TODO: subarrays - // https://www.hex-rays.com/products/ida/support/sdkdoc/group__tf__enum.html#ga9ae7aa54dbc597ec17cbb17555306a02 - if (bte & flag::tf_enum::BTE_BITFIELD) != 0 { - let group_size = read_dt(&mut *input)?; - group_sizes.push(group_size); - } - // TODO check is this is wrapping by default - let next_step = cur.wrapping_add(step); - cur = next_step; - members.push(cur); - } - Ok(EnumRaw::NonRef { - group_sizes, - taenum_bits, - bte, - members, - bytesize: bytesize.into(), - }) - } -} - -#[derive(Debug, Clone)] -pub struct Bitfield { - pub unsigned: bool, - pub width: u16, - pub nbytes: i32, -} - -impl Bitfield { - fn read(input: &mut I, metadata: u8) -> Result { - let nbytes = 1 << (metadata >> 4); - let dt = read_dt(&mut *input)?; - let width = dt >> 1; - let unsigned = (dt & 1) > 0; - let _tag = TAH::read(&mut *input)?; - Ok(Self { - unsigned, - width, - nbytes, - }) - } -} - -#[derive(Clone, Debug)] -pub struct StructMember { - pub name: Option, - pub member_type: Type, - pub sdacl: SDACL, -} - -impl StructMember { - fn new(til: &TILSectionHeader, name: Option, m: StructMemberRaw) -> Result { - Ok(Self { - name, - member_type: Type::new(til, m.0, None)?, - sdacl: m.1, - }) - } -} -#[derive(Clone, Debug)] -struct StructMemberRaw(pub TypeRaw, pub SDACL); -impl StructMemberRaw { - fn read(input: &mut I, header: &TILSectionHeader) -> Result { - let member_type = TypeRaw::read(&mut *input, header)?; - let sdacl = SDACL::read(&mut *input)?; - Ok(Self(member_type, sdacl)) - } -} - -#[derive(Debug, Clone)] -pub struct TILMacro { - pub name: String, - pub value: String, -} - -impl TILMacro { - fn read(input: &mut I) -> Result { - let name = read_c_string(&mut *input)?; - // TODO find what this is - let _flag: u16 = bincode::deserialize_from(&mut *input)?; - let value = read_c_string(&mut *input)?; - Ok(Self { name, value }) - } -} - -#[derive(Clone, Default, Debug)] -pub struct TypeMetadata(pub u8); -impl TypeMetadata { - fn new(value: u8) -> Self { - // TODO check for invalid values - Self(value) - } - fn read(input: I) -> Result { - Ok(Self::new(bincode::deserialize_from(input)?)) - } -} - -// TODO make those inner fields into enums or private -#[derive(Clone, Copy, Debug)] -pub struct BaseTypeFlag(pub u8); -#[derive(Clone, Copy, Debug)] -pub struct FullTypeFlag(pub u8); -#[derive(Clone, Copy, Debug)] -pub struct TypeFlag(pub u8); -#[derive(Clone, Copy, Debug)] -pub struct CallingConventionFlag(pub u8); - -#[derive(Clone, Copy, Debug)] -pub struct TypeAttribute(pub u16); -impl TypeAttribute { - fn read(input: &mut I) -> Result { - let mut val: u16 = 0; - let tah: u8 = bincode::deserialize_from(&mut *input)?; - let tmp = ((tah & 1) | ((tah >> 3) & 6)) + 1; - if tah == 0xFE || tmp == 8 { - if tmp == 8 { - val = tmp as u16; - } - let mut shift = 0; - loop { - let next_byte: u8 = bincode::deserialize_from(&mut *input)?; - if next_byte == 0 { - return Err(anyhow!("Failed to parse TypeAttribute")); - } - val |= ((next_byte & 0x7F) as u16) << shift; - if next_byte & 0x80 == 0 { - break; - } - shift += 7; - } - } - if (val & 0x0010) > 0 { - val = read_dt(&mut *input)?; - for _ in 0..val { - let _string = read_dt_string(&mut *input)?; - let another_de = read_dt(&mut *input)?; - let mut other_string = vec![0; another_de.into()]; - input.read_exact(&mut other_string)?; - } - } - Ok(TypeAttribute(val)) - } -} - -#[derive(Clone, Copy, Debug)] -pub struct TAH(pub TypeAttribute); -impl TAH { - fn read(input: &mut I) -> Result { - let Some(tah) = input.fill_buf()?.first().copied() else { - return Err(anyhow!(std::io::Error::new( - std::io::ErrorKind::UnexpectedEof, - "Unexpected EoF on DA" - ))); - }; - if tah == 0xFE { - Ok(Self(TypeAttribute::read(input)?)) - } else { - Ok(Self(TypeAttribute(0))) - } - } -} - -#[derive(Clone, Copy, Debug)] -pub struct SDACL(pub TypeAttribute); -impl SDACL { - fn read(input: &mut I) -> Result { - let Some(sdacl) = input.fill_buf()?.first().copied() else { - return Err(anyhow!(std::io::Error::new( - std::io::ErrorKind::UnexpectedEof, - "Unexpected EoF on SDACL" - ))); - }; - if ((sdacl & !0x30) ^ 0xC0) <= 0x01 { - Ok(Self(TypeAttribute::read(input)?)) - } else { - Ok(Self(TypeAttribute(0))) - } - } -} - -impl CallingConventionFlag { - fn is_spoiled(&self) -> bool { - self.0 == 0xA0 - } - - fn is_void_arg(&self) -> bool { - self.0 == 0x20 - } - - fn is_special_pe(&self) -> bool { - self.0 == 0xD0 || self.0 == 0xE0 || self.0 == 0xF0 - } -} - -impl TypeMetadata { - pub fn get_base_type_flag(&self) -> BaseTypeFlag { - BaseTypeFlag(self.0 & flag::tf_mask::TYPE_BASE_MASK) - } - - pub fn get_full_type_flag(&self) -> FullTypeFlag { - FullTypeFlag(self.0 & flag::tf_mask::TYPE_FULL_MASK) - } - - pub fn get_type_flag(&self) -> TypeFlag { - TypeFlag(self.0 & flag::tf_mask::TYPE_FLAGS_MASK) - } - - pub fn get_calling_convention(&self) -> CallingConventionFlag { - CallingConventionFlag(self.0 & 0xF0) - } -} - -fn read_dt_bytes(input: &mut I) -> Result> { - let buf_len = read_dt(&mut *input)?; - let mut buf = vec![0; buf_len.into()]; - input.read_exact(&mut buf)?; - Ok(buf) -} - -fn read_dt_string(input: &mut I) -> Result { - let buf = read_dt_bytes(input)?; - Ok(String::from_utf8(buf)?) -} - -/// Reads 1 to 5 bytes -/// Value Range: 0-0xFFFFFFFF -/// Usage: Enum Deltas -fn read_de(input: &mut I) -> std::io::Result { - let mut val: u32 = 0; - for _ in 0..5 { - let mut hi = val << 6; - let mut b = [0; 1]; - input.read_exact(&mut b)?; - let b: u32 = b[0].into(); - let sign = b & 0x80; - if sign == 0 { - let lo = b & 0x3F; - val = lo | hi; - return Ok(val); - } else { - let lo = 2 * hi; - hi = b & 0x7F; - val = lo | hi; - } - } - Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "Can't find the end of DE", - )) -} - -/// Reads 1 or 2 bytes. -/// Value Range: 0-0xFFFE -/// Usage: 16bit numbers -fn read_dt(input: &mut I) -> std::io::Result { - let mut value = [0u8; 1]; - input.read_exact(&mut value)?; - let value = value[0].into(); - - let value = match value { - 0 => { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "DT can't have 0 value", - )) - } - //SEG = 2 - value if value & 0x80 != 0 => { - let mut iter = [0u8; 1]; - input.read_exact(&mut iter)?; - let inter: u16 = iter[0].into(); - value & 0x7F | inter << 7 - } - //SEG = 1 - _ => value, - }; - Ok(value - 1) -} - -fn serialize_dt(value: u16) -> Result> { - if value > 0x7FFE { - return Err(anyhow!("Invalid value for DT")); - } - let lo = value + 1; - let mut hi = value + 1; - let mut result: Vec = Vec::with_capacity(2); - if lo > 127 { - result.push((lo & 0x7F | 0x80) as u8); - hi = (lo >> 7) & 0xFF; - } - result.push(hi as u8); - Ok(result) -} - -/// Reads 1 to 9 bytes. -/// ValueRange: 0-0x7FFFFFFF, 0-0xFFFFFFFF -/// Usage: Arrays -fn read_da(input: &mut I) -> Result<(u8, u8)> { - let mut a = 0; - let mut b = 0; - let mut da = 0; - let mut base = 0; - let mut nelem = 0; - // TODO check no more then 9 bytes are read - loop { - let Some(typ) = input.fill_buf()?.first().copied() else { - return Err(anyhow!(std::io::Error::new( - std::io::ErrorKind::UnexpectedEof, - "Unexpected EoF on DA" - ))); - }; - if typ & 0x80 == 0 { - break; - } - input.consume(1); - - da = (da << 7) | typ & 0x7F; - b += 1; - if b >= 4 { - let z: u8 = bincode::deserialize_from(&mut *input)?; - if z != 0 { - base = (da << 4) | z & 0xF - } - nelem = (z >> 4) & 7; - loop { - let Some(y) = input.fill_buf()?.first().copied() else { - return Err(anyhow!(std::io::Error::new( - std::io::ErrorKind::UnexpectedEof, - "Unexpected EoF on DA" - ))); - }; - if (y & 0x80) == 0 { - break; - } - input.consume(1); - nelem = (nelem << 7) | y & 0x7F; - a += 1; - if a >= 4 { - return Ok((nelem, base)); - } - } - } - } - Ok((nelem, base)) -} - -/// Reads 2 to 7 bytes. -/// Value Range: Nothing or 0-0xFFFF_FFFF -/// Usage: some kind of size -fn read_dt_de(input: &mut I) -> std::io::Result> { - match read_dt(&mut *input)? { - 0 => Ok(None), - 0x7FFE => read_de(&mut *input).map(Some), - n => Ok(Some(n.into())), - } -} - -fn associate_field_name_and_member( - fields: Option>, - members: Vec, -) -> Result, T)>> { - let fields_len: usize = fields.iter().filter(|t| !t.is_empty()).count(); - ensure!(fields_len <= members.len(), "More fields then members"); - // allow to have less fields then members, first fields will have names, others not - Ok(fields - .into_iter() - .flat_map(Vec::into_iter) - .map(Option::Some) - .chain(std::iter::repeat(None)) - .zip(members)) -} diff --git a/src/tools/decompress_til.rs b/src/tools/decompress_til.rs new file mode 100644 index 0000000..97162ce --- /dev/null +++ b/src/tools/decompress_til.rs @@ -0,0 +1,29 @@ +use std::fs::File; +use std::io::{BufReader, BufWriter, Write}; + +use anyhow::{anyhow, Result}; +use idb_rs::til::section::{TILSection, TILSizes}; +use idb_rs::IDBParser; + +use crate::{Args, DecompressTilArgs, FileType}; + +pub fn decompress_til(args: &Args, til_args: &DecompressTilArgs) -> Result<()> { + // parse the til sector/file + let til = match args.input_type() { + FileType::IDB => { + let input = BufReader::new(File::open(&args.input)?); + let mut parser = IDBParser::new(input)?; + let til_offset = parser + .til_section_offset() + .ok_or_else(|| anyhow!("IDB file don't contains a TIL sector"))?; + // TODO make decompress til public + todo!(); + } + FileType::TIL => { + let input = BufReader::new(File::open(&args.input)?); + // TODO make decompress til public + todo!(); + } + }; + todo!(); +} diff --git a/src/tools/dump_file_regions.rs b/src/tools/dump_file_regions.rs new file mode 100644 index 0000000..7c0649a --- /dev/null +++ b/src/tools/dump_file_regions.rs @@ -0,0 +1,34 @@ +use std::fs::File; +use std::io::BufReader; + +use crate::{Args, FileType}; + +use anyhow::{anyhow, Result}; +use idb_rs::IDBParser; + +pub fn dump_file_regions(args: &Args) -> Result<()> { + // parse the id0 sector/file + let id0 = match args.input_type() { + FileType::TIL => return Err(anyhow!("TIL don't contains any ID0 data")), + FileType::IDB => { + let input = BufReader::new(File::open(&args.input)?); + let mut parser = IDBParser::new(input)?; + let id0_offset = parser + .id0_section_offset() + .ok_or_else(|| anyhow!("IDB file don't contains a TIL sector"))?; + parser.read_id0_section(id0_offset)? + } + }; + + // TODO create a function for that in ida_info + let version = match id0.ida_info()? { + idb_rs::id0::IDBParam::V1(idb_rs::id0::IDBParam1 { version, .. }) => version, + idb_rs::id0::IDBParam::V2(idb_rs::id0::IDBParam2 { version, .. }) => version, + }; + println!("Segments AKA `$ fileregions`: "); + for entry in id0.file_regions(version)? { + println!(" {:x?}", entry?); + } + + Ok(()) +} diff --git a/src/tools/dump_functions.rs b/src/tools/dump_functions.rs new file mode 100644 index 0000000..95dad5a --- /dev/null +++ b/src/tools/dump_functions.rs @@ -0,0 +1,61 @@ +use std::fs::File; +use std::io::BufReader; + +use crate::{Args, FileType}; + +use anyhow::{anyhow, Result}; +use idb_rs::{id0::EntryPoint, IDBParser}; + +pub fn dump_functions(args: &Args) -> Result<()> { + // parse the id0 sector/file + let id0 = match args.input_type() { + FileType::TIL => return Err(anyhow!("TIL don't contains any ID0 data")), + FileType::IDB => { + let input = BufReader::new(File::open(&args.input)?); + let mut parser = IDBParser::new(input)?; + let id0_offset = parser + .id0_section_offset() + .ok_or_else(|| anyhow!("IDB file don't contains a TIL sector"))?; + parser.read_id0_section(id0_offset)? + } + }; + println!("Function and Comments AKA `$ funcs`: "); + for entry in id0.functions_and_comments()? { + match entry? { + idb_rs::id0::FunctionsAndComments::Name => {} + idb_rs::id0::FunctionsAndComments::Function(idbfunction) => { + println!( + " Function at {:#x}..{:#x}", + idbfunction.address.start, idbfunction.address.end + ); + } + idb_rs::id0::FunctionsAndComments::Comment { address, value } => { + println!(" Comment at {address:#x}: `{value}`",); + } + idb_rs::id0::FunctionsAndComments::RepeatableComment { address, value } => { + println!(" RepeatableComment at {address:#x}: `{value}`",); + } + idb_rs::id0::FunctionsAndComments::Unknown { .. } => {} + } + } + + println!(); + println!("Entry points, AKA `$ entry points`"); + for entry in id0.entry_points()? { + let EntryPoint { + name, + address, + forwarded, + entry_type, + } = entry; + print!(" {address:#x}:{name}"); + if let Some(forwarded) = forwarded { + print!(",forwarded:`{forwarded}`"); + } + if let Some(entry_type) = entry_type { + print!(",type:`{entry_type:?}`"); + } + println!(); + } + Ok(()) +} diff --git a/src/tools/dump_id0.rs b/src/tools/dump_id0.rs new file mode 100644 index 0000000..605fae4 --- /dev/null +++ b/src/tools/dump_id0.rs @@ -0,0 +1,23 @@ +use std::fs::File; +use std::io::BufReader; + +use crate::{Args, FileType}; + +use anyhow::{anyhow, Result}; +use idb_rs::IDBParser; + +pub fn dump_id0(args: &Args) -> Result<()> { + // parse the id0 sector/file + let id0 = match args.input_type() { + FileType::TIL => return Err(anyhow!("TIL don't contains any ID0 data")), + FileType::IDB => { + let input = BufReader::new(File::open(&args.input)?); + let mut parser = IDBParser::new(input)?; + let id0_offset = parser + .id0_section_offset() + .ok_or_else(|| anyhow!("IDB file don't contains a TIL sector"))?; + parser.read_id0_section(id0_offset)? + } + }; + todo!(); +} diff --git a/src/tools/dump_loader_name.rs b/src/tools/dump_loader_name.rs new file mode 100644 index 0000000..b896fd0 --- /dev/null +++ b/src/tools/dump_loader_name.rs @@ -0,0 +1,28 @@ +use std::fs::File; +use std::io::BufReader; + +use crate::{Args, FileType}; + +use anyhow::{anyhow, Result}; +use idb_rs::IDBParser; + +pub fn dump_loader_name(args: &Args) -> Result<()> { + // parse the id0 sector/file + let id0 = match args.input_type() { + FileType::TIL => return Err(anyhow!("TIL don't contains any ID0 data")), + FileType::IDB => { + let input = BufReader::new(File::open(&args.input)?); + let mut parser = IDBParser::new(input)?; + let id0_offset = parser + .id0_section_offset() + .ok_or_else(|| anyhow!("IDB file don't contains a TIL sector"))?; + parser.read_id0_section(id0_offset)? + } + }; + println!("Loader Name AKA `$ loader name`: "); + for name in id0.loader_name()? { + println!(" {}", name?); + } + + Ok(()) +} diff --git a/src/tools/dump_root_info.rs b/src/tools/dump_root_info.rs new file mode 100644 index 0000000..d0db413 --- /dev/null +++ b/src/tools/dump_root_info.rs @@ -0,0 +1,28 @@ +use std::fs::File; +use std::io::BufReader; + +use crate::{Args, FileType}; + +use anyhow::{anyhow, Result}; +use idb_rs::IDBParser; + +pub fn dump_root_info(args: &Args) -> Result<()> { + // parse the id0 sector/file + let id0 = match args.input_type() { + FileType::TIL => return Err(anyhow!("TIL don't contains any ID0 data")), + FileType::IDB => { + let input = BufReader::new(File::open(&args.input)?); + let mut parser = IDBParser::new(input)?; + let id0_offset = parser + .id0_section_offset() + .ok_or_else(|| anyhow!("IDB file don't contains a TIL sector"))?; + parser.read_id0_section(id0_offset)? + } + }; + println!("Segments AKA `Root Node`: "); + for entry in id0.root_info()? { + println!(" {:x?}", entry?); + } + + Ok(()) +} diff --git a/src/tools/dump_segments.rs b/src/tools/dump_segments.rs new file mode 100644 index 0000000..a075325 --- /dev/null +++ b/src/tools/dump_segments.rs @@ -0,0 +1,28 @@ +use std::fs::File; +use std::io::BufReader; + +use crate::{Args, FileType}; + +use anyhow::{anyhow, Result}; +use idb_rs::IDBParser; + +pub fn dump_segments(args: &Args) -> Result<()> { + // parse the id0 sector/file + let id0 = match args.input_type() { + FileType::TIL => return Err(anyhow!("TIL don't contains any ID0 data")), + FileType::IDB => { + let input = BufReader::new(File::open(&args.input)?); + let mut parser = IDBParser::new(input)?; + let id0_offset = parser + .id0_section_offset() + .ok_or_else(|| anyhow!("IDB file don't contains a TIL sector"))?; + parser.read_id0_section(id0_offset)? + } + }; + println!("Segments AKA `$ segs`: "); + for entry in id0.segments()? { + println!(" {:x?}", entry?); + } + + Ok(()) +} diff --git a/src/tools/dump_til.rs b/src/tools/dump_til.rs new file mode 100644 index 0000000..5cee9ec --- /dev/null +++ b/src/tools/dump_til.rs @@ -0,0 +1,93 @@ +use std::fs::File; +use std::io::BufReader; + +use anyhow::{anyhow, Result}; +use idb_rs::til::section::{TILSection, TILSizes}; +use idb_rs::til::TILMacro; +use idb_rs::IDBParser; + +use crate::{Args, FileType}; + +pub fn dump_til(args: &Args) -> Result<()> { + // parse the til sector/file + let til = match args.input_type() { + FileType::IDB => { + let input = BufReader::new(File::open(&args.input)?); + let mut parser = IDBParser::new(input)?; + let til_offset = parser + .til_section_offset() + .ok_or_else(|| anyhow!("IDB file don't contains a TIL sector"))?; + parser.read_til_section(til_offset)? + } + FileType::TIL => { + let input = BufReader::new(File::open(&args.input)?); + idb_rs::til::section::TILSection::parse(input)? + } + }; + + // this deconstruction is to changes on TILSection to force a review on this code + let TILSection { + format, + title, + description, + id, + cm, + def_align, + type_ordinal_numbers, + size_i, + size_b, + sizes, + size_long_double, + is_universal, + symbols, + types, + macros, + } = til; + // write the header info + println!("format: {format}"); + println!("title: {title}"); + println!("description: {description}"); + println!("id: {id}"); + println!("cm: {cm}"); + println!("def_align: {def_align}"); + println!("size_i: {size_i}"); + println!("size_b: {size_b}"); + println!("is_universal: {is_universal}"); + if let Some(type_ordinal_numbers) = type_ordinal_numbers { + println!("type_ordinal_numbers: {type_ordinal_numbers}"); + } + if let Some(size_long_double) = size_long_double { + println!("size_long_double: {size_long_double}"); + } + if let Some(TILSizes { + size_short, + size_long, + size_long_long, + }) = sizes + { + println!("size short: {size_short}"); + println!("size long: {size_long}"); + println!("size long_long: {size_long_long}"); + } + + // TODO implement Display for TILTypeInfo + println!("types:"); + for til_type in types { + println!(" {til_type:?}"); + } + println!("\nsymbols:"); + for til_type in symbols { + println!(" {til_type:?}"); + } + + if let Some(macros) = macros { + println!("\n------------------------------macros------------------------------"); + for TILMacro { name, value } in macros { + println!("------------------------------`{name}`------------------------------",); + println!("{value}"); + println!("------------------------------`{name}`-end------------------------------",); + } + println!("------------------------------macros-end------------------------------"); + } + Ok(()) +} diff --git a/src/tools/split_idb.rs b/src/tools/split_idb.rs new file mode 100644 index 0000000..5733ffc --- /dev/null +++ b/src/tools/split_idb.rs @@ -0,0 +1,19 @@ +use std::{fs::File, io::BufReader}; + +use crate::{Args, FileType, SplitIDBArgs}; + +use anyhow::{anyhow, Result}; +use idb_rs::IDBParser; + +pub fn split_idb(args: &Args, id0_args: &SplitIDBArgs) -> Result<()> { + // parse the id0 sector/file + let parser = match args.input_type() { + FileType::IDB => { + let input = BufReader::new(File::open(&args.input)?); + IDBParser::new(input)? + } + FileType::TIL => return Err(anyhow!("TIL don't contains any Sections")), + }; + // TODO implement uncompressed raw-section read capability + todo!(); +} diff --git a/src/tools/tools.rs b/src/tools/tools.rs new file mode 100644 index 0000000..0688ed5 --- /dev/null +++ b/src/tools/tools.rs @@ -0,0 +1,114 @@ +mod dump_til; +use dump_til::dump_til; +//mod dump_id0; +//use dump_id0::dump_id0; +//mod split_idb; +//use split_idb::split_idb; +//mod decompress_til; +//use decompress_til::decompress_til; +mod dump_functions; +use dump_functions::dump_functions; +mod dump_segments; +use dump_segments::dump_segments; +mod dump_loader_name; +use dump_loader_name::dump_loader_name; +mod dump_root_info; +use dump_root_info::dump_root_info; +mod dump_file_regions; +use dump_file_regions::dump_file_regions; + +use std::path::PathBuf; + +use anyhow::Result; +use clap::{Parser, Subcommand, ValueEnum}; + +/// Parse IDA files and output it's data +#[derive(Clone, Debug, Parser)] +struct Args { + /// input filename to parse + #[arg(short, long)] + input: PathBuf, + /// parse the filename using this format, if not specified use the input file ext, otherwise default to idb 32bits + #[arg(short, long, value_enum)] + force_type: Option, + // operation to execute + #[command(subcommand)] + operation: Operation, +} + +/// File type to parse +#[derive(Clone, Copy, Debug, ValueEnum)] +enum FileType { + /// IDB file + IDB, + // TODO verify if is necessary to parse standalone id0 files + ///// ID0 database file + //ID0, + /// TIL lib types file + TIL, +} + +/// File type to parse +#[derive(Clone, Debug, Subcommand)] +enum Operation { + DumpTil, + //DumpID0, + //SplitIDB(SplitIDBArgs), + //DecompressTil(DecompressTilArgs), + DumpFunctions, + DumpSegments, + DumpLoaderNames, + DumpRootInfo, + DumpFileRegions, +} + +///// Split the IDB file into it's decompressed sectors. Allow IDB and I64 files. +//#[derive(Clone, Debug, Parser)] +//struct SplitIDBArgs { +// /// output path, defaults to the input file path +// output_path: Option, +// /// output filename, defatuls to the input filename (without the extension) +// output_filename: Option, +//} + +///// Decompress the TIL into a uncompressed version of the TIL. Allow IDB, I64 and TIL files. +//#[derive(Clone, Debug, Parser)] +//struct DecompressTilArgs { +// /// output filename +// output: PathBuf, +//} + +impl Args { + pub fn input_type(&self) -> FileType { + if let Some(input_type) = self.force_type { + return input_type; + } + match self + .input + .extension() + .map(std::ffi::OsStr::to_str) + .flatten() + { + Some("idb") | Some("i64") => FileType::IDB, + Some("til") => FileType::TIL, + //Some("id0") => FileType::ID0, + _ => FileType::IDB, + } + } +} + +fn main() -> Result<()> { + let args = Args::parse(); + + match &args.operation { + Operation::DumpTil => dump_til(&args), + //Operation::DumpID0 => dump_id0(&args), + //Operation::SplitIDB(split_idbargs) => split_idb(&args, split_idbargs), + //Operation::DecompressTil(decompress_til_args) => decompress_til(&args, decompress_til_args), + Operation::DumpFunctions => dump_functions(&args), + Operation::DumpSegments => dump_segments(&args), + Operation::DumpLoaderNames => dump_loader_name(&args), + Operation::DumpRootInfo => dump_root_info(&args), + Operation::DumpFileRegions => dump_file_regions(&args), + } +}