diff --git a/psoutils/src/quest/dat.rs b/psoutils/src/quest/dat.rs index ec583d5..c23bf27 100644 --- a/psoutils/src/quest/dat.rs +++ b/psoutils/src/quest/dat.rs @@ -118,11 +118,13 @@ impl From<&QuestDatTableType> for u32 { } } +#[derive(Debug)] pub struct QuestDatTableHeader { pub table_type: QuestDatTableType, pub area: u32, } +#[derive(Debug)] pub struct QuestDatTable { pub header: QuestDatTableHeader, pub bytes: Box<[u8]>, @@ -160,6 +162,7 @@ impl QuestDatTable { } } +#[derive(Debug)] pub struct QuestDat { pub tables: Box<[QuestDatTable]>, } @@ -215,6 +218,18 @@ impl QuestDat { index += 1; } + // i wrote this check thinking that an empty .dat file is the most useless thing ever, + // but maybe it is possible to exist in a legitimate quest for a totally script-driven + // "quest" ... e.g. some sort of "utility quest" that exists just for the purpose of letting + // a user interact with the script stored in the .bin? dunno really, but i guess i might + // as well disable this check ... + // + //if tables.len() == 0 { + // return Err(QuestDatError::DataFormatError(String::from( + // "no tables found, probably not a .dat file?", + // ))); + //} + Ok(QuestDat { tables: tables.into_boxed_slice(), }) @@ -292,6 +307,9 @@ impl QuestDat { #[cfg(test)] pub mod tests { + use claim::*; + use rand::prelude::StdRng; + use rand::{Fill, SeedableRng}; use tempfile::TempDir; use super::*; @@ -612,4 +630,79 @@ pub mod tests { validate_quest_118_dat(&dat); Ok(()) } + + #[test] + pub fn bad_input_data_results_in_errors() -> Result<(), QuestDatError> { + let mut data: &[u8] = &[]; + assert_matches!( + QuestDat::from_uncompressed_bytes(&mut data), + Err(QuestDatError::IoError(..)) + ); + assert_matches!( + QuestDat::from_compressed_bytes(&mut data), + Err(QuestDatError::PrsCompressionError(..)) + ); + + let mut data: &[u8] = b"This is definitely not a quest"; + assert_matches!( + QuestDat::from_uncompressed_bytes(&mut data), + Err(QuestDatError::DataFormatError(..)) + ); + assert_matches!( + QuestDat::from_compressed_bytes(&mut data), + Err(QuestDatError::PrsCompressionError(..)) + ); + + // dat table header with a table_size issue + let mut header: &[u8] = &[ + 0x01, 0x00, 0x00, 0x00, // table_type + 0xD3, 0x08, 0x00, 0x00, // table_size + 0x00, 0x00, 0x00, 0x00, // area + 0xC4, 0x08, 0x00, 0x00, // table_body_size + ]; + assert_matches!( + QuestDat::from_uncompressed_bytes(&mut header), + Err(QuestDatError::DataFormatError(..)) + ); + + // dat table header with a table_type issue + let mut header: &[u8] = &[ + 0x11, 0x00, 0x00, 0x00, // table_type + 0xD4, 0x08, 0x00, 0x00, // table_size + 0x00, 0x00, 0x00, 0x00, // area + 0xC4, 0x08, 0x00, 0x00, // table_body_size + ]; + assert_matches!( + QuestDat::from_uncompressed_bytes(&mut header), + Err(QuestDatError::DataFormatError(..)) + ); + + // a valid dat table header ... + let header: &[u8] = &[ + 0x01, 0x00, 0x00, 0x00, // table_type + 0xD4, 0x08, 0x00, 0x00, // table_size + 0x00, 0x00, 0x00, 0x00, // area + 0xC4, 0x08, 0x00, 0x00, // table_body_size + ]; + // ... with not enough random garbage in the table body area + let mut random_garbage = [0u8; 256]; + let mut rng = StdRng::seed_from_u64(76478964); + random_garbage.try_fill(&mut rng).unwrap(); + let data = [header, &random_garbage].concat(); + assert_matches!( + QuestDat::from_uncompressed_bytes(&mut data.as_slice()), + Err(QuestDatError::IoError(..)) + ); + + // totally random data which should be interpreted as an invalid table header + let mut data = vec![0u8; 1024]; + let mut rng = StdRng::seed_from_u64(15785357); + data.try_fill(&mut rng).unwrap(); + assert_matches!( + QuestDat::from_uncompressed_bytes(&mut data.as_slice()), + Err(QuestDatError::DataFormatError(..)) + ); + + Ok(()) + } }