Skip to content

Commit 8440345

Browse files
committed
full GIM and BIP support
1 parent f834cda commit 8440345

File tree

15 files changed

+459
-163
lines changed

15 files changed

+459
-163
lines changed

kidfile-explorer/src/batch_decode.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,7 @@ impl BatchDecode {
145145
}
146146

147147
fn clean_threads(&mut self) {
148-
let mut to_remove = Vec::new();
149-
for (i, thread) in self.threads.iter().enumerate().rev() {
150-
if thread.is_finished() {
151-
to_remove.push(i);
152-
}
153-
}
154-
for i in to_remove {
155-
self.threads.swap_remove(i);
156-
}
148+
self.threads.retain(|thread| !thread.is_finished());
157149
}
158150

159151
pub fn show(&mut self, ctx: &Context) -> bool {

kidfile-explorer/src/data_view.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::ffi::OsString;
12
use egui::{Align, ColorImage, Layout, ScrollArea, TextEdit, TextureHandle, Ui, Vec2};
23
use kidfile::{file_data::FileData, image::PixelFormat};
34

@@ -6,6 +7,8 @@ use crate::icon_button;
67
pub enum DataView {
78
None,
89
Raw {
10+
file_data: FileData,
11+
file_name: OsString,
912
hex: String,
1013
error_msg: String,
1114
reset_view: bool
@@ -14,7 +17,7 @@ pub enum DataView {
1417
}
1518

1619
impl DataView {
17-
pub fn new_raw(file_data: &mut FileData, error_msg: String) -> Self {
20+
pub fn new_raw(mut file_data: FileData, file_name: OsString, error_msg: String) -> Self {
1821
const MAX_LEN: usize = 2048;
1922
const BYTES_IN_LINE: usize = 16;
2023
let mut buf = unsafe {Box::new_uninit_slice(file_data.len().min(MAX_LEN)).assume_init()};
@@ -48,18 +51,25 @@ impl DataView {
4851
} else {
4952
hex += &format!("file displayed in full, size is 0x{:X}/{}", file_data.len(), file_data.len());
5053
}
51-
Self::Raw {hex, error_msg, reset_view: true}
54+
Self::Raw {file_data, file_name, hex, error_msg, reset_view: true}
5255
} else {
53-
Self::Raw {hex: "<error reading file>".into(), error_msg, reset_view: true}
56+
Self::Raw {file_data, file_name, hex: "<error reading file>".into(), error_msg, reset_view: true}
5457
}
5558
}
5659

5760
pub fn ui(&mut self, ui: &mut Ui) {
5861
match self {
5962
Self::None => {}
60-
Self::Raw {hex, error_msg, reset_view} => {
63+
Self::Raw {file_data, file_name, hex, error_msg, reset_view} => {
6164
ui.vertical(|ui| {
62-
ui.label(error_msg.as_str());
65+
ui.horizontal(|ui| {
66+
ui.label(error_msg.as_str());
67+
if ui.add(icon_button!("icons/edit-download.svg").small()).on_hover_text("Save").clicked() {
68+
if let Some(dst) = rfd::FileDialog::new().set_file_name(file_name.to_string_lossy()).save_file() {
69+
std::fs::write(dst, file_data.read()).unwrap();
70+
}
71+
}
72+
});
6373
ScrollArea::both().id_salt("hex").show(ui, |ui| {
6474
if *reset_view {
6575
ui.scroll_to_cursor(Some(Align::Min));
@@ -75,13 +85,14 @@ impl DataView {
7585
});
7686
}
7787
Self::Image(frames) => {
78-
const OTHER_FRAMES_WIDTH: f32 = 128.0;
7988
ui.with_layout(Layout::right_to_left(Align::TOP), |ui| {
8089
if frames.len() > 1 {
90+
const OTHER_FRAMES_WIDTH: f32 = 160.0;
91+
const OTHER_FRAMES_HEIGHT: f32 = 128.0;
8192
ui.allocate_ui(Vec2::new(OTHER_FRAMES_WIDTH, ui.available_height()), |ui| {
8293
ScrollArea::vertical().show(ui, |ui| {
8394
ui.set_min_width(OTHER_FRAMES_WIDTH);
84-
ui.vertical(|ui| {
95+
ui.vertical_centered_justified(|ui| {
8596
for (frame_idx, (egui_img, tex, fmt)) in frames.iter().enumerate().skip(1) {
8697
if frame_idx >= 2 {
8798
ui.separator();
@@ -92,7 +103,7 @@ impl DataView {
92103
ui.ctx().copy_image(egui_img.clone());
93104
}
94105
});
95-
ui.add(egui::Image::new(tex).fit_to_exact_size(Vec2::new(OTHER_FRAMES_WIDTH, OTHER_FRAMES_WIDTH)));
106+
ui.add(egui::Image::new(tex).fit_to_exact_size(Vec2::new(OTHER_FRAMES_WIDTH, OTHER_FRAMES_HEIGHT)));
96107
}
97108
});
98109
});

kidfile-explorer/src/main.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use complex_path::ComplexPath;
66
use data_view::DataView;
77
use egui::{epaint::text::{FontInsert, FontPriority, InsertFontFamily}, popup, vec2, Align, Button, CentralPanel, Context, FontData, FontFamily, Grid, Key, Label, Layout, Modifiers, PopupCloseBehavior, Pos2, Rect, ScrollArea, Separator, TextStyle, TextWrapMode, TextureOptions, TopBottomPanel, Ui, UiBuilder, Vec2, ViewportBuilder, Visuals};
88
use egui_dock::{DockArea, DockState, NodeIndex, SurfaceIndex, TabAddAlign, TabViewer};
9-
use kidfile::{auto_decode_full, file_data::FileData, DynData};
9+
use kidfile::{auto_decode_full, DynData};
1010
use rfd::FileDialog;
1111
use serde_json::Value;
1212

@@ -129,7 +129,7 @@ struct ExplorerTab {
129129
pub id: usize,
130130
pub path: ComplexPath,
131131
pub children: Vec<ExplorerEntry>,
132-
pub selection: Option<(usize, DynData, Vec<&'static str>)>,
132+
pub selection: Option<(usize, Vec<&'static str>)>,
133133
pub view: DataView,
134134
pub selection_size: Option<usize>,
135135
pub past: Vec<ComplexPath>,
@@ -180,16 +180,16 @@ impl ExplorerTab {
180180
let in_archive = self.path.get_archive_format();
181181
if let Ok(mut file_data) = self.path.load_file(&c.name) {
182182
let mut len = file_data.len();
183-
let mut decoded = auto_decode_full(file_data.to_mut(), in_archive);
183+
let decoded = auto_decode_full(file_data.to_mut(), in_archive);
184184
match decoded.data {
185-
DynData::Raw(ref mut raw_data) => {
185+
DynData::Raw(raw_data) => {
186186
let msg = if decoded.steps_taken.is_empty() {
187187
format!("no steps taken; {}", decoded.error_msg)
188188
} else {
189189
format!("steps taken: {}; {}", decoded.steps_taken.join(" -> "), decoded.error_msg)
190190
};
191-
self.view = DataView::new_raw(raw_data, msg);
192191
len = raw_data.len();
192+
self.view = DataView::new_raw(raw_data, c.name.clone(), msg);
193193
}
194194
DynData::Image(ref img) =>{
195195
let mut frames = Vec::new();
@@ -206,7 +206,7 @@ impl ExplorerTab {
206206
return;
207207
}
208208
}
209-
self.selection = Some((idx, decoded.data, decoded.steps_taken));
209+
self.selection = Some((idx, decoded.steps_taken));
210210
self.selection_size = Some(len);
211211
}
212212
break;
@@ -225,7 +225,7 @@ impl ExplorerTab {
225225

226226
pub fn select(&mut self, ctx: &Context, idx: usize) {
227227
if idx < self.children.len() {
228-
self.selection = Some((idx, DynData::Raw(FileData::Memory {buf: Box::new([])}), Vec::new()));
228+
self.selection = Some((idx, Vec::new()));
229229
self.refresh(ctx);
230230
} else {
231231
self.selection = None;

kidfile/src/archive_formats/afs.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,42 @@
1-
use crate::{file_data::FileData, Certainty, Decoder};
1+
use crate::{Certainty, Decoder};
22
use super::{Archive, ArchiveEntry};
33

44
pub const ENTRY_AFS: Decoder<Archive> = Decoder {
55
id: "afs",
66
desc: "CRI AFS archive used in most KID games",
77
detect: |file| Certainty::certain_if(file.starts_with(b"AFS\0")),
88
decode: |file| {
9-
let count = file.read_u32_field(4, "entry count")? as usize;
9+
let count = file.read_u32(4)? as usize;
1010
if count >= 0xFFFF {
1111
return Err("impossibly large entry count".into());
1212
}
13+
let mut entry_ranges = Vec::with_capacity(count);
1314
let mut entries = Vec::with_capacity(count);
1415
let mut end = 0;
1516
for i in 0..count {
16-
let offset = file.read_u32_field(8 + i * 8, "entry offset")?;
17-
let len = file.read_u32_field(12 + i * 8, "entry length")?;
17+
let offset = file.read_u32(8 + i * 8)? as usize;
18+
let len = file.read_u32(12 + i * 8)? as usize;
1819
end = end.max(offset + len);
19-
entries.push(ArchiveEntry {
20-
name: String::new(),
21-
data: file.subfile(offset as usize, len as usize).unwrap(),
22-
timestamp: None
23-
});
20+
entry_ranges.push((offset, len));
2421
}
2522
end = end.next_multiple_of(0x800);
2623
for i in 0..count {
2724
let pos = end as usize + i * 48;
2825
let mut name_buf = [0u8; 32];
2926
file.read_chunk_exact(&mut name_buf, pos).map_err(|_| "could not read entry name")?;
3027
let len = name_buf.iter().position(|x| *x == 0).unwrap_or(32);
31-
entries[i].name = std::str::from_utf8(&name_buf[0..len]).map_err(|_| "entry name is not valid UTF-8")?.into();
32-
let year = file.read_u16_field(pos + 32, "entry timestamp")?;
33-
let month = file.read_u16_field(pos + 34, "entry timestamp")?;
34-
let day = file.read_u16_field(pos + 36, "entry timestamp")?;
35-
let hour = file.read_u16_field(pos + 38, "entry timestamp")?;
36-
let minute = file.read_u16_field(pos + 40, "entry timestamp")?;
37-
let second = file.read_u16_field(pos + 42, "entry timestamp")?;
38-
entries[i].timestamp = Some((year, month, day, hour, minute, second));
28+
let year = file.read_u16(pos + 32)?;
29+
let month = file.read_u16(pos + 34)?;
30+
let day = file.read_u16(pos + 36)?;
31+
let hour = file.read_u16(pos + 38)?;
32+
let minute = file.read_u16(pos + 40)?;
33+
let second = file.read_u16(pos + 42)?;
34+
let name = String::from_utf8(name_buf[0..len].to_vec()).map_err(|_| "entry name is not valid UTF-8")?;
35+
entries.push(ArchiveEntry {
36+
name: name.clone(),
37+
data: file.subfile(entry_ranges[i].0, entry_ranges[i].1, name.into()).unwrap(),
38+
timestamp: Some((year, month, day, hour, minute, second))
39+
});
3940
}
4041
Ok(Archive {format: "afs", entries: entries.into()})
4142
}

kidfile/src/archive_formats/concat2k.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@ use crate::{Certainty, Decoder};
22
use super::{Archive, ArchiveEntry};
33

44
// it's annoying that this has to exist, but here we go
5-
// this is a decoder that detects concatenated files based on the heuristic of...
6-
// finding 2048 byte boundaries following an amount of null bytes and at least one non-null byte in the next bytes
7-
// really hoping that doesn't cause false positives
5+
// files on N7 PS2 are literally just aligned and concatenated, without even file name info
6+
// here we simply use the heuristic of checking for TIM2 and OGDT signatures every 2048 bytes
87

98
const ALIGNMENT: usize = 2048;
109

1110
pub const ENTRY_CONCAT2K: Decoder<Archive> = Decoder {
1211
id: "concat2k",
13-
desc: "Not an actual format, just concatenated files aligned to 2048 bytes",
14-
detect: |file| Certainty::possible_if(file.len() > ALIGNMENT * 2 && !file.starts_with(b"\x7FELF") && !file.starts_with(b"\0\0\x01\xBA") && {
12+
desc: "Not an actual format, just concatenated images aligned to 2048 bytes",
13+
detect: |file| Certainty::possible_if(file.len() > ALIGNMENT * 2 && !file.starts_with(b"\0\0\x01\xBA") && {
1514
let mut boundary = ALIGNMENT;
1615
loop {
1716
let mut check_buf = [0u8; 8];
@@ -31,9 +30,10 @@ pub const ENTRY_CONCAT2K: Decoder<Archive> = Decoder {
3130
loop {
3231
let mut check_buf = [0u8; 8];
3332
if file.read_chunk_exact(&mut check_buf, boundary).is_err() {
33+
let name = entries.len().to_string();
3434
entries.push(ArchiveEntry {
35-
name: entries.len().to_string(),
36-
data: file.subfile(cur_entry_start, file.len() - cur_entry_start).unwrap(),
35+
name: name.clone(),
36+
data: file.subfile(cur_entry_start, file.len() - cur_entry_start, name.into()).unwrap(),
3737
timestamp: None
3838
});
3939
if entries.len() > 1 {
@@ -43,9 +43,10 @@ pub const ENTRY_CONCAT2K: Decoder<Archive> = Decoder {
4343
}
4444
}
4545
if check_signature(&check_buf) {
46+
let name = entries.len().to_string();
4647
entries.push(ArchiveEntry {
47-
name: entries.len().to_string(),
48-
data: file.subfile(cur_entry_start, boundary - cur_entry_start).unwrap(),
48+
name: name.clone(),
49+
data: file.subfile(cur_entry_start, boundary - cur_entry_start, name.into()).unwrap(),
4950
timestamp: None
5051
});
5152
cur_entry_start = boundary;

kidfile/src/archive_formats/lnk.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
1-
use crate::{file_data::FileData, Certainty, Decoder};
1+
use crate::{Certainty, Decoder};
22
use super::{Archive, ArchiveEntry};
33

44
pub const ENTRY_LNK: Decoder<Archive> = Decoder {
55
id: "lnk",
66
desc: "KID PC archive",
77
detect: |file| Certainty::certain_if(file.starts_with(b"LNK\0")),
88
decode: |file| {
9-
let count = file.read_u32_field(4, "signature")? as usize;
9+
let count = file.read_u32(4)? as usize;
1010
if count >= 0xFFFF {
1111
return Err("impossibly large entry count".into());
1212
}
1313
let mut entries = Vec::with_capacity(count);
1414
let mut index_ptr = 16;
15-
let mut data_section_start = 16 + count * 32;
15+
let data_section_start = 16 + count * 32;
1616
for _ in 0..count {
17-
let offset = file.read_u32_field(index_ptr, "entry offset")?;
18-
let mut len = file.read_u32_field(index_ptr + 4, "entry length")?;
17+
let offset = file.read_u32(index_ptr)?;
18+
let mut len = file.read_u32(index_ptr + 4)?;
1919
let is_compressed = len & 1 != 0;
2020
len >>= 1;
2121
let mut name_buf = [0u8; 24];
2222
file.read_chunk_exact(&mut name_buf, index_ptr + 8).map_err(|_| "could not read entry name")?;
2323
let name_len = name_buf.iter().position(|x| *x == 0).unwrap_or(32);
24+
let name = String::from_utf8(name_buf[0..name_len].to_vec()).map_err(|_| "entry name is not valid UTF-8")?;
2425
entries.push(ArchiveEntry {
25-
name: std::str::from_utf8(&name_buf[0..name_len]).map_err(|_| "entry name is not valid UTF-8")?.into(),
26-
data: file.subfile(data_section_start + offset as usize, len as usize).unwrap(),
26+
name: name.clone(),
27+
data: file.subfile(data_section_start + offset as usize, len as usize, name.into()).unwrap(),
2728
timestamp: None
2829
});
2930
index_ptr += 32;

kidfile/src/byte_slice.rs

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
macro_rules! impl_byte_readers {
22
($($t:ty),*) => {paste::paste! {$(
3-
fn [<read_ $t _field>](&self, offset: usize, name: &'static str) -> Result<$t, String> {
4-
Ok($t::from_le_bytes(self.get(offset..offset + size_of::<$t>()).ok_or_else(#[cold] || format!("could not read {name}"))?.try_into().unwrap()))
3+
fn [<read_ $t>](&self, offset: usize) -> Result<$t, String> {
4+
Ok($t::from_le_bytes(self.get(offset..offset + size_of::<$t>()).ok_or_else(#[cold] || format!("error reading field"))?.try_into().unwrap()))
55
}
6-
fn [<read_ $t _field_be>](&self, offset: usize, name: &'static str) -> Result<$t, String> {
7-
Ok($t::from_be_bytes(self.get(offset..offset + size_of::<$t>()).ok_or_else(#[cold] || format!("could not read {name}"))?.try_into().unwrap()))
6+
fn [<read_ $t _be>](&self, offset: usize) -> Result<$t, String> {
7+
Ok($t::from_be_bytes(self.get(offset..offset + size_of::<$t>()).ok_or_else(#[cold] || format!("error reading field"))?.try_into().unwrap()))
88
}
99
fn [<get_ $t _at>](&self, offset: usize) -> Option<$t> {
1010
Some($t::from_le_bytes(self.get(offset..offset + size_of::<$t>())?.try_into().unwrap()))
@@ -16,26 +16,28 @@ macro_rules! impl_byte_readers {
1616
}
1717

1818
pub trait ByteSlice {
19-
fn read_u8_field(&self, offset: usize, name: &'static str) -> Result<u8, String>;
20-
fn read_u16_field(&self, offset: usize, name: &'static str) -> Result<u16, String>;
21-
fn read_u32_field(&self, offset: usize, name: &'static str) -> Result<u32, String>;
22-
fn read_u64_field(&self, offset: usize, name: &'static str) -> Result<u64, String>;
23-
fn read_usize_field(&self, offset: usize, name: &'static str) -> Result<usize, String>;
24-
fn read_i8_field(&self, offset: usize, name: &'static str) -> Result<i8, String>;
25-
fn read_i16_field(&self, offset: usize, name: &'static str) -> Result<i16, String>;
26-
fn read_i32_field(&self, offset: usize, name: &'static str) -> Result<i32, String>;
27-
fn read_i64_field(&self, offset: usize, name: &'static str) -> Result<i64, String>;
28-
fn read_isize_field(&self, offset: usize, name: &'static str) -> Result<isize, String>;
29-
fn read_u8_field_be(&self, offset: usize, name: &'static str) -> Result<u8, String>;
30-
fn read_u16_field_be(&self, offset: usize, name: &'static str) -> Result<u16, String>;
31-
fn read_u32_field_be(&self, offset: usize, name: &'static str) -> Result<u32, String>;
32-
fn read_u64_field_be(&self, offset: usize, name: &'static str) -> Result<u64, String>;
33-
fn read_usize_field_be(&self, offset: usize, name: &'static str) -> Result<usize, String>;
34-
fn read_i8_field_be(&self, offset: usize, name: &'static str) -> Result<i8, String>;
35-
fn read_i16_field_be(&self, offset: usize, name: &'static str) -> Result<i16, String>;
36-
fn read_i32_field_be(&self, offset: usize, name: &'static str) -> Result<i32, String>;
37-
fn read_i64_field_be(&self, offset: usize, name: &'static str) -> Result<i64, String>;
38-
fn read_isize_field_be(&self, offset: usize, name: &'static str) -> Result<isize, String>;
19+
fn starts_with_at(&self, needle: &[u8], offset: usize) -> bool;
20+
fn read_bytes(&self, offset: usize, len: usize, name: &str) -> Result<&[u8], String>;
21+
fn read_u8(&self, offset: usize) -> Result<u8, String>;
22+
fn read_u16(&self, offset: usize) -> Result<u16, String>;
23+
fn read_u32(&self, offset: usize) -> Result<u32, String>;
24+
fn read_u64(&self, offset: usize) -> Result<u64, String>;
25+
fn read_usize(&self, offset: usize) -> Result<usize, String>;
26+
fn read_i8(&self, offset: usize) -> Result<i8, String>;
27+
fn read_i16(&self, offset: usize) -> Result<i16, String>;
28+
fn read_i32(&self, offset: usize) -> Result<i32, String>;
29+
fn read_i64(&self, offset: usize) -> Result<i64, String>;
30+
fn read_isize(&self, offset: usize) -> Result<isize, String>;
31+
fn read_u8_be(&self, offset: usize) -> Result<u8, String>;
32+
fn read_u16_be(&self, offset: usize) -> Result<u16, String>;
33+
fn read_u32_be(&self, offset: usize) -> Result<u32, String>;
34+
fn read_u64_be(&self, offset: usize) -> Result<u64, String>;
35+
fn read_usize_be(&self, offset: usize) -> Result<usize, String>;
36+
fn read_i8_be(&self, offset: usize) -> Result<i8, String>;
37+
fn read_i16_be(&self, offset: usize) -> Result<i16, String>;
38+
fn read_i32_be(&self, offset: usize) -> Result<i32, String>;
39+
fn read_i64_be(&self, offset: usize) -> Result<i64, String>;
40+
fn read_isize_be(&self, offset: usize) -> Result<isize, String>;
3941
fn get_u8_at(&self, offset: usize) -> Option<u8>;
4042
fn get_u16_at(&self, offset: usize) -> Option<u16>;
4143
fn get_u32_at(&self, offset: usize) -> Option<u32>;
@@ -56,8 +58,34 @@ pub trait ByteSlice {
5658
fn get_i32_at_be(&self, offset: usize) -> Option<i32>;
5759
fn get_i64_at_be(&self, offset: usize) -> Option<i64>;
5860
fn get_isize_at_be(&self, offset: usize) -> Option<isize>;
61+
fn unswizzled_psp(&self, width: u32, height: u32) -> Vec<u8>;
5962
}
6063

61-
impl ByteSlice for &[u8] {
64+
impl ByteSlice for [u8] {
65+
fn starts_with_at(&self, needle: &[u8], offset: usize) -> bool {
66+
self.get(offset..offset + needle.len()).map_or(false, |x| x.starts_with(needle))
67+
}
68+
69+
fn read_bytes(&self, offset: usize, len: usize, name: &str) -> Result<&[u8], String> {
70+
Ok(self.get(offset..offset + len).ok_or_else(#[cold] || format!("error reading {name}"))?.try_into().unwrap())
71+
}
72+
6273
impl_byte_readers!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
74+
75+
fn unswizzled_psp(&self, width_bytes: u32, height: u32) -> Vec<u8> {
76+
let mut pixels = Vec::with_capacity(self.len());
77+
let blocks_per_row = width_bytes / 16;
78+
for y in 0..height {
79+
for x in 0..width_bytes {
80+
let block_idx_x = x / 16;
81+
let block_idx_y = y / 8;
82+
let x_in_block = x % 16;
83+
let y_in_block = y % 8;
84+
let block_start = (block_idx_y * blocks_per_row + block_idx_x) * (16 * 8);
85+
let swizzled_idx = block_start + y_in_block * 16 + x_in_block;
86+
pixels.push(self.get(swizzled_idx as usize).cloned().unwrap_or_default());
87+
}
88+
}
89+
pixels
90+
}
6391
}

0 commit comments

Comments
 (0)