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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
use std::io::{Read, Seek};
use crate::core::error::*;
use crate::core::seeker::*;
use crate::core::table::*;
use crate::core::util::*;
#[derive(Debug)]
/// Implementation of a MoPaQ archive viewer.
///
/// Refer to top-level documentation to see which features are supported.
///
/// Will work on any reader that implements `Read + Seek`.
pub struct Archive<R: Read + Seek> {
seeker: Seeker<R>,
hash_table: FileHashTable,
block_table: FileBlockTable,
}
impl<R: Read + Seek> Archive<R> {
/// Try to open an MPQ archive from the specified `reader`.
///
/// Immediately, this will perform the following:
///
/// 1. Locate an MPQ header.
/// 2. Locate and read the Hash Table.
/// 3. Locate and read the Block Table.
///
/// If any of these steps fail, the archive is deemed corrupted and
/// an appropriate error is returned.
///
/// No other operations will be performed.
pub fn open(reader: R) -> Result<Archive<R>, MpqError> {
let mut seeker = Seeker::new(reader)?;
let hash_table = FileHashTable::from_seeker(&mut seeker)?;
let block_table = FileBlockTable::from_seeker(&mut seeker)?;
Ok(Archive {
seeker,
hash_table,
block_table,
})
}
/// Read a file's contents.
///
/// Notably, the filename resolution algorithm
/// is case, and will treat backslashes (`\`) and forward slashes (`/`)
/// as different characters.
///
/// Does not support single-unit files or uncompressed files.
pub fn read_file(&mut self, name: &str) -> Result<Vec<u8>, MpqError> {
// find the hash entry and use it to find the block entry
let hash_entry = self
.hash_table
.find_entry(name)
.ok_or(MpqError::FileNotFound)?;
let block_entry = self
.block_table
.get(hash_entry.block_index as usize)
.ok_or(MpqError::FileNotFound)?;
// calculate the file key
let encryption_key = if block_entry.is_encrypted() {
Some(calculate_file_key(
name,
block_entry.file_pos as u32,
block_entry.uncompressed_size as u32,
block_entry.is_key_adjusted(),
))
} else {
None
};
// read the sector offsets
let sector_offsets = SectorOffsets::from_reader(
&mut self.seeker,
block_entry,
encryption_key.map(|k| k - 1),
)?;
// read out all the sectors
let sector_range = sector_offsets.all();
let raw_data = self.seeker.read(
block_entry.file_pos + u64::from(sector_range.0),
u64::from(sector_range.1),
)?;
let mut result = Vec::with_capacity(block_entry.uncompressed_size as usize);
let sector_size = self.seeker.info().sector_size;
let sector_count = sector_offsets.count();
let first_sector_offset = sector_offsets.one(0).unwrap().0;
for i in 0..sector_count {
let sector_offset = sector_offsets.one(i).unwrap();
let slice_start = (sector_offset.0 - first_sector_offset) as usize;
let slice_end = slice_start + sector_offset.1 as usize;
// if this is the last sector, then its size will be less than
// one archive sector size, so account for that
let uncompressed_size = if (i + 1) == sector_count {
let size = block_entry.uncompressed_size % sector_size;
if size == 0 {
sector_size
} else {
size
}
} else {
sector_size
};
// decode the block and append it to the final result buffer
let decoded_sector = decode_mpq_block(
&raw_data[slice_start..slice_end],
uncompressed_size,
encryption_key.map(|k| k + i as u32),
)?;
result.extend(decoded_sector.iter());
}
Ok(result)
}
/// If the archive contains a `(listfile)`, this will method
/// parse it and return a `Vec` containing all known filenames.
pub fn files(&mut self) -> Option<Vec<String>> {
let listfile = self.read_file("(listfile)").ok()?;
let mut list = Vec::new();
let mut line_start = 0;
for i in 0..listfile.len() {
let byte = listfile[i];
if byte == b'\r' || byte == b'\n' {
if i - line_start > 0 {
let line = &listfile[line_start..i];
let line = std::str::from_utf8(line);
if let Ok(line) = line {
list.push(line.to_string());
}
}
line_start = i + 1;
}
}
Some(list)
}
// Returns the start of the archive in the reader, which is the MPQ header,
// relative to the beginning of the reader.
pub fn start(&self) -> u64 {
self.seeker.info().header_offset
}
// Returns the end of the archive in the reader, relative to the beginning of the reader.
pub fn end(&self) -> u64 {
self.seeker.info().header_offset + self.seeker.info().archive_size
}
// Returns the size of the archive as specified in the MPQ header.
pub fn size(&self) -> u64 {
self.seeker.info().archive_size
}
// Returns a mutable reference to the underlying reader.
pub fn reader(&mut self) -> &mut R {
self.seeker.reader()
}
}