Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
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
|
|||
|
aa7ae6128d
|
|||
|
|
ae08d4475c |
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,31 +1,35 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report to help us improve
|
||||
title: "[Bug] Summarize the bug here"
|
||||
name: "[请按照此模板填写] 报告 Bug"
|
||||
about: "创建一个 Bug 报告,不按照模板的 Issue 会被关闭。"
|
||||
title: "[Bug] 总结你的 Bug 报告"
|
||||
labels: bug
|
||||
assignees: taurusxin
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
**Bug 描述**
|
||||
清晰地描述一下 Bug 的大致问题。
|
||||
|
||||
**To Reproduce**
|
||||
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.
|
||||
复现此 Bug 的方法
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
1. 打开 '...'
|
||||
2. 点击 '....'
|
||||
3. 发生报错
|
||||
|
||||
**预期行为**
|
||||
|
||||
解释一下原本应该出现的结果。
|
||||
|
||||
**屏幕截图**
|
||||
|
||||
如果可以,屏幕截图可以更好地阐述你的问题。
|
||||
|
||||
**环境**
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows 11]
|
||||
- 软件版本: [e.g. 1.3.2]
|
||||
|
||||
**附加内容**
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
添加更多其他内容以帮助开发者更好地了解这个 Bug。
|
||||
|
||||
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.
|
||||
8
.github/workflows/autobuild.yml
vendored
8
.github/workflows/autobuild.yml
vendored
@@ -53,12 +53,18 @@ jobs:
|
||||
- name: Build
|
||||
run: cmake --build ${{ env.BUILD_PATH }} -j 4 --config ${{ env.BUILD_TYPE }}
|
||||
|
||||
- name: Upload artifact
|
||||
- name: Upload artifact executable
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Windows amd64 Build - MSVC
|
||||
path: ${{ env.BUILD_PATH }}/${{ env.BUILD_TYPE }}/ncmdump.exe
|
||||
|
||||
- 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:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
@@ -14,34 +14,73 @@ if(MSVC)
|
||||
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/execution-charset:utf-8>")
|
||||
endif()
|
||||
|
||||
FILE(GLOB HEADERS ./*.h)
|
||||
FILE(GLOB COMMON_SOURCES cJSON.cpp aes.cpp main.cpp ncmcrypt.cpp)
|
||||
FILE(GLOB WIN_SOURCES win32_init.cpp)
|
||||
FILE(GLOB COMMON_HEADERS src/include/*.h)
|
||||
FILE(GLOB COMMON_SOURCES src/ncmcrypt.cpp src/utils/*.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)
|
||||
set(SOURCES ${COMMON_SOURCES} ${WIN_SOURCES})
|
||||
else()
|
||||
set(SOURCES ${COMMON_SOURCES})
|
||||
endif()
|
||||
add_executable(ncmdump
|
||||
${HEADERS}
|
||||
${SOURCES}
|
||||
add_executable(ncmdump_exec
|
||||
${COMMON_HEADERS}
|
||||
${COMMON_SOURCES}
|
||||
${EXECUTABLE_SOURCES}
|
||||
${WIN_SOURCES}
|
||||
)
|
||||
add_library(ncmdump_lib SHARED
|
||||
${COMMON_HEADERS}
|
||||
${COMMON_SOURCES}
|
||||
${LIBRARY_HEADERS}
|
||||
${LIBRARY_SOURCES}
|
||||
${WIN_SOURCES}
|
||||
)
|
||||
target_link_libraries(ncmdump tag)
|
||||
target_include_directories(ncmdump PRIVATE taglib)
|
||||
target_include_directories(ncmdump PRIVATE taglib/taglib)
|
||||
target_include_directories(ncmdump PRIVATE taglib/taglib/toolkit)
|
||||
target_include_directories(ncmdump PRIVATE taglib/taglib/mpeg/id3v2)
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties(ncmdump_lib PROPERTIES OUTPUT_NAME "libncmdump")
|
||||
|
||||
target_include_directories(ncmdump_lib PRIVATE src/include)
|
||||
|
||||
target_link_libraries(ncmdump_lib tag)
|
||||
target_include_directories(ncmdump_lib PRIVATE taglib)
|
||||
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(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()
|
||||
else()
|
||||
add_executable(ncmdump_exec
|
||||
${COMMON_HEADERS}
|
||||
${COMMON_SOURCES}
|
||||
${EXECUTABLE_SOURCES}
|
||||
)
|
||||
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)
|
||||
install(TARGETS ncmdump
|
||||
|
||||
if(WIN32)
|
||||
install(TARGETS ncmdump_exec ncmdump_lib
|
||||
BUNDLE DESTINATION .
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
else()
|
||||
install(TARGETS ncmdump_exec
|
||||
BUNDLE DESTINATION .
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
21
README.md
21
README.md
@@ -6,8 +6,9 @@
|
||||
|
||||
该版本为最早的 C++ 版本,也是作者开发的市面上第一个支持 ncm 转换的程序
|
||||
|
||||
源码复刻自 anonymous5l/ncmdump,感谢前辈的付出!
|
||||
做了 Windows 下的移植,修复了一些编译问题
|
||||
源码复刻自 anonymous5l/ncmdump,感谢前辈的付出!做了 Windows 下的移植,修复了一些编译问题
|
||||
|
||||
1.3.0 版本更新说明:因为之前有小伙伴反馈无法解密带有特殊字符的文件名,例如中文、日文、韩文或者是表情符号等,在1.3.0以及之后的版本彻底修复了这个问题,所有的 UTF-8 字符都可以正常解密。并且还自带了 dll 的构建,可以供其他应用程序(C#、Python、Java等)调用。
|
||||
|
||||
## 传送门
|
||||
|
||||
@@ -15,7 +16,9 @@
|
||||
|
||||
## 使用
|
||||
|
||||
你可以使用 Homebrew 来安装 ncmdump
|
||||
### 命令行工具
|
||||
|
||||
你可以使用 Homebrew 来安装 ncmdump 的 cli 版本
|
||||
|
||||
```shell
|
||||
brew install ncmdump
|
||||
@@ -41,6 +44,12 @@ ncmdump file1 file2...
|
||||
ncmdump -d folder
|
||||
```
|
||||
|
||||
### 动态库
|
||||
|
||||
或者,如果你想利用此项目进行二次开发,例如在你的 C#、Python、Java 等项目中调用,你可以使用 `libncmdump` 动态库,具体使用方法见仓库的 `example` 文件夹
|
||||
|
||||
请注意!如果你在 Windows 下开发,传递到库构造函数的文件名编码**必须为 UTF-8 编码**,否则会抛出运行时错误。
|
||||
|
||||
## 编译项目
|
||||
|
||||
克隆本仓库
|
||||
@@ -78,3 +87,9 @@ cmake --build build -j 8 --config Release
|
||||
# Windows MinGW / Linux / macOS
|
||||
cmake --build build -j 8
|
||||
```
|
||||
|
||||
你可以在 `build` 文件夹下找到编译好的二进制文件,以及一个可供其它项目使用的动态库(Windows Only),使用方法见仓库的 `example` 文件夹
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#taurusxin/ncmdump&Date)
|
||||
|
||||
67
example/csharp/NeteaseCrypt.cs
Normal file
67
example/csharp/NeteaseCrypt.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
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);
|
||||
|
||||
[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>
|
||||
/// <returns>返回一个整数,指示转储过程的结果。如果成功,返回0;如果失败,返回1。</returns>
|
||||
public int Dump()
|
||||
{
|
||||
return Dump(NeteaseCryptClass);
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
try
|
||||
{
|
||||
neteaseCrypt->Dump();
|
||||
}
|
||||
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);
|
||||
API void FixMetadata(NeteaseCrypt* neteaseCrypt);
|
||||
API void DestroyNeteaseCrypt(NeteaseCrypt* neteaseCrypt);
|
||||
}
|
||||
@@ -35,7 +35,7 @@ void processFile(const fs::path &filePath)
|
||||
crypt.Dump();
|
||||
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)
|
||||
{
|
||||
@@ -77,7 +77,7 @@ int main(int argc, char **argv)
|
||||
#define COMPARE_STR(s1, s2) (strcmp(s1, s2) == 0)
|
||||
#define HELP_SHORT "-h"
|
||||
#define HELP_LONG "--help"
|
||||
#define FOLDER "-d"
|
||||
#define PROCESS_FOLDER "-d"
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
if (COMPARE_STR(argv[i], HELP_SHORT) || COMPARE_STR(argv[i], HELP_LONG))
|
||||
@@ -85,13 +85,13 @@ int main(int argc, char **argv)
|
||||
displayHelp();
|
||||
return 0;
|
||||
}
|
||||
else if (COMPARE_STR(argv[i], FOLDER))
|
||||
else if (COMPARE_STR(argv[i], PROCESS_FOLDER))
|
||||
{
|
||||
processFolders = true;
|
||||
if (i + 1 < argc && argv[i + 1][0] != '-')
|
||||
{
|
||||
folderProvided = true;
|
||||
processFilesInFolder(argv[i + 1]);
|
||||
processFilesInFolder(fs::u8path(argv[i + 1]));
|
||||
// Skip the folder name
|
||||
++i;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "aes.h"
|
||||
#include "base64.h"
|
||||
#include "cJSON.h"
|
||||
#include "color.h"
|
||||
|
||||
#define TAGLIB_STATIC
|
||||
#include "taglib/toolkit/tfile.h"
|
||||
@@ -94,12 +95,18 @@ NeteaseMusicMetadata::NeteaseMusicMetadata(cJSON *raw)
|
||||
artistLen = cJSON_GetArraySize(swap);
|
||||
|
||||
i = 0;
|
||||
for (i = 0; i < artistLen - 1; i++)
|
||||
for (i = 0; i < artistLen; i++)
|
||||
{
|
||||
auto artist = cJSON_GetArrayItem(swap, i);
|
||||
if (cJSON_GetArraySize(artist) > 0)
|
||||
{
|
||||
if (!mArtist.empty())
|
||||
{
|
||||
mArtist += std::string(cJSON_GetStringValue(cJSON_GetArrayItem(cJSON_GetArrayItem(swap, i), 0)));
|
||||
mArtist += "/";
|
||||
}
|
||||
mArtist += std::string(cJSON_GetStringValue(cJSON_GetArrayItem(cJSON_GetArrayItem(swap, i), 0)));
|
||||
mArtist += std::string(cJSON_GetStringValue(cJSON_GetArrayItem(artist, 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
swap = cJSON_GetObjectItem(raw, "bitrate");
|
||||
@@ -247,9 +254,10 @@ void NeteaseCrypt::FixMetadata()
|
||||
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->~File();
|
||||
}
|
||||
|
||||
void NeteaseCrypt::Dump()
|
||||
@@ -351,7 +359,7 @@ NeteaseCrypt::NeteaseCrypt(std::string const &path)
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -384,23 +392,24 @@ NeteaseCrypt::NeteaseCrypt(std::string const &path)
|
||||
mMetaData = new NeteaseMusicMetadata(cJSON_Parse(modifyDecryptData.c_str()));
|
||||
}
|
||||
|
||||
// skip crc32 & unuse charset
|
||||
if (!mFile.seekg(9, mFile.cur))
|
||||
// skip crc32 & image version
|
||||
if (!mFile.seekg(5, mFile.cur))
|
||||
{
|
||||
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));
|
||||
|
||||
if (n > 0)
|
||||
{
|
||||
char *imageData = (char *)malloc(n);
|
||||
read(imageData, n);
|
||||
|
||||
mImageData = std::string(imageData, n);
|
||||
mImageData = std::string(n, '\0');
|
||||
read(&mImageData[0], n);
|
||||
}
|
||||
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);
|
||||
}
|
||||
BIN
test/expect.bin
BIN
test/expect.bin
Binary file not shown.
Reference in New Issue
Block a user