Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55c824c3ee | |||
| 0502cdc8b8 | |||
| 937a476743 | |||
| 13aa156214 |
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) [year] [fullname]
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
3
Makefile
3
Makefile
@@ -1,5 +1,6 @@
|
|||||||
default:
|
default:
|
||||||
@echo "Please choose a platform: linux, macos, win32"
|
@echo "Please choose a platform: linux, macos-intel, macos-arm64, win32"
|
||||||
|
@echo "Or use 'make clean' to clean up the build files"
|
||||||
|
|
||||||
linux:
|
linux:
|
||||||
g++ main.cpp cJSON.cpp aes.cpp ncmcrypt.cpp -o ncmdump -ltag -std=c++17
|
g++ main.cpp cJSON.cpp aes.cpp ncmcrypt.cpp -o ncmdump -ltag -std=c++17
|
||||||
|
|||||||
17
color.h
Normal file
17
color.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#define RESET "\033[0m"
|
||||||
|
#define BLACK "\033[30m" /* Black */
|
||||||
|
#define RED "\033[31m" /* Red */
|
||||||
|
#define GREEN "\033[32m" /* Green */
|
||||||
|
#define YELLOW "\033[33m" /* Yellow */
|
||||||
|
#define BLUE "\033[34m" /* Blue */
|
||||||
|
#define MAGENTA "\033[35m" /* Magenta */
|
||||||
|
#define CYAN "\033[36m" /* Cyan */
|
||||||
|
#define WHITE "\033[37m" /* White */
|
||||||
|
#define BOLDBLACK "\033[1m\033[30m" /* Bold Black */
|
||||||
|
#define BOLDRED "\033[1m\033[31m" /* Bold Red */
|
||||||
|
#define BOLDGREEN "\033[1m\033[32m" /* Bold Green */
|
||||||
|
#define BOLDYELLOW "\033[1m\033[33m" /* Bold Yellow */
|
||||||
|
#define BOLDBLUE "\033[1m\033[34m" /* Bold Blue */
|
||||||
|
#define BOLDMAGENTA "\033[1m\033[35m" /* Bold Magenta */
|
||||||
|
#define BOLDCYAN "\033[1m\033[36m" /* Bold Cyan */
|
||||||
|
#define BOLDWHITE "\033[1m\033[37m" /* Bold White */
|
||||||
8
main.cpp
8
main.cpp
@@ -9,6 +9,8 @@
|
|||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "color.h"
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
void displayHelp() {
|
void displayHelp() {
|
||||||
@@ -29,11 +31,11 @@ void processFile(const fs::path& filePath) {
|
|||||||
crypt.Dump();
|
crypt.Dump();
|
||||||
crypt.FixMetadata();
|
crypt.FixMetadata();
|
||||||
|
|
||||||
std::cout << "Done: '" << crypt.dumpFilepath().string() << "'" << std::endl;
|
std::cout << BOLDGREEN << "Done: '" << RESET << crypt.dumpFilepath().string() << "'" << std::endl;
|
||||||
} catch (const std::invalid_argument& e) {
|
} catch (const std::invalid_argument& e) {
|
||||||
std::cout << "Exception: '" << filePath << "'" << e.what() << std::endl;
|
std::cerr << BOLDRED << "Exception: " << RESET << RED << e.what() << RESET << " '" << filePath.string() << "'" << std::endl;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
std::cout << "Unexpected exception while processing file: " << filePath << std::endl;
|
std::cerr << BOLDRED << "Unexpected exception while processing file: " << RESET << filePath.string() << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
88
ncmcrypt.cpp
88
ncmcrypt.cpp
@@ -45,13 +45,13 @@ static void aesEcbDecrypt(const unsigned char *key, std::string& src, std::strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void replace(std::string& str, const std::string& from, const std::string& to) {
|
static void replace(std::string& str, const std::string& from, const std::string& to) {
|
||||||
if(from.empty())
|
if(from.empty())
|
||||||
return;
|
return;
|
||||||
size_t start_pos = 0;
|
size_t start_pos = 0;
|
||||||
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
||||||
str.replace(start_pos, from.length(), to);
|
str.replace(start_pos, from.length(), to);
|
||||||
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
|
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NeteaseMusicMetadata::~NeteaseMusicMetadata() {
|
NeteaseMusicMetadata::~NeteaseMusicMetadata() {
|
||||||
@@ -107,12 +107,12 @@ NeteaseMusicMetadata::NeteaseMusicMetadata(cJSON* raw) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool NeteaseCrypt::openFile(std::filesystem::path const& path) {
|
bool NeteaseCrypt::openFile(std::filesystem::path const& path) {
|
||||||
mFile.open(path, std::ios::in | std::ios::binary);
|
mFile.open(path, std::ios::in | std::ios::binary);
|
||||||
if (!mFile.is_open()) {
|
if (!mFile.is_open()) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeteaseCrypt::isNcmFile() {
|
bool NeteaseCrypt::isNcmFile() {
|
||||||
@@ -137,7 +137,7 @@ int NeteaseCrypt::read(char *s, std::streamsize n) {
|
|||||||
int gcount = mFile.gcount();
|
int gcount = mFile.gcount();
|
||||||
|
|
||||||
if (gcount <= 0) {
|
if (gcount <= 0) {
|
||||||
throw std::invalid_argument(" can't read file");
|
throw std::invalid_argument("Can't read file");
|
||||||
}
|
}
|
||||||
|
|
||||||
return gcount;
|
return gcount;
|
||||||
@@ -219,38 +219,16 @@ void NeteaseCrypt::FixMetadata() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NeteaseCrypt::Dump() {
|
void NeteaseCrypt::Dump() {
|
||||||
int n, i;
|
|
||||||
|
|
||||||
// mDumpFilepath.clear();
|
|
||||||
// mDumpFilepath.resize(1024);
|
|
||||||
|
|
||||||
// if (mMetaData) {
|
|
||||||
// mDumpFilepath = mMetaData->name();
|
|
||||||
|
|
||||||
// replace(mDumpFilepath, "\\", "\");
|
|
||||||
// replace(mDumpFilepath, "/", "/");
|
|
||||||
// replace(mDumpFilepath, "?", "?");
|
|
||||||
// replace(mDumpFilepath, ":", ":");
|
|
||||||
// replace(mDumpFilepath, "*", "*");
|
|
||||||
// replace(mDumpFilepath, "\"", """);
|
|
||||||
// replace(mDumpFilepath, "<", "<");
|
|
||||||
// replace(mDumpFilepath, ">", ">");
|
|
||||||
// replace(mDumpFilepath, "|", "|");
|
|
||||||
// } else {
|
|
||||||
mDumpFilepath = mFilepath;
|
mDumpFilepath = mFilepath;
|
||||||
// }
|
|
||||||
|
|
||||||
n = 0x8000;
|
std::vector<unsigned char> buffer(0x8000);
|
||||||
i = 0;
|
|
||||||
|
|
||||||
unsigned char buffer[n];
|
|
||||||
|
|
||||||
std::ofstream output;
|
std::ofstream output;
|
||||||
|
|
||||||
while (!mFile.eof()) {
|
while (!mFile.eof()) {
|
||||||
n = read((char*)buffer, n);
|
int n = read((char*)buffer.data(), buffer.size());
|
||||||
|
|
||||||
for (i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
int j = (i + 1) & 0xff;
|
int j = (i + 1) & 0xff;
|
||||||
buffer[i] ^= mKeyBox[(mKeyBox[j] + mKeyBox[(mKeyBox[j] + j) & 0xff]) & 0xff];
|
buffer[i] ^= mKeyBox[(mKeyBox[j] + mKeyBox[(mKeyBox[j] + j) & 0xff]) & 0xff];
|
||||||
}
|
}
|
||||||
@@ -262,14 +240,14 @@ void NeteaseCrypt::Dump() {
|
|||||||
mDumpFilepath.replace_extension(".mp3");
|
mDumpFilepath.replace_extension(".mp3");
|
||||||
mFormat = NeteaseCrypt::MP3;
|
mFormat = NeteaseCrypt::MP3;
|
||||||
} else {
|
} else {
|
||||||
mDumpFilepath.replace_extension(".flac");
|
mDumpFilepath.replace_extension(".flac");
|
||||||
mFormat = NeteaseCrypt::FLAC;
|
mFormat = NeteaseCrypt::FLAC;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.open(mDumpFilepath, output.out | output.binary);
|
output.open(mDumpFilepath, output.out | output.binary);
|
||||||
}
|
}
|
||||||
|
|
||||||
output.write((char*)buffer, n);
|
output.write((char*)buffer.data(), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
output.flush();
|
output.flush();
|
||||||
@@ -286,36 +264,34 @@ NeteaseCrypt::~NeteaseCrypt() {
|
|||||||
|
|
||||||
NeteaseCrypt::NeteaseCrypt(std::filesystem::path const& path) {
|
NeteaseCrypt::NeteaseCrypt(std::filesystem::path const& path) {
|
||||||
if (!openFile(path)) {
|
if (!openFile(path)) {
|
||||||
throw std::invalid_argument(" can't open file");
|
throw std::invalid_argument("Can't open file");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNcmFile()) {
|
if (!isNcmFile()) {
|
||||||
throw std::invalid_argument(" not netease protected file");
|
throw std::invalid_argument("Not netease protected file");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mFile.seekg(2, mFile.cur)) {
|
if (!mFile.seekg(2, mFile.cur)) {
|
||||||
throw std::invalid_argument(" can't seek file");
|
throw std::invalid_argument("Can't seek file");
|
||||||
}
|
}
|
||||||
|
|
||||||
mFilepath = path;
|
mFilepath = path;
|
||||||
|
|
||||||
int i;
|
|
||||||
|
|
||||||
unsigned int n;
|
unsigned int n;
|
||||||
read(reinterpret_cast<char *>(&n), sizeof(n));
|
read(reinterpret_cast<char *>(&n), sizeof(n));
|
||||||
|
|
||||||
if (n <= 0) {
|
if (n <= 0) {
|
||||||
throw std::invalid_argument("broken ncm file");
|
throw std::invalid_argument("Broken NCM file");
|
||||||
}
|
}
|
||||||
|
|
||||||
char keydata[n];
|
std::vector<char> keydata(n);
|
||||||
read(keydata, n);
|
read(keydata.data(), n);
|
||||||
|
|
||||||
for (i = 0; i < n; i++) {
|
for (size_t i = 0; i < n; i++) {
|
||||||
keydata[i] ^= 0x64;
|
keydata[i] ^= 0x64;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string rawKeyData(keydata, n);
|
std::string rawKeyData(keydata.begin(), keydata.end());
|
||||||
std::string mKeyData;
|
std::string mKeyData;
|
||||||
|
|
||||||
aesEcbDecrypt(sCoreKey, rawKeyData, mKeyData);
|
aesEcbDecrypt(sCoreKey, rawKeyData, mKeyData);
|
||||||
@@ -329,10 +305,10 @@ NeteaseCrypt::NeteaseCrypt(std::filesystem::path const& path) {
|
|||||||
|
|
||||||
mMetaData = NULL;
|
mMetaData = NULL;
|
||||||
} else {
|
} else {
|
||||||
char modifyData[n];
|
std::vector<char> modifyData(n);
|
||||||
read(modifyData, n);
|
read(modifyData.data(), n);
|
||||||
|
|
||||||
for (i = 0; i < n; i++) {
|
for (size_t i = 0; i < n; i++) {
|
||||||
modifyData[i] ^= 0x63;
|
modifyData[i] ^= 0x63;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +316,7 @@ NeteaseCrypt::NeteaseCrypt(std::filesystem::path const& path) {
|
|||||||
std::string modifyOutData;
|
std::string modifyOutData;
|
||||||
std::string modifyDecryptData;
|
std::string modifyDecryptData;
|
||||||
|
|
||||||
swapModifyData = std::string(modifyData + 22, n - 22);
|
swapModifyData = std::string(modifyData.begin() + 22, modifyData.end());
|
||||||
|
|
||||||
// escape `163 key(Don't modify):`
|
// escape `163 key(Don't modify):`
|
||||||
Base64::Decode(swapModifyData, modifyOutData);
|
Base64::Decode(swapModifyData, modifyOutData);
|
||||||
|
|||||||
Reference in New Issue
Block a user