use crate::error::Error;
use crate::utils::from_latin1_or_shift_jis;
use crate::utils::trim;
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::vec::Vec;
pub struct Banner {
pub data: Vec<u16>,
pub game_name: String,
pub developer: String,
pub full_title: String,
pub full_developer: String,
pub description: String,
}
impl TryFrom<&[u8]> for Banner {
type Error = Error;
fn try_from(binary: &[u8]) -> Result<Self, Self::Error> {
if binary.len() < 6496 {
return Err(Error::Parse(format!(
"wrong size for a banner: {}",
binary.len()
)));
}
let data = binary[0x20..0x1820]
.chunks_exact(2)
.map(|a| u16::from_be_bytes([a[0], a[1]]))
.collect();
let game_name = trim(&from_latin1_or_shift_jis(&binary[0x1820..0x1840])?).to_string();
let developer = trim(&from_latin1_or_shift_jis(&binary[0x1840..0x1860])?).to_string();
let full_title = trim(&from_latin1_or_shift_jis(&binary[0x1860..0x18A0])?).to_string();
let full_developer = trim(&from_latin1_or_shift_jis(&binary[0x18A0..0x18e0])?).to_string();
let description = trim(&from_latin1_or_shift_jis(&binary[0x18E0..0x1960])?).to_string();
Ok(Self {
data,
game_name,
developer,
full_title,
full_developer,
description,
})
}
}
#[allow(clippy::cast_possible_truncation)]
const fn scale_to_bits<const BITS: usize>(data: u16) -> u8 {
const { assert!(BITS <= 16) };
let mask: u16 = ((1u32 << BITS) - 1) as u16;
let t_mask: u16 = u8::MAX as u16;
(((data & mask) * t_mask / mask) & t_mask) as u8
}
impl Banner {
pub fn new<T: Read + Seek>(io: &mut T) -> Result<Self, Error> {
io.seek(SeekFrom::Start(0x20))?;
let mut data = vec![0u8; 0x1800];
io.read_exact(&mut data)?;
let data = data
.chunks_exact(2)
.map(|a| u16::from_be_bytes([a[0], a[1]]))
.collect();
let mut tmp = [0u8; 0x20];
io.read_exact(&mut tmp)?;
let game_name = trim(&from_latin1_or_shift_jis(&tmp)?).to_string();
io.read_exact(&mut tmp)?;
let developer = trim(&from_latin1_or_shift_jis(&tmp)?).to_string();
let mut tmp = [0u8; 0x40];
io.read_exact(&mut tmp)?;
let full_title = trim(&from_latin1_or_shift_jis(&tmp)?).to_string();
io.read_exact(&mut tmp)?;
let full_developer = trim(&from_latin1_or_shift_jis(&tmp)?).to_string();
let mut tmp = [0u8; 0x80];
io.read_exact(&mut tmp)?;
let description = trim(&from_latin1_or_shift_jis(&tmp)?).to_string();
Ok(Self {
data,
game_name,
developer,
full_title,
full_developer,
description,
})
}
#[must_use]
pub fn extract_rgba_banner_image(&self) -> Vec<u8> {
let width = 96;
let height = 32;
let tile_w = 4;
let tile_h = 4;
let mut index = 0;
let mut output = vec![0u8; width * height * 4];
for y in (0..height).step_by(tile_h) {
for x in (0..width).step_by(tile_w) {
for ty in 0..tile_h {
for tx in 0..tile_w {
let pixel: u16 = self.data[index];
index += 1;
let (a, r, g, b) = if pixel >> 15 == 0 {
(
scale_to_bits::<3>(pixel >> 12),
scale_to_bits::<4>(pixel >> 8),
scale_to_bits::<4>(pixel >> 4),
scale_to_bits::<4>(pixel),
)
} else {
(
0xFF,
scale_to_bits::<5>(pixel >> 10),
scale_to_bits::<5>(pixel >> 5),
scale_to_bits::<5>(pixel),
)
};
output[4 * ((y + ty) * 96 + (x + tx))] = r;
output[4 * ((y + ty) * 96 + (x + tx)) + 1] = g;
output[4 * ((y + ty) * 96 + (x + tx)) + 2] = b;
output[4 * ((y + ty) * 96 + (x + tx)) + 3] = a;
}
}
}
}
output
}
}