dex_checksum_tools/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
use adler32::adler32;
use std::fmt::Display;
use std::fs::File;
use std::io;
use std::io::{Read, Write};
/// A `Dex` structure that holds the bytes of a DEX (Dalvik Executable) file.
///
/// # Fields
/// * `bytes` - A vector of bytes representing the contents of the DEX file.
///
/// # Examples
///
/// ```
/// use dex_checksum_tools::Dex;
///
/// if let Ok(mut dex) = Dex::try_from("/path/to/incorrect.dex") {
/// println!("Before Correcting: current_checksum {:?}", dex.current_checksum());
/// println!("Before Correcting: check_checksum {:?}", dex.check_checksum());
/// println!("Before Correcting: expect_checksum {:?}", dex.expect_checksum());
/// dex.correct_checksum();
/// println!("After Correcting: current_checksum {:?}", dex.current_checksum());
/// println!("After Correcting: check_checksum {:?}", dex.check_checksum());
/// println!("After Correcting: expect_checksum {:?}", dex.expect_checksum());
/// match dex.write_to_file("/path/to/correct.dex") {
/// Ok(_) => println!("Successfully wrote to file!"),
/// Err(e) => println!("Failed to write to file: {}", e),
/// }
/// }
/// ````
#[derive(Debug)]
pub struct Dex {
bytes: Vec<u8>,
}
impl Dex {
/// Calculates the current checksum from the DEX file's header.
///
/// This method extracts the checksum bytes that are stored at offset 8 through 11 in the DEX file header
/// and converts them into a 4-byte array. The checksum is a part of the file's integrity verification
/// and should match the expected checksum calculated over the rest of the file.
///
/// # Returns
/// A 4-byte array representing the checksum stored in the DEX file header.
///
/// # Panics
/// Panics if the slice of bytes cannot be converted into an array, which indicates an issue with the DEX file format.
pub fn current_checksum(&self) -> [u8; 4] {
self.bytes[8..12]
.try_into()
.expect("Could not convert slice to array!")
}
/// Calculates the expected checksum for the DEX file.
///
/// This method computes the Adler-32 checksum for the data part of the DEX file
/// starting from byte 12 to the end of the file. The Adler-32 checksum is used
/// for error-checking of the data. It should match the current checksum in the
/// file header for the file to be considered valid.
///
/// # Returns
/// A 4-byte array representing the expected checksum for the DEX file.
///
/// # Errors
/// Returns an error if the Adler-32 checksum cannot be calculated.
pub fn expect_checksum(&self) -> [u8; 4] {
let mut hash = adler32(&self.bytes[12..]).expect("Unable to calculate adler32 checksum!");
let mut buffer: [u8; 4] = [0; 4];
for i in (0..4).rev() {
buffer[i] = (hash % 256) as u8;
hash >>= 8;
}
buffer.reverse();
buffer
}
/// Checks if the current checksum matches the expected checksum.
///
/// This method compares the current checksum from the file's header
/// with the expected checksum calculated over the data part of the file.
///
/// # Returns
/// - `true` if the checksums match.
/// - `false` otherwise.
pub fn check_checksum(&self) -> bool {
self.current_checksum() == self.expect_checksum()
}
/// Corrects the checksum in the DEX file header if it does not match the expected checksum.
///
/// This method calculates the expected checksum using the `expect_checksum` method
/// and updates the bytes in the DEX file header if the current checksum is incorrect.
/// After calling this method, the checksum in the file header should match the
/// expected checksum for the data part of the DEX file.
///
/// # Returns
/// - `true` if the checksum was uncorrected.
/// - `false` otherwise.
pub fn correct_checksum(&mut self) -> bool {
let expect = self.expect_checksum();
if self.current_checksum() != expect {
self.bytes[8..12].copy_from_slice(&expect);
true
} else {
false
}
}
/// Writes the DEX file's bytes to the specified path.
///
/// This function creates a new file at the given `path` and writes the
/// bytes representing the DEX file to it. If the file already exists,
/// it will be truncated before writing.
///
/// # Arguments
///
/// * `path` - A string slice that holds the path where the file will be written.
///
/// # Returns
///
/// An `io::Result<()>` which is `Ok` if the file was written successfully,
/// or an `Err` with more information if the file could not be written.
pub fn write_to_file(&self, path: &str) -> io::Result<()> {
File::create(path)?.write_all(&self.bytes)
}
}
impl Display for Dex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Dex {{ bytes: {:?} }}", self.bytes)
}
}
impl TryFrom<File> for Dex {
type Error = io::Error;
fn try_from(mut f: File) -> Result<Self, Self::Error> {
let mut bytes = Vec::<u8>::new();
f.read_to_end(&mut bytes).map(|_| Dex { bytes })
}
}
impl TryFrom<String> for Dex {
type Error = io::Error;
fn try_from(path: String) -> Result<Self, Self::Error> {
match File::open(path) {
Ok(f) => Dex::try_from(f),
Err(e) => Err(e),
}
}
}
impl TryFrom<&str> for Dex {
type Error = io::Error;
fn try_from(path: &str) -> Result<Self, Self::Error> {
Dex::try_from(String::from(path))
}
}