use log::warn; use std::collections::{HashMap, BTreeMap, BTreeSet}; use std::fs::File; use std::io::{Read, Write, Cursor, Seek, SeekFrom}; use std::path::PathBuf; use std::convert::TryInto; use thiserror::Error; use serde::{Serialize, Deserialize}; use ages_prs::{LegacyPrsDecoder, LegacyPrsEncoder}; use byteorder::{LittleEndian, ReadBytesExt}; use libpso::util::array_to_utf16; use crate::ship::map::{MapArea, MapAreaError, MapObject, MapEnemy, enemy_data_from_stream, objects_from_stream}; use crate::ship::room::Episode; #[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct QuestCategory { index: usize, pub name: String, pub description: String, } #[derive(Debug, Serialize, Deserialize, Hash)] struct QuestListEntry { bin: String, dat: String, } #[derive(Debug, Serialize, Deserialize, Hash)] struct QuestListCategory { list_order: usize, description: String, quests: Vec, } #[derive(Debug, Serialize, Deserialize)] struct QuestListConfig { questlist: HashMap>, } #[derive(Error, Debug)] #[error("")] pub enum ParseDatError { IoError(#[from] std::io::Error), MapError(#[from] MapAreaError), UnknownDatHeader(u32), CouldNotDetermineEpisode, } const DAT_OBJECT_HEADER_ID: u32 = 1; const DAT_ENEMY_HEADER_ID: u32 = 2; const DAT_WAVE_HEADER_ID: u32 = 3; enum DatBlock { Object(Vec>), Enemy(Vec>), Wave, } fn read_dat_section_header(cursor: &mut T, episode: &Episode) -> Result { let header = cursor.read_u32::()?; let _offset = cursor.read_u32::()?; let area = cursor.read_u32::()?; let length = cursor.read_u32::()?; let map_area = MapArea::from_value(episode, area)?; match header { DAT_OBJECT_HEADER_ID => { let mut obj_data = vec![0u8; length as usize]; cursor.read(&mut obj_data)?; let mut obj_cursor = Cursor::new(obj_data); let objects = objects_from_stream(&mut obj_cursor, episode, &map_area); Ok(DatBlock::Object(objects)) }, DAT_ENEMY_HEADER_ID => { let mut enemy_data = vec![0u8; length as usize]; cursor.read(&mut enemy_data)?; let mut enemy_cursor = Cursor::new(enemy_data); let enemies = enemy_data_from_stream(&mut enemy_cursor, &map_area, episode); Ok(DatBlock::Enemy(enemies)) }, DAT_WAVE_HEADER_ID => { cursor.seek(SeekFrom::Current(length as i64))?; Ok(DatBlock::Wave) }, _ => Err(ParseDatError::UnknownDatHeader(header)) } } fn quest_episode(bin: &[u8]) -> Option { for bytes in bin.windows(3) { if bytes[0] == 0xF8 && bytes[1] == 0xBC { return Some(Episode::from_quest(bytes[2]).ok()?) } } None } fn parse_dat(dat: &[u8], episode: &Episode) -> Result<(Vec>, Vec>), ParseDatError> { let mut cursor = Cursor::new(dat); let header_iter = std::iter::from_fn(move || { match read_dat_section_header(&mut cursor, episode) { Ok(dat_block) => Some(dat_block), Err(err) => { warn!("unknown header in dat: {:?}", err); None } } }); Ok(header_iter.fold((Vec::new(), Vec::new()), |(mut enemies, mut objects), dat_block| { match dat_block { DatBlock::Object(mut objs) => { objects.append(&mut objs) }, DatBlock::Enemy(mut enemy) => { enemies.append(&mut enemy) }, _ => {} } (enemies, objects) })) } #[derive(Error, Debug)] #[error("")] pub enum QuestLoadError { IoError(#[from] std::io::Error), ParseDatError(#[from] ParseDatError), CouldNotReadMetadata, CouldNotLoadConfigFile, } #[derive(Debug)] pub struct Quest { pub name: String, pub description: String, pub full_description: String, pub language: u16, pub id: u16, pub bin_blob: Vec, pub dat_blob: Vec, pub enemies: Vec>, pub objects: Vec>, } impl Quest { fn from_bin_dat(bin: Vec, dat: Vec) -> Result { let id = u16::from_le_bytes(bin[16..18].try_into().map_err(|_| QuestLoadError::CouldNotReadMetadata)?); let language = u16::from_le_bytes(bin[18..20].try_into().map_err(|_| QuestLoadError::CouldNotReadMetadata)?); let name = array_to_utf16(&bin[24..88]); let description = array_to_utf16(&bin[88..334]); let full_description = array_to_utf16(&bin[334..920]); let episode = quest_episode(&bin).ok_or(ParseDatError::CouldNotDetermineEpisode)?; let (enemies, objects) = parse_dat(&dat, &episode)?; let mut prs_bin = LegacyPrsEncoder::new(Vec::new()); prs_bin.write(&bin)?; let mut prs_dat = LegacyPrsEncoder::new(Vec::new()); prs_dat.write(&dat)?; Ok(Quest { name: name, description: description, full_description: full_description, id: id, language: language, bin_blob: prs_bin.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?, dat_blob: prs_dat.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?, enemies: enemies, objects: objects, }) } } // QuestCollection pub type QuestList = BTreeMap>; pub fn load_quests(quest_path: PathBuf) -> Result { let mut f = File::open(quest_path).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?; let mut s = String::new(); f.read_to_string(&mut s)?; let mut used_quest_ids = BTreeSet::new(); let ql: BTreeMap = toml::from_str(s.as_str()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?; Ok(ql.into_iter().map(|(category, category_details)| { let quests = category_details.quests .into_iter() .filter_map(|quest| { let dat_file = File::open(format!("data/quests/{}", quest.dat)) .map_err(|err| { warn!("could not load quest file {}: {:?}", quest.dat, err) }).ok()?; let bin_file = File::open(format!("data/quests/{}", quest.bin)) .map_err(|err| { warn!("could not load quest file {}: {:?}", quest.bin, err) }).ok()?; let mut dat_prs = LegacyPrsDecoder::new(dat_file); let mut bin_prs = LegacyPrsDecoder::new(bin_file); let mut dat = Vec::new(); let mut bin = Vec::new(); dat_prs.read_to_end(&mut dat).ok()?; bin_prs.read_to_end(&mut bin).ok()?; let quest = Quest::from_bin_dat(bin, dat).map_err(|err| { warn!("could not parse quest file {}/{}: {:?}", quest.bin, quest.dat, err) }).ok()?; if used_quest_ids.contains(&quest.id) { warn!("quest id already exists: {}", quest.id); return None; } used_quest_ids.insert(quest.id); Some(quest) }); (QuestCategory{ index: category_details.list_order, name: category, description: category_details.description, }, quests.collect()) }).collect()) }