1.添加多线程支持
2.优化代码结构 3.升级依赖
This commit is contained in:
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@ -1,5 +1,8 @@
|
|||||||
name: build
|
name: build
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
workflow_dispatch: {}
|
workflow_dispatch: {}
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@ -10,7 +13,7 @@ jobs:
|
|||||||
build: [linux, macos, windows]
|
build: [linux, macos, windows]
|
||||||
include:
|
include:
|
||||||
- build: linux
|
- build: linux
|
||||||
os: ubuntu-18.04
|
os: ubuntu-latest
|
||||||
rust: stable
|
rust: stable
|
||||||
target: x86_64-unknown-linux-musl
|
target: x86_64-unknown-linux-musl
|
||||||
archive-name: ncmmiao-linux.tar.gz
|
archive-name: ncmmiao-linux.tar.gz
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
/target
|
/target
|
||||||
src/main.rs.old
|
src/main.rs.old
|
||||||
src/lib.rs.bak
|
src/lib.rs.bak
|
||||||
|
/testfile
|
||||||
|
/output
|
||||||
988
Cargo.lock
generated
988
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -7,11 +7,12 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes = "0.8.3"
|
aes = "0.8.3"
|
||||||
base64 = "0.21.7"
|
base64 = "0.22.*"
|
||||||
|
clap = { version = "4.5.9", features = ["derive"] }
|
||||||
colored = "2.1.0"
|
colored = "2.1.0"
|
||||||
env_logger = "0.11.1"
|
env_logger = "0.11.1"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
image = "0.24.8"
|
image = "0.25.*"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
serde = { version = "1.0.195", features = ["derive"] }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
serde_derive = "1.0.195"
|
serde_derive = "1.0.195"
|
||||||
|
|||||||
10
README.md
10
README.md
@ -15,7 +15,7 @@ cargo build -r
|
|||||||
## 使用
|
## 使用
|
||||||
支持单一文件,多文件夹递归批量解密。
|
支持单一文件,多文件夹递归批量解密。
|
||||||
```
|
```
|
||||||
cargo build -r <文件或文件夹路径1> <文件或文件夹路径2> <文件或文件夹路径3> ...
|
cargo run -r <文件或文件夹路径1> <文件或文件夹路径2> <文件或文件夹路径3> ...
|
||||||
```
|
```
|
||||||
> 注意!如果没有指定任何文件夹或者文件,那么程序将自动读取工作目录下的CloudMusic和input文件夹下的文件。如果都没有将自动退出。
|
> 注意!如果没有指定任何文件夹或者文件,那么程序将自动读取工作目录下的CloudMusic和input文件夹下的文件。如果都没有将自动退出。
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ cargo build -r <文件或文件夹路径1> <文件或文件夹路径2> <文件
|
|||||||
---
|
---
|
||||||
|
|
||||||
# TODO :construction:
|
# TODO :construction:
|
||||||
- [ ] 多线程支持
|
- [x] 多线程支持
|
||||||
- [ ] 解密进度条
|
- [ ] 解密进度条
|
||||||
- [ ] 命令行解析
|
- [ ] 命令行解析
|
||||||
- [ ] 自定义输出文件夹
|
- [ ] 自定义输出文件夹
|
||||||
@ -50,6 +50,12 @@ cargo build -r <文件或文件夹路径1> <文件或文件夹路径2> <文件
|
|||||||
## [1.1.3] - 2024-2-6
|
## [1.1.3] - 2024-2-6
|
||||||
### Fixed :bug:
|
### Fixed :bug:
|
||||||
- 修正了部分音乐名称中含有不合法字符时创建文件失败的问题
|
- 修正了部分音乐名称中含有不合法字符时创建文件失败的问题
|
||||||
|
## [1.1.4] - 2024-7-14
|
||||||
|
### Features :sparkles:
|
||||||
|
- 增加多线程支持!
|
||||||
|
> 目前固定4线程,还没写命令行参数。可以源代码修改线程数
|
||||||
|
### Fixed
|
||||||
|
- 优化代码结构
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
27
src/main.rs
27
src/main.rs
@ -1,19 +1,26 @@
|
|||||||
use env_logger::Builder;
|
use env_logger::Builder;
|
||||||
use hex::decode;
|
use hex::decode;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use ncmmiao::{dump, Key, Ncmfile};
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
// use std::time::SystemTime;
|
// use std::time::SystemTime;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir; //遍历目录
|
||||||
#[warn(unreachable_code)]
|
|
||||||
|
mod ncmdump;
|
||||||
|
mod threadpool;
|
||||||
|
use ncmdump::{dump, Key, Ncmfile};
|
||||||
|
mod test;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
// 最大线程数
|
||||||
|
let max_workers = 4;
|
||||||
|
|
||||||
let mut builder = Builder::new();
|
let mut builder = Builder::new();
|
||||||
builder.filter(None, log::LevelFilter::Info);
|
builder.filter(None, log::LevelFilter::Info);
|
||||||
builder.init(); //初始化logger
|
builder.init(); //初始化logger
|
||||||
|
|
||||||
let keys = Key {
|
let keys: Key = Key {
|
||||||
core: decode("687A4852416D736F356B496E62617857").unwrap(),
|
core: decode("687A4852416D736F356B496E62617857").unwrap(),
|
||||||
meta: decode("2331346C6A6B5F215C5D2630553C2728").unwrap(),
|
meta: decode("2331346C6A6B5F215C5D2630553C2728").unwrap(),
|
||||||
};
|
};
|
||||||
@ -21,6 +28,7 @@ fn main() {
|
|||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
let args = if args.len() == 1 {
|
let args = if args.len() == 1 {
|
||||||
warn!("未指定文件夹,将使用默认文件夹。");
|
warn!("未指定文件夹,将使用默认文件夹。");
|
||||||
|
|
||||||
let mut args_temp = Vec::new();
|
let mut args_temp = Vec::new();
|
||||||
if Path::new("CloudMusic").exists() {
|
if Path::new("CloudMusic").exists() {
|
||||||
warn!("CloudMusic文件夹存在,将自动使用。");
|
warn!("CloudMusic文件夹存在,将自动使用。");
|
||||||
@ -39,7 +47,6 @@ fn main() {
|
|||||||
} else {
|
} else {
|
||||||
args[1..].to_vec()
|
args[1..].to_vec()
|
||||||
};
|
};
|
||||||
// let args = &args[1..];
|
|
||||||
|
|
||||||
let mut undumpfile = Vec::new(); // 该列表将存入文件的路径
|
let mut undumpfile = Vec::new(); // 该列表将存入文件的路径
|
||||||
|
|
||||||
@ -78,8 +85,14 @@ fn main() {
|
|||||||
// let filepaths = undumpfile;
|
// let filepaths = undumpfile;
|
||||||
// let count = undumpfile.len();
|
// let count = undumpfile.len();
|
||||||
// let mut time = 0usize;
|
// let mut time = 0usize;
|
||||||
|
|
||||||
|
// 初始化线程池
|
||||||
|
let pool = threadpool::Pool::new(max_workers);
|
||||||
for filepath in undumpfile {
|
for filepath in undumpfile {
|
||||||
let mut ncmfile = Ncmfile::new(&filepath).unwrap();
|
let tkey = keys.clone();
|
||||||
dump(&mut ncmfile, &keys, Path::new("output")).unwrap();
|
pool.execute(move || {
|
||||||
|
let mut ncmfile = Ncmfile::new(filepath.as_str()).unwrap();
|
||||||
|
dump(&mut ncmfile, &tkey, Path::new("output")).unwrap();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,9 +16,9 @@ use std::str::from_utf8;
|
|||||||
pub struct Ncmfile {
|
pub struct Ncmfile {
|
||||||
/// 文件对象
|
/// 文件对象
|
||||||
pub file: File,
|
pub file: File,
|
||||||
/// 只是名称,不带后缀
|
/// 歌曲名称,不带文件后缀
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// 带后缀名
|
/// 文件名称,带后缀名
|
||||||
pub filename: String,
|
pub filename: String,
|
||||||
/// 文件大小
|
/// 文件大小
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
@ -182,6 +182,7 @@ struct Metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 存储各种密钥的结构体
|
// 存储各种密钥的结构体
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Key {
|
pub struct Key {
|
||||||
pub core: Vec<u8>,
|
pub core: Vec<u8>,
|
||||||
pub meta: Vec<u8>,
|
pub meta: Vec<u8>,
|
||||||
@ -257,7 +258,7 @@ pub fn dump(ncmfile: &mut Ncmfile, keys: &Key, outputdir: &Path) -> Result<(), M
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 跳过4个字节的校验码
|
// 跳过4个字节的校验码
|
||||||
trace!("跳过4个字节的校验码");
|
trace!("读取校验码");
|
||||||
let crc32 = u32::from_le_bytes(ncmfile.seekread(4).unwrap().try_into().unwrap()) as u64;
|
let crc32 = u32::from_le_bytes(ncmfile.seekread(4).unwrap().try_into().unwrap()) as u64;
|
||||||
|
|
||||||
// 跳过5个字节
|
// 跳过5个字节
|
||||||
@ -278,7 +279,7 @@ pub fn dump(ncmfile: &mut Ncmfile, keys: &Key, outputdir: &Path) -> Result<(), M
|
|||||||
// trace!("保存图片");
|
// trace!("保存图片");
|
||||||
// let mut file = File::create(format!("TEST.jpg",)).unwrap();
|
// let mut file = File::create(format!("TEST.jpg",)).unwrap();
|
||||||
// file.write_all(&image_data).unwrap();
|
// file.write_all(&image_data).unwrap();
|
||||||
|
trace!("组成密码盒");
|
||||||
let key_box = {
|
let key_box = {
|
||||||
let key_length = key_data.len();
|
let key_length = key_data.len();
|
||||||
let key_data = Vec::from(key_data);
|
let key_data = Vec::from(key_data);
|
||||||
@ -430,6 +431,7 @@ pub fn dump(ncmfile: &mut Ncmfile, keys: &Key, outputdir: &Path) -> Result<(), M
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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]) -> Vec<GenericArray<u8, U16>> {
|
||||||
// 确保输入的长度是16的倍数
|
// 确保输入的长度是16的倍数
|
||||||
assert!(
|
assert!(
|
||||||
@ -470,6 +472,8 @@ fn aes128(key: &[u8], blocks: &[u8]) -> String {
|
|||||||
|
|
||||||
x.to_string()
|
x.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ## AES128解密
|
||||||
fn aes128_to_slice(key: &[u8], blocks: &[u8]) -> Vec<u8> {
|
fn aes128_to_slice(key: &[u8], blocks: &[u8]) -> Vec<u8> {
|
||||||
trace!("进行AES128解密");
|
trace!("进行AES128解密");
|
||||||
let key = GenericArray::from_slice(key);
|
let key = GenericArray::from_slice(key);
|
||||||
@ -481,6 +485,8 @@ fn aes128_to_slice(key: &[u8], blocks: &[u8]) -> Vec<u8> {
|
|||||||
|
|
||||||
// 开始解密
|
// 开始解密
|
||||||
cipher.decrypt_blocks(&mut blocks);
|
cipher.decrypt_blocks(&mut blocks);
|
||||||
|
|
||||||
|
//取出解密后的值
|
||||||
let mut x: Vec<u8> = Vec::new();
|
let mut x: Vec<u8> = Vec::new();
|
||||||
for block in blocks.iter() {
|
for block in blocks.iter() {
|
||||||
for i in block {
|
for i in block {
|
||||||
@ -496,8 +502,9 @@ fn aes128_to_slice(key: &[u8], blocks: &[u8]) -> Vec<u8> {
|
|||||||
/// - \ / * ? " : < > |
|
/// - \ / * ? " : < > |
|
||||||
/// - _ _ * ? " : ⟨ ⟩ _
|
/// - _ _ * ? " : ⟨ ⟩ _
|
||||||
fn standardize_filename(old_filename: String) -> String {
|
fn standardize_filename(old_filename: String) -> String {
|
||||||
|
trace!("格式化文件名");
|
||||||
let mut new_filename = String::from(old_filename);
|
let mut new_filename = String::from(old_filename);
|
||||||
debug!("规范文件名:{}", new_filename);
|
// debug!("规范文件名:{}", new_filename);
|
||||||
let standard = ["\\", "/", "*", "?", "\"", ":", "<", ">", "|"];
|
let standard = ["\\", "/", "*", "?", "\"", ":", "<", ">", "|"];
|
||||||
let resolution = ["_", "_", "*", "?", """, ":", "⟨", "⟩", "_"];
|
let resolution = ["_", "_", "*", "?", """, ":", "⟨", "⟩", "_"];
|
||||||
for i in 0..standard.len() {
|
for i in 0..standard.len() {
|
||||||
14
src/test.rs
Normal file
14
src/test.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "测验成功"]
|
||||||
|
fn it_works() {
|
||||||
|
let p = threadpool::Pool::new(4);
|
||||||
|
p.execute(|| println!("do new job1"));
|
||||||
|
p.execute(|| println!("do new job2"));
|
||||||
|
p.execute(|| println!("do new job3"));
|
||||||
|
p.execute(|| println!("do new job4"));
|
||||||
|
}
|
||||||
|
}
|
||||||
90
src/threadpool.rs
Normal file
90
src/threadpool.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
use std::sync::{mpsc, Arc, Mutex};
|
||||||
|
use std::thread::{self, JoinHandle};
|
||||||
|
use log::{info,debug};
|
||||||
|
use serde::de;
|
||||||
|
|
||||||
|
type Job = Box<dyn FnOnce() + 'static + Send>;
|
||||||
|
enum Message {
|
||||||
|
ByeBye,
|
||||||
|
NewJob(Job),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Worker {
|
||||||
|
_id: usize,
|
||||||
|
t: Option<JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Worker {
|
||||||
|
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker {
|
||||||
|
let t = thread::spawn(move || loop {
|
||||||
|
let message = receiver.lock().unwrap().recv().unwrap();
|
||||||
|
match message {
|
||||||
|
Message::NewJob(job) => {
|
||||||
|
debug!("线程[{}]获得任务", id);
|
||||||
|
job();
|
||||||
|
}
|
||||||
|
Message::ByeBye => {
|
||||||
|
debug!("线程[{}]结束任务", id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Worker {
|
||||||
|
_id: id,
|
||||||
|
t: Some(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Pool {
|
||||||
|
workers: Vec<Worker>,
|
||||||
|
max_workers: usize,
|
||||||
|
sender: mpsc::Sender<Message>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pool {
|
||||||
|
pub fn new(max_workers: usize) -> Pool {
|
||||||
|
if max_workers == 0 {
|
||||||
|
panic!("最大线程数不能小于零!")
|
||||||
|
}else {
|
||||||
|
debug!("将开启{}线程",max_workers);
|
||||||
|
}
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
let mut workers = Vec::with_capacity(max_workers);
|
||||||
|
let receiver = Arc::new(Mutex::new(rx));
|
||||||
|
for i in 0..max_workers {
|
||||||
|
workers.push(Worker::new(i, Arc::clone(&receiver)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Pool {
|
||||||
|
workers: workers,
|
||||||
|
max_workers: max_workers,
|
||||||
|
sender: tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute<F>(&self, f: F)
|
||||||
|
where
|
||||||
|
F: FnOnce() + 'static + Send,
|
||||||
|
{
|
||||||
|
let job = Message::NewJob(Box::new(f));
|
||||||
|
self.sender.send(job).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Pool {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
for _ in 0..self.max_workers {
|
||||||
|
self.sender.send(Message::ByeBye).unwrap();
|
||||||
|
}
|
||||||
|
for w in self.workers.iter_mut() {
|
||||||
|
if let Some(t) = w.t.take() {
|
||||||
|
t.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user