-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathmod.rs
166 lines (143 loc) · 5.76 KB
/
mod.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
161
162
163
164
165
166
use std::collections::HashMap;
use std::ffi::CStr;
use std::fs::File;
use std::io::{Read, Seek};
use crate::tracks::{get_track_id_by_filename, TrackId};
use crate::utils::copy_bytes_from_file;
use crate::JukeboxError::*;
use crate::Result;
mod ciso;
#[derive(Debug, Clone, Copy)]
enum IsoKind {
Standard,
Ciso,
Unknown,
}
/// Produces a hashmap containing offsets and sizes of .hps files contained within the iso.
/// These can be looked up by TrackId
///
/// e.g.
/// `TrackId => (offset in the iso, file size)`
pub(crate) fn create_track_map(iso_path: &str) -> Result<HashMap<TrackId, (u64, usize)>> {
const RAW_FST_LOCATION_OFFSET: u64 = 0x424;
const RAW_FST_SIZE_OFFSET: u64 = 0x428;
const FST_ENTRY_SIZE: usize = 0xC;
// `get_true_offset` is a fn that takes an offset for a standard disc image, and
// returns it's _true_ offset (which differs between standard and ciso)
let get_true_offset = create_offset_locator_fn(iso_path)?;
let mut iso = File::open(iso_path)?;
// Filesystem Table (FST)
let fst_location_offset =
get_true_offset(RAW_FST_LOCATION_OFFSET).ok_or(FstParse("FST location offset is missing from the ISO".to_string()))?;
let fst_size_offset =
get_true_offset(RAW_FST_SIZE_OFFSET).ok_or(FstParse("FST size offset is missing from the ISO".to_string()))?;
let fst_location = u32::from_be_bytes(
copy_bytes_from_file(&mut iso, fst_location_offset as u64, 0x4)?
.try_into()
.map_err(|_| FstParse("Unable to read FST offset as u32".to_string()))?,
);
let fst_location =
get_true_offset(fst_location as u64).ok_or(FstParse("FST location is missing from the ISO".to_string()))?;
if fst_location <= 0 {
return Err(FstParse("FST location is 0".to_string()));
}
let fst_size = u32::from_be_bytes(
copy_bytes_from_file(&mut iso, fst_size_offset as u64, 0x4)?
.try_into()
.map_err(|_| FstParse("Unable to read FST size as u32".to_string()))?,
);
let fst = copy_bytes_from_file(&mut iso, fst_location as u64, fst_size as usize)?;
// FST String Table
let str_table_offset = read_u32(&fst, 0x8)? as usize * FST_ENTRY_SIZE;
// Collect the .hps file metadata in the FST into a hash map
Ok(fst[..str_table_offset]
.chunks(FST_ENTRY_SIZE)
.filter_map(|entry| {
let is_file = entry[0] == 0;
let name_offset = str_table_offset + read_u24(entry, 0x1).ok()? as usize;
let offset = read_u32(entry, 0x4).ok()? as u64;
let size = read_u32(entry, 0x8).ok()? as usize;
let name = CStr::from_bytes_until_nul(&fst[name_offset..]).ok()?.to_str().ok()?;
if is_file && name.ends_with(".hps") {
match get_track_id_by_filename(&name) {
Some(track_id) => {
let offset = get_true_offset(offset)?;
Some((track_id, (offset, size)))
},
None => None,
}
} else {
None
}
})
.collect())
}
/// Get an unsigned 24 bit integer from a byte slice
fn read_u24(bytes: &[u8], offset: usize) -> Result<u32> {
let size = 3;
let end = offset + size;
let mut padded_bytes = [0; 4];
let slice = &bytes
.get(offset..end)
.ok_or(FstParse("Too few bytes to read u24".to_string()))?;
padded_bytes[1..4].copy_from_slice(slice);
Ok(u32::from_be_bytes(padded_bytes))
}
/// Get an unsigned 32 bit integer from a byte slice
fn read_u32(bytes: &[u8], offset: usize) -> Result<u32> {
let size = (u32::BITS / 8) as usize;
let end: usize = offset + size;
Ok(u32::from_be_bytes(
bytes
.get(offset..end)
.ok_or(FstParse("Too few bytes to read u32".to_string()))?
.try_into()
.unwrap_or_else(|_| unreachable!("u32::BITS / 8 is always 4")),
))
}
/// Given an iso file, determine what kind it is
fn get_iso_kind(iso: &mut File) -> Result<IsoKind> {
// Get the first four bytes
iso.rewind().map_err(IsoSeek)?;
let mut initial_bytes = [0; 4];
iso.read_exact(&mut initial_bytes).map_err(IsoRead)?;
// Get the four bytes at 0x1c
iso.seek(std::io::SeekFrom::Start(0x1c)).map_err(IsoSeek)?;
let mut dvd_magic_bytes = [0; 4];
iso.read_exact(&mut dvd_magic_bytes).map_err(IsoRead)?;
match (initial_bytes, dvd_magic_bytes) {
// DVD Magic Word
(_, [0xc2, 0x33, 0x9F, 0x3D]) => Ok(IsoKind::Standard),
// CISO header
([0x43, 0x49, 0x53, 0x4F], _) => Ok(IsoKind::Ciso),
_ => Ok(IsoKind::Unknown),
}
}
/// When we want to read data from any given iso file, but we only know the
/// offset for a standard disc image, we need a way to be able to get the
/// _actual_ offset for the file we have on hand.
///
/// This can vary depending on the kind of disc image that we are dealing with
/// (standard vs ciso, for example)
///
/// This HoF returns a fn that can be used to locate the true offset.
///
/// Example Usage:
/// ```ignore
/// let get_true_offset = create_offset_locator_fn("/foo/bar.iso");
/// let offset = get_true_offset(0x424);
/// ```
fn create_offset_locator_fn(iso_path: &str) -> Result<impl Fn(u64) -> Option<u64> + '_> {
let mut iso = File::open(iso_path)?;
// Get the ciso header (block size and block map) of the provided file.
// If the file is not a ciso, this will be `None`
let ciso_header = match get_iso_kind(&mut iso)? {
IsoKind::Standard => None,
IsoKind::Ciso => ciso::get_ciso_header(&mut iso)?,
IsoKind::Unknown => return Err(UnsupportedIso),
};
Ok(move |offset| match ciso_header {
Some(ciso_header) => ciso::get_ciso_offset(&ciso_header, offset),
None => Some(offset),
})
}