大量重构错误传递逻辑.修复少量bug.

This commit is contained in:
Lkhsss
2024-11-24 01:38:56 +08:00
parent f791f9b7e9
commit 40b7a6072b
9 changed files with 247 additions and 141 deletions

4
.gitignore vendored
View File

@ -1,5 +1,5 @@
/target /target
src/main.rs.old src/*.old
src/lib.rs.bak src/*.bak
/testfile /testfile
/output /output

44
CHANGELOG.md Normal file
View File

@ -0,0 +1,44 @@
# CHANGELOG
## [1.0.0] - 2024-1-27
### Features :sparkles:
- 初步完成解密函数。主程序成型
## [1.1.1] - 2024-2-1
### Features :sparkles:
- 完成批量解密
### Fixed :bug:
- 修正了提取音乐信息会有数据类型错误导致panic的问题
## [1.1.2] - 2024-2-5
### Fixed :bug:
- 修正了提取音乐信息时,部分歌曲信息提取失败的问题
- 修正了音乐数据解密失败的问题
## [1.1.3] - 2024-2-6
### Fixed :bug:
- 修正了部分音乐名称中含有不合法字符时创建文件失败的问题
## [1.1.4] - 2024-7-14
### Features :sparkles:
- 增加多线程支持!
> ~~目前固定4线程还没写命令行参数。可以源代码修改线程数~~ 已于2.1.4版本修复~
### Fixed
- 优化代码结构
## [2.2.4] - 2024-11-17
### Features :sparkles:
- :ambulance:完整的多线程支持!可自定线程数!
- :ambulance:自动添加封面解密文件不再需要musictag!
- :sparkles: 完整的命令行参数支持!
- :sparkles: 计时功能!
### Fixed
- :arrow_up: 升级依赖
- 优化保存文件逻辑,保存时间缩短到毫秒
### Refactoring
- :hammer: 重构代码!
## [2.2.4] - 2024-11-17
### Features :sparkles:
- 完整的自动构建流程!
## [2.3.7] - 2024.11.24
### Refactoring
- 优化读取逻辑
- :hammer: 重构代码大量减少panic!

106
Cargo.lock generated
View File

@ -220,9 +220,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.19.0" version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -349,6 +349,19 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "console"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"unicode-width 0.1.14",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.7" version = "0.8.7"
@ -357,9 +370,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.15" version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -420,6 +433,12 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]] [[package]]
name = "env_filter" name = "env_filter"
version = "0.1.2" version = "0.1.2"
@ -573,9 +592,9 @@ dependencies = [
[[package]] [[package]]
name = "id3" name = "id3"
version = "1.14.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55f4e785f2c700217ee82a1c727c720449421742abd5fcb2f1df04e1244760e9" checksum = "ab55e9dc8caf811048062c5711b338e0582c89504737d5a95c4568f723a46cf3"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"byteorder", "byteorder",
@ -631,6 +650,19 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "indicatif"
version = "0.17.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281"
dependencies = [
"console",
"number_prefix",
"portable-atomic",
"unicode-width 0.2.0",
"web-time",
]
[[package]] [[package]]
name = "inout" name = "inout"
version = "0.1.3" version = "0.1.3"
@ -668,9 +700,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.11" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2"
[[package]] [[package]]
name = "jobserver" name = "jobserver"
@ -799,7 +831,7 @@ checksum = "07dcca13d1740c0a665f77104803360da0bdb3323ecce2e93fa2c959a6d52806"
[[package]] [[package]]
name = "ncmmiao" name = "ncmmiao"
version = "2.3.7" version = "2.3.8"
dependencies = [ dependencies = [
"aes", "aes",
"audiotags", "audiotags",
@ -810,6 +842,7 @@ dependencies = [
"env_logger", "env_logger",
"hex", "hex",
"image", "image",
"indicatif",
"lazy_static", "lazy_static",
"log", "log",
"serde", "serde",
@ -890,6 +923,12 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "number_prefix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.20.2" version = "1.20.2"
@ -921,6 +960,12 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "portable-atomic"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.20" version = "0.2.20"
@ -932,9 +977,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.89" version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -1214,9 +1259,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.87" version = "2.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1315,9 +1360,21 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.13" version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
@ -1419,6 +1476,16 @@ version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "weezl" name = "weezl"
version = "0.1.8" version = "0.1.8"
@ -1452,6 +1519,15 @@ dependencies = [
"windows-targets 0.48.5", "windows-targets 0.48.5",
] ]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.59.0" version = "0.59.0"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ncmmiao" name = "ncmmiao"
version = "2.3.7" version = "2.3.8"
edition = "2021" edition = "2021"
authors = ["Lkhsss <lkhsss1019@gmail.com>"] authors = ["Lkhsss <lkhsss1019@gmail.com>"]
description = "A magic tool convert ncm to flac" description = "A magic tool convert ncm to flac"
@ -21,6 +21,7 @@ colored = "2.1.0"
env_logger = "0.11.1" env_logger = "0.11.1"
hex = "0.4.3" hex = "0.4.3"
image = "0.25.*" image = "0.25.*"
indicatif = "0.17.9"
lazy_static = "1.5.0" lazy_static = "1.5.0"
log = "0.4.20" log = "0.4.20"
serde = { version = "1.0.195", features = ["derive"] } serde = { version = "1.0.195", features = ["derive"] }

View File

@ -40,47 +40,6 @@ Options:
--- ---
# CHANGELOG
## [1.0.0] - 2024-1-27
### Features :sparkles:
- 初步完成解密函数。主程序成型
## [1.1.1] - 2024-2-1
### Features :sparkles:
- 完成批量解密
### Fixed :bug:
- 修正了提取音乐信息会有数据类型错误导致panic的问题
## [1.1.2] - 2024-2-5
### Fixed :bug:
- 修正了提取音乐信息时,部分歌曲信息提取失败的问题
- 修正了音乐数据解密失败的问题
## [1.1.3] - 2024-2-6
### Fixed :bug:
- 修正了部分音乐名称中含有不合法字符时创建文件失败的问题
## [1.1.4] - 2024-7-14
### Features :sparkles:
- 增加多线程支持!
> ~~目前固定4线程还没写命令行参数。可以源代码修改线程数~~ 已于2.1.4版本修复~
### Fixed
- 优化代码结构
## [2.2.4] - 2024-11-17
### Features :sparkles:
- :ambulance:完整的多线程支持!可自定线程数!
- :ambulance:自动添加封面解密文件不再需要musictag!
- :sparkles: 完整的命令行参数支持!
- :sparkles: 计时功能!
### Fixed
- :arrow_up: 升级依赖
- 优化保存文件逻辑,保存时间缩短到毫秒
### Refactoring
- :hammer: 重构代码!
## [2.2.4] - 2024-11-17
### Features :sparkles:
- 完整的自动构建流程!
---
# 附 - ncm文件结构 # 附 - ncm文件结构
|信息|大小|作用| |信息|大小|作用|
|:-:|:-:|:-:| |:-:|:-:|:-:|

View File

@ -3,7 +3,7 @@ use clap::Parser;
#[derive(Parser)] #[derive(Parser)]
#[command(name = "ncmmiao")] #[command(name = "ncmmiao")]
#[command(author = "lkhsss")] #[command(author = "lkhsss")]
#[command(version,about = "一个解密ncm文件的神秘程序", long_about = None)] #[command(version,about = "一个解密ncm文件的神秘程序 By Lkhsss", long_about = None)]
pub struct Cli { pub struct Cli {
/// 并发的最大线程数默认为4线程 /// 并发的最大线程数默认为4线程
#[arg(short, long)] #[arg(short, long)]

View File

@ -32,7 +32,7 @@ impl Logger {
} }
log::Level::Debug => { log::Level::Debug => {
let style = buf.default_level_style(log::Level::Debug); let style = buf.default_level_style(log::Level::Debug);
format!("{style}Warn{style:#}") format!("{style}Debug{style:#}")
} }
log::Level::Trace => { log::Level::Trace => {
let style = buf.default_level_style(log::Level::Trace); let style = buf.default_level_style(log::Level::Trace);

View File

@ -1,8 +1,10 @@
use std::{ops::Add, path::Path, sync::{Arc, Mutex}};
use ::clap::Parser; use ::clap::Parser;
#[allow(unused_imports)] #[allow(unused_imports)]
use log::{error, info, warn}; use log::{error, info, warn};
use std::path::Path; use colored::Colorize;
use walkdir::WalkDir; //遍历目录 use walkdir::WalkDir; //遍历目录
mod clap; mod clap;
@ -70,6 +72,7 @@ fn main() {
} }
} }
let taskcount = undumpfile.len(); let taskcount = undumpfile.len();
let successful = Arc::new(Mutex::new(0));
if taskcount == 0 { if taskcount == 0 {
error!("没有找到有效文件。使用-i参数输入需要解密的文件或文件夹。") error!("没有找到有效文件。使用-i参数输入需要解密的文件或文件夹。")
} else { } else {
@ -79,18 +82,33 @@ fn main() {
for filepath in undumpfile { for filepath in undumpfile {
let output = outputdir.clone(); let output = outputdir.clone();
let successful = Arc::clone(&successful);
pool.execute(move || { pool.execute(move || {
Ncmfile::new(filepath.as_str()) match Ncmfile::new(filepath.as_str()) {
.unwrap() Ok(mut n) => match n.dump(Path::new(&output)) {
.dump(Path::new(&output)) Ok(_) => {
.unwrap(); let mut num = successful.lock().unwrap();
*num += 1;},
Err(e) => error!("[{}]解密失败: {}", filepath.yellow(), e),
},
Err(e) => error!("[{}]解密失败: {}", filepath.yellow(), e),
}
}); });
} }
} }
let timecount = timer.compare(); let timecount = timer.compare();
let showtime = || {
if timecount > 2000 { if timecount > 2000 {
info!("解密{}个文件,共计用时{}秒", taskcount, timecount / 1000) format!("共计用时{}", timecount / 1000)
} else { } else {
info!("解密{}个文件,共计用时{}毫秒", taskcount, timecount) format!("共计用时{}毫秒", timecount)
} }
};
let successful = *successful.lock().unwrap();
info!(
"成功解密{}个文件,{}个文件解密失败,{}",
successful.to_string().bright_green(),
(taskcount - successful).to_string().bright_red(),
showtime()
)
} }

View File

@ -11,7 +11,7 @@ use log::{debug, error, info, trace, warn};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use serde_json::{self, Value}; use serde_json::{self, Value};
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::{BufReader, BufWriter, Error, ErrorKind, Read, Seek, SeekFrom, Write}; use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::from_utf8; use std::str::from_utf8;
use std::vec; use std::vec;
@ -39,14 +39,17 @@ pub struct Ncmfile {
pub position: u64, pub position: u64,
} }
impl Ncmfile { impl Ncmfile {
pub fn new(filepath: &str) -> Result<Ncmfile, Error> { pub fn new(filepath: &str) -> Result<Ncmfile, NcmError> {
let file = File::open(filepath)?; let file = match File::open(filepath) {
Ok(f) => f,
Err(_) => return Err(NcmError::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.file_name().unwrap().to_str().unwrap().to_string();
let size = file.metadata().unwrap().len(); let size = file.metadata().unwrap().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().unwrap().to_string(),
None => panic!("获取文件名失败"), None => return Err(NcmError::CannotReadFileName),
}; };
Ok(Ncmfile { Ok(Ncmfile {
file, file,
@ -60,17 +63,14 @@ impl Ncmfile {
/// ///
/// 该函数可以记录上次读取的位置,下次读取时从上次读取的位置开始 /// 该函数可以记录上次读取的位置,下次读取时从上次读取的位置开始
/// - length 想要读取的长度 /// - length 想要读取的长度
pub fn seekread(&mut self, length: u64) -> Result<Vec<u8>, std::io::Error> { pub fn seekread(&mut self, length: u64) -> Result<Vec<u8>, NcmError> {
if self.position + length > self.size { if self.position + length > self.size {
return Err(Error::new( return Err(NcmError::FileReadError);
ErrorKind::UnexpectedEof,
"无法读取!读取长度大于剩余文件大小!",
));
} else { } else {
let mut reader = BufReader::new(&self.file); let mut reader = BufReader::new(&self.file);
reader.seek(SeekFrom::Start(self.position))?; let _ = reader.seek(SeekFrom::Start(self.position));
let mut buf = vec![0; length as usize]; let mut buf = vec![0; length as usize];
reader.read_exact(&mut buf)?; let _ = reader.read_exact(&mut buf);
self.position += length; self.position += length;
Ok(buf[..].to_vec()) Ok(buf[..].to_vec())
} }
@ -82,17 +82,14 @@ 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>, std::io::Error> { pub fn seekread_from(&mut self, offset: u64, length: u64) -> Result<Vec<u8>, NcmError> {
if self.position + length > self.size { if self.position + length > self.size {
return Err(Error::new( return Err(NcmError::FileReadError);
ErrorKind::UnexpectedEof,
"无法读取!读取长度大于剩余文件大小!",
));
} else { } else {
let mut reader = BufReader::new(&self.file); let mut reader = BufReader::new(&self.file);
reader.seek(SeekFrom::Start(offset))?; let _ = reader.seek(SeekFrom::Start(offset));
let mut buf = vec![0; length as usize]; let mut buf = vec![0; length as usize];
reader.read_exact(&mut buf)?; let _ = reader.read_exact(&mut buf);
self.position = offset + length; self.position = offset + length;
Ok(buf[..].to_vec()) Ok(buf[..].to_vec())
} }
@ -129,12 +126,9 @@ impl Ncmfile {
} }
} }
/// 跳过某些数据 /// 跳过某些数据
pub fn skip(&mut self, length: u64) -> Result<(), std::io::Error> { pub fn skip(&mut self, length: u64) -> Result<(), NcmError> {
if self.position + length > self.size { if self.position + length > self.size {
return Err(Error::new( return Err(NcmError::FileReadError);
ErrorKind::UnexpectedEof,
"无法跳过!跳过长度大于剩余文件大小!",
));
} else { } else {
self.position += length; self.position += length;
Ok(()) Ok(())
@ -150,14 +144,13 @@ impl Ncmfile {
/// 解密函数 /// 解密函数
#[allow(unused_assignments)] #[allow(unused_assignments)]
pub fn dump(&mut self, outputdir: &Path) -> Result<(), MyError> { pub fn dump(&mut self, outputdir: &Path) -> Result<(), NcmError> {
info!("开始解密[{}]文件", self.fullfilename.yellow()); info!("开始解密[{}]文件", self.fullfilename.yellow());
// 获取magic header 。应为CTENFDAM // 获取magic header 。应为CTENFDAM
let magic_header = match self.seekread(8) { let magic_header = match self.seekread(8) {
Ok(header) => header, Ok(header) => header,
Err(_e) => { Err(_e) => {
error!("读取magic header失败"); return Err(NcmError::FileReadError);
return Err(MyError::MagicHeaderError);
} }
}; };
@ -166,27 +159,21 @@ impl Ncmfile {
Ok(header) => { Ok(header) => {
if header != "CTENFDAM" { if header != "CTENFDAM" {
// 传播错误至dump // 传播错误至dump
return Err(MyError::MagicHeaderError); return Err(NcmError::NotNcmFile);
} else {
trace!("[{}]为ncm格式文件", self.fullfilename.yellow());
debug!("magic header: {}", header);
} }
} }
// 传播错误至dump // 传播错误至dump
Err(_e) => return Err(MyError::MagicHeaderError), Err(_e) => return Err(NcmError::NotNcmFile),
} }
// 跳过2字节 // 跳过2字节
trace!("跳过2字节"); trace!("跳过2字节");
match self.skip(2) { self.skip(2)?;
Ok(_) => (),
Err(_e) => return Err(MyError::FileSkipError),
};
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).unwrap().try_into().unwrap()) as u64;
debug!("RC4密钥长度为{}", key_length); // debug!("RC4密钥长度为{}", key_length);
//读取密钥 开头应为 neteasecloudmusic //读取密钥 开头应为 neteasecloudmusic
trace!("读取RC4密钥"); trace!("读取RC4密钥");
@ -194,55 +181,58 @@ impl Ncmfile {
//aes128解密 //aes128解密
let key_data = &aes128_to_slice(&KEY_CORE, Self::parse_key(&mut key_data[..])); //先把密钥按照字节进行0x64异或 let key_data = &aes128_to_slice(&KEY_CORE, Self::parse_key(&mut key_data[..])); //先把密钥按照字节进行0x64异或
// RC4密钥 // RC4密钥
let key_data = unpad(&key_data[..])[17..].to_vec(); let key_data = unpad(&key_data[..])[17..].to_vec(); //去掉neteasecloudmusic
//读取meta信息的数据大小 //读取meta信息的数据大小
trace!("获取meta信息数据大小"); trace!("获取meta信息数据大小");
let meta_length = u32::from_le_bytes(self.seekread(4).unwrap().try_into().unwrap()) as u64; let meta_length = u32::from_le_bytes(self.seekread(4)?.try_into().unwrap()) as u64;
// 读取meta信息 // 读取meta信息
trace!("读取meta信息"); trace!("读取meta信息");
let meta_data = { let meta_data = {
let mut meta_data = self.seekread(meta_length).unwrap(); //读取源数据 let mut meta_data = self.seekread(meta_length)?; //读取源数据
//字节对0x63进行异或。 //字节对0x63进行异或。
for i in 0..meta_data.len() { for i in 0..meta_data.len() {
meta_data[i] ^= 0x63; meta_data[i] ^= 0x63;
} }
// base64解密 // base64解密
let mut decode_data = Vec::<u8>::new(); let mut decode_data = Vec::<u8>::new();
let _ = &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)
.unwrap(); {
Err(_) => return Err(NcmError::CannotReadMetaInfo),
_ => (),
};
// aes128解密 // aes128解密
let aes_data = aes128_to_slice(&KEY_META, &decode_data); let aes_data = aes128_to_slice(&KEY_META, &decode_data);
// unpadding // unpadding
let json_data = String::from_utf8(unpad(&aes_data)[6..].to_vec()).unwrap(); let json_data = match String::from_utf8(unpad(&aes_data)[6..].to_vec()) {
Ok(o) => o,
Err(_) => return Err(NcmError::CannotReadMetaInfo),
};
debug!("json_data: {}", json_data); debug!("json_data: {}", json_data);
let data: Value = serde_json::from_str(&json_data[..]).unwrap(); //解析json数据 let data: Value = match serde_json::from_str(&json_data[..]){Ok(o) => o,
Err(_) => return Err(NcmError::CannotReadMetaInfo),
}; //解析json数据
data data
}; };
// 跳过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).unwrap().try_into().unwrap()) as u64;
self.skip(4)?;
// 跳过5个字节 // 跳过5个字节
trace!("跳过5个字节"); trace!("跳过5个字节");
self.skip(5).unwrap(); self.skip(5)?;
// 获取图片数据的大小 // 获取图片数据的大小
trace!("获取图片数据的大小"); trace!("获取图片数据的大小");
let image_data_length = let image_data_length =
u32::from_le_bytes(self.seekread(4).unwrap().try_into().unwrap()) as u64; u32::from_le_bytes(self.seekread(4)?.try_into().unwrap()) as u64;
// 读取图片,并写入文件当中 // 读取图片,并写入文件当中
trace!("暂不需要保存图片,跳过{}字节", image_data_length); let image_data = self.seekread(image_data_length)?; //读取图片数据
let image_data = self.seekread(image_data_length).unwrap(); //读取图片数据
// let _ = self.skip(image_data_length); //暂不需要保存图片,直接跳过这些字节就好
//保存图片
// trace!("保存图片");
// let mut file = File::create(format!("TEST.jpg",)).unwrap();
// file.write_all(&image_data).unwrap();
trace!("组成密码盒"); trace!("组成密码盒");
let key_box = { let key_box = {
@ -264,7 +254,7 @@ impl Ncmfile {
key_box[temp as usize] = swap as u8; key_box[temp as usize] = swap as u8;
last_byte = temp; last_byte = temp;
} }
let key_box = key_box.clone(); // let key_box = key_box.clone();
key_box key_box
}; };
@ -347,16 +337,19 @@ impl Ncmfile {
// let filename = standardize_filename(filename); // let filename = standardize_filename(filename);
debug!("文件名:{}", filename.yellow()); debug!("文件名:{}", filename.yellow());
//链级创建输出目录 //链级创建输出目录
fs::create_dir_all(outputdir).unwrap(); match fs::create_dir_all(outputdir){Err(_)=>return Err(NcmError::FileWriteError),_=>()};
outputdir.join(filename) outputdir.join(filename)
}; };
debug!("文件路径: {:?}", path); debug!("文件路径: {:?}", path);
self.save(&path, music_data); self.save(&path, music_data)?;
{ {
// 保存封面 // 保存封面
let mut tag = Tag::new().read_from_path(&path).unwrap(); let mut tag = match Tag::new().read_from_path(&path){
Ok(o)=>o,
Err(_)=>return Err(NcmError::CoverCannotSave)
};
let cover = Picture { let cover = Picture {
mime_type: MimeType::Jpeg, mime_type: MimeType::Jpeg,
data: &image_data, data: &image_data,
@ -377,12 +370,19 @@ impl Ncmfile {
); );
Ok(()) Ok(())
} }
fn save(&mut self, path: &PathBuf, data: Vec<u8>) { fn save(&mut self, path: &PathBuf, data: Vec<u8>)->Result<(),NcmError> {
let music_file = File::create(path).unwrap(); let music_file = match File::create(path){
Ok(o)=>o,
Err(_)=>return Err(NcmError::FileWriteError)
};
let mut writer = BufWriter::new(music_file); let mut writer = BufWriter::new(music_file);
let _ = writer.write_all(&data); let _ = writer.write_all(&data);
// 关闭文件 // 关闭文件
writer.flush().unwrap(); match writer.flush(){
Ok(o)=>o,
Err(_)=>return Err(NcmError::FileWriteError)
};
Ok(())
} }
} }
@ -514,6 +514,7 @@ fn aes128_to_slice(key: &[u8], blocks: &[u8]) -> Vec<u8> {
/// 符号一一对应: /// 符号一一对应:
/// - \ / * ? " : < > | /// - \ / * ? " : < > |
/// - _ _ ⟨ ⟩ _ /// - _ _ ⟨ ⟩ _
#[allow(dead_code)]
fn standardize_filename(old_fullfilename: String) -> String { fn standardize_filename(old_fullfilename: String) -> String {
trace!("格式化文件名"); trace!("格式化文件名");
let mut new_fullfilename = String::from(old_fullfilename); let mut new_fullfilename = String::from(old_fullfilename);
@ -534,8 +535,11 @@ fn unpad(data: &[u8]) -> Vec<u8> {
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)] #[allow(dead_code)]
pub enum MyError { pub enum NcmError {
MagicHeaderError, NotNcmFile,
CannotReadFileName,
CannotReadMetaInfo,
CoverCannotSave,
FileReadError, FileReadError,
FileSkipError, FileSkipError,
FileWriteError, FileWriteError,
@ -543,14 +547,18 @@ pub enum MyError {
FileNotFoundError, FileNotFoundError,
} }
impl std::error::Error for MyError {} impl std::error::Error for NcmError {}
impl std::fmt::Display for MyError { impl std::fmt::Display for NcmError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self { match self {
Self::MagicHeaderError => write!(f, "文件不为NCM格式"), Self::NotNcmFile => write!(f, "文件不为NCM格式"),
Self::FileReadError => write!(f, "文件读取错误"), Self::CannotReadFileName => write!(f, "无法读取文件名称"),
Self::FileWriteError => write!(f, "文件写入错误"), Self::CannotReadMetaInfo => write!(f, "无法读取歌曲元信息"),
Self::CoverCannotSave => write!(f, "封面无法保存"),
Self::FileReadError => write!(f, "读取文件时发生错误"),
Self::FileWriteError => write!(f, "写入文件时错误"),
Self::FullFilenameError => write!(f, "文件名不符合规范"), Self::FullFilenameError => write!(f, "文件名不符合规范"),
_ => write!(f, "未知错误"), _ => write!(f, "未知错误"),
} }