diff --git a/Cargo.toml b/Cargo.toml index 67ba23c..97deda6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,7 @@ tokio = { version = "1.12.0", features = ["net"], optional = true } vec_mut_scan = "0.4" webbrowser = "0.5.5" winit = { git = "https://github.com/alula/winit.git", rev = "6acf76ff192dd8270aaa119b9f35716c03685f9f", optional = true, default_features = false, features = ["x11"] } +xmltree = "0.10.3" #[build-dependencies] #gl_generator = { version = "0.14.0", optional = true } diff --git a/src/engine_constants/mod.rs b/src/engine_constants/mod.rs index 36aeebb..2c3f764 100644 --- a/src/engine_constants/mod.rs +++ b/src/engine_constants/mod.rs @@ -3,6 +3,7 @@ use std::io::{BufRead, BufReader, Cursor, Read}; use byteorder::{LE, ReadBytesExt}; use case_insensitive_hashmap::CaseInsensitiveHashMap; +use xmltree::Element; use crate::case_insensitive_hashmap; use crate::common::{BulletFlag, Color, Rect}; @@ -301,6 +302,7 @@ pub struct EngineConstants { pub organya_paths: Vec, pub credit_illustration_paths: Vec, pub animated_face_table: Vec, + pub string_table: HashMap, } impl Clone for EngineConstants { @@ -329,6 +331,7 @@ impl Clone for EngineConstants { organya_paths: self.organya_paths.clone(), credit_illustration_paths: self.credit_illustration_paths.clone(), animated_face_table: self.animated_face_table.clone(), + string_table: self.string_table.clone(), } } } @@ -1601,6 +1604,7 @@ impl EngineConstants { "endpic/".to_owned(), // NXEngine ], animated_face_table: vec![AnimatedFace { face_id: 0, anim_id: 0, anim_frames: vec![(0, 0)] }], + string_table: HashMap::new(), } } @@ -1727,6 +1731,27 @@ impl EngineConstants { self.game.new_game_player_pos = pos; } + pub fn load_nx_stringtable(&mut self, ctx: &mut Context) -> GameResult { + if let Ok(file) = filesystem::open(ctx, "/base/stringtable.sta") { + let mut reader = BufReader::new(file); + let _ = reader.read_exact(&mut [0; 3]); + if let Ok(xml) = Element::parse(reader) { + for node in &xml.get_child("category").unwrap().children { + let element = node.as_element().unwrap(); + let key = element.attributes.get_key_value("name").unwrap().1.to_string(); + let english = element + .get_child("string") + .unwrap() + .get_text() + .unwrap_or(std::borrow::Cow::Borrowed("")) + .to_string(); + self.string_table.insert(key, english); + } + } + } + Ok(()) + } + pub fn apply_constant_json_files(&mut self) {} /// Loads bullet.tbl and arms_level.tbl from CS+ files, diff --git a/src/mod_list.rs b/src/mod_list.rs index 3e19152..051040b 100644 --- a/src/mod_list.rs +++ b/src/mod_list.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::io::{BufRead, BufReader}; use std::iter::Peekable; use std::str::Chars; @@ -11,6 +12,8 @@ pub struct ModInfo { pub requirement: Requirement, pub priority: u32, pub path: String, + pub name: String, + pub description: String, } #[derive(Debug, Copy, Clone)] @@ -32,7 +35,7 @@ pub struct ModList { } impl ModList { - pub fn load(ctx: &mut Context) -> GameResult { + pub fn load(ctx: &mut Context, string_table: &HashMap) -> GameResult { let mut mods = Vec::new(); if let Ok(file) = filesystem::open(ctx, "/mods.txt") { @@ -132,7 +135,22 @@ impl ModList { } } - mods.push(ModInfo { id, requirement, priority, path }) + let mut name = String::new(); + let mut description = String::new(); + + if let Ok(file) = filesystem::open(ctx, [&path, "/mod.txt"].join("")) { + let reader = BufReader::new(file); + let mut lines = reader.lines(); + if let Some(line) = lines.nth(2) { + let read_name = line.unwrap_or("No Mod Name".to_string()).to_string(); + name = string_table.get(&read_name).unwrap_or(&read_name).to_string(); + } + if let Some(line) = lines.next() { + description = line.unwrap_or("No Description".to_string()).to_string(); + } + } + + mods.push(ModInfo { id, requirement, priority, path, name, description }) } } diff --git a/src/scene/title_scene.rs b/src/scene/title_scene.rs index d109a7b..7d7f7ac 100644 --- a/src/scene/title_scene.rs +++ b/src/scene/title_scene.rs @@ -162,7 +162,7 @@ impl Scene for TitleScene { self.save_select_menu.init(state, ctx)?; for mod_info in state.mod_list.mods.iter() { - self.challenges_menu.push_entry(MenuEntry::Active(mod_info.path.clone())); + self.challenges_menu.push_entry(MenuEntry::Active(mod_info.name.clone())); } self.challenges_menu.push_entry(MenuEntry::Active("< Back".to_string())); diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index 95b067a..6c21642 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -201,6 +201,7 @@ impl SharedGameState { ctx.size_hint = (854, 480); constants.apply_csplus_patches(&sound_manager); constants.apply_csplus_nx_patches(); + constants.load_nx_stringtable(ctx)?; } else if filesystem::exists(ctx, "/base/Nicalis.bmp") || filesystem::exists(ctx, "/base/Nicalis.png") { info!("Cave Story+ (PC) data files detected."); constants.apply_csplus_patches(&sound_manager); @@ -218,7 +219,7 @@ impl SharedGameState { BMFontRenderer::load(&vec!["/".to_owned()], "/builtin/builtin_font.fnt", ctx) })?; - let mut mod_list = ModList::load(ctx)?; + let mut mod_list = ModList::load(ctx, &constants.string_table)?; for i in 0..0xffu8 { let path = format!("/pxt/fx{:02x}.pxt", i);