move actual quest info display functions to psoutils

doing this because i figure this quest info display stuff is _probably_
generally useful, and at the very least i will be re-using it for the
separate gci extraction tool that is going to be committed here next ...
This commit is contained in:
Gered 2021-05-25 18:36:29 -04:00
parent e54bdc600b
commit bb82496b65
7 changed files with 206 additions and 135 deletions

View file

@ -8,7 +8,6 @@ edition = "2018"
[dependencies] [dependencies]
anyhow = "1.0.40" anyhow = "1.0.40"
crc = "1.8.1"
[dependencies.psoutils] [dependencies.psoutils]
path = "../psoutils" path = "../psoutils"

View file

@ -2,135 +2,8 @@ use std::path::Path;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use psoutils::quest::bin::QuestBin;
use psoutils::quest::dat::{QuestDat, QuestDatTableType};
use psoutils::quest::Quest; 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<()> { pub fn quest_info(args: &[String]) -> Result<()> {
println!("Showing quest information"); println!("Showing quest information");
@ -159,8 +32,9 @@ pub fn quest_info(args: &[String]) -> Result<()> {
}; };
println!(); println!();
display_quest_bin_info(&quest.bin); println!("{}", quest.display_bin_info());
display_quest_dat_info(&quest.dat, quest.bin.header.episode() as u32); println!();
println!("{}", quest.display_dat_info());
println!(); println!();
Ok(()) Ok(())
@ -168,9 +42,10 @@ pub fn quest_info(args: &[String]) -> Result<()> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use claim::*; use claim::*;
use super::*;
// TODO: some way to match the specific error message string? or probably should just replace // TODO: some way to match the specific error message string? or probably should just replace
// anyhow usage with a specific error type ... // anyhow usage with a specific error type ...

View file

@ -1,3 +1,2 @@
pub mod convert; pub mod convert;
pub mod info; pub mod info;
mod utils;

View file

@ -13,6 +13,7 @@ encoding_rs = "0.8.28"
libc = "0.2.94" libc = "0.2.94"
rand = "0.8.3" rand = "0.8.3"
itertools = "0.10.0" itertools = "0.10.0"
crc = "1.8.1"
[dev-dependencies] [dev-dependencies]
claim = "0.5.0" claim = "0.5.0"

View file

@ -4,3 +4,4 @@ pub mod encryption;
pub mod packets; pub mod packets;
pub mod quest; pub mod quest;
pub mod text; pub mod text;
mod utils;

View file

@ -1,17 +1,25 @@
use std::fmt::Write;
use std::path::Path; use std::path::Path;
use byteorder::WriteBytesExt;
use thiserror::Error; use thiserror::Error;
use crate::quest::bin::{QuestBin, QuestBinError}; 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::quest::qst::{QuestQst, QuestQstError};
use crate::text::Language; use crate::text::Language;
use byteorder::WriteBytesExt; use crate::utils::crc32;
pub mod bin; pub mod bin;
pub mod dat; pub mod dat;
pub mod qst; pub mod qst;
fn format_description_field(description: &String) -> String {
description
.trim()
.replace("\n", "\n ")
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum QuestError { pub enum QuestError {
#[error("I/O error reading quest")] #[error("I/O error reading quest")]
@ -138,14 +146,202 @@ impl Quest {
pub fn episode(&self) -> u8 { pub fn episode(&self) -> u8 {
self.bin.header.episode() 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)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use claim::*; use claim::*;
use tempfile::*; use tempfile::*;
use super::*;
#[test] #[test]
pub fn can_load_from_compressed_bindat_files() { pub fn can_load_from_compressed_bindat_files() {
let bin_path = Path::new("../test-assets/q058-ret-gc.bin"); let bin_path = Path::new("../test-assets/q058-ret-gc.bin");