diff --git a/psogc_quest_tool/Cargo.toml b/psogc_quest_tool/Cargo.toml index 2df3d21..a65d1df 100644 --- a/psogc_quest_tool/Cargo.toml +++ b/psogc_quest_tool/Cargo.toml @@ -8,7 +8,6 @@ edition = "2018" [dependencies] anyhow = "1.0.40" -crc = "1.8.1" [dependencies.psoutils] path = "../psoutils" diff --git a/psogc_quest_tool/src/info.rs b/psogc_quest_tool/src/info.rs index a4cb344..b9be263 100644 --- a/psogc_quest_tool/src/info.rs +++ b/psogc_quest_tool/src/info.rs @@ -2,135 +2,8 @@ use std::path::Path; use anyhow::{anyhow, Context, Result}; -use psoutils::quest::bin::QuestBin; -use psoutils::quest::dat::{QuestDat, QuestDatTableType}; use psoutils::quest::Quest; -use crate::utils::crc32; - -fn format_description_field(description: &String) -> String { - description - .trim() - .replace("\n", "\n ") -} - -fn display_quest_bin_info(bin: &QuestBin) { - let object_code_crc32 = crc32(bin.object_code.as_ref()); - let function_offset_table_crc32 = crc32(bin.function_offset_table.as_ref()); - - println!("QUEST .BIN FILE"); - println!("======================================================================"); - println!("name: {}", bin.header.name); - println!( - "object_code: size: {}, crc32: {:08x}", - bin.object_code.len(), - object_code_crc32 - ); - println!( - "function_offset_table: size: {}, crc32: {:08x}", - bin.function_offset_table.len(), - function_offset_table_crc32 - ); - println!("is_download: {}", bin.header.is_download); - println!( - "quest_number: {0} (8-bit) {1}, 0x{1:04x} (16-bit)", - bin.header.quest_number(), - bin.header.quest_number_u16() - ); - println!( - "episode: {} (0x{:02x})", - bin.header.episode() + 1, - bin.header.episode() - ); - println!( - "language: {:?}, encoding: {}", - bin.header.language, - bin.header.language.get_encoding().name() - ); - println!( - "short_description: {}\n", - format_description_field(&bin.header.short_description) - ); - println!( - "long_description: {}\n", - format_description_field(&bin.header.long_description) - ); - println!() -} - -fn display_quest_dat_info(dat: &QuestDat, episode: u32) { - println!("QUEST .DAT FILE"); - println!("================================================================================"); - println!("(Using episode {} to lookup table area names)", episode + 1); - - println!("Idx Size Table Type Area Count CRC32"); - - for (index, table) in dat.tables.iter().enumerate() { - let body_size = table.bytes.len(); - let body_crc32 = crc32(table.bytes.as_ref()); - - match table.table_type() { - QuestDatTableType::Object => { - let num_entities = body_size / 68; - println!( - "{:3} {:5} {:<21} {:30} {:5} {:08x}", - index, - body_size, - table.table_type().to_string(), - table.area_name(episode).to_string(), - num_entities, - body_crc32 - ); - } - QuestDatTableType::NPC => { - let num_entities = body_size / 72; - println!( - "{:3} {:5} {:<21} {:30} {:5} {:08x}", - index, - body_size, - table.table_type().to_string(), - table.area_name(episode).to_string(), - num_entities, - body_crc32 - ); - } - QuestDatTableType::Wave => { - println!( - "{:3} {:5} {:<21} {:30} {:08x}", - index, - body_size, - table.table_type().to_string(), - table.area_name(episode).to_string(), - body_crc32 - ); - } - QuestDatTableType::ChallengeModeSpawns => { - println!( - "{:3} {:5} {:<21} {:30} {:08x}", - index, - body_size, - table.table_type().to_string(), - table.area_name(episode).to_string(), - body_crc32 - ); - } - QuestDatTableType::ChallengeModeUnknown => { - println!( - "{:3} {:5} {:<21} {:30} {:08x}", - index, - body_size, - table.table_type().to_string(), - table.area_name(episode).to_string(), - body_crc32 - ); - } - QuestDatTableType::Unknown(n) => { - println!("{:3} {:5} Unknown: {}", index, body_size, n); - } - }; - } -} - pub fn quest_info(args: &[String]) -> Result<()> { println!("Showing quest information"); @@ -159,8 +32,9 @@ pub fn quest_info(args: &[String]) -> Result<()> { }; println!(); - display_quest_bin_info(&quest.bin); - display_quest_dat_info(&quest.dat, quest.bin.header.episode() as u32); + println!("{}", quest.display_bin_info()); + println!(); + println!("{}", quest.display_dat_info()); println!(); Ok(()) @@ -168,9 +42,10 @@ pub fn quest_info(args: &[String]) -> Result<()> { #[cfg(test)] mod tests { - use super::*; use claim::*; + use super::*; + // TODO: some way to match the specific error message string? or probably should just replace // anyhow usage with a specific error type ... diff --git a/psogc_quest_tool/src/lib.rs b/psogc_quest_tool/src/lib.rs index 0b357c0..c1144e3 100644 --- a/psogc_quest_tool/src/lib.rs +++ b/psogc_quest_tool/src/lib.rs @@ -1,3 +1,2 @@ pub mod convert; pub mod info; -mod utils; diff --git a/psoutils/Cargo.toml b/psoutils/Cargo.toml index be00411..67b7c2d 100644 --- a/psoutils/Cargo.toml +++ b/psoutils/Cargo.toml @@ -13,6 +13,7 @@ encoding_rs = "0.8.28" libc = "0.2.94" rand = "0.8.3" itertools = "0.10.0" +crc = "1.8.1" [dev-dependencies] claim = "0.5.0" diff --git a/psoutils/src/lib.rs b/psoutils/src/lib.rs index f3ed4f8..49d8d35 100644 --- a/psoutils/src/lib.rs +++ b/psoutils/src/lib.rs @@ -4,3 +4,4 @@ pub mod encryption; pub mod packets; pub mod quest; pub mod text; +mod utils; diff --git a/psoutils/src/quest.rs b/psoutils/src/quest.rs index aadddf0..097c492 100644 --- a/psoutils/src/quest.rs +++ b/psoutils/src/quest.rs @@ -1,17 +1,25 @@ +use std::fmt::Write; use std::path::Path; +use byteorder::WriteBytesExt; use thiserror::Error; use crate::quest::bin::{QuestBin, QuestBinError}; -use crate::quest::dat::{QuestDat, QuestDatError}; +use crate::quest::dat::{QuestDat, QuestDatError, QuestDatTableType}; use crate::quest::qst::{QuestQst, QuestQstError}; use crate::text::Language; -use byteorder::WriteBytesExt; +use crate::utils::crc32; pub mod bin; pub mod dat; pub mod qst; +fn format_description_field(description: &String) -> String { + description + .trim() + .replace("\n", "\n ") +} + #[derive(Error, Debug)] pub enum QuestError { #[error("I/O error reading quest")] @@ -138,14 +146,202 @@ impl Quest { pub fn episode(&self) -> u8 { self.bin.header.episode() } + + pub fn display_bin_info(&self) -> String { + let object_code_crc32 = crc32(self.bin.object_code.as_ref()); + let function_offset_table_crc32 = crc32(self.bin.function_offset_table.as_ref()); + + let mut s = String::new(); + + // HACK: i'm just directly calling .unwrap() for all of these because we're writing into + // a string buffer that we own here, so this should really never fail and i didn't + // want to have this method return a Result<> + + writeln!(s, "QUEST .BIN FILE").unwrap(); + writeln!( + s, + "======================================================================" + ) + .unwrap(); + writeln!( + s, + "Decompressed Size: {}", + self.bin.calculate_size() + ) + .unwrap(); + writeln!(s, "Name: {}", self.bin.header.name).unwrap(); + writeln!( + s, + "object_code: size: {}, crc32: {:08x}", + self.bin.object_code.len(), + object_code_crc32 + ) + .unwrap(); + writeln!( + s, + "function_offset_table: size: {}, crc32: {:08x}", + self.bin.function_offset_table.len(), + function_offset_table_crc32 + ) + .unwrap(); + writeln!( + s, + "Is Download? {}", + self.bin.header.is_download + ) + .unwrap(); + writeln!( + s, + "Quest Number/ID: {0} (8-bit) {1}, 0x{1:04x} (16-bit)", + self.bin.header.quest_number(), + self.bin.header.quest_number_u16() + ) + .unwrap(); + writeln!( + s, + "Episode: {} (0x{:02x})", + self.bin.header.episode() + 1, + self.bin.header.episode() + ) + .unwrap(); + writeln!( + s, + "Language: {:?}, encoding: {}", + self.bin.header.language, + self.bin.header.language.get_encoding().name() + ) + .unwrap(); + writeln!( + s, + "Short Description: {}\n", + format_description_field(&self.bin.header.short_description) + ) + .unwrap(); + writeln!( + s, + "Long Description: {}\n", + format_description_field(&self.bin.header.long_description) + ) + .unwrap(); + + s + } + + pub fn display_dat_info(&self) -> String { + let mut s = String::new(); + + let episode = self.bin.header.episode() as u32; + + // HACK: i'm just directly calling .unwrap() for all of these because we're writing into + // a string buffer that we own here, so this should really never fail and i didn't + // want to have this method return a Result<> + + writeln!(s, "QUEST .DAT FILE").unwrap(); + writeln!( + s, + "================================================================================" + ) + .unwrap(); + writeln!(s, "Decompressed size: {}\n", self.dat.calculate_size()).unwrap(); + writeln!( + s, + "(Using episode {} to lookup table area names)", + episode + 1 + ) + .unwrap(); + + writeln!( + s, + "Idx Size Table Type Area Count CRC32" + ) + .unwrap(); + + for (index, table) in self.dat.tables.iter().enumerate() { + let body_size = table.bytes.len(); + let body_crc32 = crc32(table.bytes.as_ref()); + + match table.table_type() { + QuestDatTableType::Object => { + let num_entities = body_size / 68; + writeln!( + s, + "{:3} {:5} {:<21} {:30} {:5} {:08x}", + index, + body_size, + table.table_type().to_string(), + table.area_name(episode).to_string(), + num_entities, + body_crc32 + ) + .unwrap(); + } + QuestDatTableType::NPC => { + let num_entities = body_size / 72; + writeln!( + s, + "{:3} {:5} {:<21} {:30} {:5} {:08x}", + index, + body_size, + table.table_type().to_string(), + table.area_name(episode).to_string(), + num_entities, + body_crc32 + ) + .unwrap(); + } + QuestDatTableType::Wave => { + writeln!( + s, + "{:3} {:5} {:<21} {:30} {:08x}", + index, + body_size, + table.table_type().to_string(), + table.area_name(episode).to_string(), + body_crc32 + ) + .unwrap(); + } + QuestDatTableType::ChallengeModeSpawns => { + writeln!( + s, + "{:3} {:5} {:<21} {:30} {:08x}", + index, + body_size, + table.table_type().to_string(), + table.area_name(episode).to_string(), + body_crc32 + ) + .unwrap(); + } + QuestDatTableType::ChallengeModeUnknown => { + writeln!( + s, + "{:3} {:5} {:<21} {:30} {:08x}", + index, + body_size, + table.table_type().to_string(), + table.area_name(episode).to_string(), + body_crc32 + ) + .unwrap(); + } + QuestDatTableType::Unknown(n) => { + writeln!(s, "{:3} {:5} Unknown: {}", index, body_size, n).unwrap(); + } + }; + } + + s + } } #[cfg(test)] mod tests { - use super::*; use claim::*; use tempfile::*; + use super::*; + #[test] pub fn can_load_from_compressed_bindat_files() { let bin_path = Path::new("../test-assets/q058-ret-gc.bin"); diff --git a/psogc_quest_tool/src/utils.rs b/psoutils/src/utils.rs similarity index 100% rename from psogc_quest_tool/src/utils.rs rename to psoutils/src/utils.rs