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]
|
||||
anyhow = "1.0.40"
|
||||
crc = "1.8.1"
|
||||
|
||||
[dependencies.psoutils]
|
||||
path = "../psoutils"
|
||||
|
|
|
@ -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 ...
|
||||
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
pub mod convert;
|
||||
pub mod info;
|
||||
mod utils;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -4,3 +4,4 @@ pub mod encryption;
|
|||
pub mod packets;
|
||||
pub mod quest;
|
||||
pub mod text;
|
||||
mod utils;
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in a new issue