Skip to content

Commit 5ac3b21

Browse files
authored
Merge pull request #1 from Qlonever/ever17dc
Support AFS without file metadata, PVR with multiple palettes
2 parents b2b21da + c594153 commit 5ac3b21

File tree

2 files changed

+121
-95
lines changed

2 files changed

+121
-95
lines changed

kidfile/src/archive_formats/afs.rs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,28 @@ pub const ENTRY_AFS: Decoder<Archive> = Decoder {
2020
entry_ranges.push((offset, len));
2121
}
2222
end = end.next_multiple_of(0x800);
23+
let info_present = file.len() > end;
2324
for i in 0..count {
24-
let pos = end as usize + i * 48;
25-
let mut name_buf = [0u8; 32];
26-
file.read_chunk_exact(&mut name_buf, pos).map_err(|_| "could not read entry name")?;
27-
let len = name_buf.iter().position(|x| *x == 0).unwrap_or(32);
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")?;
25+
let mut name = format!("{}.BIN", i);
26+
let mut year = 2000;
27+
let mut month = 1;
28+
let mut day = 1;
29+
let mut hour = 0;
30+
let mut minute = 0;
31+
let mut second = 0;
32+
if info_present {
33+
let pos = end as usize + i * 48;
34+
let mut name_buf = [0u8; 32];
35+
file.read_chunk_exact(&mut name_buf, pos).map_err(|_| "could not read entry name")?;
36+
let len = name_buf.iter().position(|x| *x == 0).unwrap_or(32);
37+
year = file.read_u16(pos + 32)?;
38+
month = file.read_u16(pos + 34)?;
39+
day = file.read_u16(pos + 36)?;
40+
hour = file.read_u16(pos + 38)?;
41+
minute = file.read_u16(pos + 40)?;
42+
second = file.read_u16(pos + 42)?;
43+
name = String::from_utf8(name_buf[0..len].to_vec()).map_err(|_| "entry name is not valid UTF-8")?;
44+
}
3545
entries.push(ArchiveEntry {
3646
name: name.clone(),
3747
data: file.subfile(entry_ranges[i].0, entry_ranges[i].1).unwrap(),

kidfile/src/image_formats/pvr.rs

Lines changed: 100 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -13,104 +13,120 @@ pub const ENTRY_PVR: Decoder<Image> = Decoder {
1313
Certainty::certain_if(file.starts_with_at(b"PVRT", file_start) || file.starts_with_at(b"PVPL", file_start))
1414
},
1515
decode: |file| {
16-
let mut file_start = if file.starts_with(b"GBIX") {16} else {0};
17-
let mut palette_bytes = Default::default();
18-
if file.starts_with_at(b"PVPL", file_start) {
19-
let palette_len = file.read_u32(file_start + 4)? as usize;
20-
palette_bytes = unsafe {Box::new_uninit_slice(palette_len - 8).assume_init()};
21-
file.read_chunk_exact(&mut palette_bytes, file_start + 16).map_err(|_| "PVPL length field is incorrect")?;
22-
file_start += palette_len + 8;
23-
};
24-
let file_len = file.read_u32(file_start + 4)? as usize;
25-
let mut buf = unsafe {Box::new_uninit_slice(file_len + 8).assume_init()};
26-
file.read_chunk_exact(&mut buf, file_start).map_err(|_| "PVRT length field is incorrect")?;
27-
if !buf.starts_with(b"PVRT") {
28-
return Err(format!("PVRT header not found, expected at {:#X}", file_start));
16+
let mut file_start = 0;
17+
let mut tex_start = 0;
18+
let mut tex_len = 0;
19+
let mut palettes = Vec::new();
20+
while file_start < file.len() {
21+
let file_len = file.read_u32(file_start + 4)? as usize;
22+
if file.starts_with_at(b"PVPL", file_start) {
23+
let mut palette_bytes = Default::default();
24+
palette_bytes = unsafe {Box::new_uninit_slice(file_len - 8).assume_init()};
25+
file.read_chunk_exact(&mut palette_bytes, file_start + 16).map_err(|_| "PVPL length field is incorrect")?;
26+
palettes.push(palette_bytes);
27+
} else if file.starts_with_at(b"PVRT", file_start) {
28+
tex_start = file_start;
29+
tex_len = file_len;
30+
}
31+
file_start += file_len + 8;
32+
}
33+
if tex_len <= 0 {
34+
return Err("PVRT header not found in file".to_string());
2935
}
36+
let mut buf = unsafe {Box::new_uninit_slice(tex_len + 8).assume_init()};
37+
file.read_chunk_exact(&mut buf, tex_start).map_err(|_| "PVRT length field is incorrect")?;
3038
let pixel_fmt = buf.read_u8(8)?;
3139
let twiddle_type = buf.read_u8(9)?;
3240
println!("twiddle type {twiddle_type}");
3341
let width = buf.read_u16(12)? as usize;
3442
let height = buf.read_u16(14)? as usize;
35-
let mut frame = match pixel_fmt {
36-
0 | 1 | 2 => {
37-
if twiddle_type == 3 { // vq compression
38-
let codebook = buf.get(16..16 + 2048).ok_or("not enough 16-bit VQ codebook data")?;
39-
let indices = buf.get(16 + 2048..16 + 2048 + width * height / 4).ok_or("not enough VQ index data")?;
40-
let mut pixels = vec![0u8; width * height * 2];
41-
for block_y in 0..height / 2 {
42-
let twiddled_block_y = bit_twiddle(block_y);
43-
for block_x in 0..width / 2 {
44-
let twiddled_block_idx = twiddled_block_y | bit_twiddle(block_x) << 1;
45-
let codebook_pos = indices[twiddled_block_idx] as usize * 8;
46-
let x = block_x * 2;
47-
let y = block_y * 2;
48-
pixels[(y * width + x) * 2] = codebook[codebook_pos];
49-
pixels[(y * width + x) * 2 + 1] = codebook[codebook_pos + 1];
50-
pixels[(y * width + x + 1) * 2] = codebook[codebook_pos + 4];
51-
pixels[(y * width + x + 1) * 2 + 1] = codebook[codebook_pos + 5];
52-
pixels[((y + 1) * width + x) * 2] = codebook[codebook_pos + 2];
53-
pixels[((y + 1) * width + x) * 2 + 1] = codebook[codebook_pos + 3];
54-
pixels[((y + 1) * width + x + 1) * 2] = codebook[codebook_pos + 6];
55-
pixels[((y + 1) * width + x + 1) * 2 + 1] = codebook[codebook_pos + 7];
43+
let mut frames = Vec::new();
44+
if palettes.len() == 0 {
45+
let palette_bytes = unsafe {Box::new_uninit_slice(0).assume_init()};
46+
palettes.push(palette_bytes);
47+
}
48+
for palette_bytes in palettes.iter() {
49+
let mut frame = match pixel_fmt {
50+
0 | 1 | 2 => {
51+
if twiddle_type == 3 { // vq compression
52+
let codebook = buf.get(16..16 + 2048).ok_or("not enough 16-bit VQ codebook data")?;
53+
let indices = buf.get(16 + 2048..16 + 2048 + width * height / 4).ok_or("not enough VQ index data")?;
54+
let mut pixels = vec![0u8; width * height * 2];
55+
for block_y in 0..height / 2 {
56+
let twiddled_block_y = bit_twiddle(block_y);
57+
for block_x in 0..width / 2 {
58+
let twiddled_block_idx = twiddled_block_y | bit_twiddle(block_x) << 1;
59+
let codebook_pos = indices[twiddled_block_idx] as usize * 8;
60+
let x = block_x * 2;
61+
let y = block_y * 2;
62+
pixels[(y * width + x) * 2] = codebook[codebook_pos];
63+
pixels[(y * width + x) * 2 + 1] = codebook[codebook_pos + 1];
64+
pixels[(y * width + x + 1) * 2] = codebook[codebook_pos + 4];
65+
pixels[(y * width + x + 1) * 2 + 1] = codebook[codebook_pos + 5];
66+
pixels[((y + 1) * width + x) * 2] = codebook[codebook_pos + 2];
67+
pixels[((y + 1) * width + x) * 2 + 1] = codebook[codebook_pos + 3];
68+
pixels[((y + 1) * width + x + 1) * 2] = codebook[codebook_pos + 6];
69+
pixels[((y + 1) * width + x + 1) * 2 + 1] = codebook[codebook_pos + 7];
70+
}
71+
}
72+
if pixel_fmt == 0 {
73+
Frame::from_bgra5551(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgra5551Vq8)
74+
} else if pixel_fmt == 1 {
75+
Frame::from_bgr565(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgr565Vq8)
76+
} else {
77+
Frame::from_bgra4444(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgra4444Vq8)
5678
}
57-
}
58-
if pixel_fmt == 0 {
59-
Frame::from_bgra5551(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgra5551Vq8)
60-
} else if pixel_fmt == 1 {
61-
Frame::from_bgr565(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgr565Vq8)
6279
} else {
63-
Frame::from_bgra4444(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgra4444Vq8)
80+
if pixel_fmt == 0 {
81+
Frame::from_bgra5551(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGRA5551")?)
82+
} else if pixel_fmt == 1 {
83+
Frame::from_bgr565(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGR565")?)
84+
} else {
85+
Frame::from_bgra4444(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGRA4444")?)
86+
}
6487
}
65-
} else {
66-
if pixel_fmt == 0 {
67-
Frame::from_bgra5551(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGRA5551")?)
68-
} else if pixel_fmt == 1 {
69-
Frame::from_bgr565(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGR565")?)
88+
}
89+
5 => {
90+
if twiddle_type == 7 && palette_bytes.is_empty(){
91+
return Err("file needs external palette, unimplemented".into());
92+
} else if palette_bytes.is_empty() {
93+
Frame::from_bgra_clut4(
94+
width as u32, height as u32,
95+
buf.get(16..16 + 1024).ok_or("not enough palette data for BGRA clut4")?,
96+
buf.get(16 + 1024..16 + 1024 + width * height / 2).ok_or("not enough index data for BGRA clut4")?
97+
)
7098
} else {
71-
Frame::from_bgra4444(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGRA4444")?)
99+
Frame::from_bgra_clut4(
100+
width as u32, height as u32,
101+
&palette_bytes,
102+
buf.get(16..16 + width * height / 2).ok_or("not enough data for BGRA clut4")?
103+
)
72104
}
73105
}
74-
}
75-
5 => {
76-
if twiddle_type == 7 {
77-
return Err("file needs external palette, unimplemented".into());
78-
} else if palette_bytes.is_empty() {
79-
Frame::from_bgra_clut4(
80-
width as u32, height as u32,
81-
buf.get(16..16 + 1024).ok_or("not enough palette data for BGRA clut4")?,
82-
buf.get(16 + 1024..16 + 1024 + width * height / 2).ok_or("not enough index data for BGRA clut4")?
83-
)
84-
} else {
85-
Frame::from_bgra_clut4(
86-
width as u32, height as u32,
87-
&palette_bytes,
88-
buf.get(16..16 + width * height / 2).ok_or("not enough data for BGRA clut4")?
89-
)
90-
}
91-
}
92-
6 => {
93-
if twiddle_type == 7 {
94-
return Err("file needs external palette, unimplemented".into());
95-
} else if palette_bytes.is_empty() {
96-
Frame::from_bgra_clut8(
97-
width as u32, height as u32,
98-
buf.get(16..16 + 1024).ok_or("not enough palette data for BGRA clut8")?,
99-
buf.get(16 + 1024..16 + 1024 + width * height).ok_or("not enough index data for BGRA clut8")?
100-
)
101-
} else {
102-
Frame::from_bgra_clut8(
103-
width as u32, height as u32,
104-
&palette_bytes,
105-
buf.get(16..16 + width * height).ok_or("not enough data for BGRA clut8")?
106-
)
106+
6 => {
107+
if twiddle_type == 7 && palette_bytes.is_empty(){
108+
return Err("file needs external palette, unimplemented".into());
109+
} else if palette_bytes.is_empty() {
110+
Frame::from_bgra_clut8(
111+
width as u32, height as u32,
112+
buf.get(16..16 + 1024).ok_or("not enough palette data for BGRA clut8")?,
113+
buf.get(16 + 1024..16 + 1024 + width * height).ok_or("not enough index data for BGRA clut8")?
114+
)
115+
} else {
116+
Frame::from_bgra_clut8(
117+
width as u32, height as u32,
118+
&palette_bytes,
119+
buf.get(16..16 + width * height).ok_or("not enough data for BGRA clut8")?
120+
)
121+
}
107122
}
123+
_ => return Err(format!("unhandled PVR pixel format {pixel_fmt}"))
124+
};
125+
if [1, 2, 5, 6, 7, 8, 13].contains(&twiddle_type) {
126+
frame = frame.twiddled_dc();
108127
}
109-
_ => return Err(format!("unhandled PVR pixel format {pixel_fmt}"))
110-
};
111-
if [1, 2, 5, 6, 7, 8, 13].contains(&twiddle_type) {
112-
frame = frame.twiddled_dc();
128+
frames.push(frame);
113129
}
114-
Ok(Image {frames: Box::new([frame])})
130+
Ok(Image {frames: frames.into_boxed_slice()})
115131
}
116132
};

0 commit comments

Comments
 (0)