mirror of
https://github.com/pcvolkmer/osc-variant.git
synced 2025-04-19 19:56:50 +00:00
Merge pull request #25 from CCC-MF/issue_24
feat #24: List content of OSB files
This commit is contained in:
commit
2d2fb0dc61
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -478,6 +478,7 @@ dependencies = [
|
|||||||
name = "osc-variant"
|
name = "osc-variant"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"console",
|
"console",
|
||||||
|
@ -24,6 +24,7 @@ indicatif = "0.17"
|
|||||||
|
|
||||||
deob = { path = "./libs/deob", optional = true }
|
deob = { path = "./libs/deob", optional = true }
|
||||||
zip = { version = "0.6", optional = true }
|
zip = { version = "0.6", optional = true }
|
||||||
|
bytes = "1.5.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# Requires env var OSB_KEY to be present at build time
|
# Requires env var OSB_KEY to be present at build time
|
||||||
|
193
src/file_io.rs
Normal file
193
src/file_io.rs
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::file_io::InputFile::{Osc, Other};
|
||||||
|
use crate::model::onkostar_editor::OnkostarEditor;
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use deob::deobfuscate;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
pub enum FileError {
|
||||||
|
Reading(String, String),
|
||||||
|
Writing(String, String),
|
||||||
|
Parsing(String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for FileError {}
|
||||||
|
|
||||||
|
impl Debug for FileError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Display::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FileError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match &self {
|
||||||
|
FileError::Reading(filename, err) => format!("Kann Datei '{}' nicht lesen: {}", filename, err),
|
||||||
|
FileError::Writing(filename, err) => format!("Kann Datei '{}' nicht schreiben: {}", filename, err),
|
||||||
|
FileError::Parsing(filename, err) => format!(
|
||||||
|
"Die Datei '{}' ist entweder keine OSB- oder OSC-Datei, fehlerhaft oder enthält zusätzliche Inhalte\n{}",
|
||||||
|
filename,
|
||||||
|
err
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum InputFile {
|
||||||
|
Osc {
|
||||||
|
filename: String,
|
||||||
|
content: String,
|
||||||
|
},
|
||||||
|
Profile {
|
||||||
|
filename: String,
|
||||||
|
content: String,
|
||||||
|
},
|
||||||
|
Osb {
|
||||||
|
filename: String,
|
||||||
|
content: Vec<InputFile>,
|
||||||
|
},
|
||||||
|
Other {
|
||||||
|
filename: String,
|
||||||
|
content: Vec<u8>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputFile {
|
||||||
|
pub fn filename(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Osc { filename, .. } => filename,
|
||||||
|
InputFile::Profile { filename, .. } => filename,
|
||||||
|
InputFile::Osb { filename, .. } => filename,
|
||||||
|
Other { filename, .. } => filename,
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(filename: String, password: Option<String>) -> Result<Self, FileError> {
|
||||||
|
if let Some(extension) = Path::new(filename.as_str()).extension() {
|
||||||
|
return match extension.to_str() {
|
||||||
|
Some("osc") => match fs::read_to_string(filename.clone()) {
|
||||||
|
Ok(content) => Ok(Osc { filename, content }),
|
||||||
|
Err(err) => Err(FileError::Reading(filename, err.to_string())),
|
||||||
|
},
|
||||||
|
#[cfg(feature = "unzip-osb")]
|
||||||
|
Some("osb") => {
|
||||||
|
let file = match fs::File::open(filename.clone()) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(err) => return Err(FileError::Reading(filename, err.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut archive = match zip::ZipArchive::new(file) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(err) => return Err(FileError::Reading(filename, err.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result = vec![];
|
||||||
|
|
||||||
|
let password = password.unwrap_or_else(|| {
|
||||||
|
#[cfg(feature = "unzip-osb")]
|
||||||
|
{
|
||||||
|
deobfuscate(env!("OSB_KEY").trim())
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "unzip-osb"))]
|
||||||
|
{
|
||||||
|
return Err(FileError::Reading(filename.clone(), "No Password".into()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for i in 0..archive.len() {
|
||||||
|
if let Ok(Ok(mut zip_file)) =
|
||||||
|
archive.by_index_decrypt(i, password.as_bytes())
|
||||||
|
{
|
||||||
|
if zip_file.is_file() && zip_file.name().ends_with(".osc") {
|
||||||
|
let mut buf = String::new();
|
||||||
|
let _ = zip_file.read_to_string(&mut buf);
|
||||||
|
result.push(Osc {
|
||||||
|
filename: zip_file.name().to_string(),
|
||||||
|
content: buf,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let mut buf = BytesMut::new();
|
||||||
|
let _ = zip_file.read(&mut buf);
|
||||||
|
result.push(Other {
|
||||||
|
filename: zip_file.name().to_string(),
|
||||||
|
content: buf.to_vec(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(FileError::Parsing(
|
||||||
|
filename.into(),
|
||||||
|
"Kann OSB-Datei nicht lesen".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(InputFile::Osb {
|
||||||
|
filename,
|
||||||
|
content: result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => Err(FileError::Parsing(
|
||||||
|
filename,
|
||||||
|
"Nur OSB- oder OSC-Dateien werden unterstützt".to_string(),
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(FileError::Reading(filename, String::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<InputFile> for OnkostarEditor {
|
||||||
|
type Error = FileError;
|
||||||
|
|
||||||
|
fn try_from(value: InputFile) -> Result<Self, Self::Error> {
|
||||||
|
return match value {
|
||||||
|
Osc {
|
||||||
|
filename, content, ..
|
||||||
|
} => match OnkostarEditor::from_str(content.as_str()) {
|
||||||
|
Ok(data) => Ok(data),
|
||||||
|
Err(err) => Err(FileError::Parsing(filename, err)),
|
||||||
|
},
|
||||||
|
InputFile::Osb { filename, .. }
|
||||||
|
| InputFile::Profile { filename, .. }
|
||||||
|
| Other { filename, .. } => Err(FileError::Parsing(
|
||||||
|
filename,
|
||||||
|
"Nur OSC-Dateien werden unterstützt".to_string(),
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
154
src/main.rs
154
src/main.rs
@ -23,7 +23,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::{Debug, Display, Formatter};
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@ -40,66 +39,18 @@ use sha256::digest;
|
|||||||
|
|
||||||
use crate::checks::{check_file, print_checks, CheckNotice};
|
use crate::checks::{check_file, print_checks, CheckNotice};
|
||||||
use crate::cli::{Cli, SubCommand};
|
use crate::cli::{Cli, SubCommand};
|
||||||
|
use crate::file_io::{FileError, InputFile};
|
||||||
use crate::model::onkostar_editor::OnkostarEditor;
|
use crate::model::onkostar_editor::OnkostarEditor;
|
||||||
use crate::profile::Profile;
|
use crate::profile::Profile;
|
||||||
|
|
||||||
mod checks;
|
mod checks;
|
||||||
mod cli;
|
mod cli;
|
||||||
|
mod file_io;
|
||||||
mod model;
|
mod model;
|
||||||
mod profile;
|
mod profile;
|
||||||
#[cfg(feature = "unzip-osb")]
|
#[cfg(feature = "unzip-osb")]
|
||||||
mod unzip_osb;
|
mod unzip_osb;
|
||||||
|
|
||||||
enum FileError {
|
|
||||||
Reading(String, String),
|
|
||||||
Writing(String, String),
|
|
||||||
Parsing(String, String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for FileError {}
|
|
||||||
|
|
||||||
impl Debug for FileError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
Display::fmt(self, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for FileError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
match &self {
|
|
||||||
FileError::Reading(filename, err) => format!("Kann Datei '{}' nicht lesen: {}", filename, err),
|
|
||||||
FileError::Writing(filename, err) => format!("Kann Datei '{}' nicht schreiben: {}", filename, err),
|
|
||||||
FileError::Parsing(filename, err) => format!(
|
|
||||||
"Die Datei '{}' ist entweder keine OSC-Datei, fehlerhaft oder enthält zusätzliche Inhalte\n{}",
|
|
||||||
filename,
|
|
||||||
err
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_inputfile(inputfile: String) -> Result<OnkostarEditor, FileError> {
|
|
||||||
let filename = Path::new(&inputfile);
|
|
||||||
|
|
||||||
if let Some(extension) = filename.extension() {
|
|
||||||
if let Some("osc") = extension.to_ascii_lowercase().to_str() {
|
|
||||||
return match fs::read_to_string(filename) {
|
|
||||||
Ok(content) => match OnkostarEditor::from_str(content.as_str()) {
|
|
||||||
Ok(data) => Ok(data),
|
|
||||||
Err(err) => Err(FileError::Parsing(inputfile, err)),
|
|
||||||
},
|
|
||||||
Err(err) => Err(FileError::Reading(inputfile, err.to_string())),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(FileError::Reading(inputfile, "Keine OSC-Datei".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_outputfile(filename: String, content: &String) -> Result<(), FileError> {
|
fn write_outputfile(filename: String, content: &String) -> Result<(), FileError> {
|
||||||
OpenOptions::new()
|
OpenOptions::new()
|
||||||
.read(false)
|
.read(false)
|
||||||
@ -129,32 +80,89 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
inputfile,
|
inputfile,
|
||||||
sorted,
|
sorted,
|
||||||
filter,
|
filter,
|
||||||
} => {
|
} => match InputFile::read(inputfile, None)? {
|
||||||
let mut data = read_inputfile(inputfile)?;
|
osc @ InputFile::Osc { .. } => {
|
||||||
if sorted {
|
let mut content: OnkostarEditor = osc.try_into()?;
|
||||||
data.sorted()
|
if sorted {
|
||||||
|
content.sorted()
|
||||||
|
}
|
||||||
|
if let Some(name) = filter {
|
||||||
|
OnkostarEditor::print_list_filtered(&mut content, name.as_str());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
content.print_list();
|
||||||
}
|
}
|
||||||
if let Some(name) = filter {
|
InputFile::Osb { content, .. } => {
|
||||||
OnkostarEditor::print_list_filtered(&mut data, name.as_str());
|
for file in content {
|
||||||
return Ok(());
|
match file {
|
||||||
|
InputFile::Osc { .. } => {
|
||||||
|
println!(
|
||||||
|
"{}{}",
|
||||||
|
style("OSB-Paketinhalt: ").bold().yellow(),
|
||||||
|
style(format!("{}", file.filename())).bold()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut content: OnkostarEditor = match file.try_into() {
|
||||||
|
Ok(oe) => oe,
|
||||||
|
Err(err) => {
|
||||||
|
println!("{}", err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if sorted {
|
||||||
|
content.sorted()
|
||||||
|
}
|
||||||
|
if let Some(name) = filter {
|
||||||
|
OnkostarEditor::print_list_filtered(&mut content, name.as_str());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
content.print_list();
|
||||||
|
println!()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!(
|
||||||
|
"{}{}{}",
|
||||||
|
style("OSB-Paketinhalt: ").bold().yellow(),
|
||||||
|
style(format!("{}", file.filename())).bold(),
|
||||||
|
style(" ignoriert").yellow()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
data.print_list();
|
InputFile::Profile { filename, .. } | InputFile::Other { filename, .. } => {
|
||||||
}
|
return Err(Box::new(FileError::Reading(
|
||||||
|
filename,
|
||||||
|
"Nur OSB- und OSC-Dateien werden unterstützt".to_string(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
},
|
||||||
SubCommand::Tree {
|
SubCommand::Tree {
|
||||||
inputfile,
|
inputfile,
|
||||||
sorted,
|
sorted,
|
||||||
filter,
|
filter,
|
||||||
} => {
|
} => match InputFile::read(inputfile, None)? {
|
||||||
let mut data = read_inputfile(inputfile)?;
|
osc @ InputFile::Osc { .. } => {
|
||||||
if sorted {
|
let mut content: OnkostarEditor = osc.try_into()?;
|
||||||
data.sorted()
|
if sorted {
|
||||||
|
content.sorted()
|
||||||
|
}
|
||||||
|
if let Some(name) = filter {
|
||||||
|
OnkostarEditor::print_tree_filtered(&mut content, name.as_str());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
OnkostarEditor::print_tree(&content);
|
||||||
}
|
}
|
||||||
if let Some(name) = filter {
|
InputFile::Osb { filename, .. }
|
||||||
OnkostarEditor::print_tree_filtered(&mut data, name.as_str());
|
| InputFile::Profile { filename, .. }
|
||||||
return Ok(());
|
| InputFile::Other { filename, .. } => {
|
||||||
|
return Err(Box::new(FileError::Reading(
|
||||||
|
filename,
|
||||||
|
"Nur OSC-Dateien werden unterstützt".to_string(),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
OnkostarEditor::print_tree(&data);
|
},
|
||||||
}
|
|
||||||
SubCommand::Modify {
|
SubCommand::Modify {
|
||||||
inputfile,
|
inputfile,
|
||||||
profile,
|
profile,
|
||||||
@ -165,7 +173,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
interactive,
|
interactive,
|
||||||
fix,
|
fix,
|
||||||
} => {
|
} => {
|
||||||
let data = &mut read_inputfile(inputfile)?;
|
let mut data: OnkostarEditor = InputFile::read(inputfile, None)?.try_into()?;
|
||||||
|
|
||||||
if let Some(profile) = profile {
|
if let Some(profile) = profile {
|
||||||
let profile = if profile.contains('.') {
|
let profile = if profile.contains('.') {
|
||||||
@ -254,8 +262,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
style(&inputfile_b).yellow()
|
style(&inputfile_b).yellow()
|
||||||
);
|
);
|
||||||
|
|
||||||
let data_a = &mut read_inputfile(inputfile_a)?;
|
let data_a: &mut OnkostarEditor =
|
||||||
let data_b = &mut read_inputfile(inputfile_b)?;
|
&mut InputFile::read(inputfile_a, None)?.try_into()?;
|
||||||
|
let data_b: &mut OnkostarEditor =
|
||||||
|
&mut InputFile::read(inputfile_b, None)?.try_into()?;
|
||||||
|
|
||||||
data_a.print_diff(data_b, strict);
|
data_a.print_diff(data_b, strict);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user