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.

325 lines
11 KiB

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