You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

292 lines
9.2 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. use log::warn;
  2. use std::collections::{HashMap, BTreeMap, BTreeSet};
  3. use std::fs::File;
  4. use std::io::{Read, Write, Cursor, Seek, SeekFrom};
  5. use std::path::PathBuf;
  6. use std::convert::TryInto;
  7. use thiserror::Error;
  8. use serde::{Serialize, Deserialize};
  9. use ages_prs::{LegacyPrsDecoder, LegacyPrsEncoder};
  10. use byteorder::{LittleEndian, ReadBytesExt};
  11. use libpso::util::array_to_utf16;
  12. use crate::ship::map::{MapArea, MapAreaError, MapObject, MapEnemy, enemy_data_from_stream, objects_from_stream};
  13. use crate::ship::room::Episode;
  14. use crate::ship::map::area::{MapAreaLookup, MapAreaLookupBuilder};
  15. #[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
  16. pub struct QuestCategory {
  17. index: usize,
  18. pub name: String,
  19. pub description: String,
  20. }
  21. #[derive(Debug, Serialize, Deserialize, Hash)]
  22. struct QuestListEntry {
  23. bin: String,
  24. dat: String,
  25. }
  26. #[derive(Debug, Serialize, Deserialize, Hash)]
  27. struct QuestListCategory {
  28. list_order: usize,
  29. description: String,
  30. quests: Vec<QuestListEntry>,
  31. }
  32. #[derive(Debug, Serialize, Deserialize)]
  33. struct QuestListConfig {
  34. questlist: HashMap<String, Vec<QuestListEntry>>,
  35. }
  36. #[derive(Error, Debug)]
  37. #[error("")]
  38. pub enum ParseDatError {
  39. IoError(#[from] std::io::Error),
  40. MapError(#[from] MapAreaError),
  41. UnknownDatHeader(u32),
  42. CouldNotDetermineEpisode,
  43. InvalidMapAreaId(u16),
  44. }
  45. const DAT_OBJECT_HEADER_ID: u32 = 1;
  46. const DAT_ENEMY_HEADER_ID: u32 = 2;
  47. const DAT_WAVE_HEADER_ID: u32 = 3;
  48. enum DatBlock {
  49. Object(Vec<Option<MapObject>>),
  50. Enemy(Vec<Option<MapEnemy>>),
  51. Wave,
  52. }
  53. fn read_dat_section_header<T: Read + Seek>(cursor: &mut T, episode: &Episode, map_areas: &MapAreaLookup) -> Result<DatBlock, ParseDatError> {
  54. let header = cursor.read_u32::<LittleEndian>()?;
  55. let _offset = cursor.read_u32::<LittleEndian>()?;
  56. let area = cursor.read_u16::<LittleEndian>()?;
  57. let _unknown1 = cursor.read_u16::<LittleEndian>()?;
  58. let length = cursor.read_u32::<LittleEndian>()?;
  59. let map_area = map_areas.get_area_map(area).map_err(|_| ParseDatError::InvalidMapAreaId(area))?;
  60. match header {
  61. DAT_OBJECT_HEADER_ID => {
  62. let mut obj_data = vec![0u8; length as usize];
  63. cursor.read_exact(&mut obj_data)?;
  64. let mut obj_cursor = Cursor::new(obj_data);
  65. let objects = objects_from_stream(&mut obj_cursor, episode, map_area);
  66. Ok(DatBlock::Object(objects))
  67. },
  68. DAT_ENEMY_HEADER_ID => {
  69. let mut enemy_data = vec![0u8; length as usize];
  70. cursor.read_exact(&mut enemy_data)?;
  71. let mut enemy_cursor = Cursor::new(enemy_data);
  72. let enemies = enemy_data_from_stream(&mut enemy_cursor, map_area, episode);
  73. Ok(DatBlock::Enemy(enemies))
  74. },
  75. DAT_WAVE_HEADER_ID => {
  76. cursor.seek(SeekFrom::Current(length as i64))?;
  77. Ok(DatBlock::Wave)
  78. },
  79. _ => Err(ParseDatError::UnknownDatHeader(header))
  80. }
  81. }
  82. fn quest_episode(bin: &[u8]) -> Option<Episode> {
  83. for bytes in bin.windows(3) {
  84. // set_episode
  85. if bytes[0] == 0xF8 && bytes[1] == 0xBC {
  86. return Episode::from_quest(bytes[2]).ok()
  87. }
  88. }
  89. None
  90. }
  91. fn map_area_mappings(bin: &[u8]) -> MapAreaLookup {
  92. let mut map_areas = MapAreaLookupBuilder::default();
  93. for bytes in bin.windows(4) {
  94. // BB_Map_Designate
  95. if bytes[0] == 0xF9 && bytes[1] == 0x51 {
  96. //return Some(Episode::from_quest(bytes[2]).ok()?)
  97. let floor_value = bytes[2] as u16;
  98. if let Some(map_area) = MapArea::from_bb_map_designate(bytes[3]) {
  99. map_areas = map_areas.add(floor_value, map_area);
  100. }
  101. }
  102. }
  103. map_areas.build()
  104. }
  105. #[allow(clippy::type_complexity)]
  106. fn parse_dat(dat: &[u8], episode: &Episode, map_areas: &MapAreaLookup) -> Result<(Vec<Option<MapEnemy>>, Vec<Option<MapObject>>), ParseDatError> {
  107. let mut cursor = Cursor::new(dat);
  108. let header_iter = std::iter::from_fn(move || {
  109. match read_dat_section_header(&mut cursor, episode, map_areas) {
  110. Ok(dat_block) => Some(dat_block),
  111. Err(err) => {
  112. warn!("unknown header in dat: {:?}", err);
  113. None
  114. }
  115. }
  116. });
  117. Ok(header_iter.fold((Vec::new(), Vec::new()), |(mut enemies, mut objects), dat_block| {
  118. match dat_block {
  119. DatBlock::Object(mut objs) => {
  120. objects.append(&mut objs)
  121. },
  122. DatBlock::Enemy(mut enemy) => {
  123. enemies.append(&mut enemy)
  124. },
  125. _ => {}
  126. }
  127. (enemies, objects)
  128. }))
  129. }
  130. #[derive(Error, Debug)]
  131. #[error("")]
  132. pub enum QuestLoadError {
  133. IoError(#[from] std::io::Error),
  134. ParseDatError(#[from] ParseDatError),
  135. CouldNotReadMetadata,
  136. CouldNotLoadConfigFile,
  137. }
  138. #[derive(Debug)]
  139. pub struct Quest {
  140. pub name: String,
  141. pub description: String,
  142. pub full_description: String,
  143. pub language: u16,
  144. pub id: u16,
  145. pub bin_blob: Vec<u8>,
  146. pub dat_blob: Vec<u8>,
  147. pub enemies: Vec<Option<MapEnemy>>,
  148. pub objects: Vec<Option<MapObject>>,
  149. pub map_areas: MapAreaLookup,
  150. }
  151. impl Quest {
  152. fn from_bin_dat(bin: Vec<u8>, dat: Vec<u8>) -> Result<Quest, QuestLoadError> {
  153. let id = u16::from_le_bytes(bin[16..18].try_into().map_err(|_| QuestLoadError::CouldNotReadMetadata)?);
  154. let language = u16::from_le_bytes(bin[18..20].try_into().map_err(|_| QuestLoadError::CouldNotReadMetadata)?);
  155. let name = array_to_utf16(&bin[24..88]);
  156. let description = array_to_utf16(&bin[88..334]);
  157. let full_description = array_to_utf16(&bin[334..920]);
  158. let episode = quest_episode(&bin).ok_or(ParseDatError::CouldNotDetermineEpisode)?;
  159. let map_areas = map_area_mappings(&bin);
  160. let (enemies, objects) = parse_dat(&dat, &episode, &map_areas)?;
  161. let mut prs_bin = LegacyPrsEncoder::new(Vec::new());
  162. prs_bin.write_all(&bin)?;
  163. let mut prs_dat = LegacyPrsEncoder::new(Vec::new());
  164. prs_dat.write_all(&dat)?;
  165. Ok(Quest {
  166. name,
  167. description,
  168. full_description,
  169. id,
  170. language,
  171. bin_blob: prs_bin.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?,
  172. dat_blob: prs_dat.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?,
  173. enemies,
  174. objects,
  175. map_areas,
  176. })
  177. }
  178. }
  179. // QuestCollection
  180. pub type QuestList = BTreeMap<QuestCategory, Vec<Quest>>;
  181. pub fn load_quest(bin_path: PathBuf, dat_path: PathBuf) -> Option<Quest> {
  182. let dat_file = File::open(PathBuf::from("data/quests/").join(dat_path.clone()))
  183. .map_err(|err| {
  184. warn!("could not load quest file {:?}: {:?}", dat_path, err)
  185. }).ok()?;
  186. //let bin_file = File::open(format!("data/quests/{}", bin_path))
  187. let bin_file = File::open(PathBuf::from("data/quests/").join(bin_path.clone()))
  188. .map_err(|err| {
  189. warn!("could not load quest file {:?}: {:?}", bin_path, err)
  190. }).ok()?;
  191. let mut dat_prs = LegacyPrsDecoder::new(dat_file);
  192. let mut bin_prs = LegacyPrsDecoder::new(bin_file);
  193. let mut dat = Vec::new();
  194. let mut bin = Vec::new();
  195. dat_prs.read_to_end(&mut dat).ok()?;
  196. bin_prs.read_to_end(&mut bin).ok()?;
  197. let quest = Quest::from_bin_dat(bin, dat).map_err(|err| {
  198. warn!("could not parse quest file {:?}/{:?}: {:?}", bin_path, dat_path, err)
  199. }).ok()?;
  200. Some(quest)
  201. }
  202. pub fn load_quests(quest_path: PathBuf) -> Result<QuestList, QuestLoadError> {
  203. let mut f = File::open(quest_path).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
  204. let mut s = String::new();
  205. f.read_to_string(&mut s)?;
  206. let mut used_quest_ids = BTreeSet::new();
  207. let ql: BTreeMap<String, QuestListCategory> = toml::from_str(s.as_str()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
  208. Ok(ql.into_iter().map(|(category, category_details)| {
  209. let quests = category_details.quests
  210. .into_iter()
  211. .filter_map(|quest| {
  212. load_quest(quest.bin.into(), quest.dat.into())
  213. .and_then(|quest | {
  214. if used_quest_ids.contains(&quest.id) {
  215. warn!("quest id already exists: {}", quest.id);
  216. return None;
  217. }
  218. used_quest_ids.insert(quest.id);
  219. Some(quest)
  220. })
  221. });
  222. (QuestCategory{
  223. index: category_details.list_order,
  224. name: category,
  225. description: category_details.description,
  226. }, quests.collect())
  227. }).collect())
  228. }
  229. #[cfg(test)]
  230. mod test {
  231. use super::*;
  232. // the quest phantasmal world 4 uses the tower map twice, to do this it had to remap
  233. // one of the other maps to be a second tower
  234. #[test]
  235. fn test_quest_with_remapped_floors() {
  236. let pw4 = load_quest("q236-ext-bb.bin".into(), "q236-ext-bb.dat".into()).unwrap();
  237. let enemies_not_in_tower = pw4.enemies.iter()
  238. .filter(|enemy| {
  239. enemy.is_some()
  240. })
  241. .filter(|enemy| {
  242. enemy.unwrap().map_area != MapArea::Tower
  243. });
  244. assert!(enemies_not_in_tower.count() == 0);
  245. }
  246. }