Skip to content

Commit d2120c1

Browse files
committed
initial N7 DC support
1 parent e850296 commit d2120c1

File tree

9 files changed

+265
-21
lines changed

9 files changed

+265
-21
lines changed

kidfile-explorer/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ impl<'a> TabViewer for ExplorerTabViewer<'a> {
292292
}
293293
}
294294
let mut new_selection = None;
295-
if ui.input_mut(|inp| inp.consume_key(Modifiers::CTRL | Modifiers::SHIFT, Key::Tab)) {
295+
if ui.input_mut(|inp| inp.consume_key(Modifiers::NONE, Key::PageUp)) {
296296
if let Some((selected_idx, ..)) = tab.selection {
297297
if selected_idx == 0 {
298298
new_selection = Some(tab.children.len() - 1);
@@ -302,7 +302,7 @@ impl<'a> TabViewer for ExplorerTabViewer<'a> {
302302
} else if tab.children.len() != 0 {
303303
new_selection = Some(tab.children.len() - 1);
304304
}
305-
} else if ui.input_mut(|inp| inp.consume_key(Modifiers::CTRL, Key::Tab)) {
305+
} else if ui.input_mut(|inp| inp.consume_key(Modifiers::NONE, Key::PageDown)) {
306306
if let Some((selected_idx, ..)) = tab.selection {
307307
if selected_idx >= tab.children.len() - 1 {
308308
new_selection = Some(0);

kidfile/src/byte_iter.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::mem::MaybeUninit;
2+
3+
pub trait ByteIter: Iterator<Item = u8> + ExactSizeIterator {
4+
fn next_bytes<const LEN: usize>(&mut self) -> Option<[u8; LEN]>;
5+
fn next_u16(&mut self) -> Option<u16>;
6+
fn next_u32(&mut self) -> Option<u32>;
7+
fn next_u64(&mut self) -> Option<u64>;
8+
fn next_usize(&mut self) -> Option<usize>;
9+
fn next_i8(&mut self) -> Option<i8>;
10+
fn next_i16(&mut self) -> Option<i16>;
11+
fn next_i32(&mut self) -> Option<i32>;
12+
fn next_i64(&mut self) -> Option<i64>;
13+
fn next_isize(&mut self) -> Option<isize>;
14+
fn next_u16_be(&mut self) -> Option<u16>;
15+
fn next_u32_be(&mut self) -> Option<u32>;
16+
fn next_u64_be(&mut self) -> Option<u64>;
17+
fn next_usize_be(&mut self) -> Option<usize>;
18+
fn next_i8_be(&mut self) -> Option<i8>;
19+
fn next_i16_be(&mut self) -> Option<i16>;
20+
fn next_i32_be(&mut self) -> Option<i32>;
21+
fn next_i64_be(&mut self) -> Option<i64>;
22+
fn next_isize_be(&mut self) -> Option<isize>;
23+
}
24+
25+
macro_rules! impl_for_types {
26+
($($t:ty),*) => {paste::paste! {$(
27+
fn [<next_ $t>](&mut self) -> Option<$t> {
28+
self.next_bytes::<{size_of::<$t>()}>().map(|x| $t::from_le_bytes(x))
29+
}
30+
fn [<next_ $t _be>](&mut self) -> Option<$t> {
31+
self.next_bytes::<{size_of::<$t>()}>().map(|x| $t::from_be_bytes(x))
32+
}
33+
)*}}
34+
}
35+
36+
impl<T: Iterator<Item = u8> + ExactSizeIterator> ByteIter for T {
37+
fn next_bytes<const LEN: usize>(&mut self) -> Option<[u8; LEN]> {
38+
if LEN <= self.len() {
39+
let mut arr = unsafe {MaybeUninit::<[u8; LEN]>::uninit().assume_init()};
40+
for (i, x) in self.take(LEN).enumerate() {
41+
arr[i] = x;
42+
}
43+
Some(arr)
44+
} else {
45+
None
46+
}
47+
}
48+
49+
impl_for_types!(u16, u32, u64, usize, i8, i16, i32, i64, isize);
50+
}

kidfile/src/byte_slice.rs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,3 @@
1-
macro_rules! impl_byte_readers {
2-
($($t:ty),*) => {paste::paste! {$(
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()))
5-
}
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()))
8-
}
9-
fn [<get_ $t _at>](&self, offset: usize) -> Option<$t> {
10-
Some($t::from_le_bytes(self.get(offset..offset + size_of::<$t>())?.try_into().unwrap()))
11-
}
12-
fn [<get_ $t _at_be>](&self, offset: usize) -> Option<$t> {
13-
Some($t::from_be_bytes(self.get(offset..offset + size_of::<$t>())?.try_into().unwrap()))
14-
}
15-
)*}}
16-
}
17-
181
pub trait ByteSlice {
192
fn starts_with_at(&self, needle: &[u8], offset: usize) -> bool;
203
fn read_bytes(&self, offset: usize, len: usize, name: &str) -> Result<&[u8], String>;
@@ -61,6 +44,23 @@ pub trait ByteSlice {
6144
fn unswizzled_psp(&self, width: u32, height: u32) -> Vec<u8>;
6245
}
6346

47+
macro_rules! impl_for_types {
48+
($($t:ty),*) => {paste::paste! {$(
49+
fn [<read_ $t>](&self, offset: usize) -> Result<$t, String> {
50+
Ok($t::from_le_bytes(self.get(offset..offset + size_of::<$t>()).ok_or_else(#[cold] || format!("error reading field"))?.try_into().unwrap()))
51+
}
52+
fn [<read_ $t _be>](&self, offset: usize) -> Result<$t, String> {
53+
Ok($t::from_be_bytes(self.get(offset..offset + size_of::<$t>()).ok_or_else(#[cold] || format!("error reading field"))?.try_into().unwrap()))
54+
}
55+
fn [<get_ $t _at>](&self, offset: usize) -> Option<$t> {
56+
Some($t::from_le_bytes(self.get(offset..offset + size_of::<$t>())?.try_into().unwrap()))
57+
}
58+
fn [<get_ $t _at_be>](&self, offset: usize) -> Option<$t> {
59+
Some($t::from_be_bytes(self.get(offset..offset + size_of::<$t>())?.try_into().unwrap()))
60+
}
61+
)*}}
62+
}
63+
6464
impl ByteSlice for [u8] {
6565
fn starts_with_at(&self, needle: &[u8], offset: usize) -> bool {
6666
self.get(offset..offset + needle.len()).map_or(false, |x| x.starts_with(needle))
@@ -70,7 +70,7 @@ impl ByteSlice for [u8] {
7070
Ok(self.get(offset..offset + len).ok_or_else(#[cold] || format!("error reading {name}"))?.try_into().unwrap())
7171
}
7272

73-
impl_byte_readers!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
73+
impl_for_types!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
7474

7575
fn unswizzled_psp(&self, width_bytes: u32, height: u32) -> Vec<u8> {
7676
let mut pixels = Vec::with_capacity(self.len());
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use crate::{byte_iter::ByteIter, file_data::FileData, Certainty, Decoder};
2+
3+
// thanks to BoilingTeapot for reverse engineering the compression
4+
5+
pub const ENTRY_LZSS_DC: Decoder<Box<[u8]>> = Decoder {
6+
id: "lzss-dc",
7+
desc: "LZSS variant used in N7 DC",
8+
detect: |buf| Certainty::possible_if(decode_header(buf).is_some()),
9+
decode
10+
};
11+
12+
fn decode_header(data: &mut FileData) -> Option<usize> {
13+
let size = data.get_u32_at_be(0)?;
14+
if size > 32 && size < 32 * 1024 * 1024 && data.len() > 32 {
15+
Some(size as usize)
16+
} else {
17+
None
18+
}
19+
}
20+
21+
fn decode(data: &mut FileData) -> Result<Box<[u8]>, String> {
22+
if let Some(expected_size) = decode_header(data) {
23+
match decompress_lzss_dc(&data.read()[4..], expected_size) {
24+
Ok(decompressed) => Ok(decompressed),
25+
Err(Some(actual_size)) => Err(format!("expected {expected_size} bytes when decompressing, got only {actual_size}")),
26+
Err(None) => Err(format!("error while decompressing"))
27+
}
28+
} else {
29+
Err(String::new())
30+
}
31+
}
32+
33+
fn decompress_lzss_dc(inp: &[u8], expected_size: usize) -> Result<Box<[u8]>, Option<usize>> {
34+
let mut out = Vec::with_capacity(expected_size);
35+
let mut src = inp.iter().cloned();
36+
while out.len() < expected_size {
37+
let chunk_size = src.next_u16_be().ok_or(out.len())? as usize;
38+
let mut chunk = src.by_ref().take(chunk_size);
39+
// the flags byte determines whether the next 8 tokens are literals or references
40+
while let Some(mut flags) = chunk.next() {
41+
for _ in 0..8 {
42+
if flags & 1 == 0 { // literal token
43+
if let Some(byte) = chunk.next() {
44+
if out.len() >= expected_size {
45+
return Err(None);
46+
}
47+
out.push(byte);
48+
} else {
49+
break;
50+
}
51+
} else { // reference token
52+
let ref_value = chunk.next_u16_be().ok_or(None)? as usize;
53+
let ref_off = (ref_value >> 5) + 1;
54+
let ref_len = (ref_value & 0b11111) + 3;
55+
if ref_off >= out.len() {
56+
return Err(None);
57+
}
58+
let start = out.len() - ref_off;
59+
if out.len() + ref_len > expected_size {
60+
return Err(None);
61+
}
62+
for i in start..start + ref_len {
63+
out.push(*out.get(i).ok_or(None)?);
64+
}
65+
}
66+
flags >>= 1;
67+
}
68+
}
69+
}
70+
if out.len() == expected_size {
71+
Ok(out.into_boxed_slice())
72+
} else {
73+
Err(Some(out.len()))
74+
}
75+
}

kidfile/src/data_formats/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ use super::Decoder;
33

44
mod lzss;
55
mod cps;
6+
mod lzss_dc;
67

78
pub const DATA_DECODERS: LazyLock<Vec<Decoder<Box<[u8]>>>> = LazyLock::new(|| [
89
lzss::ENTRY_LZSS,
9-
cps::ENTRY_CPS
10+
cps::ENTRY_CPS,
11+
lzss_dc::ENTRY_LZSS_DC
1012
].into());

kidfile/src/image.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ pub enum PixelFormat {
1111
Bgr,
1212
Rgba16,
1313
Rgb16,
14+
Rgba4444,
15+
Bgra16,
16+
Bgr16,
17+
Bgra4444,
1418
RgbaClut8,
1519
RgbxClut8,
1620
RgbClut8,
@@ -36,6 +40,10 @@ impl PixelFormat {
3640
PixelFormat::Bgr => 24,
3741
PixelFormat::Rgba16 => 16,
3842
PixelFormat::Rgb16 => 16,
43+
PixelFormat::Rgba4444 => 16,
44+
PixelFormat::Bgra16 => 16,
45+
PixelFormat::Bgr16 => 16,
46+
PixelFormat::Bgra4444 => 16,
3947
PixelFormat::RgbaClut8 => 8,
4048
PixelFormat::RgbxClut8 => 8,
4149
PixelFormat::RgbClut8 => 8,
@@ -75,6 +83,10 @@ impl Display for PixelFormat {
7583
Self::Bgr => write!(f, "BGR"),
7684
Self::Rgba16 => write!(f, "RGBA5551"),
7785
Self::Rgb16 => write!(f, "RGB565"),
86+
Self::Rgba4444 => write!(f, "RGBA4444"),
87+
Self::Bgra16 => write!(f, "BGRA5551"),
88+
Self::Bgr16 => write!(f, "BGR565"),
89+
Self::Bgra4444 => write!(f, "BGRA4444"),
7890
Self::RgbaClut8 => write!(f, "RGBA clut8"),
7991
Self::RgbxClut8 => write!(f, "RGBX clut8"),
8092
Self::RgbClut8 => write!(f, "RGB clut8"),
@@ -107,9 +119,14 @@ pub struct Frame {
107119
pub pixels: Box<[Pixel]>
108120
}
109121

122+
fn bits_4_to_8(x: u8) -> u8 {
123+
x << 4 | (x & 0xF)
124+
}
125+
110126
fn bits_5_to_8(x: u8) -> u8 {
111127
x << 3 | (x & 1) << 2 | (x & 1) << 1 | (x & 1)
112128
}
129+
113130
fn bits_6_to_8(x: u8) -> u8 {
114131
x << 2 | (x & 1) << 1 | (x & 1)
115132
}
@@ -147,6 +164,51 @@ impl Frame {
147164
}
148165
}
149166

167+
pub fn from_bgra16(width: u32, height: u32, buf: &[u8]) -> Self {
168+
let needed_size = width * height * 2;
169+
assert!(buf.len() as u32 >= needed_size);
170+
let buf = &buf[0..needed_size as usize];
171+
Self {
172+
width, height, og_fmt: PixelFormat::Bgra16,
173+
pixels: buf.chunks_exact(2).map(|x| Pixel {
174+
r: bits_5_to_8(x[1] >> 3),
175+
g: bits_5_to_8(x[0] >> 5 | x[1] << 3),
176+
b: bits_5_to_8(x[0]),
177+
a: if x[1] & 0x80 != 0 {0xFF} else {0}
178+
}).collect()
179+
}
180+
}
181+
182+
pub fn from_rgba4444(width: u32, height: u32, buf: &[u8]) -> Self {
183+
let needed_size = width * height * 2;
184+
assert!(buf.len() as u32 >= needed_size);
185+
let buf = &buf[0..needed_size as usize];
186+
Self {
187+
width, height, og_fmt: PixelFormat::Rgba4444,
188+
pixels: buf.chunks_exact(2).map(|x| Pixel {
189+
r: bits_4_to_8(x[0]),
190+
g: bits_4_to_8(x[0] >> 4),
191+
b: bits_4_to_8(x[1]),
192+
a: bits_4_to_8(x[1] >> 4)
193+
}).collect()
194+
}
195+
}
196+
197+
pub fn from_bgra4444(width: u32, height: u32, buf: &[u8]) -> Self {
198+
let needed_size = width * height * 2;
199+
assert!(buf.len() as u32 >= needed_size);
200+
let buf = &buf[0..needed_size as usize];
201+
Self {
202+
width, height, og_fmt: PixelFormat::Bgra4444,
203+
pixels: buf.chunks_exact(2).map(|x| Pixel {
204+
r: bits_4_to_8(x[1]),
205+
g: bits_4_to_8(x[0] >> 4),
206+
b: bits_4_to_8(x[0]),
207+
a: bits_4_to_8(x[1] >> 4)
208+
}).collect()
209+
}
210+
}
211+
150212
pub fn from_rgb16(width: u32, height: u32, buf: &[u8]) -> Self {
151213
let needed_size = width * height * 2;
152214
assert!(buf.len() as u32 >= needed_size);
@@ -162,6 +224,21 @@ impl Frame {
162224
}
163225
}
164226

227+
pub fn from_bgr16(width: u32, height: u32, buf: &[u8]) -> Self {
228+
let needed_size = width * height * 2;
229+
assert!(buf.len() as u32 >= needed_size);
230+
let buf = &buf[0..needed_size as usize];
231+
Self {
232+
width, height, og_fmt: PixelFormat::Bgr16,
233+
pixels: buf.chunks_exact(2).map(|x| Pixel {
234+
r: bits_5_to_8(x[1] >> 3),
235+
g: bits_6_to_8(x[0] >> 5 | x[1] << 3),
236+
b: bits_5_to_8(x[0]),
237+
a: 0xFF
238+
}).collect()
239+
}
240+
}
241+
165242
pub fn from_rgba_clut8(width: u32, height: u32, clut: &[u8], buf: &[u8]) -> Self {
166243
let needed_size = width * height;
167244
assert!(buf.len() as u32 >= needed_size);

kidfile/src/image_formats/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod ogdt;
77
mod gim;
88
mod klz;
99
mod bip;
10+
mod pvr;
1011
mod common_image;
1112

1213
pub const IMAGE_DECODERS: LazyLock<Vec<Decoder<Image>>> = LazyLock::new(|| [
@@ -15,6 +16,7 @@ pub const IMAGE_DECODERS: LazyLock<Vec<Decoder<Image>>> = LazyLock::new(|| [
1516
gim::ENTRY_GIM,
1617
klz::ENTRY_KLZ,
1718
bip::ENTRY_BIP,
19+
pvr::ENTRY_PVR,
1820
common_image::ENTRY_PNG,
1921
common_image::ENTRY_JPEG,
2022
common_image::ENTRY_BMP,

kidfile/src/image_formats/pvr.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use crate::{byte_slice::ByteSlice, image::{Frame, Image}, Certainty, Decoder};
2+
3+
// https://www.fabiensanglard.net/Mykaruga/tools/segaPVRFormat.txt
4+
5+
pub const ENTRY_PVR: Decoder<Image> = Decoder {
6+
id: "pvr",
7+
desc: "Dreamcast image format",
8+
detect: |file| Certainty::certain_if(file.starts_with(b"PVRT") || (file.starts_with(b"GBIX") && file.starts_with_at(b"PVRT", 16))),
9+
decode: |file| {
10+
let gbix = file.starts_with(b"GBIX");
11+
let file_len = file.read_u32(if gbix {20} else {4})? as usize;
12+
let mut buf = unsafe {Box::new_uninit_slice(file_len + 8).assume_init()};
13+
file.read_chunk_exact(&mut buf, if gbix {16} else {0}).map_err(|_| "file length field is incorrect")?;
14+
let pixel_fmt = buf.read_u8(8)?;
15+
let twiddle_type = buf.read_u8(9)?;
16+
println!("twiddle type {twiddle_type}");
17+
let width = buf.read_u16(12)? as usize;
18+
let height = buf.read_u16(14)? as usize;
19+
let frame = match pixel_fmt {
20+
0 => Frame::from_bgra16(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data")?),
21+
1 => Frame::from_bgr16(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data")?),
22+
2 => Frame::from_bgra4444(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data")?),
23+
5 => Frame::from_rgba_clut4(
24+
width as u32, height as u32,
25+
buf.get(16..16 + 1024).ok_or("not enough palette data")?,
26+
buf.get(16 + 1024..16 + 1024 + width * height / 2).ok_or("not enough pixel data")?
27+
),
28+
6 => Frame::from_rgba_clut8(
29+
width as u32, height as u32,
30+
buf.get(16..16 + 1024).ok_or("not enough palette data")?,
31+
buf.get(16 + 1024..16 + 1024 + width * height).ok_or("not enough pixel data")?
32+
),
33+
_ => return Err(format!("unhandled PVR pixel format {pixel_fmt}"))
34+
};
35+
Ok(Image {frames: Box::new([frame])})
36+
}
37+
};

kidfile/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use image::Image;
33

44
pub mod file_data;
55
pub mod byte_slice;
6+
pub mod byte_iter;
67
pub mod image;
78
mod data_formats;
89
pub use data_formats::DATA_DECODERS;

0 commit comments

Comments
 (0)