|
1 | | -use destiny_pkg::TagHash; |
2 | | -use eframe::egui::Ui; |
3 | | - |
4 | | -#[derive(Copy, Clone)] |
5 | | -enum DataViewMode { |
6 | | - Float, |
7 | | - Raw, |
8 | | - U32, |
9 | | -} |
| 1 | +use crate::package_manager::package_manager; |
| 2 | +use crate::references::REFERENCE_NAMES; |
| 3 | +use crate::swap_to_ne; |
| 4 | +use binrw::{binread, BinReaderExt, Endian}; |
| 5 | +use destiny_pkg::{PackageVersion, TagHash}; |
| 6 | +use eframe::egui; |
| 7 | +use eframe::egui::{vec2, Color32, Rgba, RichText, ScrollArea, Sense, Ui}; |
| 8 | +use itertools::Itertools; |
| 9 | +use std::io::{Cursor, Seek, SeekFrom}; |
10 | 10 |
|
11 | 11 | pub struct TagHexView { |
12 | 12 | data: Vec<u8>, |
| 13 | + rows: Vec<DataRow>, |
| 14 | + array_ranges: Vec<ArrayRange>, |
| 15 | + |
13 | 16 | mode: DataViewMode, |
| 17 | + detect_floats: bool, |
| 18 | + split_arrays: bool, |
14 | 19 | } |
15 | 20 |
|
16 | 21 | impl TagHexView { |
17 | | - pub fn new(data: Vec<u8>) -> Self { |
| 22 | + pub fn new(mut data: Vec<u8>) -> Self { |
| 23 | + // Pad data to an alignment of 16 bytes |
| 24 | + let remainder = data.len() % 16; |
| 25 | + if remainder != 0 { |
| 26 | + data.extend(vec![0; 16 - remainder]); |
| 27 | + } |
| 28 | + |
18 | 29 | Self { |
| 30 | + rows: data |
| 31 | + .chunks_exact(16) |
| 32 | + .map(|chunk| DataRow::from(<[u8; 16]>::try_from(chunk).unwrap())) |
| 33 | + .collect(), |
| 34 | + array_ranges: find_all_array_ranges(&data), |
19 | 35 | data, |
20 | | - mode: DataViewMode::Raw, |
| 36 | + mode: DataViewMode::Auto, |
| 37 | + detect_floats: true, |
| 38 | + split_arrays: true, |
21 | 39 | } |
22 | 40 | } |
23 | 41 |
|
24 | 42 | pub fn show(&mut self, ui: &mut Ui) -> Option<TagHash> { |
| 43 | + ScrollArea::vertical() |
| 44 | + .auto_shrink([false, false]) |
| 45 | + .show(ui, |ui| { |
| 46 | + if self.split_arrays && !self.array_ranges.is_empty() { |
| 47 | + let first_array_offset = self.array_ranges[0].start as usize; |
| 48 | + self.show_row_block(ui, &self.rows[..first_array_offset / 16], 0); |
| 49 | + |
| 50 | + for array in &self.array_ranges { |
| 51 | + ui.add_space(16.0); |
| 52 | + ui.horizontal(|ui| { |
| 53 | + let ref_label = REFERENCE_NAMES |
| 54 | + .read() |
| 55 | + .get(&array.class) |
| 56 | + .map(|s| format!("{s} ({:08X})", array.class)) |
| 57 | + .unwrap_or_else(|| format!("{:08X}", array.class)); |
| 58 | + |
| 59 | + ui.heading( |
| 60 | + RichText::new(format!( |
| 61 | + "Array {ref_label} ({} elements)", |
| 62 | + array.length |
| 63 | + )) |
| 64 | + .color(Color32::WHITE) |
| 65 | + .strong(), |
| 66 | + ); |
| 67 | + }); |
| 68 | + |
| 69 | + self.show_row_block( |
| 70 | + ui, |
| 71 | + &self.rows[array.data_start as usize / 16..array.end as usize / 16], |
| 72 | + array.data_start as usize, |
| 73 | + ); |
| 74 | + } |
| 75 | + } else { |
| 76 | + self.show_row_block(ui, &self.rows, 0); |
| 77 | + } |
| 78 | + }); |
| 79 | + |
25 | 80 | None |
26 | 81 | } |
| 82 | + |
| 83 | + fn show_row_block(&self, ui: &mut Ui, rows: &[DataRow], base_offset: usize) { |
| 84 | + for (i, row) in rows.iter().enumerate() { |
| 85 | + ui.horizontal(|ui| { |
| 86 | + ui.strong(format!("{:08X}:", base_offset + i * 16)); |
| 87 | + match row { |
| 88 | + DataRow::Raw(data) => { |
| 89 | + let string = data |
| 90 | + .chunks_exact(4) |
| 91 | + .map(|b| format!("{:02X} {:02X} {:02X} {:02X}", b[0], b[1], b[2], b[2])) |
| 92 | + .join(" "); |
| 93 | + ui.monospace(string); |
| 94 | + } |
| 95 | + DataRow::Float(data) => { |
| 96 | + let string = data.iter().map(|f| format!("{f:<11.2}")).join(" "); |
| 97 | + ui.monospace(string); |
| 98 | + |
| 99 | + if data.iter().all(|&v| v >= 0.0) { |
| 100 | + let needs_normalization = data.iter().any(|&v| v > 1.0); |
| 101 | + let floats = if needs_normalization { |
| 102 | + let factor = data.clone().into_iter().reduce(f32::max).unwrap(); |
| 103 | + [ |
| 104 | + data[0] / factor, |
| 105 | + data[1] / factor, |
| 106 | + data[2] / factor, |
| 107 | + data[3] / factor, |
| 108 | + ] |
| 109 | + } else { |
| 110 | + data.clone() |
| 111 | + }; |
| 112 | + |
| 113 | + let color = |
| 114 | + Rgba::from_rgb(floats[0].abs(), floats[1].abs(), floats[2].abs()); |
| 115 | + |
| 116 | + let (response, painter) = |
| 117 | + ui.allocate_painter(vec2(16.0, 16.0), Sense::hover()); |
| 118 | + |
| 119 | + painter.rect_filled(response.rect, 0.0, color); |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | + }); |
| 124 | + } |
| 125 | + } |
| 126 | +} |
| 127 | + |
| 128 | +#[derive(Copy, Clone)] |
| 129 | +enum DataViewMode { |
| 130 | + Auto, |
| 131 | + Raw, |
| 132 | + Float, |
| 133 | + U32, |
| 134 | +} |
| 135 | + |
| 136 | +enum DataRow { |
| 137 | + Raw([u8; 16]), |
| 138 | + Float([f32; 4]), |
| 139 | + // U32([u32; 4]), |
| 140 | +} |
| 141 | + |
| 142 | +impl From<[u8; 16]> for DataRow { |
| 143 | + fn from(data: [u8; 16]) -> Self { |
| 144 | + let from_xe_bytes = if package_manager().version.endian() == Endian::Big { |
| 145 | + f32::from_be_bytes |
| 146 | + } else { |
| 147 | + f32::from_le_bytes |
| 148 | + }; |
| 149 | + |
| 150 | + let floats = [ |
| 151 | + from_xe_bytes(data[0..4].try_into().unwrap()), |
| 152 | + from_xe_bytes(data[4..8].try_into().unwrap()), |
| 153 | + from_xe_bytes(data[8..12].try_into().unwrap()), |
| 154 | + from_xe_bytes(data[12..16].try_into().unwrap()), |
| 155 | + ]; |
| 156 | + |
| 157 | + let mut all_valid_floats = floats |
| 158 | + .iter() |
| 159 | + .all(|&v| (v.is_normal() && v.abs() < 1e7 && v.abs() > 1e-10) || v == 0.0); |
| 160 | + if floats.iter().all(|&v| v == 0.0) { |
| 161 | + all_valid_floats = false; |
| 162 | + } |
| 163 | + |
| 164 | + if all_valid_floats { |
| 165 | + DataRow::Float(floats) |
| 166 | + } else { |
| 167 | + DataRow::Raw(data) |
| 168 | + } |
| 169 | + } |
| 170 | +} |
| 171 | + |
| 172 | +#[derive(Debug)] |
| 173 | +struct ArrayRange { |
| 174 | + /// Start of array header |
| 175 | + start: u64, |
| 176 | + /// Start of array data |
| 177 | + data_start: u64, |
| 178 | + end: u64, |
| 179 | + |
| 180 | + class: u32, |
| 181 | + length: u64, |
| 182 | +} |
| 183 | + |
| 184 | +fn find_all_array_ranges(data: &[u8]) -> Vec<ArrayRange> { |
| 185 | + let mut cur = Cursor::new(data); |
| 186 | + let endian = package_manager().version.endian(); |
| 187 | + |
| 188 | + let mut data_chunks_u32 = vec![0u32; data.len() / 4]; |
| 189 | + |
| 190 | + unsafe { |
| 191 | + std::ptr::copy_nonoverlapping( |
| 192 | + data.as_ptr(), |
| 193 | + data_chunks_u32.as_mut_ptr() as *mut u8, |
| 194 | + data_chunks_u32.len() * 4, |
| 195 | + ); |
| 196 | + } |
| 197 | + |
| 198 | + for value in data_chunks_u32.iter_mut() { |
| 199 | + *value = swap_to_ne!(*value, endian); |
| 200 | + } |
| 201 | + |
| 202 | + let mut array_offsets = vec![]; |
| 203 | + for (i, &value) in data_chunks_u32.iter().enumerate() { |
| 204 | + let offset = i as u64 * 4; |
| 205 | + |
| 206 | + if matches!(value, 0x80809fb8 | 0x80800184 | 0x80800142) { |
| 207 | + array_offsets.push(offset + 4); |
| 208 | + } |
| 209 | + } |
| 210 | + |
| 211 | + let arrays: Vec<(u64, TagArrayHeader)> = if matches!( |
| 212 | + package_manager().version, |
| 213 | + PackageVersion::DestinyInternalAlpha | PackageVersion::DestinyTheTakenKing |
| 214 | + ) { |
| 215 | + array_offsets |
| 216 | + .into_iter() |
| 217 | + .filter_map(|o| { |
| 218 | + cur.seek(SeekFrom::Start(o)).ok()?; |
| 219 | + Some(( |
| 220 | + o, |
| 221 | + TagArrayHeader { |
| 222 | + count: cur.read_be::<u32>().ok()? as _, |
| 223 | + tagtype: cur.read_be::<u32>().ok()?, |
| 224 | + }, |
| 225 | + )) |
| 226 | + }) |
| 227 | + .collect_vec() |
| 228 | + } else { |
| 229 | + array_offsets |
| 230 | + .into_iter() |
| 231 | + .filter_map(|o| { |
| 232 | + cur.seek(SeekFrom::Start(o)).ok()?; |
| 233 | + Some((o, cur.read_le().ok()?)) |
| 234 | + }) |
| 235 | + .collect_vec() |
| 236 | + }; |
| 237 | + |
| 238 | + let mut array_ranges = vec![]; |
| 239 | + |
| 240 | + let file_end = data.len() as u64; |
| 241 | + for (offset, header) in arrays { |
| 242 | + let start = offset; |
| 243 | + let data_start = offset + 16; |
| 244 | + |
| 245 | + array_ranges.push(ArrayRange { |
| 246 | + start, |
| 247 | + data_start, |
| 248 | + end: file_end, |
| 249 | + class: header.tagtype, |
| 250 | + length: header.count, |
| 251 | + }) |
| 252 | + } |
| 253 | + |
| 254 | + for i in 0..(array_ranges.len().max(1) - 1) { |
| 255 | + let next_start = array_ranges.get(i + 1).map(|r| r.start).unwrap_or(file_end); |
| 256 | + array_ranges[i].end = next_start; |
| 257 | + } |
| 258 | + |
| 259 | + array_ranges |
| 260 | +} |
| 261 | + |
| 262 | +#[binread] |
| 263 | +struct TagArrayHeader { |
| 264 | + pub count: u64, |
| 265 | + pub tagtype: u32, |
27 | 266 | } |
0 commit comments