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, TryFrom}; use thiserror::Error; use serde::{Serialize, Deserialize}; use ages_prs::{LegacyPrsDecoder, LegacyPrsEncoder}; use byteorder::{LittleEndian, ReadBytesExt}; use libpso::packet::ship::QuestChunk; 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<QuestListEntry>, } #[derive(Debug, Serialize, Deserialize)] struct QuestListConfig { questlist: HashMap<String, Vec<QuestListEntry>>, } #[derive(Error, Debug)] #[error("")] 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<Option<MapObject>>), Enemy(Vec<Option<MapEnemy>>), Wave, } fn read_dat_section_header<T: Read + Seek>(cursor: &mut T, episode: &Episode) -> Result<DatBlock, ParseDatError> { let header = cursor.read_u32::<LittleEndian>()?; let offset = cursor.read_u32::<LittleEndian>()?; let area = cursor.read_u32::<LittleEndian>()?; let length = cursor.read_u32::<LittleEndian>()?; 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<Episode> { for bytes in bin.windows(3) { if bytes[0] == 0xF8 && bytes[1] == 0xBC { warn!("ep? {:?}", bytes[2]); return Some(Episode::from_quest(bytes[2]).ok()?) } } None } fn parse_dat(dat: &[u8], episode: &Episode) -> Result<(Vec<Option<MapEnemy>>, Vec<Option<MapObject>>), 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("")] enum QuestLoadError { ParseDatError(#[from] ParseDatError), } #[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<u8>, pub dat_blob: Vec<u8>, pub enemies: Vec<Option<MapEnemy>>, pub objects: Vec<Option<MapObject>>, } impl Quest { fn from_bin_dat(bin: Vec<u8>, dat: Vec<u8>) -> Result<Quest, QuestLoadError> { let id = u16::from_le_bytes(bin[16..18].try_into().unwrap()); let language = u16::from_le_bytes(bin[18..20].try_into().unwrap()); 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().unwrap(), dat_blob: prs_dat.into_inner().unwrap(), enemies: enemies, objects: objects, }) } } // QuestCollection pub type QuestList = BTreeMap<QuestCategory, Vec<Quest>>; pub fn load_quests(quest_path: PathBuf) -> QuestList { let mut f = File::open(quest_path).unwrap(); let mut s = String::new(); f.read_to_string(&mut s); let mut used_quest_ids = BTreeSet::new(); let ql: BTreeMap<String, QuestListCategory> = toml::from_str(s.as_str()).unwrap(); ql.into_iter().map(|(category, category_details)| { let quests = category_details.quests .into_iter() .filter_map(|quest| { warn!("{:?}", quest.bin); let dat_file = File::open(format!("data/quests/{}", quest.dat)).unwrap(); let bin_file = File::open(format!("data/quests/{}", quest.bin)).unwrap(); 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).unwrap(); bin_prs.read_to_end(&mut bin).unwrap(); let quest = Quest::from_bin_dat(bin, dat).unwrap(); if used_quest_ids.contains(&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() }