1
0
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:
Paul-Christian Volkmer 2023-12-30 07:35:52 +01:00 committed by GitHub
commit 2d2fb0dc61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 277 additions and 72 deletions

1
Cargo.lock generated
View File

@ -478,6 +478,7 @@ dependencies = [
name = "osc-variant"
version = "0.7.0"
dependencies = [
"bytes",
"clap",
"clap_complete",
"console",

View File

@ -24,6 +24,7 @@ indicatif = "0.17"
deob = { path = "./libs/deob", optional = true }
zip = { version = "0.6", optional = true }
bytes = "1.5.0"
[features]
# Requires env var OSB_KEY to be present at build time

193
src/file_io.rs Normal file
View 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(),
)),
};
}
}

View File

@ -23,7 +23,6 @@
*/
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
@ -40,66 +39,18 @@ use sha256::digest;
use crate::checks::{check_file, print_checks, CheckNotice};
use crate::cli::{Cli, SubCommand};
use crate::file_io::{FileError, InputFile};
use crate::model::onkostar_editor::OnkostarEditor;
use crate::profile::Profile;
mod checks;
mod cli;
mod file_io;
mod model;
mod profile;
#[cfg(feature = "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> {
OpenOptions::new()
.read(false)
@ -129,32 +80,89 @@ fn main() -> Result<(), Box<dyn Error>> {
inputfile,
sorted,
filter,
} => {
let mut data = read_inputfile(inputfile)?;
} => match InputFile::read(inputfile, None)? {
osc @ InputFile::Osc { .. } => {
let mut content: OnkostarEditor = osc.try_into()?;
if sorted {
data.sorted()
content.sorted()
}
if let Some(name) = filter {
OnkostarEditor::print_list_filtered(&mut data, name.as_str());
OnkostarEditor::print_list_filtered(&mut content, name.as_str());
return Ok(());
}
data.print_list();
content.print_list();
}
InputFile::Osb { content, .. } => {
for file in content {
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()
);
}
}
}
}
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 {
inputfile,
sorted,
filter,
} => {
let mut data = read_inputfile(inputfile)?;
} => match InputFile::read(inputfile, None)? {
osc @ InputFile::Osc { .. } => {
let mut content: OnkostarEditor = osc.try_into()?;
if sorted {
data.sorted()
content.sorted()
}
if let Some(name) = filter {
OnkostarEditor::print_tree_filtered(&mut data, name.as_str());
OnkostarEditor::print_tree_filtered(&mut content, name.as_str());
return Ok(());
}
OnkostarEditor::print_tree(&data);
OnkostarEditor::print_tree(&content);
}
InputFile::Osb { filename, .. }
| InputFile::Profile { filename, .. }
| InputFile::Other { filename, .. } => {
return Err(Box::new(FileError::Reading(
filename,
"Nur OSC-Dateien werden unterstützt".to_string(),
)))
}
},
SubCommand::Modify {
inputfile,
profile,
@ -165,7 +173,7 @@ fn main() -> Result<(), Box<dyn Error>> {
interactive,
fix,
} => {
let data = &mut read_inputfile(inputfile)?;
let mut data: OnkostarEditor = InputFile::read(inputfile, None)?.try_into()?;
if let Some(profile) = profile {
let profile = if profile.contains('.') {
@ -254,8 +262,10 @@ fn main() -> Result<(), Box<dyn Error>> {
style(&inputfile_b).yellow()
);
let data_a = &mut read_inputfile(inputfile_a)?;
let data_b = &mut read_inputfile(inputfile_b)?;
let data_a: &mut OnkostarEditor =
&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);
}