Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
e29fdaf850 | |||
b812fe4e30 | |||
8c4ec2ab0a | |||
22768594ba | |||
6af854b376 | |||
ed6c05b4bf | |||
dc2830d5c3 | |||
cb7a84662f | |||
39adf93e46 | |||
0befc5bc93 | |||
b9299d8988 | |||
84b5c0044d | |||
f060bee5ad | |||
bc5719ac11 | |||
5e41d41874 | |||
3906a49f7a | |||
67cba29a8f | |||
2e5fc75fee | |||
|
f58c6c3880 | ||
|
2b52a367f2 | ||
36e6801f29 | |||
2cd7d69aa5 | |||
1aaaab10dc | |||
e04415db9b | |||
911beb25bc | |||
0a6ac353ff | |||
f89ea7eab6 | |||
6eb3649753 | |||
8a13d0fe07 | |||
321e5f342e | |||
dbdb73c120 | |||
7ad6f27b75 | |||
dd8b2d6fec | |||
981913d729 | |||
5fbfc2db81 | |||
e5cd9bddde | |||
068b1a6512 | |||
265fe6765d | |||
5c970f4a09 | |||
53b0d99431 | |||
37acbf07a7 | |||
d47d29426e | |||
602c6ed43a | |||
d8a0944810 |
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,31 +1,32 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: "[请按照此模板填写] 报告 Bug"
|
||||||
about: Create a bug report to help us improve
|
about: "创建一个 Bug 报告,不按照模板的 Issue 会被直接关闭,不予回复。"
|
||||||
title: "[Bug] Summarize the bug here"
|
title: "[Bug] 总结你的 Bug 报告"
|
||||||
labels: bug
|
labels: bug
|
||||||
assignees: taurusxin
|
assignees: taurusxin
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
**Bug 描述**
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
清晰地描述一下 Bug 的大致问题,例如无法转换,或者其他问题。
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
**复现方法**
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
复现此 Bug 的方法
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
1. 使用本项目处理文件 '...'
|
||||||
- OS: [e.g. Windows 11]
|
2. 发生报错
|
||||||
|
|
||||||
|
**屏幕截图**
|
||||||
|
|
||||||
**Additional context**
|
如果可以,屏幕截图可以更好地阐述你的问题。
|
||||||
Add any other context about the problem here.
|
|
||||||
|
**环境**
|
||||||
|
|
||||||
|
- 操作系统: Windows / macOS / Linux
|
||||||
|
- 网易云版本(重要): [e.g. 3.0.1]
|
||||||
|
- 所选择的音质: 极高、无损等
|
||||||
|
|
||||||
|
**附加内容**
|
||||||
|
|
||||||
|
如果遇到无法转换的问题,请将样本附加到这里,便于分析。
|
||||||
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: "[Feature] Summarize new feature here"
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
49
.github/workflows/autobuild.yml
vendored
49
.github/workflows/autobuild.yml
vendored
@ -1,6 +1,6 @@
|
|||||||
name: CI
|
name: CI
|
||||||
env:
|
env:
|
||||||
BUILD_TYPE: Release
|
BUILD_TYPE: MinSizeRel
|
||||||
BUILD_PATH: build
|
BUILD_PATH: build
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Windows amd64 Build - MinGW
|
name: windows_amd64_build_msys2_exe
|
||||||
path: ${{ env.BUILD_PATH }}/ncmdump.exe
|
path: ${{ env.BUILD_PATH }}/ncmdump.exe
|
||||||
|
|
||||||
build_on_windows_msvc:
|
build_on_windows_msvc:
|
||||||
@ -53,13 +53,19 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build ${{ env.BUILD_PATH }} -j 4 --config ${{ env.BUILD_TYPE }}
|
run: cmake --build ${{ env.BUILD_PATH }} -j 4 --config ${{ env.BUILD_TYPE }}
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact executable
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Windows amd64 Build - MSVC
|
name: windows_amd64_build_msvc_exe
|
||||||
path: ${{ env.BUILD_PATH }}/${{ env.BUILD_TYPE }}/ncmdump.exe
|
path: ${{ env.BUILD_PATH }}/${{ env.BUILD_TYPE }}/ncmdump.exe
|
||||||
|
|
||||||
build_on_linux:
|
- name: Upload artifact DLL
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows_amd64_build_msvc_dll
|
||||||
|
path: ${{ env.BUILD_PATH }}/${{ env.BUILD_TYPE }}/libncmdump.dll
|
||||||
|
|
||||||
|
build_on_linux_amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -82,10 +88,10 @@ jobs:
|
|||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Linux amd64 Build
|
name: linux_build_amd64
|
||||||
path: ${{ env.BUILD_PATH }}/ncmdump
|
path: ${{ env.BUILD_PATH }}/ncmdump
|
||||||
|
|
||||||
build_on_macos:
|
build_on_macos_amd64:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -99,7 +105,7 @@ jobs:
|
|||||||
brew install git cmake
|
brew install git cmake
|
||||||
|
|
||||||
- name: Configure build
|
- name: Configure build
|
||||||
run: cmake -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -B ${{ env.BUILD_PATH }}
|
run: cmake -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -B ${{ env.BUILD_PATH }} -DCMAKE_OSX_ARCHITECTURES=x86_64
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build ${{ env.BUILD_PATH }} -j 4
|
run: cmake --build ${{ env.BUILD_PATH }} -j 4
|
||||||
@ -107,5 +113,30 @@ jobs:
|
|||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macOS amd64 Build
|
name: macOS_build_amd64
|
||||||
|
path: ${{ env.BUILD_PATH }}/ncmdump
|
||||||
|
|
||||||
|
build_on_macos_arm64:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
brew install git cmake
|
||||||
|
|
||||||
|
- name: Configure build
|
||||||
|
run: cmake -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -B ${{ env.BUILD_PATH }} -DCMAKE_OSX_ARCHITECTURES=arm64
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build ${{ env.BUILD_PATH }} -j 4
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: macOS_build_arm64
|
||||||
path: ${{ env.BUILD_PATH }}/ncmdump
|
path: ${{ env.BUILD_PATH }}/ncmdump
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,3 +5,5 @@ ncmdump
|
|||||||
.idea
|
.idea
|
||||||
|
|
||||||
build
|
build
|
||||||
|
|
||||||
|
cmake-build-*
|
@ -14,38 +14,73 @@ if(MSVC)
|
|||||||
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/execution-charset:utf-8>")
|
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/execution-charset:utf-8>")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
FILE(GLOB HEADERS ./src/include/*.h)
|
FILE(GLOB COMMON_HEADERS src/include/*.h)
|
||||||
FILE(GLOB COMMON_SOURCES ./src/*.cpp ./src/utils/*.cpp)
|
FILE(GLOB COMMON_SOURCES src/ncmcrypt.cpp src/utils/*.cpp)
|
||||||
FILE(GLOB WIN_SOURCES ./src/platform/win32_init.cpp)
|
FILE(GLOB EXECUTABLE_SOURCES src/main.cpp)
|
||||||
|
FILE(GLOB LIBRARY_HEADERS src/lib/libncmdump.h)
|
||||||
|
FILE(GLOB LIBRARY_SOURCES src/lib/*.cpp)
|
||||||
|
FILE(GLOB WIN_SOURCES src/platform/win32_init.cpp)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(SOURCES ${COMMON_SOURCES} ${WIN_SOURCES})
|
add_executable(ncmdump_exec
|
||||||
else()
|
${COMMON_HEADERS}
|
||||||
set(SOURCES ${COMMON_SOURCES})
|
${COMMON_SOURCES}
|
||||||
endif()
|
${EXECUTABLE_SOURCES}
|
||||||
add_executable(ncmdump
|
${WIN_SOURCES}
|
||||||
${HEADERS}
|
)
|
||||||
${SOURCES}
|
add_library(ncmdump_lib SHARED
|
||||||
)
|
${COMMON_HEADERS}
|
||||||
|
${COMMON_SOURCES}
|
||||||
|
${LIBRARY_HEADERS}
|
||||||
|
${LIBRARY_SOURCES}
|
||||||
|
${WIN_SOURCES}
|
||||||
|
)
|
||||||
|
|
||||||
target_include_directories(ncmdump PRIVATE src/include)
|
set_target_properties(ncmdump_lib PROPERTIES OUTPUT_NAME "libncmdump")
|
||||||
|
|
||||||
target_link_libraries(ncmdump tag)
|
target_include_directories(ncmdump_lib PRIVATE src/include)
|
||||||
target_include_directories(ncmdump PRIVATE taglib)
|
|
||||||
target_include_directories(ncmdump PRIVATE taglib/taglib)
|
target_link_libraries(ncmdump_lib tag)
|
||||||
target_include_directories(ncmdump PRIVATE taglib/taglib/toolkit)
|
target_include_directories(ncmdump_lib PRIVATE taglib)
|
||||||
target_include_directories(ncmdump PRIVATE taglib/taglib/mpeg/id3v2)
|
target_include_directories(ncmdump_lib PRIVATE taglib/taglib)
|
||||||
|
target_include_directories(ncmdump_lib PRIVATE taglib/taglib/toolkit)
|
||||||
|
target_include_directories(ncmdump_lib PRIVATE taglib/taglib/mpeg/id3v2)
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
if(CMAKE_COMPILER_IS_GNUCXX)
|
if(CMAKE_COMPILER_IS_GNUCXX)
|
||||||
target_link_options(ncmdump PRIVATE -static)
|
# static link when using MinGW
|
||||||
|
target_link_options(ncmdump_exec PRIVATE -static)
|
||||||
|
target_link_options(ncmdump_lib PRIVATE -static)
|
||||||
endif()
|
endif()
|
||||||
|
else()
|
||||||
|
add_executable(ncmdump_exec
|
||||||
|
${COMMON_HEADERS}
|
||||||
|
${COMMON_SOURCES}
|
||||||
|
${EXECUTABLE_SOURCES}
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# all executable target
|
||||||
|
set_target_properties(ncmdump_exec PROPERTIES OUTPUT_NAME "ncmdump")
|
||||||
|
target_include_directories(ncmdump_exec PRIVATE src/include)
|
||||||
|
|
||||||
|
target_link_libraries(ncmdump_exec tag)
|
||||||
|
target_include_directories(ncmdump_exec PRIVATE taglib)
|
||||||
|
target_include_directories(ncmdump_exec PRIVATE taglib/taglib)
|
||||||
|
target_include_directories(ncmdump_exec PRIVATE taglib/taglib/toolkit)
|
||||||
|
target_include_directories(ncmdump_exec PRIVATE taglib/taglib/mpeg/id3v2)
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
install(TARGETS ncmdump
|
|
||||||
|
if(WIN32)
|
||||||
|
install(TARGETS ncmdump_exec ncmdump_lib
|
||||||
BUNDLE DESTINATION .
|
BUNDLE DESTINATION .
|
||||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
)
|
)
|
||||||
|
else()
|
||||||
|
install(TARGETS ncmdump_exec
|
||||||
|
BUNDLE DESTINATION .
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
62
README.md
62
README.md
@ -6,8 +6,9 @@
|
|||||||
|
|
||||||
该版本为最早的 C++ 版本,也是作者开发的市面上第一个支持 ncm 转换的程序
|
该版本为最早的 C++ 版本,也是作者开发的市面上第一个支持 ncm 转换的程序
|
||||||
|
|
||||||
源码复刻自 anonymous5l/ncmdump,感谢前辈的付出!
|
源码复刻自 anonymous5l/ncmdump,感谢前辈的付出!做了 Windows 下的移植,修复了一些编译问题
|
||||||
做了 Windows 下的移植,修复了一些编译问题
|
|
||||||
|
1.3.0 版本更新说明:因为之前有小伙伴反馈无法解密带有特殊字符的文件名,例如中文、日文、韩文或者是表情符号等,在1.3.0以及之后的版本彻底修复了这个问题,所有的 UTF-8 字符都可以正常解密。并且还自带了 dll 的构建,可以供其他应用程序(C#、Python、Java等)调用。
|
||||||
|
|
||||||
## 传送门
|
## 传送门
|
||||||
|
|
||||||
@ -15,7 +16,13 @@
|
|||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
|
|
||||||
你可以使用 Homebrew 来安装 ncmdump
|
注意:自网易云音乐 3.0 版本开始,下载的 ncm 文件均不内置歌曲专辑的封面图片,需要从网络获取,介于在一个小工具中嵌入庞大网络库的非必要性,可以移步我的另一个仓库(<https://git.taurusxin.com/taurusxin/ncmdump-go>),下载完全使用 Golang 重写的新版本,支持从网络自动获取封面图并嵌入到目标音乐文件。
|
||||||
|
|
||||||
|
### 命令行工具
|
||||||
|
|
||||||
|
**[不推荐]** 你可以使用 Homebrew 来安装 Linux 或者 macOS 下的 ncmdump
|
||||||
|
|
||||||
|
注意:由于本项目依赖的库 taglib 的 2.0 版本尚未发布到 homebrew-core(主要是因为 taglib 2.0 导致其他 brew 包存在版本兼容问题),目前的 cmake 构建链无法在 macOS 上正常使用。根据 brew 的要求,如果依赖库已有官方的 brew 包,构建时必须使用官方仓库中的包,不能从 git 获取。而 taglib 2.0 版本开始才支持 cmake 构建。因此,ncmdump 在 homebrew 上只能发布到 `1.2.1` 版本。建议直接通过二进制方式安装,`1.3.0` 后版本修复了许多 bug,使用体验会更好。
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
brew install ncmdump
|
brew install ncmdump
|
||||||
@ -29,18 +36,49 @@ brew install ncmdump
|
|||||||
ncmdump -h
|
ncmdump -h
|
||||||
```
|
```
|
||||||
|
|
||||||
命令行下输入一个或多个文件
|
使用 `-v` 或 `--version` 参数来打印版本信息
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
ncmdump file1 file2...
|
ncmdump -v
|
||||||
```
|
```
|
||||||
|
|
||||||
你可以使用 `-d` 参数来指定一个文件夹,对文件夹下的所有文件批量处理
|
处理单个或多个文件
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
ncmdump -d folder
|
ncmdump 1.ncm 2.ncm...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
使用 `-d` 参数来指定一个文件夹,对文件夹下的所有以 ncm 为扩展名的文件进行批量处理
|
||||||
|
|
||||||
|
```shell
|
||||||
|
ncmdump -d source_dir
|
||||||
|
```
|
||||||
|
|
||||||
|
使用 `-r` 配合 `-d` 参数来递归处理文件夹下的所有以 ncm 为扩展名的文件
|
||||||
|
|
||||||
|
```shell
|
||||||
|
ncmdump -d source_dir -r
|
||||||
|
```
|
||||||
|
|
||||||
|
使用 `-o` 参数来指定输出目录,将转换后的文件输出到指定目录,该参数支持与 `-r` 参数一起使用
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 处理单个或多个文件并输出到指定目录
|
||||||
|
ncmdump 1.ncm 2.ncm -o output_dir
|
||||||
|
|
||||||
|
# 处理文件夹下的所有以 ncm 为扩展名并输出到指定目录,不包含子文件夹
|
||||||
|
ncmdump -d source_dir -o output_dir
|
||||||
|
|
||||||
|
# 递归处理文件夹并输出到指定目录,并保留目录结构
|
||||||
|
ncmdump -d source_dir -o output_dir -r
|
||||||
|
```
|
||||||
|
|
||||||
|
### 动态库
|
||||||
|
|
||||||
|
或者,如果你想利用此项目进行二次开发,例如在你的 C#、Python、Java 等项目中调用,你可以使用 `libncmdump` 动态库,具体使用方法见仓库的 `example` 文件夹
|
||||||
|
|
||||||
|
请注意!如果你在 Windows 下开发,传递到库构造函数的文件名编码**必须为 UTF-8 编码**,否则会抛出运行时错误。
|
||||||
|
|
||||||
## 编译项目
|
## 编译项目
|
||||||
|
|
||||||
克隆本仓库
|
克隆本仓库
|
||||||
@ -67,6 +105,10 @@ cmake -G "Visual Studio 17 2022" -A x64 -B build
|
|||||||
|
|
||||||
# Linux / macOS
|
# Linux / macOS
|
||||||
cmake -DCMAKE_BUILD_TYPE=Release -B build
|
cmake -DCMAKE_BUILD_TYPE=Release -B build
|
||||||
|
|
||||||
|
# 如果需要在 macOS 下交叉编译,可以指定 `CMAKE_OSX_ARCHITECTURES` 变量来指明目标系统架构
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -B build # arm64
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=x86_64 -B build # Intel-based
|
||||||
```
|
```
|
||||||
|
|
||||||
编译项目
|
编译项目
|
||||||
@ -78,3 +120,9 @@ cmake --build build -j 8 --config Release
|
|||||||
# Windows MinGW / Linux / macOS
|
# Windows MinGW / Linux / macOS
|
||||||
cmake --build build -j 8
|
cmake --build build -j 8
|
||||||
```
|
```
|
||||||
|
|
||||||
|
你可以在 `build` 文件夹下找到编译好的二进制文件,以及一个可供其它项目使用的动态库(Windows Only),使用方法见仓库的 `example` 文件夹
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
[](https://star-history.com/#taurusxin/ncmdump&Date)
|
||||||
|
74
example/csharp/NeteaseCrypt.cs
Normal file
74
example/csharp/NeteaseCrypt.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace libncmdump_demo_cli
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// NeteaseCrypt C# Wrapper
|
||||||
|
/// </summary>
|
||||||
|
class NeteaseCrypt
|
||||||
|
{
|
||||||
|
const string DLL_PATH = "libncmdump.dll";
|
||||||
|
|
||||||
|
[DllImport(DLL_PATH, CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
private static extern IntPtr CreateNeteaseCrypt(IntPtr path);
|
||||||
|
|
||||||
|
[DllImport(DLL_PATH, CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
private static extern int Dump(IntPtr NeteaseCrypt, IntPtr outputPath);
|
||||||
|
|
||||||
|
[DllImport(DLL_PATH, CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
private static extern void FixMetadata(IntPtr NeteaseCrypt);
|
||||||
|
|
||||||
|
[DllImport(DLL_PATH, CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
private static extern void DestroyNeteaseCrypt(IntPtr NeteaseCrypt);
|
||||||
|
|
||||||
|
private IntPtr NeteaseCryptClass = IntPtr.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 NeteaseCrypt 类的实例。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="FileName">网易云音乐 ncm 加密文件路径</param>
|
||||||
|
public NeteaseCrypt(string FileName)
|
||||||
|
{
|
||||||
|
byte[] bytes = Encoding.UTF8.GetBytes(FileName);
|
||||||
|
|
||||||
|
IntPtr inputPtr = Marshal.AllocHGlobal(bytes.Length + 1);
|
||||||
|
Marshal.Copy(bytes, 0, inputPtr, bytes.Length);
|
||||||
|
Marshal.WriteByte(inputPtr, bytes.Length, 0);
|
||||||
|
|
||||||
|
NeteaseCryptClass = CreateNeteaseCrypt(inputPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动转换过程。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="OutputPath">指定一个路径输出,如果为空,则输出到原路径</param>
|
||||||
|
/// <returns>返回一个整数,指示转储过程的结果。如果成功,返回0;如果失败,返回1。</returns>
|
||||||
|
public int Dump(string OutputPath)
|
||||||
|
{
|
||||||
|
byte[] bytes = Encoding.UTF8.GetBytes(OutputPath);
|
||||||
|
|
||||||
|
IntPtr outputPtr = Marshal.AllocHGlobal(bytes.Length + 1);
|
||||||
|
Marshal.Copy(bytes, 0, outputPtr, bytes.Length);
|
||||||
|
Marshal.WriteByte(outputPtr, bytes.Length, 0);
|
||||||
|
|
||||||
|
return Dump(NeteaseCryptClass, outputPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 修复音乐文件元数据。
|
||||||
|
/// </summary>
|
||||||
|
public void FixMetadata()
|
||||||
|
{
|
||||||
|
FixMetadata(NeteaseCryptClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 销毁 NeteaseCrypt 类的实例。
|
||||||
|
/// </summary>
|
||||||
|
public void Destroy()
|
||||||
|
{
|
||||||
|
DestroyNeteaseCrypt(NeteaseCryptClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
example/csharp/Program.cs
Normal file
26
example/csharp/Program.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace libncmdump_demo_cli
|
||||||
|
{
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
// 文件名
|
||||||
|
string filePath = "test.ncm";
|
||||||
|
|
||||||
|
// 创建 NeteaseCrypt 类的实例
|
||||||
|
NeteaseCrypt neteaseCrypt = new NeteaseCrypt(filePath);
|
||||||
|
|
||||||
|
// 启动转换过程
|
||||||
|
int result = neteaseCrypt.Dump(""); // 为空则输出到源
|
||||||
|
|
||||||
|
// 修复元数据
|
||||||
|
neteaseCrypt.FixMetadata();
|
||||||
|
|
||||||
|
// [务必]销毁 NeteaseCrypt 类的实例
|
||||||
|
neteaseCrypt.Destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2909
src/include/cxxopts.hpp
Normal file
2909
src/include/cxxopts.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -47,7 +47,7 @@ private:
|
|||||||
NcmFormat mFormat;
|
NcmFormat mFormat;
|
||||||
std::string mImageData;
|
std::string mImageData;
|
||||||
std::ifstream mFile;
|
std::ifstream mFile;
|
||||||
unsigned char mKeyBox[256];
|
unsigned char mKeyBox[256]{};
|
||||||
NeteaseMusicMetadata* mMetaData;
|
NeteaseMusicMetadata* mMetaData;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -66,6 +66,6 @@ public:
|
|||||||
~NeteaseCrypt();
|
~NeteaseCrypt();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void Dump();
|
void Dump(std::string const&);
|
||||||
void FixMetadata();
|
void FixMetadata();
|
||||||
};
|
};
|
3
src/include/version.h
Normal file
3
src/include/version.h
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#define VERSION_MAJOR 1
|
||||||
|
#define VERSION_MINOR 5
|
||||||
|
#define VERSION_PATCH 0
|
31
src/lib/libncmdump.cpp
Normal file
31
src/lib/libncmdump.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#include "libncmdump.h"
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
API NeteaseCrypt* CreateNeteaseCrypt(const char* path) {
|
||||||
|
fs::path fPath = fs::u8path(path);
|
||||||
|
return new NeteaseCrypt(fPath.u8string());
|
||||||
|
}
|
||||||
|
|
||||||
|
API int Dump(NeteaseCrypt* neteaseCrypt, const char* outputPath) {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
neteaseCrypt->Dump(outputPath);
|
||||||
|
}
|
||||||
|
catch (const std::invalid_argument& e)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
API void FixMetadata(NeteaseCrypt* neteaseCrypt) {
|
||||||
|
neteaseCrypt->FixMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
API void DestroyNeteaseCrypt(NeteaseCrypt* neteaseCrypt) {
|
||||||
|
delete neteaseCrypt;
|
||||||
|
}
|
||||||
|
}
|
14
src/lib/libncmdump.h
Normal file
14
src/lib/libncmdump.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#include "ncmcrypt.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define API __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define API
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
API NeteaseCrypt* CreateNeteaseCrypt(const char* path);
|
||||||
|
API int Dump(NeteaseCrypt* neteaseCrypt, const char* outputPath);
|
||||||
|
API void FixMetadata(NeteaseCrypt* neteaseCrypt);
|
||||||
|
API void DestroyNeteaseCrypt(NeteaseCrypt* neteaseCrypt);
|
||||||
|
}
|
212
src/main.cpp
212
src/main.cpp
@ -10,115 +10,207 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "color.h"
|
#include "color.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "cxxopts.hpp"
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
void displayHelp()
|
void processFile(const fs::path &filePath, const fs::path &outputFolder)
|
||||||
{
|
|
||||||
std::cout << "Usage: ncmdump [-d] [-h] file1 file2 ..." << std::endl;
|
|
||||||
std::cout << "Options:" << std::endl;
|
|
||||||
std::cout << " -d Process files in a folder (requires folder path)" << std::endl;
|
|
||||||
std::cout << " -h, --help Display this help message" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void processFile(const fs::path &filePath)
|
|
||||||
{
|
{
|
||||||
if (fs::exists(filePath) == false)
|
if (fs::exists(filePath) == false)
|
||||||
{
|
{
|
||||||
std::cerr << BOLDRED << "Error: " << RESET << "file '" << filePath.u8string() << "' does not exist." << std::endl;
|
std::cerr << BOLDRED << "[Error] " << RESET << "file '" << filePath.u8string() << "' does not exist." << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip if not ending with ".ncm"
|
||||||
|
if (!filePath.has_extension() || filePath.extension().u8string() != ".ncm")
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NeteaseCrypt crypt(filePath.u8string());
|
NeteaseCrypt crypt(filePath.u8string());
|
||||||
crypt.Dump();
|
crypt.Dump(outputFolder.u8string());
|
||||||
crypt.FixMetadata();
|
crypt.FixMetadata();
|
||||||
|
|
||||||
std::cout << BOLDGREEN << "Done: " << RESET << "'" << crypt.dumpFilepath().u8string() << "'" << std::endl;
|
std::cout << BOLDGREEN << "[Done] " << RESET << "'" << filePath.u8string() << "' -> '" << crypt.dumpFilepath().u8string() << "'" << std::endl;
|
||||||
}
|
}
|
||||||
catch (const std::invalid_argument &e)
|
catch (const std::invalid_argument &e)
|
||||||
{
|
{
|
||||||
std::cerr << BOLDRED << "Exception: " << RESET << RED << e.what() << RESET << " '" << filePath.u8string() << "'" << std::endl;
|
std::cerr << BOLDRED << "[Exception] " << RESET << RED << e.what() << RESET << " '" << filePath.u8string() << "'" << std::endl;
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
std::cerr << BOLDRED << "Unexpected exception while processing file: " << RESET << filePath.u8string() << std::endl;
|
std::cerr << BOLDRED << "[Error] Unexpected exception while processing file: " << RESET << filePath.u8string() << std::endl;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void processFilesInFolder(const fs::path &folderPath)
|
|
||||||
{
|
|
||||||
for (const auto &entry : fs::directory_iterator(folderPath))
|
|
||||||
{
|
|
||||||
if (fs::is_regular_file(entry.status()))
|
|
||||||
{
|
|
||||||
processFile(entry.path());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
win32_utf8argv(&argc, &argv);
|
win32_utf8argv(&argc, &argv); // Convert command line arguments to UTF-8 under Windows
|
||||||
#endif
|
#endif
|
||||||
if (argc <= 1)
|
|
||||||
{
|
cxxopts::Options options("ncmdump");
|
||||||
displayHelp();
|
|
||||||
|
options.add_options()
|
||||||
|
("h,help", "Print usage")
|
||||||
|
("d,directory", "Process files in a folder (requires folder path)", cxxopts::value<std::string>())
|
||||||
|
("r,recursive", "Process files recursively (requires -d option)", cxxopts::value<bool>()->default_value("false"))
|
||||||
|
("o,output", "Output folder (default: original file folder)", cxxopts::value<std::string>())
|
||||||
|
("v,version", "Print version information", cxxopts::value<bool>()->default_value("false"))
|
||||||
|
("filenames", "Input files", cxxopts::value<std::vector<std::string>>());
|
||||||
|
|
||||||
|
options.positional_help("<files>");
|
||||||
|
|
||||||
|
options.parse_positional({"filenames"});
|
||||||
|
|
||||||
|
options.allow_unrecognised_options();
|
||||||
|
|
||||||
|
// Parse options the usual way
|
||||||
|
cxxopts::ParseResult result;
|
||||||
|
try {
|
||||||
|
result = options.parse(argc, argv);
|
||||||
|
} catch(cxxopts::exceptions::parsing const& e) {
|
||||||
|
std::cout << options.help() << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<fs::path> files;
|
// print usage message if unrecognised options are present
|
||||||
bool processFolders = false;
|
if (result.unmatched().size() > 0)
|
||||||
|
|
||||||
bool folderProvided = false;
|
|
||||||
|
|
||||||
#define COMPARE_STR(s1, s2) (strcmp(s1, s2) == 0)
|
|
||||||
#define HELP_SHORT "-h"
|
|
||||||
#define HELP_LONG "--help"
|
|
||||||
#define PROCESS_FOLDER "-d"
|
|
||||||
for (int i = 1; i < argc; ++i)
|
|
||||||
{
|
{
|
||||||
if (COMPARE_STR(argv[i], HELP_SHORT) || COMPARE_STR(argv[i], HELP_LONG))
|
std::cout << options.help() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// display help message
|
||||||
|
if (result.count("help"))
|
||||||
{
|
{
|
||||||
displayHelp();
|
std::cout << options.help() << std::endl;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
else if (COMPARE_STR(argv[i], PROCESS_FOLDER))
|
|
||||||
|
// display version information
|
||||||
|
if (result.count("version"))
|
||||||
{
|
{
|
||||||
processFolders = true;
|
std::cout << "ncmdump version " << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_PATCH << std::endl;
|
||||||
if (i + 1 < argc && argv[i + 1][0] != '-')
|
return 0;
|
||||||
{
|
|
||||||
folderProvided = true;
|
|
||||||
processFilesInFolder(argv[i + 1]);
|
|
||||||
// Skip the folder name
|
|
||||||
++i;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// no input files or folder provided
|
||||||
|
if (result.count("directory") == 0 && result.count("filenames") == 0)
|
||||||
{
|
{
|
||||||
std::cerr << "Error: -d option requires a folder path." << std::endl;
|
std::cout << options.help() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only -r option without -d option
|
||||||
|
if (result.count("recursive") && result.count("directory") == 0)
|
||||||
|
{
|
||||||
|
std::cerr << BOLDRED << "[Error] " << RESET << "-r option requires -d option." << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check output folder
|
||||||
|
fs::path outputDir = fs::u8path("");
|
||||||
|
bool outputDirSpecified = result.count("output") > 0;
|
||||||
|
|
||||||
|
if (outputDirSpecified)
|
||||||
|
{
|
||||||
|
outputDir = fs::u8path(result["output"].as<std::string>());
|
||||||
|
if (fs::exists(outputDir))
|
||||||
|
{
|
||||||
|
if (!fs::is_directory(outputDir))
|
||||||
|
{
|
||||||
|
std::cerr << BOLDRED << "[Error] " << RESET << "'" << outputDir.u8string() << "' is not a valid directory." << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
fs::create_directories(outputDir);
|
||||||
{
|
|
||||||
fs::path path = fs::u8path(argv[i]);
|
|
||||||
files.push_back(path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &file : files)
|
// process files in a folder
|
||||||
|
if (result.count("directory"))
|
||||||
{
|
{
|
||||||
if (processFolders && fs::is_directory(file))
|
fs::path sourceDir = fs::u8path(result["directory"].as<std::string>());
|
||||||
|
if (!fs::is_directory(sourceDir))
|
||||||
{
|
{
|
||||||
processFilesInFolder(file);
|
std::cerr << BOLDRED << "[Error] " << RESET << "'" << sourceDir.u8string() << "' is not a valid directory." << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool recursive = result["recursive"].as<bool>();
|
||||||
|
|
||||||
|
if (recursive)
|
||||||
|
{
|
||||||
|
// 递归遍历源目录
|
||||||
|
for (const auto &entry : fs::recursive_directory_iterator(sourceDir))
|
||||||
|
{
|
||||||
|
// 没有指定输出目录,则使用源目录作为输出目录
|
||||||
|
if (!outputDirSpecified)
|
||||||
|
{
|
||||||
|
outputDir = sourceDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获得递归遍历的相对路径
|
||||||
|
const auto &path = fs::u8path(entry.path().u8string());
|
||||||
|
auto relativePath = fs::relative(path, sourceDir);
|
||||||
|
fs::path destinationPath = outputDir / relativePath;
|
||||||
|
|
||||||
|
if (fs::is_regular_file(path))
|
||||||
|
{
|
||||||
|
// 确保输出文件的目录存在
|
||||||
|
fs::create_directories(destinationPath.parent_path());
|
||||||
|
|
||||||
|
// 处理文件
|
||||||
|
processFile(path, destinationPath.parent_path());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
processFile(file);
|
for (const auto &entry : fs::directory_iterator(sourceDir))
|
||||||
|
{
|
||||||
|
const auto &path = fs::u8path(entry.path().u8string());
|
||||||
|
if (entry.is_regular_file())
|
||||||
|
{
|
||||||
|
if (outputDirSpecified)
|
||||||
|
{
|
||||||
|
processFile(path, outputDir);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processFile(path, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// process individual files
|
||||||
|
if (result.count("filenames"))
|
||||||
|
{
|
||||||
|
for (const auto &filePath : result["filenames"].as<std::vector<std::string>>())
|
||||||
|
{
|
||||||
|
fs::path filePathU8 = fs::u8path(filePath);
|
||||||
|
if (!fs::is_regular_file(filePathU8))
|
||||||
|
{
|
||||||
|
std::cerr << BOLDRED << "[Error] " << RESET << "'" << filePathU8.u8string() << "' is not a valid file." << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputDirSpecified)
|
||||||
|
{
|
||||||
|
processFile(filePathU8, outputDir);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processFile(filePathU8, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#include "aes.h"
|
#include "aes.h"
|
||||||
#include "base64.h"
|
#include "base64.h"
|
||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
|
#include "color.h"
|
||||||
|
|
||||||
#define TAGLIB_STATIC
|
#define TAGLIB_STATIC
|
||||||
#include "taglib/toolkit/tfile.h"
|
#include "taglib/toolkit/tfile.h"
|
||||||
@ -15,6 +16,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
#pragma warning(disable:4267)
|
||||||
|
#pragma warning(disable:4244)
|
||||||
|
|
||||||
const unsigned char NeteaseCrypt::sCoreKey[17] = {0x68, 0x7A, 0x48, 0x52, 0x41, 0x6D, 0x73, 0x6F, 0x35, 0x6B, 0x49, 0x6E, 0x62, 0x61, 0x78, 0x57, 0};
|
const unsigned char NeteaseCrypt::sCoreKey[17] = {0x68, 0x7A, 0x48, 0x52, 0x41, 0x6D, 0x73, 0x6F, 0x35, 0x6B, 0x49, 0x6E, 0x62, 0x61, 0x78, 0x57, 0};
|
||||||
const unsigned char NeteaseCrypt::sModifyKey[17] = {0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21, 0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28, 0};
|
const unsigned char NeteaseCrypt::sModifyKey[17] = {0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21, 0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28, 0};
|
||||||
|
|
||||||
@ -220,7 +224,7 @@ void NeteaseCrypt::FixMetadata()
|
|||||||
audioFile = new TagLib::MPEG::File(mDumpFilepath.c_str());
|
audioFile = new TagLib::MPEG::File(mDumpFilepath.c_str());
|
||||||
tag = dynamic_cast<TagLib::MPEG::File *>(audioFile)->ID3v2Tag(true);
|
tag = dynamic_cast<TagLib::MPEG::File *>(audioFile)->ID3v2Tag(true);
|
||||||
|
|
||||||
if (mImageData.length() > 0)
|
if (!mImageData.empty())
|
||||||
{
|
{
|
||||||
TagLib::ID3v2::AttachedPictureFrame *frame = new TagLib::ID3v2::AttachedPictureFrame;
|
TagLib::ID3v2::AttachedPictureFrame *frame = new TagLib::ID3v2::AttachedPictureFrame;
|
||||||
|
|
||||||
@ -235,7 +239,7 @@ void NeteaseCrypt::FixMetadata()
|
|||||||
audioFile = new TagLib::FLAC::File(mDumpFilepath.c_str());
|
audioFile = new TagLib::FLAC::File(mDumpFilepath.c_str());
|
||||||
tag = audioFile->tag();
|
tag = audioFile->tag();
|
||||||
|
|
||||||
if (mImageData.length() > 0)
|
if (!mImageData.empty())
|
||||||
{
|
{
|
||||||
TagLib::FLAC::Picture *cover = new TagLib::FLAC::Picture;
|
TagLib::FLAC::Picture *cover = new TagLib::FLAC::Picture;
|
||||||
cover->setMimeType(mimeType(mImageData));
|
cover->setMimeType(mimeType(mImageData));
|
||||||
@ -253,14 +257,20 @@ void NeteaseCrypt::FixMetadata()
|
|||||||
tag->setAlbum(TagLib::String(mMetaData->album(), TagLib::String::UTF8));
|
tag->setAlbum(TagLib::String(mMetaData->album(), TagLib::String::UTF8));
|
||||||
}
|
}
|
||||||
|
|
||||||
tag->setComment(TagLib::String("Create by netease copyright protected dump tool. author 5L", TagLib::String::UTF8));
|
// tag->setComment(TagLib::String("Create by taurusxin/ncmdump.", TagLib::String::UTF8));
|
||||||
|
|
||||||
audioFile->save();
|
audioFile->save();
|
||||||
|
audioFile->~File();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeteaseCrypt::Dump()
|
void NeteaseCrypt::Dump(std::string const &outputDir = "")
|
||||||
{
|
{
|
||||||
|
if (outputDir.empty())
|
||||||
|
{
|
||||||
mDumpFilepath = std::filesystem::u8path(mFilepath);
|
mDumpFilepath = std::filesystem::u8path(mFilepath);
|
||||||
|
} else {
|
||||||
|
mDumpFilepath = std::filesystem::u8path(outputDir) / std::filesystem::u8path(mFilepath).filename();
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> buffer(0x8000);
|
std::vector<unsigned char> buffer(0x8000);
|
||||||
|
|
||||||
@ -291,7 +301,7 @@ void NeteaseCrypt::Dump()
|
|||||||
mFormat = NeteaseCrypt::FLAC;
|
mFormat = NeteaseCrypt::FLAC;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.open(mDumpFilepath, output.out | output.binary);
|
output.open(mDumpFilepath, std::ofstream::out | std::ofstream::binary);
|
||||||
}
|
}
|
||||||
|
|
||||||
output.write((char *)buffer.data(), n);
|
output.write((char *)buffer.data(), n);
|
||||||
@ -357,7 +367,7 @@ NeteaseCrypt::NeteaseCrypt(std::string const &path)
|
|||||||
|
|
||||||
if (n <= 0)
|
if (n <= 0)
|
||||||
{
|
{
|
||||||
std::cout << "[Warn] " << path << " missing metadata infomation can't fix some infomation!" << std::endl;
|
std::cout << BOLDYELLOW << "[Warn] " << RESET << "'" << path << "' missing metadata infomation can't fix some infomation!" << std::endl;
|
||||||
|
|
||||||
mMetaData = NULL;
|
mMetaData = NULL;
|
||||||
}
|
}
|
||||||
@ -390,23 +400,24 @@ NeteaseCrypt::NeteaseCrypt(std::string const &path)
|
|||||||
mMetaData = new NeteaseMusicMetadata(cJSON_Parse(modifyDecryptData.c_str()));
|
mMetaData = new NeteaseMusicMetadata(cJSON_Parse(modifyDecryptData.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip crc32 & unuse charset
|
// skip crc32 & image version
|
||||||
if (!mFile.seekg(9, mFile.cur))
|
if (!mFile.seekg(5, mFile.cur))
|
||||||
{
|
{
|
||||||
throw std::invalid_argument("can't seek file");
|
throw std::invalid_argument("can't seek file");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t cover_frame_len{0};
|
||||||
|
read(reinterpret_cast<char *>(&cover_frame_len), 4);
|
||||||
read(reinterpret_cast<char *>(&n), sizeof(n));
|
read(reinterpret_cast<char *>(&n), sizeof(n));
|
||||||
|
|
||||||
if (n > 0)
|
if (n > 0)
|
||||||
{
|
{
|
||||||
char *imageData = (char *)malloc(n);
|
mImageData = std::string(n, '\0');
|
||||||
read(imageData, n);
|
read(&mImageData[0], n);
|
||||||
|
|
||||||
mImageData = std::string(imageData, n);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::cout << "[Warn] " << path << " missing album can't fix album image!" << std::endl;
|
std::cout << BOLDYELLOW << "[Warn] " << RESET << "'" << path << "' missing album can't fix album image!" << std::endl;
|
||||||
}
|
}
|
||||||
|
mFile.seekg(cover_frame_len - n, mFile.cur);
|
||||||
}
|
}
|
||||||
|
2
taglib
2
taglib
@ -1 +1 @@
|
|||||||
Subproject commit 0896fb90920c125e55248360d271d1a1674e2a4d
|
Subproject commit e3de03501ff66221d1f1f971022b248d5b38ba06
|
BIN
test/expect.bin
BIN
test/expect.bin
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user