- 修正了trace级别日志输出时显示debug级别的问题
- 🔨 优雅处理所有的错误 - 🔨 将代码分离为单个文件
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,4 +2,5 @@
|
|||||||
src/*.old
|
src/*.old
|
||||||
src/*.bak
|
src/*.bak
|
||||||
/testfile
|
/testfile
|
||||||
/output
|
/output
|
||||||
|
/test
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -820,11 +820,11 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"colored",
|
"colored",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"hex",
|
|
||||||
"image",
|
"image",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
|
"rayon",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@ -19,11 +19,11 @@ chrono = {version = "0.4.*",features = ["clock"],default-features = false}
|
|||||||
clap = { version = "4.5.*", features = ["derive","std"]}
|
clap = { version = "4.5.*", features = ["derive","std"]}
|
||||||
colored = {version = "3.0.0",default-features = false}
|
colored = {version = "3.0.0",default-features = false}
|
||||||
env_logger = {version = "0.11.7",default-features = false}
|
env_logger = {version = "0.11.7",default-features = false}
|
||||||
hex = "0.4.3"
|
|
||||||
image = "0.25.*"
|
image = "0.25.*"
|
||||||
indicatif = "0.17.9"
|
indicatif = "0.17.9"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
|
rayon = "1.10.0"
|
||||||
serde = { version = "1.0.195", features = ["derive"] }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
serde_derive = "1.0.195"
|
serde_derive = "1.0.195"
|
||||||
serde_json = "1.0.111"
|
serde_json = "1.0.111"
|
||||||
|
|||||||
@ -41,7 +41,16 @@ Options:
|
|||||||
- [x] 自定义输出文件夹
|
- [x] 自定义输出文件夹
|
||||||
- [x] 计时功能
|
- [x] 计时功能
|
||||||
- [x] 自动覆盖开关
|
- [x] 自动覆盖开关
|
||||||
|
- [ ] 优化并发设置
|
||||||
|
- [ ] 优化信息传递
|
||||||
|
|
||||||
|
# 性能对比
|
||||||
|
|线程池框架|数量|时间ms|
|
||||||
|
|-|-|-|
|
||||||
|
|自制|16|1470|
|
||||||
|
|自制|8|1514|
|
||||||
|
|自制|8|1573|
|
||||||
|
|自制|8|1448|
|
||||||
---
|
---
|
||||||
# [Changelog](CHANGELOG.md)
|
# [Changelog](CHANGELOG.md)
|
||||||
---
|
---
|
||||||
|
|||||||
41
src/apperror.rs
Normal file
41
src/apperror.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum AppError {
|
||||||
|
NotNcmFile,
|
||||||
|
CannotReadFileName,
|
||||||
|
CannotReadMetaInfo,
|
||||||
|
CoverCannotSave,
|
||||||
|
FileReadError,
|
||||||
|
FileSkipError,
|
||||||
|
FileWriteError,
|
||||||
|
FullFilenameError,
|
||||||
|
FileNotFound,
|
||||||
|
ProtectFile,
|
||||||
|
FileDataError,
|
||||||
|
SaveError,
|
||||||
|
SystemTimeError,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for AppError {}
|
||||||
|
|
||||||
|
impl std::fmt::Display for AppError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
let result = match self {
|
||||||
|
Self::NotNcmFile => "该文件不为NCM格式",
|
||||||
|
Self::CannotReadFileName => "无法读取文件名称",
|
||||||
|
Self::CannotReadMetaInfo => "无法读取歌曲元信息",
|
||||||
|
Self::CoverCannotSave => "封面无法保存",
|
||||||
|
Self::FileSkipError => "跳过数据时出错。可能是文件大小小于预期",
|
||||||
|
Self::FileReadError => "读取文件时发生错误",
|
||||||
|
Self::FileWriteError => "写入文件时错误",
|
||||||
|
Self::FullFilenameError => "文件名不符合规范",
|
||||||
|
Self::FileNotFound => "未找到文件",
|
||||||
|
Self::ProtectFile => "已关闭文件强制覆盖且文件已存在。使用-f或-forcesave开启强制覆盖。",
|
||||||
|
Self::FileDataError => "处理文件数据时出错",
|
||||||
|
Self::SaveError => "保存文件出错",
|
||||||
|
Self::SystemTimeError => "获取时间戳失败",
|
||||||
|
// _ => "未知错误",
|
||||||
|
};
|
||||||
|
write!(f, "{}", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,7 +14,7 @@ pub struct Cli {
|
|||||||
|
|
||||||
/// 输出目录
|
/// 输出目录
|
||||||
#[arg(short, long, name = "输出目录", default_value = "NcmmiaoOutput")]
|
#[arg(short, long, name = "输出目录", default_value = "NcmmiaoOutput")]
|
||||||
pub output: Option<String>,
|
pub output: String,
|
||||||
|
|
||||||
/// 强制覆盖保存开关
|
/// 强制覆盖保存开关
|
||||||
#[arg(short, long, name = "强制覆盖开关")]
|
#[arg(short, long, name = "强制覆盖开关")]
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use colored::Color::{Blue, Cyan, Green, Red, Yellow};
|
use colored::Color::{Cyan, Green, Red, Yellow,Magenta};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use indicatif::MultiProgress;
|
use indicatif::MultiProgress;
|
||||||
use log::{LevelFilter, Log, Metadata, Record, SetLoggerError};
|
use log::{LevelFilter, Log, Metadata, Record, SetLoggerError};
|
||||||
@ -20,8 +20,8 @@ impl Log for MultiProgressLogger {
|
|||||||
log::Level::Error => ("Error").color(Red),
|
log::Level::Error => ("Error").color(Red),
|
||||||
log::Level::Warn => ("Warn").color(Yellow),
|
log::Level::Warn => ("Warn").color(Yellow),
|
||||||
log::Level::Info => ("Info").color(Green),
|
log::Level::Info => ("Info").color(Green),
|
||||||
log::Level::Debug => ("Debug").color(Blue),
|
log::Level::Debug => ("Debug").color(Magenta),
|
||||||
log::Level::Trace => ("Debug").color(Cyan),
|
log::Level::Trace => ("Trace").color(Cyan),
|
||||||
};
|
};
|
||||||
let message = format!(
|
let message = format!(
|
||||||
"[{}][{}] {}",
|
"[{}][{}] {}",
|
||||||
@ -43,7 +43,7 @@ pub fn init_logger() -> Result<(), SetLoggerError> {
|
|||||||
};
|
};
|
||||||
log::set_boxed_logger(Box::new(logger))?;
|
log::set_boxed_logger(Box::new(logger))?;
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
log::set_max_level(LevelFilter::Debug);
|
log::set_max_level(LevelFilter::Trace);//FIXME
|
||||||
} else {
|
} else {
|
||||||
log::set_max_level(LevelFilter::Info);
|
log::set_max_level(LevelFilter::Info);
|
||||||
}
|
}
|
||||||
|
|||||||
158
src/main.rs
158
src/main.rs
@ -1,9 +1,12 @@
|
|||||||
|
#![deny(clippy::all)]
|
||||||
use ::clap::Parser;
|
use ::clap::Parser;
|
||||||
use colored::{Color, Colorize};
|
use colored::{Color, Colorize};
|
||||||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, trace, warn};
|
||||||
use messager::Message;
|
use messager::Message;
|
||||||
|
use rayon::ThreadPoolBuilder;
|
||||||
|
use std::process::exit;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{
|
use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
@ -13,6 +16,7 @@ use std::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod apperror;
|
||||||
mod clap;
|
mod clap;
|
||||||
mod logger;
|
mod logger;
|
||||||
mod messager;
|
mod messager;
|
||||||
@ -21,14 +25,27 @@ mod opendir;
|
|||||||
mod pathparse;
|
mod pathparse;
|
||||||
mod test;
|
mod test;
|
||||||
mod threadpool;
|
mod threadpool;
|
||||||
|
mod time;
|
||||||
|
use apperror::AppError;
|
||||||
use ncmdump::Ncmfile;
|
use ncmdump::Ncmfile;
|
||||||
|
use time::TimeCompare;
|
||||||
const DEFAULT_MAXWORKER: usize = 8;
|
const DEFAULT_MAXWORKER: usize = 8;
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), AppError> {
|
||||||
let timer = ncmdump::TimeCompare::new();
|
let timer = match TimeCompare::new() {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => {
|
||||||
|
error!("无法初始化时间戳系统。{}", e);
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
// 初始化日志系统
|
// 初始化日志系统
|
||||||
logger::init_logger().unwrap();
|
match logger::init_logger() {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => {
|
||||||
|
println!("初始化日志系统失败")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let cli = clap::Cli::parse();
|
let cli = clap::Cli::parse();
|
||||||
|
|
||||||
@ -43,10 +60,11 @@ fn main() {
|
|||||||
}
|
}
|
||||||
None => DEFAULT_MAXWORKER,
|
None => DEFAULT_MAXWORKER,
|
||||||
};
|
};
|
||||||
|
//输入目录
|
||||||
let input = cli.input;
|
let input = cli.input;
|
||||||
|
//输出目录
|
||||||
let outputdir = cli.output.unwrap();
|
let outputdir = cli.output;
|
||||||
|
// 强制覆盖
|
||||||
let forcesave = cli.forcesave;
|
let forcesave = cli.forcesave;
|
||||||
if forcesave {
|
if forcesave {
|
||||||
warn!("文件{}已开启!", "强制覆盖".bright_red())
|
warn!("文件{}已开启!", "强制覆盖".bright_red())
|
||||||
@ -57,69 +75,78 @@ fn main() {
|
|||||||
let taskcount = undumpfile.len();
|
let taskcount = undumpfile.len();
|
||||||
let mut success_count = 0; //成功任务数
|
let mut success_count = 0; //成功任务数
|
||||||
|
|
||||||
|
|
||||||
if taskcount == 0 {
|
if taskcount == 0 {
|
||||||
error!("没有找到有效文件。使用-i参数输入需要解密的文件或文件夹。")
|
error!("没有找到有效文件。使用-i参数输入需要解密的文件或文件夹。");
|
||||||
} else {
|
exit(2);
|
||||||
// 初始化线程池
|
};
|
||||||
let pool = threadpool::Pool::new(max_workers);
|
// 初始化线程池
|
||||||
info!(
|
let pool = threadpool::Pool::new(max_workers);
|
||||||
"将启用{}线程",
|
// let threadpoolbuilder = ThreadPoolBuilder::new()
|
||||||
max_workers.to_string().color(Color::BrightGreen)
|
// .num_threads(max_workers)
|
||||||
);
|
// .build()
|
||||||
// 初始化通讯
|
// .unwrap(); //初始化线程池
|
||||||
let (tx, rx) = mpsc::channel();
|
// //FIXME暂时unwrap
|
||||||
|
info!(
|
||||||
|
"将启用{}线程",
|
||||||
|
max_workers.to_string().color(Color::BrightGreen)
|
||||||
|
);
|
||||||
|
// 初始化通讯
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
// 循环开始
|
// 循环开始
|
||||||
for filepath in undumpfile {
|
for filepath in undumpfile {
|
||||||
let output = outputdir.clone();
|
let output = outputdir.clone();
|
||||||
let senderin: Sender<messager::Message> = tx.clone();
|
let senderin: Sender<messager::Message> = tx.clone();
|
||||||
let senderon: Sender<messager::Message> = tx.clone();
|
let senderon: Sender<messager::Message> = tx.clone();
|
||||||
pool.execute(move || match Ncmfile::new(filepath.as_str()) {
|
// 多线程
|
||||||
Ok(mut n) => match n.dump(Path::new(&output), senderin, forcesave) {
|
pool.execute(move || match Ncmfile::new(filepath.as_str()) {
|
||||||
Ok(_) => {}
|
Ok(mut n) => match n.dump(Path::new(&output), senderin, forcesave) {
|
||||||
Err(e) => {
|
Ok(_) => {}
|
||||||
let messager = messager::Messager::new(n.fullfilename, senderon);
|
|
||||||
messager.send(messager::Signals::Err(e));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let messager = messager::Messager::new(filepath, senderon);
|
let messager = messager::Messager::new(n.fullfilename, senderon);
|
||||||
messager.send(messager::Signals::Err(e));
|
let _ = messager.send(messager::Signals::Err(e));
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
}
|
Err(e) => {
|
||||||
//循环到此结束
|
let messager = messager::Messager::new(filepath, senderon);
|
||||||
//进度条
|
let _ = messager.send(messager::Signals::Err(e));
|
||||||
|
|
||||||
let pb = ProgressBar::new((taskcount * 6) as u64) //长度乘积取决于Signal的数量
|
|
||||||
.with_elapsed(Duration::from_millis(50))
|
|
||||||
.with_style(
|
|
||||||
ProgressStyle::default_bar()
|
|
||||||
.progress_chars("#>-")
|
|
||||||
.template("{spinner:.green} [{wide_bar:.cyan/blue}] {percent_precise}% ({eta})")
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.with_message("解密中");
|
|
||||||
let progressbar = MP.add(pb);
|
|
||||||
|
|
||||||
//定义计数器
|
|
||||||
// 接受消息!!!!!!!!!!
|
|
||||||
for messages in rx {
|
|
||||||
match messages.signal{
|
|
||||||
messager::Signals::End|messager::Signals::Err(_)=>{success_count+=1},
|
|
||||||
_=>()
|
|
||||||
}
|
}
|
||||||
if success_count < taskcount {
|
});
|
||||||
progressbar.inc(1);
|
// threadpoolbuilder.install(|| {
|
||||||
messages.log(); //发送log
|
// //TODO
|
||||||
} else {
|
// })
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
progressbar.finish_and_clear();
|
|
||||||
}
|
}
|
||||||
let timecount = timer.compare();
|
//循环到此结束
|
||||||
|
//进度条
|
||||||
|
|
||||||
|
let pb = ProgressBar::new((taskcount * 6) as u64) //长度乘积取决于Signal的数量
|
||||||
|
.with_elapsed(Duration::from_millis(50))
|
||||||
|
.with_style(
|
||||||
|
ProgressStyle::default_bar()
|
||||||
|
.progress_chars("#>-")
|
||||||
|
.template("{spinner:.green} [{wide_bar:.cyan/blue}] {percent_precise}% ({eta})")
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.with_message("解密中");
|
||||||
|
let progressbar = MP.add(pb);
|
||||||
|
|
||||||
|
//定义计数器
|
||||||
|
// 接受消息!!!!!!!!!!
|
||||||
|
for messages in rx {
|
||||||
|
match messages.signal {
|
||||||
|
messager::Signals::End | messager::Signals::Err(_) => success_count += 1,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
if success_count < taskcount {
|
||||||
|
progressbar.inc(1);
|
||||||
|
messages.log(); //发送log
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progressbar.finish_and_clear();
|
||||||
|
|
||||||
|
let timecount = timer.compare().unwrap();
|
||||||
let showtime = || {
|
let showtime = || {
|
||||||
if timecount > 2000 {
|
if timecount > 2000 {
|
||||||
format!("共计用时{}秒", timecount / 1000)
|
format!("共计用时{}秒", timecount / 1000)
|
||||||
@ -141,6 +168,7 @@ fn main() {
|
|||||||
} else {
|
} else {
|
||||||
info!("输出文件夹:[{}]", outputdir.cyan());
|
info!("输出文件夹:[{}]", outputdir.cyan());
|
||||||
};
|
};
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
|
|
||||||
use crate::{messager, ncmdump};
|
use crate::{messager, ncmdump,AppError};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
pub struct Messager {
|
pub struct Messager {
|
||||||
@ -23,16 +23,15 @@ impl Message {
|
|||||||
Signals::Decrypt => "解密歌曲信息",
|
Signals::Decrypt => "解密歌曲信息",
|
||||||
Signals::Save => "保存文件",
|
Signals::Save => "保存文件",
|
||||||
Signals::End => "成功!",
|
Signals::End => "成功!",
|
||||||
Signals::Err(e)=>&e.to_string(),
|
Signals::Err(e) => &e.to_string(),
|
||||||
};
|
};
|
||||||
match &self.signal{
|
match &self.signal {
|
||||||
Signals::Err(e)=>{match e{
|
Signals::Err(e) => match e {
|
||||||
ncmdump::NcmError::ProtectFile=>warn!("[{}] {}", self.name.cyan(), loginfo),
|
AppError::ProtectFile => warn!("[{}] {}", self.name.cyan(), loginfo),
|
||||||
_=>error!("[{}] {}", self.name.cyan(), loginfo),
|
_ => error!("[{}] {}", self.name.cyan(), loginfo),
|
||||||
}},
|
},
|
||||||
_=>info!("[{}] {}", self.name.cyan(), loginfo)
|
_ => info!("[{}] {}", self.name.cyan(), loginfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
@ -43,7 +42,7 @@ pub enum Signals {
|
|||||||
Decrypt,
|
Decrypt,
|
||||||
Save,
|
Save,
|
||||||
End,
|
End,
|
||||||
Err(ncmdump::NcmError),
|
Err(AppError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Messager {
|
impl Messager {
|
||||||
@ -66,8 +65,7 @@ impl Debug for Message {
|
|||||||
Signals::End => "破解完成",
|
Signals::End => "破解完成",
|
||||||
Signals::GetMetaInfo => "获取元数据",
|
Signals::GetMetaInfo => "获取元数据",
|
||||||
Signals::GetCover => "获取封面",
|
Signals::GetCover => "获取封面",
|
||||||
Signals::Err(e)=>&e.to_string(),
|
Signals::Err(e) => &e.to_string(),
|
||||||
|
|
||||||
};
|
};
|
||||||
write!(f, "[{}] {}", self.name, message)
|
write!(f, "[{}] {}", self.name, message)
|
||||||
}
|
}
|
||||||
|
|||||||
331
src/ncmdump.rs
331
src/ncmdump.rs
@ -1,3 +1,4 @@
|
|||||||
|
use crate::apperror::AppError;
|
||||||
use crate::messager;
|
use crate::messager;
|
||||||
use aes::cipher::generic_array::typenum::U16;
|
use aes::cipher::generic_array::typenum::U16;
|
||||||
use aes::cipher::{generic_array::GenericArray, BlockDecrypt, KeyInit};
|
use aes::cipher::{generic_array::GenericArray, BlockDecrypt, KeyInit};
|
||||||
@ -5,8 +6,6 @@ use aes::Aes128;
|
|||||||
use audiotags::{MimeType, Picture, Tag};
|
use audiotags::{MimeType, Picture, Tag};
|
||||||
use base64::{self, Engine};
|
use base64::{self, Engine};
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use hex::decode;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use messager::Signals;
|
use messager::Signals;
|
||||||
@ -20,14 +19,16 @@ use std::str::from_utf8;
|
|||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
// lazy_static! {
|
||||||
|
// // 解密需要的密钥
|
||||||
lazy_static! {
|
// static ref KEY_CORE: Vec<u8> = decode("687A4852416D736F356B496E62617857").unwrap();//绝对正确
|
||||||
// 解密需要的密钥
|
// static ref KEY_META: Vec<u8> = decode("2331346C6A6B5F215C5D2630553C2728").unwrap();
|
||||||
static ref KEY_CORE: Vec<u8> = decode("687A4852416D736F356B496E62617857").unwrap();
|
// }
|
||||||
static ref KEY_META: Vec<u8> = decode("2331346C6A6B5F215C5D2630553C2728").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 原KEY_CORE数据:687A4852416D736F356B496E62617857
|
||||||
|
const NEW_KEY_CORE:[u8;16] = [0x68, 0x7A, 0x48, 0x52, 0x41, 0x6D, 0x73, 0x6F, 0x35, 0x6B, 0x49, 0x6E, 0x62, 0x61, 0x78, 0x57];
|
||||||
|
// 原KEY_META数据:2331346C6A6B5F215C5D2630553C2728
|
||||||
|
const NEW_KEY_META:[u8;16]=[0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21, 0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28];
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub struct Ncmfile {
|
pub struct Ncmfile {
|
||||||
@ -42,18 +43,28 @@ pub struct Ncmfile {
|
|||||||
/// 游标
|
/// 游标
|
||||||
pub position: u64,
|
pub position: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ncmfile {
|
impl Ncmfile {
|
||||||
pub fn new(filepath: &str) -> Result<Ncmfile, NcmError> {
|
/// 各种工具方法
|
||||||
|
pub fn new(filepath: &str) -> Result<Ncmfile, AppError> {
|
||||||
let file = match File::open(filepath) {
|
let file = match File::open(filepath) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(_) => return Err(NcmError::FileReadError),
|
Err(_) => return Err(AppError::FileReadError),
|
||||||
};
|
};
|
||||||
let path = Path::new(filepath);
|
let path = Path::new(filepath);
|
||||||
let fullfilename = path.file_name().unwrap().to_str().unwrap().to_string();
|
let fullfilename = path
|
||||||
let size = file.metadata().unwrap().len();
|
.file_name()
|
||||||
|
.ok_or(AppError::FileReadError)?
|
||||||
|
.to_str()
|
||||||
|
.ok_or(AppError::FileReadError)?
|
||||||
|
.to_string();
|
||||||
|
let size = file
|
||||||
|
.metadata()
|
||||||
|
.map_err(|_| AppError::CannotReadMetaInfo)?
|
||||||
|
.len();
|
||||||
let filename = match Path::new(&filepath).file_stem() {
|
let filename = match Path::new(&filepath).file_stem() {
|
||||||
Some(f) => f.to_str().unwrap().to_string(),
|
Some(f) => f.to_str().ok_or(AppError::FileReadError)?.to_string(),
|
||||||
None => return Err(NcmError::CannotReadFileName),
|
None => return Err(AppError::CannotReadFileName),
|
||||||
};
|
};
|
||||||
Ok(Ncmfile {
|
Ok(Ncmfile {
|
||||||
file,
|
file,
|
||||||
@ -67,9 +78,9 @@ impl Ncmfile {
|
|||||||
///
|
///
|
||||||
/// 该函数可以记录上次读取的位置,下次读取时从上次读取的位置开始
|
/// 该函数可以记录上次读取的位置,下次读取时从上次读取的位置开始
|
||||||
/// - length 想要读取的长度
|
/// - length 想要读取的长度
|
||||||
pub fn seekread(&mut self, length: u64) -> Result<Vec<u8>, NcmError> {
|
pub fn seekread(&mut self, length: u64) -> Result<Vec<u8>, AppError> {
|
||||||
if self.position + length > self.size {
|
if self.position + length > self.size {
|
||||||
return Err(NcmError::FileReadError);
|
return Err(AppError::FileReadError);
|
||||||
} else {
|
} else {
|
||||||
let mut reader = BufReader::new(&self.file);
|
let mut reader = BufReader::new(&self.file);
|
||||||
let _ = reader.seek(SeekFrom::Start(self.position));
|
let _ = reader.seek(SeekFrom::Start(self.position));
|
||||||
@ -79,6 +90,7 @@ impl Ncmfile {
|
|||||||
Ok(buf[..].to_vec())
|
Ok(buf[..].to_vec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 从指定位置开始读取。
|
/// 从指定位置开始读取。
|
||||||
///
|
///
|
||||||
/// !!!该函数仍然会更新游标
|
/// !!!该函数仍然会更新游标
|
||||||
@ -86,9 +98,9 @@ impl Ncmfile {
|
|||||||
/// - offset 开始位置
|
/// - offset 开始位置
|
||||||
/// - length 想要读取的长度
|
/// - length 想要读取的长度
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn seekread_from(&mut self, offset: u64, length: u64) -> Result<Vec<u8>, NcmError> {
|
pub fn seekread_from(&mut self, offset: u64, length: u64) -> Result<Vec<u8>, AppError> {
|
||||||
if self.position + length > self.size {
|
if self.position + length > self.size {
|
||||||
return Err(NcmError::FileReadError);
|
return Err(AppError::FileReadError);
|
||||||
} else {
|
} else {
|
||||||
let mut reader = BufReader::new(&self.file);
|
let mut reader = BufReader::new(&self.file);
|
||||||
let _ = reader.seek(SeekFrom::Start(offset));
|
let _ = reader.seek(SeekFrom::Start(offset));
|
||||||
@ -130,9 +142,9 @@ impl Ncmfile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// 跳过某些数据
|
/// 跳过某些数据
|
||||||
pub fn skip(&mut self, length: u64) -> Result<(), NcmError> {
|
pub fn skip(&mut self, length: u64) -> Result<(), AppError> {
|
||||||
if self.position + length > self.size {
|
if self.position + length > self.size {
|
||||||
return Err(NcmError::FileReadError);
|
return Err(AppError::FileReadError);
|
||||||
} else {
|
} else {
|
||||||
self.position += length;
|
self.position += length;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -146,6 +158,35 @@ impl Ncmfile {
|
|||||||
key
|
key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn save(&mut self, path: &PathBuf, data: Vec<u8>) -> Result<(), AppError> {
|
||||||
|
let music_file = match File::create(path) {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(_) => return Err(AppError::FileWriteError),
|
||||||
|
};
|
||||||
|
let mut writer = BufWriter::new(music_file);
|
||||||
|
let _ = writer.write_all(&data);
|
||||||
|
// 关闭文件
|
||||||
|
match writer.flush() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(_) => return Err(AppError::FileWriteError),
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn is_ncm(data: Vec<u8>) -> Result<(), AppError> {
|
||||||
|
let header = from_utf8(&data).map_err(|_| AppError::NotNcmFile)?;
|
||||||
|
if header != "CTENFDAM" {
|
||||||
|
return Err(AppError::NotNcmFile);
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// 使用PKCS5Padding标准,去掉填充信息
|
||||||
|
fn unpad(data: &[u8]) -> Vec<u8> {
|
||||||
|
data[..data.len() - data[data.len() - 1] as usize].to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ncmfile {
|
||||||
/// 解密函数
|
/// 解密函数
|
||||||
#[allow(unused_assignments)]
|
#[allow(unused_assignments)]
|
||||||
pub fn dump(
|
pub fn dump(
|
||||||
@ -153,30 +194,18 @@ impl Ncmfile {
|
|||||||
outputdir: &Path,
|
outputdir: &Path,
|
||||||
tx: mpsc::Sender<messager::Message>,
|
tx: mpsc::Sender<messager::Message>,
|
||||||
force_save: bool,
|
force_save: bool,
|
||||||
) -> Result<(), NcmError> {
|
) -> Result<(), AppError> {
|
||||||
let messager = messager::Messager::new(self.fullfilename.clone(), tx);
|
let messager = messager::Messager::new(self.fullfilename.clone(), tx);
|
||||||
let _ = messager.send(Signals::Start);
|
let _ = messager.send(Signals::Start);
|
||||||
//TODO 通讯合法化
|
//TODO 通讯合法化
|
||||||
// info!("开始解密[{}]文件", self.fullfilename.yellow());
|
// info!("开始解密[{}]文件", self.fullfilename.yellow());
|
||||||
// 获取magic header 。应为CTENFDAM
|
// 获取magic header 应为CTENFDAM
|
||||||
let magic_header = match self.seekread(8) {
|
trace!("读取magic header");
|
||||||
Ok(header) => header,
|
let magic_header = self.seekread(8)?;
|
||||||
Err(_e) => {
|
|
||||||
return Err(NcmError::FileReadError); //TODO去除向上传播
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 判断是否为ncm格式的文件
|
// 判断是否为ncm格式的文件
|
||||||
match from_utf8(&magic_header) {
|
trace!("判断是否为ncm格式的文件");
|
||||||
Ok(header) => {
|
Self::is_ncm(magic_header)?;
|
||||||
if header != "CTENFDAM" {
|
|
||||||
// 传播错误至dump
|
|
||||||
return Err(NcmError::NotNcmFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 传播错误至dump
|
|
||||||
Err(_e) => return Err(NcmError::NotNcmFile),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 跳过2字节
|
// 跳过2字节
|
||||||
trace!("跳过2字节");
|
trace!("跳过2字节");
|
||||||
@ -184,20 +213,28 @@ impl Ncmfile {
|
|||||||
|
|
||||||
trace!("获取RC4密钥长度");
|
trace!("获取RC4密钥长度");
|
||||||
//小端模式读取RC4密钥长度 正常情况下应为128
|
//小端模式读取RC4密钥长度 正常情况下应为128
|
||||||
let key_length = u32::from_le_bytes(self.seekread(4).unwrap().try_into().unwrap()) as u64;
|
let key_length = u32::from_le_bytes(
|
||||||
|
self.seekread(4)?
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| AppError::FileReadError)?,
|
||||||
|
) as u64;//数据长度不够只能使用u32 然后转化为u64
|
||||||
// debug!("RC4密钥长度为:{}", key_length);
|
// debug!("RC4密钥长度为:{}", key_length);
|
||||||
|
|
||||||
//读取密钥 开头应为 neteasecloudmusic
|
//读取密钥 开头应为 neteasecloudmusic
|
||||||
trace!("读取RC4密钥");
|
trace!("读取RC4密钥");
|
||||||
let mut key_data = self.seekread(key_length).unwrap();
|
let mut key_data = self.seekread(key_length)?;
|
||||||
//aes128解密
|
//aes128解密
|
||||||
let key_data = &aes128_to_slice(&KEY_CORE, Self::parse_key(&mut key_data[..])); //先把密钥按照字节进行0x64异或
|
let key_data = &aes128_to_slice(&NEW_KEY_CORE, Self::parse_key(&mut key_data[..]).to_vec())?; //先把密钥按照字节进行0x64异或
|
||||||
// RC4密钥
|
// RC4密钥
|
||||||
let key_data = unpad(&key_data[..])[17..].to_vec(); //去掉neteasecloudmusic
|
let key_data = Self::unpad(&key_data[..])[17..].to_vec(); //去掉neteasecloudmusic
|
||||||
|
|
||||||
//读取meta信息的数据大小
|
//读取meta信息的数据大小
|
||||||
trace!("获取meta信息数据大小");
|
trace!("获取meta信息数据大小");
|
||||||
let meta_length = u32::from_le_bytes(self.seekread(4)?.try_into().unwrap()) as u64;
|
let meta_length = u32::from_le_bytes(
|
||||||
|
self.seekread(4)?
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| AppError::FileDataError)?,
|
||||||
|
) as u64;
|
||||||
let _ = messager.send(Signals::GetMetaInfo);
|
let _ = messager.send(Signals::GetMetaInfo);
|
||||||
|
|
||||||
// 读取meta信息
|
// 读取meta信息
|
||||||
@ -213,20 +250,20 @@ impl Ncmfile {
|
|||||||
let _ = match &base64::engine::general_purpose::STANDARD
|
let _ = match &base64::engine::general_purpose::STANDARD
|
||||||
.decode_vec(&mut meta_data[22..], &mut decode_data)
|
.decode_vec(&mut meta_data[22..], &mut decode_data)
|
||||||
{
|
{
|
||||||
Err(_) => return Err(NcmError::CannotReadMetaInfo),
|
Err(_) => return Err(AppError::CannotReadMetaInfo),
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
// aes128解密
|
// aes128解密
|
||||||
let aes_data = aes128_to_slice(&KEY_META, &decode_data);
|
let aes_data = aes128_to_slice(&NEW_KEY_META, decode_data)?;
|
||||||
// unpadding
|
// unpadding
|
||||||
let json_data = match String::from_utf8(unpad(&aes_data)[6..].to_vec()) {
|
let json_data = match String::from_utf8(Self::unpad(&aes_data)[6..].to_vec()) {
|
||||||
Ok(o) => o,
|
Ok(o) => o,
|
||||||
Err(_) => return Err(NcmError::CannotReadMetaInfo),
|
Err(_) => return Err(AppError::CannotReadMetaInfo),
|
||||||
};
|
};
|
||||||
debug!("json_data: {}", json_data);
|
debug!("json_data: {}", json_data);
|
||||||
let data: Value = match serde_json::from_str(&json_data[..]) {
|
let data: Value = match serde_json::from_str(&json_data[..]) {
|
||||||
Ok(o) => o,
|
Ok(o) => o,
|
||||||
Err(_) => return Err(NcmError::CannotReadMetaInfo),
|
Err(_) => return Err(AppError::CannotReadMetaInfo),
|
||||||
}; //解析json数据
|
}; //解析json数据
|
||||||
data
|
data
|
||||||
};
|
};
|
||||||
@ -237,14 +274,18 @@ impl Ncmfile {
|
|||||||
let filename = format!(
|
let filename = format!(
|
||||||
"{}.{}",
|
"{}.{}",
|
||||||
self.filename,
|
self.filename,
|
||||||
meta_data.get("format").unwrap().as_str().unwrap()
|
meta_data
|
||||||
|
.get("format")
|
||||||
|
.ok_or(AppError::CannotReadMetaInfo)?
|
||||||
|
.as_str()
|
||||||
|
.ok_or(AppError::CannotReadMetaInfo)?
|
||||||
);
|
);
|
||||||
|
|
||||||
// let filename = standardize_filename(filename);
|
// let filename = standardize_filename(filename);
|
||||||
debug!("文件名:{}", filename.yellow());
|
debug!("文件名:{}", filename.yellow());
|
||||||
//链级创建输出目录
|
//链级创建输出目录
|
||||||
match fs::create_dir_all(outputdir) {
|
match fs::create_dir_all(outputdir) {
|
||||||
Err(_) => return Err(NcmError::FileWriteError),
|
Err(_) => return Err(AppError::FileWriteError),
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
outputdir.join(filename)
|
outputdir.join(filename)
|
||||||
@ -254,12 +295,13 @@ impl Ncmfile {
|
|||||||
|
|
||||||
// 先检查是否存在
|
// 先检查是否存在
|
||||||
if !force_save && Path::new(&path).exists() {
|
if !force_save && Path::new(&path).exists() {
|
||||||
return Err(NcmError::ProtectFile);
|
return Err(AppError::ProtectFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跳过4个字节的校验码
|
// 跳过4个字节的校验码
|
||||||
trace!("读取校验码");
|
// trace!("读取校验码");
|
||||||
// let _crc32 = u32::from_le_bytes(self.seekread(4).unwrap().try_into().unwrap()) as u64;
|
// let _crc32 = u32::from_le_bytes(self.seekread(4)?.try_into().map_err(AppError::FileDataError)?) as u64;
|
||||||
|
|
||||||
self.skip(4)?;
|
self.skip(4)?;
|
||||||
|
|
||||||
// 跳过5个字节
|
// 跳过5个字节
|
||||||
@ -269,7 +311,11 @@ impl Ncmfile {
|
|||||||
let _ = messager.send(Signals::GetCover);
|
let _ = messager.send(Signals::GetCover);
|
||||||
// 获取图片数据的大小
|
// 获取图片数据的大小
|
||||||
trace!("获取图片数据的大小");
|
trace!("获取图片数据的大小");
|
||||||
let image_data_length = u32::from_le_bytes(self.seekread(4)?.try_into().unwrap()) as u64;
|
let image_data_length = u32::from_le_bytes(
|
||||||
|
self.seekread(4)?
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| AppError::FileDataError)?,
|
||||||
|
) as u64;
|
||||||
|
|
||||||
// 读取图片,并写入文件当中
|
// 读取图片,并写入文件当中
|
||||||
let image_data = self.seekread(image_data_length)?; //读取图片数据
|
let image_data = self.seekread(image_data_length)?; //读取图片数据
|
||||||
@ -298,26 +344,6 @@ impl Ncmfile {
|
|||||||
key_box
|
key_box
|
||||||
};
|
};
|
||||||
|
|
||||||
/* let mut s_box = {
|
|
||||||
let key_length = key_data.len();
|
|
||||||
let key_box = Vec::from(key_data);
|
|
||||||
let mut s = (0..=255).collect::<Vec<u8>>();
|
|
||||||
let mut j = 0;
|
|
||||||
|
|
||||||
for i in 0..=255 {
|
|
||||||
j = (j as usize + s[i] as usize + key_box[i % key_length] as usize) & 0xFF;
|
|
||||||
|
|
||||||
//记录 s[j]的值
|
|
||||||
let temp = &s.get(j as usize).unwrap().to_owned();
|
|
||||||
s[j as usize] = s[i];
|
|
||||||
s[i] = temp.to_owned();
|
|
||||||
}
|
|
||||||
|
|
||||||
s
|
|
||||||
}; */
|
|
||||||
|
|
||||||
// let key_box = key_box[0..(key_box.len()-key_box[key_box.len() as usize-1] as usize)].to_vec();
|
|
||||||
|
|
||||||
//解密音乐数据
|
//解密音乐数据
|
||||||
trace!("解密音乐数据");
|
trace!("解密音乐数据");
|
||||||
let _ = messager.send(Signals::Decrypt);
|
let _ = messager.send(Signals::Decrypt);
|
||||||
@ -342,28 +368,6 @@ impl Ncmfile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//组成流密钥
|
|
||||||
/* let mut stream = Vec::new();
|
|
||||||
for i in 0..256 {
|
|
||||||
stream.push(
|
|
||||||
s_box[(s_box[i] as usize + s_box[(i + s_box[i] as usize) & 0xFF] as usize) & 0xFF],
|
|
||||||
)
|
|
||||||
} */
|
|
||||||
|
|
||||||
// 解密音乐数据
|
|
||||||
/* loop {
|
|
||||||
let chunk = self.seekread_no_error(256); //每次读取256个字节
|
|
||||||
if chunk.len() != 0 {
|
|
||||||
for (count, &i) in chunk[..].iter().enumerate() {
|
|
||||||
music_data.push(i ^ stream[count])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
// debug!("music_data:{:?}", music_data);
|
|
||||||
// debug!("长度:{}", stream.len());
|
|
||||||
|
|
||||||
//退出循环,写入文件
|
//退出循环,写入文件
|
||||||
|
|
||||||
let _ = messager.send(Signals::Save);
|
let _ = messager.send(Signals::Save);
|
||||||
@ -373,20 +377,22 @@ impl Ncmfile {
|
|||||||
// 保存封面
|
// 保存封面
|
||||||
let mut tag = match Tag::new().read_from_path(&path) {
|
let mut tag = match Tag::new().read_from_path(&path) {
|
||||||
Ok(o) => o,
|
Ok(o) => o,
|
||||||
Err(_) => return Err(NcmError::CoverCannotSave),
|
Err(_) => return Err(AppError::CoverCannotSave),
|
||||||
};
|
};
|
||||||
let cover = Picture {
|
let cover = Picture {
|
||||||
mime_type: MimeType::Jpeg,
|
mime_type: MimeType::Jpeg,
|
||||||
data: &image_data,
|
data: &image_data,
|
||||||
};
|
};
|
||||||
tag.set_album_cover(cover); //添加封面
|
tag.set_album_cover(cover); //添加封面
|
||||||
let _ = tag.write_to_path(&path.to_str().unwrap()); //保存
|
let _ = tag
|
||||||
|
.write_to_path(&path.to_str().ok_or(AppError::SaveError)?)
|
||||||
|
.map_err(|_| AppError::SaveError); //保存
|
||||||
}
|
}
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"[{}] 文件已保存到: {}",
|
"[{}] 文件已保存到: {}",
|
||||||
self.filename.yellow(),
|
self.filename.yellow(),
|
||||||
path.to_str().unwrap().bright_cyan()
|
path.to_str().ok_or(AppError::SaveError)?.bright_cyan()
|
||||||
);
|
);
|
||||||
info!(
|
info!(
|
||||||
"[{}]{}",
|
"[{}]{}",
|
||||||
@ -396,20 +402,6 @@ impl Ncmfile {
|
|||||||
let _ = messager.send(Signals::End);
|
let _ = messager.send(Signals::End);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn save(&mut self, path: &PathBuf, data: Vec<u8>) -> Result<(), NcmError> {
|
|
||||||
let music_file = match File::create(path) {
|
|
||||||
Ok(o) => o,
|
|
||||||
Err(_) => return Err(NcmError::FileWriteError),
|
|
||||||
};
|
|
||||||
let mut writer = BufWriter::new(music_file);
|
|
||||||
let _ = writer.write_all(&data);
|
|
||||||
// 关闭文件
|
|
||||||
match writer.flush() {
|
|
||||||
Ok(o) => o,
|
|
||||||
Err(_) => return Err(NcmError::FileWriteError),
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 存储元数据的结构体
|
/// 存储元数据的结构体
|
||||||
@ -470,54 +462,26 @@ pub struct Key {
|
|||||||
|
|
||||||
// fn read_meta(file: &mut File, meta_length: u32) -> Result<Vec<u8>, Error> {}
|
// fn read_meta(file: &mut File, meta_length: u32) -> Result<Vec<u8>, Error> {}
|
||||||
|
|
||||||
fn convert_to_generic_arrays(input: &[u8]) -> Vec<GenericArray<u8, U16>> {
|
fn convert_to_generic_arrays(input: &[u8]) -> Result<Vec<GenericArray<u8, U16>>, AppError> {
|
||||||
// 确保输入的长度是16的倍数
|
// 确保输入的长度是16的倍数
|
||||||
assert!(
|
if input.len() % 16 == 0 {}
|
||||||
input.len() % 16 == 0,
|
|
||||||
"Input length must be a multiple of 16"
|
|
||||||
);
|
|
||||||
|
|
||||||
input
|
Ok(input
|
||||||
.chunks(16)
|
.chunks(16)
|
||||||
.map(|chunk| {
|
.map(|chunk| {
|
||||||
// 将每个块转换为GenericArray
|
// 将每个块转换为GenericArray
|
||||||
GenericArray::clone_from_slice(chunk)
|
GenericArray::clone_from_slice(chunk)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect())
|
||||||
}
|
|
||||||
/// aes128解密
|
|
||||||
/// !!!未对齐数据!!!
|
|
||||||
/// TODO
|
|
||||||
/// 解密NCM文件的rc4密钥前记得按字节对0x64进行异或
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn aes128(key: &[u8], blocks: &[u8]) -> String {
|
|
||||||
trace!("进行AES128解密");
|
|
||||||
let key = GenericArray::from_slice(key);
|
|
||||||
|
|
||||||
let mut blocks = convert_to_generic_arrays(blocks);
|
|
||||||
|
|
||||||
// 初始化密钥
|
|
||||||
let cipher = Aes128::new(&key);
|
|
||||||
|
|
||||||
// 开始解密
|
|
||||||
cipher.decrypt_blocks(&mut blocks);
|
|
||||||
|
|
||||||
let mut x = String::new();
|
|
||||||
for block in blocks.iter() {
|
|
||||||
x.push_str(std::str::from_utf8(&block).unwrap())
|
|
||||||
}
|
|
||||||
// 去除所有空格及控制字符
|
|
||||||
let x = x[..].trim();
|
|
||||||
|
|
||||||
x.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## AES128解密
|
/// ## AES128解密
|
||||||
fn aes128_to_slice(key: &[u8], blocks: &[u8]) -> Vec<u8> {
|
/// 解密NCM文件的rc4密钥前记得按字节对0x64进行异或
|
||||||
|
fn aes128_to_slice<T: AsRef<[u8]>>(key: &T, blocks: Vec<u8>) -> Result<Vec<u8>, AppError> {
|
||||||
trace!("进行AES128解密");
|
trace!("进行AES128解密");
|
||||||
let key = GenericArray::from_slice(key);
|
let key = GenericArray::from_slice(key.as_ref());
|
||||||
|
|
||||||
let mut blocks = convert_to_generic_arrays(blocks);
|
let mut blocks = convert_to_generic_arrays(&blocks)?;
|
||||||
|
|
||||||
// 初始化密钥
|
// 初始化密钥
|
||||||
let cipher = Aes128::new(&key);
|
let cipher = Aes128::new(&key);
|
||||||
@ -532,7 +496,7 @@ fn aes128_to_slice(key: &[u8], blocks: &[u8]) -> Vec<u8> {
|
|||||||
x.push(i.to_owned());
|
x.push(i.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
x
|
Ok(x.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## 规范文件名称
|
/// ## 规范文件名称
|
||||||
@ -553,66 +517,3 @@ fn standardize_filename(old_fullfilename: String) -> String {
|
|||||||
}
|
}
|
||||||
new_fullfilename
|
new_fullfilename
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 使用PKCS5Padding标准,去掉填充信息
|
|
||||||
fn unpad(data: &[u8]) -> Vec<u8> {
|
|
||||||
data[..data.len() - data[data.len() - 1] as usize].to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,PartialEq)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum NcmError {
|
|
||||||
NotNcmFile,
|
|
||||||
CannotReadFileName,
|
|
||||||
CannotReadMetaInfo,
|
|
||||||
CoverCannotSave,
|
|
||||||
FileReadError,
|
|
||||||
FileSkipError,
|
|
||||||
FileWriteError,
|
|
||||||
FullFilenameError,
|
|
||||||
FileNotFound,
|
|
||||||
ProtectFile,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for NcmError {}
|
|
||||||
|
|
||||||
impl std::fmt::Display for NcmError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::NotNcmFile => write!(f, "该文件不为NCM格式"),
|
|
||||||
Self::CannotReadFileName => write!(f, "无法读取文件名称"),
|
|
||||||
Self::CannotReadMetaInfo => write!(f, "无法读取歌曲元信息"),
|
|
||||||
Self::CoverCannotSave => write!(f, "封面无法保存"),
|
|
||||||
|
|
||||||
Self::FileReadError => write!(f, "读取文件时发生错误"),
|
|
||||||
Self::FileWriteError => write!(f, "写入文件时错误"),
|
|
||||||
Self::FullFilenameError => write!(f, "文件名不符合规范"),
|
|
||||||
Self::ProtectFile => write!(
|
|
||||||
f,
|
|
||||||
"已关闭文件强制覆盖且文件已存在。使用-f或-forcesave开启强制覆盖。"
|
|
||||||
),
|
|
||||||
_ => write!(f, "未知错误"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct TimeCompare(u128);
|
|
||||||
|
|
||||||
impl TimeCompare {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self(
|
|
||||||
SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_millis(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
pub fn compare(&self) -> u128 {
|
|
||||||
let time = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_millis();
|
|
||||||
time - self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -3,32 +3,29 @@ use std::{path::PathBuf, process::Command};
|
|||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn opendir(dir: PathBuf) {
|
pub fn opendir(dir: PathBuf) {
|
||||||
match Command::new("explorer")
|
if let Err(_) = Command::new("explorer")
|
||||||
.arg(&dir) // <- Specify the directory you'd like to open.
|
.arg(&dir) // <- Specify the directory you'd like to open.
|
||||||
.spawn()
|
.spawn()
|
||||||
{
|
{
|
||||||
Err(_) => error!("无法打开输出文件夹:[{}]", dir.display()),
|
error!("无法打开输出文件夹:[{}]", dir.display())
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn opendir(dir: PathBuf) {
|
pub fn opendir(dir: PathBuf) {
|
||||||
match Command::new("open")
|
if let Err(_) = Command::new("open")
|
||||||
.arg(&dir) // <- Specify the directory you'd like to open.
|
.arg(&dir) // <- Specify the directory you'd like to open.
|
||||||
.spawn()
|
.spawn()
|
||||||
{
|
{
|
||||||
Err(_) => error!("无法打开输出文件夹:[{}]", dir.display()),
|
error!("无法打开输出文件夹:[{}]", dir.display())
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn opendir(dir: PathBuf) {
|
pub fn opendir(dir: PathBuf) {
|
||||||
match Command::new("open")
|
if let Err(_) = Command::new("open")
|
||||||
.arg(&dir) // <- Specify the directory you'd like to open.
|
.arg(&dir) // <- Specify the directory you'd like to open.
|
||||||
.spawn()
|
.spawn()
|
||||||
{
|
{
|
||||||
Err(_) => error!("无法打开输出文件夹:[{}]", dir.display()),
|
error!("无法打开输出文件夹:[{}]", dir.display())
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
|
use log::{debug, error, warn};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
use crate::apperror::AppError;
|
||||||
|
|
||||||
pub fn pathparse(input: Vec<String>) -> Vec<String> {
|
pub fn pathparse(input: Vec<String>) -> Vec<String> {
|
||||||
let mut undumpfile = Vec::new(); // 该列表将存入文件的路径
|
let mut undumpfile = Vec::new(); // 该列表将存入文件的路径
|
||||||
// 遍历输入的每一个路径参数
|
// 遍历输入的每一个路径参数
|
||||||
@ -10,23 +13,34 @@ pub fn pathparse(input: Vec<String>) -> Vec<String> {
|
|||||||
|
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
// 当后缀符合为ncm时才加入列表
|
// 当后缀符合为ncm时才加入列表
|
||||||
match path.extension() {
|
if let Some(extension) = path.extension() {
|
||||||
Some(extension) => {
|
if extension == "ncm" {
|
||||||
if extension == "ncm" {
|
let _ = &mut undumpfile.push(arg.to_owned());
|
||||||
let _ = &mut undumpfile.push(arg.to_owned());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
} else if path.is_dir() {
|
} else if path.is_dir() {
|
||||||
for entry in WalkDir::new(path) {
|
for entry in WalkDir::new(path) {
|
||||||
let new_entry = entry.unwrap().clone();
|
let new_entry = match entry {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(e) => {
|
||||||
|
error!("无法遍历目录: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
let filepath = new_entry.into_path();
|
let filepath = new_entry.into_path();
|
||||||
// 当后缀符合为ncm时才加入列表
|
// 当后缀符合为ncm时才加入列表
|
||||||
match filepath.extension() {
|
match filepath.extension() {
|
||||||
Some(extension) => {
|
Some(extension) => {
|
||||||
if extension == "ncm" {
|
if extension == "ncm" {
|
||||||
let _ = &mut undumpfile.push(String::from(filepath.to_str().unwrap()));
|
match filepath.to_str() {
|
||||||
|
Some(s) => {
|
||||||
|
let _ = &mut undumpfile.push(s.into());
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
debug!("无法获取你文件路径");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
@ -19,11 +19,11 @@ impl Worker {
|
|||||||
let message = receiver.lock().unwrap().recv().unwrap();
|
let message = receiver.lock().unwrap().recv().unwrap();
|
||||||
match message {
|
match message {
|
||||||
Message::NewJob(job) => {
|
Message::NewJob(job) => {
|
||||||
debug!("线程[{}]获得任务", id);
|
// debug!("线程[{}]获得任务", id);
|
||||||
job();
|
job();
|
||||||
}
|
}
|
||||||
Message::ByeBye => {
|
Message::ByeBye => {
|
||||||
debug!("线程[{}]结束任务", id);
|
// debug!("线程[{}]结束任务", id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/time.rs
Normal file
23
src/time.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use crate::AppError;
|
||||||
|
|
||||||
|
pub struct TimeCompare(u128);
|
||||||
|
|
||||||
|
impl TimeCompare {
|
||||||
|
pub fn new() -> Result<Self, AppError> {
|
||||||
|
Ok(Self(
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map_err(|_| AppError::SystemTimeError)?
|
||||||
|
.as_millis(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
pub fn compare(&self) -> Result<u128, AppError> {
|
||||||
|
let time = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map_err(|_| AppError::SystemTimeError)?
|
||||||
|
.as_millis();
|
||||||
|
Ok(time - self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user