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}