dex_checksum_tools/
lib.rs

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