diff --git a/kidfile/src/archive_formats/afs.rs b/kidfile/src/archive_formats/afs.rs index fd34e07..0980f4e 100644 --- a/kidfile/src/archive_formats/afs.rs +++ b/kidfile/src/archive_formats/afs.rs @@ -20,18 +20,28 @@ pub const ENTRY_AFS: Decoder = Decoder { entry_ranges.push((offset, len)); } end = end.next_multiple_of(0x800); + let info_present = file.len() > end; for i in 0..count { - let pos = end as usize + i * 48; - let mut name_buf = [0u8; 32]; - file.read_chunk_exact(&mut name_buf, pos).map_err(|_| "could not read entry name")?; - let len = name_buf.iter().position(|x| *x == 0).unwrap_or(32); - let year = file.read_u16(pos + 32)?; - let month = file.read_u16(pos + 34)?; - let day = file.read_u16(pos + 36)?; - let hour = file.read_u16(pos + 38)?; - let minute = file.read_u16(pos + 40)?; - let second = file.read_u16(pos + 42)?; - let name = String::from_utf8(name_buf[0..len].to_vec()).map_err(|_| "entry name is not valid UTF-8")?; + let mut name = format!("{}.BIN", i); + let mut year = 2000; + let mut month = 1; + let mut day = 1; + let mut hour = 0; + let mut minute = 0; + let mut second = 0; + if info_present { + let pos = end as usize + i * 48; + let mut name_buf = [0u8; 32]; + file.read_chunk_exact(&mut name_buf, pos).map_err(|_| "could not read entry name")?; + let len = name_buf.iter().position(|x| *x == 0).unwrap_or(32); + year = file.read_u16(pos + 32)?; + month = file.read_u16(pos + 34)?; + day = file.read_u16(pos + 36)?; + hour = file.read_u16(pos + 38)?; + minute = file.read_u16(pos + 40)?; + second = file.read_u16(pos + 42)?; + name = String::from_utf8(name_buf[0..len].to_vec()).map_err(|_| "entry name is not valid UTF-8")?; + } entries.push(ArchiveEntry { name: name.clone(), data: file.subfile(entry_ranges[i].0, entry_ranges[i].1).unwrap(), diff --git a/kidfile/src/image_formats/pvr.rs b/kidfile/src/image_formats/pvr.rs index 210cf60..150d716 100644 --- a/kidfile/src/image_formats/pvr.rs +++ b/kidfile/src/image_formats/pvr.rs @@ -13,104 +13,120 @@ pub const ENTRY_PVR: Decoder = Decoder { Certainty::certain_if(file.starts_with_at(b"PVRT", file_start) || file.starts_with_at(b"PVPL", file_start)) }, decode: |file| { - let mut file_start = if file.starts_with(b"GBIX") {16} else {0}; - let mut palette_bytes = Default::default(); - if file.starts_with_at(b"PVPL", file_start) { - let palette_len = file.read_u32(file_start + 4)? as usize; - palette_bytes = unsafe {Box::new_uninit_slice(palette_len - 8).assume_init()}; - file.read_chunk_exact(&mut palette_bytes, file_start + 16).map_err(|_| "PVPL length field is incorrect")?; - file_start += palette_len + 8; - }; - let file_len = file.read_u32(file_start + 4)? as usize; - let mut buf = unsafe {Box::new_uninit_slice(file_len + 8).assume_init()}; - file.read_chunk_exact(&mut buf, file_start).map_err(|_| "PVRT length field is incorrect")?; - if !buf.starts_with(b"PVRT") { - return Err(format!("PVRT header not found, expected at {:#X}", file_start)); + let mut file_start = 0; + let mut tex_start = 0; + let mut tex_len = 0; + let mut palettes = Vec::new(); + while file_start < file.len() { + let file_len = file.read_u32(file_start + 4)? as usize; + if file.starts_with_at(b"PVPL", file_start) { + let mut palette_bytes = Default::default(); + palette_bytes = unsafe {Box::new_uninit_slice(file_len - 8).assume_init()}; + file.read_chunk_exact(&mut palette_bytes, file_start + 16).map_err(|_| "PVPL length field is incorrect")?; + palettes.push(palette_bytes); + } else if file.starts_with_at(b"PVRT", file_start) { + tex_start = file_start; + tex_len = file_len; + } + file_start += file_len + 8; + } + if tex_len <= 0 { + return Err("PVRT header not found in file".to_string()); } + let mut buf = unsafe {Box::new_uninit_slice(tex_len + 8).assume_init()}; + file.read_chunk_exact(&mut buf, tex_start).map_err(|_| "PVRT length field is incorrect")?; let pixel_fmt = buf.read_u8(8)?; let twiddle_type = buf.read_u8(9)?; println!("twiddle type {twiddle_type}"); let width = buf.read_u16(12)? as usize; let height = buf.read_u16(14)? as usize; - let mut frame = match pixel_fmt { - 0 | 1 | 2 => { - if twiddle_type == 3 { // vq compression - let codebook = buf.get(16..16 + 2048).ok_or("not enough 16-bit VQ codebook data")?; - let indices = buf.get(16 + 2048..16 + 2048 + width * height / 4).ok_or("not enough VQ index data")?; - let mut pixels = vec![0u8; width * height * 2]; - for block_y in 0..height / 2 { - let twiddled_block_y = bit_twiddle(block_y); - for block_x in 0..width / 2 { - let twiddled_block_idx = twiddled_block_y | bit_twiddle(block_x) << 1; - let codebook_pos = indices[twiddled_block_idx] as usize * 8; - let x = block_x * 2; - let y = block_y * 2; - pixels[(y * width + x) * 2] = codebook[codebook_pos]; - pixels[(y * width + x) * 2 + 1] = codebook[codebook_pos + 1]; - pixels[(y * width + x + 1) * 2] = codebook[codebook_pos + 4]; - pixels[(y * width + x + 1) * 2 + 1] = codebook[codebook_pos + 5]; - pixels[((y + 1) * width + x) * 2] = codebook[codebook_pos + 2]; - pixels[((y + 1) * width + x) * 2 + 1] = codebook[codebook_pos + 3]; - pixels[((y + 1) * width + x + 1) * 2] = codebook[codebook_pos + 6]; - pixels[((y + 1) * width + x + 1) * 2 + 1] = codebook[codebook_pos + 7]; + let mut frames = Vec::new(); + if palettes.len() == 0 { + let palette_bytes = unsafe {Box::new_uninit_slice(0).assume_init()}; + palettes.push(palette_bytes); + } + for palette_bytes in palettes.iter() { + let mut frame = match pixel_fmt { + 0 | 1 | 2 => { + if twiddle_type == 3 { // vq compression + let codebook = buf.get(16..16 + 2048).ok_or("not enough 16-bit VQ codebook data")?; + let indices = buf.get(16 + 2048..16 + 2048 + width * height / 4).ok_or("not enough VQ index data")?; + let mut pixels = vec![0u8; width * height * 2]; + for block_y in 0..height / 2 { + let twiddled_block_y = bit_twiddle(block_y); + for block_x in 0..width / 2 { + let twiddled_block_idx = twiddled_block_y | bit_twiddle(block_x) << 1; + let codebook_pos = indices[twiddled_block_idx] as usize * 8; + let x = block_x * 2; + let y = block_y * 2; + pixels[(y * width + x) * 2] = codebook[codebook_pos]; + pixels[(y * width + x) * 2 + 1] = codebook[codebook_pos + 1]; + pixels[(y * width + x + 1) * 2] = codebook[codebook_pos + 4]; + pixels[(y * width + x + 1) * 2 + 1] = codebook[codebook_pos + 5]; + pixels[((y + 1) * width + x) * 2] = codebook[codebook_pos + 2]; + pixels[((y + 1) * width + x) * 2 + 1] = codebook[codebook_pos + 3]; + pixels[((y + 1) * width + x + 1) * 2] = codebook[codebook_pos + 6]; + pixels[((y + 1) * width + x + 1) * 2 + 1] = codebook[codebook_pos + 7]; + } + } + if pixel_fmt == 0 { + Frame::from_bgra5551(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgra5551Vq8) + } else if pixel_fmt == 1 { + Frame::from_bgr565(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgr565Vq8) + } else { + Frame::from_bgra4444(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgra4444Vq8) } - } - if pixel_fmt == 0 { - Frame::from_bgra5551(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgra5551Vq8) - } else if pixel_fmt == 1 { - Frame::from_bgr565(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgr565Vq8) } else { - Frame::from_bgra4444(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgra4444Vq8) + if pixel_fmt == 0 { + Frame::from_bgra5551(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGRA5551")?) + } else if pixel_fmt == 1 { + Frame::from_bgr565(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGR565")?) + } else { + Frame::from_bgra4444(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGRA4444")?) + } } - } else { - if pixel_fmt == 0 { - Frame::from_bgra5551(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGRA5551")?) - } else if pixel_fmt == 1 { - Frame::from_bgr565(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGR565")?) + } + 5 => { + if twiddle_type == 7 && palette_bytes.is_empty(){ + return Err("file needs external palette, unimplemented".into()); + } else if palette_bytes.is_empty() { + Frame::from_bgra_clut4( + width as u32, height as u32, + buf.get(16..16 + 1024).ok_or("not enough palette data for BGRA clut4")?, + buf.get(16 + 1024..16 + 1024 + width * height / 2).ok_or("not enough index data for BGRA clut4")? + ) } else { - Frame::from_bgra4444(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGRA4444")?) + Frame::from_bgra_clut4( + width as u32, height as u32, + &palette_bytes, + buf.get(16..16 + width * height / 2).ok_or("not enough data for BGRA clut4")? + ) } } - } - 5 => { - if twiddle_type == 7 { - return Err("file needs external palette, unimplemented".into()); - } else if palette_bytes.is_empty() { - Frame::from_bgra_clut4( - width as u32, height as u32, - buf.get(16..16 + 1024).ok_or("not enough palette data for BGRA clut4")?, - buf.get(16 + 1024..16 + 1024 + width * height / 2).ok_or("not enough index data for BGRA clut4")? - ) - } else { - Frame::from_bgra_clut4( - width as u32, height as u32, - &palette_bytes, - buf.get(16..16 + width * height / 2).ok_or("not enough data for BGRA clut4")? - ) - } - } - 6 => { - if twiddle_type == 7 { - return Err("file needs external palette, unimplemented".into()); - } else if palette_bytes.is_empty() { - Frame::from_bgra_clut8( - width as u32, height as u32, - buf.get(16..16 + 1024).ok_or("not enough palette data for BGRA clut8")?, - buf.get(16 + 1024..16 + 1024 + width * height).ok_or("not enough index data for BGRA clut8")? - ) - } else { - Frame::from_bgra_clut8( - width as u32, height as u32, - &palette_bytes, - buf.get(16..16 + width * height).ok_or("not enough data for BGRA clut8")? - ) + 6 => { + if twiddle_type == 7 && palette_bytes.is_empty(){ + return Err("file needs external palette, unimplemented".into()); + } else if palette_bytes.is_empty() { + Frame::from_bgra_clut8( + width as u32, height as u32, + buf.get(16..16 + 1024).ok_or("not enough palette data for BGRA clut8")?, + buf.get(16 + 1024..16 + 1024 + width * height).ok_or("not enough index data for BGRA clut8")? + ) + } else { + Frame::from_bgra_clut8( + width as u32, height as u32, + &palette_bytes, + buf.get(16..16 + width * height).ok_or("not enough data for BGRA clut8")? + ) + } } + _ => return Err(format!("unhandled PVR pixel format {pixel_fmt}")) + }; + if [1, 2, 5, 6, 7, 8, 13].contains(&twiddle_type) { + frame = frame.twiddled_dc(); } - _ => return Err(format!("unhandled PVR pixel format {pixel_fmt}")) - }; - if [1, 2, 5, 6, 7, 8, 13].contains(&twiddle_type) { - frame = frame.twiddled_dc(); + frames.push(frame); } - Ok(Image {frames: Box::new([frame])}) + Ok(Image {frames: frames.into_boxed_slice()}) } };