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:
parent
e54bdc600b
commit
bb82496b65
|
@ -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"
|
||||||
|
|
|
@ -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 ...
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
pub mod convert;
|
pub mod convert;
|
||||||
pub mod info;
|
pub mod info;
|
||||||
mod utils;
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
||||||
|
|
Loading…
Reference in a new issue