diff --git a/src/extractor/main.rs b/src/extractor/main.rs index 6a4e664..dbb2f70 100644 --- a/src/extractor/main.rs +++ b/src/extractor/main.rs @@ -5,14 +5,13 @@ //! edited and then used by `Patcher` to modify the game file. //! //! # Syntax -//! The executable may be called as `./dcb-extractor {-o }` +//! The executable may be called as `./extractor {-o }` //! -//! Use the command `./dcb-extractor --help` for more information. +//! Use the command `./extractor --help` for more information. //! //! # Data extracted //! Currently only the following is extracted: //! - Card table -//! - Deck table (partial) // Features #![feature( diff --git a/src/patcher/cli.rs b/src/patcher/cli.rs new file mode 100644 index 0000000..85e4899 --- /dev/null +++ b/src/patcher/cli.rs @@ -0,0 +1,69 @@ +//! Cli manager for the extractor + +// Filesystem +use std::path::{Path, PathBuf}; + +// Clap +use clap::{Arg as ClapArg, App as ClapApp}; + +// Errors +use err_panic::ErrorExtPanic; + + +/// Data from the command line +#[derive(PartialEq, Clone, Debug)] +pub struct CliData +{ + /// The game file + pub game_file_path: PathBuf, + + /// The input directory + pub input_dir: PathBuf, +} + +impl CliData +{ + /// Constructs all of the cli data given and returns it + pub fn new() -> Self + { + // Get all matches from cli + let matches = ClapApp::new("Dcb Patcher") + .version("0.0") + .author("Filipe [...] <[...]@gmail.com>") + .about("Patches data to a Digimon Digital Card Battle `.bin` game file") + .arg( ClapArg::with_name("GAME_FILE") + .help("Sets the game file to use") + .required(true) + .index(1) + ) + .arg( ClapArg::with_name("INPUT") + .help("Sets the output directory to use") + .short("i") + .long("input") + .index(2) + .takes_value(true) + .required(true) + ) + .get_matches(); + + // Get the ouput filename + // Note: required + let game_file_path = matches.value_of("GAME_FILE") + .map(Path::new) + .map(Path::to_path_buf) + .panic_msg("Unable to get required argument `GAME_FILE`"); + + // Get the input dir + // Note: required + let input_dir = matches.value_of("INPUT") + .map(Path::new) + .map(Path::to_path_buf) + .panic_msg("Unable to get required argument `INPUT`"); + + // Return the cli data + Self { + game_file_path, + input_dir, + } + } +} diff --git a/src/patcher/main.rs b/src/patcher/main.rs new file mode 100644 index 0000000..7ae66b3 --- /dev/null +++ b/src/patcher/main.rs @@ -0,0 +1,103 @@ +//! Data patches +//! +//! # Details +//! Patches data to the game file from several other files. +//! +//! # Syntax +//! The executable may be called as `./patcher ` +//! +//! Use the command `./patcher --help` for more information. +//! +//! # Data patched +//! Currently only the following is patched: +//! - Card table + +// Features +#![feature( + box_syntax, + backtrace, + panic_info_message, +)] + +// Lints +#![warn( + clippy::restriction, + clippy::pedantic, + clippy::nursery, +)] +#![allow( + clippy::implicit_return, // We prefer implicit returns where possible + clippy::module_name_repetitions, // This happens often due to separating things into modules finely + clippy::wildcard_enum_match_arm, // We only use wildcards when we truly only care about some variants + clippy::result_expect_used, + clippy::option_expect_used, // We use expect when there is no alternative. + clippy::used_underscore_binding, // Useful for macros and such +)] + +// Modules +mod cli; +mod panic; + +// Exports +use cli::CliData; + +// Dcb +use dcb::{ + GameFile, + game::card::Table as CardTable, +}; + +// Errors +use err_ext::ResultExt; +use err_panic::ErrorExtPanic; + +fn main() { + // Initialize the logger and set the panic handler + init_logger(); + std::panic::set_hook(box panic::log_handler); + + // Get all data from cli + let CliData { game_file_path, input_dir } = CliData::new(); + + // Load the card table + let cards_table_file = std::fs::File::open( input_dir.join("cards.yaml") ) + .panic_err_msg("Unable to open `cards.yaml`"); + let cards_table: CardTable = serde_yaml::from_reader( cards_table_file ) + .panic_err_msg("Unable to parse `cards.yaml`"); + + // Open the game file + let game_file = std::fs::OpenOptions::new() + .write(true) + .truncate(false) + .open( game_file_path ) + .panic_err_msg("Unable to open game file"); + let mut game_file = GameFile::from_reader( game_file ) + .panic_err_msg("Unable to initialize game file"); + + // And write the cards table + cards_table.serialize(&mut game_file) + .panic_err_msg("Unable to serialize cards table"); +} + +/// Initializes the global logger +fn init_logger() { + use simplelog::{CombinedLogger, SharedLogger, TermLogger, WriteLogger, Config, TerminalMode}; + use log::LevelFilter::{Info, Trace}; + use std::convert::identity; + /// The type of logger required to pass to `CombinedLogger::init` + type BoxedLogger = Box; + + // All loggers to try and initialize + let loggers: Vec< Option > = vec![ + TermLogger ::new(Info, Config::default(), TerminalMode::Mixed) + .map(|logger| BoxedLogger::from(logger)), + std::fs::File::create("latest.log").ok() + .map(|file| WriteLogger::new(Trace, Config::default(), file)) + .map(|logger| BoxedLogger::from(logger)) + ]; + + // Filter all logger that actually work and initialize them + CombinedLogger::init( + loggers.into_iter().filter_map(identity).collect() + ).ignore_with_err(|_| log::warn!("Logger was already initialized at the start of the program")); +} diff --git a/src/patcher/panic.rs b/src/patcher/panic.rs new file mode 100644 index 0000000..595c8ef --- /dev/null +++ b/src/patcher/panic.rs @@ -0,0 +1,31 @@ +//! Panic handlers for this application + +// Std +use std::{ + error::Error, + backtrace::{Backtrace, BacktraceStatus}, +}; +// Error backtrace +use err_backtrace::ErrBacktraceExt; + +/// Panic handler based on logging to the current initialization +pub fn log_handler(info: &std::panic::PanicInfo) { + // Log that this thread has panicked + log::error!("Thread \"{}\" panicked", std::thread::current().name().unwrap_or("[Unknown]")); + + // Log any message that came with the panic + log::info!("Panic message: {}", info.message().unwrap_or( &format_args!("None") )); + + // Print an error backtrace if we found any + if let Some(err) = info.payload().downcast_ref::< Box >() { + log::info!("Error backtrace:\n{}", err.err_backtrace()); + } + + // And print a backtrace of where this panic occured. + let backtrace = Backtrace::force_capture(); + if backtrace.status() == BacktraceStatus::Captured { + log::info!("Backtrace:\n{}", backtrace); + } else { + log::info!("Unable to get backtrace: {}", backtrace); + } +}