use std::io::{Cursor, Read, Seek, SeekFrom};
use byteorder::{ByteOrder, ReadBytesExt};
use crate::compat::CompatTable;
use crate::error::Result;
use crate::io::read::{BdatFile, BdatReader, BdatSlice};
use crate::io::BDAT_MAGIC;
use crate::legacy::read::{LegacyBytes, LegacyReader};
use crate::modern::FileReader;
use crate::{BdatVersion, LegacyVersion, SwitchEndian, WiiEndian};
pub enum VersionReader<R: Read + Seek> {
LegacyWii(LegacyReader<R, WiiEndian>),
LegacySwitch(LegacyReader<R, SwitchEndian>),
Modern(FileReader<BdatReader<R, SwitchEndian>, SwitchEndian>),
}
pub enum VersionSlice<'b> {
LegacyWii(LegacyBytes<'b, WiiEndian>),
LegacySwitch(LegacyBytes<'b, SwitchEndian>),
Modern(FileReader<BdatSlice<'b, SwitchEndian>, SwitchEndian>),
}
#[derive(thiserror::Error, Debug)]
pub enum DetectError {
#[error("Not a BDAT file")]
NotBdat,
#[error("Can't determine legacy platform: no tables found")]
LegacyNoTables,
}
pub fn from_bytes(bytes: &mut [u8]) -> Result<VersionSlice<'_>> {
match detect_version(Cursor::new(&bytes))? {
BdatVersion::Legacy(v @ LegacyVersion::Switch | v @ LegacyVersion::New3ds) => {
Ok(VersionSlice::LegacySwitch(LegacyBytes::new(bytes, v)?))
}
BdatVersion::Legacy(v @ LegacyVersion::Wii | v @ LegacyVersion::X) => {
Ok(VersionSlice::LegacyWii(LegacyBytes::new(bytes, v)?))
}
BdatVersion::Modern => Ok(VersionSlice::Modern(
FileReader::<_, SwitchEndian>::read_file(BdatSlice::<SwitchEndian>::new(bytes))?,
)),
}
}
pub fn from_reader<R: Read + Seek>(mut reader: R) -> Result<VersionReader<R>> {
let pos = reader.stream_position()?;
let version = detect_version(&mut reader)?;
reader.seek(SeekFrom::Start(pos))?;
match version {
BdatVersion::Legacy(v @ LegacyVersion::Switch | v @ LegacyVersion::New3ds) => {
Ok(VersionReader::LegacySwitch(LegacyReader::new(reader, v)?))
}
BdatVersion::Legacy(v @ LegacyVersion::Wii | v @ LegacyVersion::X) => {
Ok(VersionReader::LegacyWii(LegacyReader::new(reader, v)?))
}
BdatVersion::Modern => Ok(VersionReader::Modern(
FileReader::<_, SwitchEndian>::read_file(BdatReader::<_, SwitchEndian>::new(reader))?,
)),
}
}
pub fn detect_bytes_version(bytes: &[u8]) -> Result<BdatVersion> {
detect_version(Cursor::new(bytes))
}
pub fn detect_file_version<R: Read + Seek>(reader: R) -> Result<BdatVersion> {
detect_version(reader)
}
fn detect_version<R: Read + Seek>(mut reader: R) -> Result<BdatVersion> {
let mut magic = [0u8; 4];
reader.read_exact(&mut magic)?;
if magic == BDAT_MAGIC {
return Ok(BdatVersion::Modern);
}
const MAGIC_INT: u32 = u32::from_le_bytes(BDAT_MAGIC);
if magic == [0, 0, 0, 0] {
return Err(DetectError::LegacyNoTables.into());
}
let mut actual_table_count = 0;
let mut new_magic = [0u8; 4];
let mut first_offset = 0;
reader.seek(SeekFrom::Current(4))?;
loop {
reader.read_exact(&mut new_magic)?;
if new_magic == [0, 0, 0, 0]
|| SwitchEndian::read_u32(&new_magic) == MAGIC_INT
|| WiiEndian::read_u32(&new_magic) == MAGIC_INT
{
break;
}
if first_offset == 0 {
first_offset = WiiEndian::read_u32(&new_magic);
}
actual_table_count += 1;
}
reader.seek(SeekFrom::Start(0))?;
let expected_table_count = SwitchEndian::read_u32(&magic);
if actual_table_count == expected_table_count {
reader.seek(SeekFrom::Start(
SwitchEndian::read_u32(&first_offset.to_be_bytes()) as u64,
))?;
reader.read_exact(&mut new_magic)?;
if WiiEndian::read_u32(&new_magic) == MAGIC_INT {
return Ok(LegacyVersion::New3ds.into());
} else if SwitchEndian::read_u32(&new_magic) == MAGIC_INT {
return Ok(LegacyVersion::Switch.into());
}
return Err(DetectError::NotBdat.into());
}
reader.seek(SeekFrom::Start(first_offset as u64))?;
if reader.read_u32::<SwitchEndian>()? != MAGIC_INT {
return Err(DetectError::NotBdat.into());
}
reader.seek(SeekFrom::Current(32 - 4 * 3))?;
let string_table_offset = reader.read_u32::<WiiEndian>()?;
let string_table_len = reader.read_u32::<WiiEndian>()?;
let final_offset = string_table_offset + string_table_len;
if first_offset + 36 > final_offset {
return Ok(LegacyVersion::Wii.into());
}
let t_32 = reader.read_u32::<WiiEndian>()? >> 16;
let t_36 = reader.read_u32::<WiiEndian>()?;
Ok(match (t_32, t_36) {
(x, 0) if x <= final_offset => LegacyVersion::X.into(),
(_, _) => LegacyVersion::Wii.into(),
})
}
impl<'b, R: Read + Seek> BdatFile<'b> for VersionReader<R> {
type TableOut = CompatTable<'b>;
fn get_tables(&mut self) -> crate::error::Result<Vec<CompatTable<'b>>> {
match self {
Self::LegacySwitch(r) => r
.get_tables()
.map(|v| v.into_iter().map(Into::into).collect()),
Self::LegacyWii(r) => r
.get_tables()
.map(|v| v.into_iter().map(Into::into).collect()),
Self::Modern(r) => r
.get_tables()
.map(|v| v.into_iter().map(Into::into).collect()),
}
}
fn table_count(&self) -> usize {
match self {
Self::LegacySwitch(r) => r.table_count(),
Self::LegacyWii(r) => r.table_count(),
Self::Modern(r) => r.table_count(),
}
}
}
impl<'b> BdatFile<'b> for VersionSlice<'b> {
type TableOut = CompatTable<'b>;
fn get_tables(&mut self) -> crate::error::Result<Vec<CompatTable<'b>>> {
match self {
Self::LegacySwitch(r) => r
.get_tables()
.map(|v| v.into_iter().map(Into::into).collect()),
Self::LegacyWii(r) => r
.get_tables()
.map(|v| v.into_iter().map(Into::into).collect()),
Self::Modern(r) => r
.get_tables()
.map(|v| v.into_iter().map(Into::into).collect()),
}
}
fn table_count(&self) -> usize {
match self {
Self::LegacySwitch(r) => r.table_count(),
Self::LegacyWii(r) => r.table_count(),
Self::Modern(r) => r.table_count(),
}
}
}