fix: use utf-8 as filename encoding
This commit is contained in:
parent
348057bd09
commit
c12be04007
|
@ -10,6 +10,10 @@ set(WITH_ZLIB OFF)
|
||||||
set(BUILD_TESTING OFF)
|
set(BUILD_TESTING OFF)
|
||||||
add_subdirectory(taglib)
|
add_subdirectory(taglib)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/execution-charset:utf-8>")
|
||||||
|
endif()
|
||||||
|
|
||||||
FILE(GLOB HEADERS ./*.h)
|
FILE(GLOB HEADERS ./*.h)
|
||||||
FILE(GLOB SOURCES ./*.cpp)
|
FILE(GLOB SOURCES ./*.cpp)
|
||||||
add_executable(ncmdump
|
add_executable(ncmdump
|
||||||
|
|
20
aes.h
20
aes.h
|
@ -1,9 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
class AES {
|
class AES
|
||||||
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AES();
|
AES();
|
||||||
|
@ -16,21 +17,21 @@ private:
|
||||||
//
|
//
|
||||||
int mNb;
|
int mNb;
|
||||||
|
|
||||||
//word length of the secret key used in one turn
|
// word length of the secret key used in one turn
|
||||||
int mNk;
|
int mNk;
|
||||||
|
|
||||||
//number of turns
|
// number of turns
|
||||||
int mNr;
|
int mNr;
|
||||||
|
|
||||||
//the secret key,which can be 16bytes,24bytes or 32bytes
|
// the secret key,which can be 16bytes,24bytes or 32bytes
|
||||||
unsigned char mKey[32];
|
unsigned char mKey[32];
|
||||||
|
|
||||||
//the extended key,which can be 176bytes,208bytes,240bytes
|
// the extended key,which can be 176bytes,208bytes,240bytes
|
||||||
unsigned char mW[60][4];
|
unsigned char mW[60][4];
|
||||||
|
|
||||||
static unsigned char sBox[];
|
static unsigned char sBox[];
|
||||||
static unsigned char invSBox[];
|
static unsigned char invSBox[];
|
||||||
//constant
|
// constant
|
||||||
static unsigned char rcon[];
|
static unsigned char rcon[];
|
||||||
void setKey(const unsigned char *key);
|
void setKey(const unsigned char *key);
|
||||||
|
|
||||||
|
@ -51,7 +52,6 @@ private:
|
||||||
void rotWord(unsigned char w[]);
|
void rotWord(unsigned char w[]);
|
||||||
void subWord(unsigned char w[]);
|
void subWord(unsigned char w[]);
|
||||||
|
|
||||||
//get the secret key
|
// get the secret key
|
||||||
void getKeyAt(unsigned char key[][4],int i);
|
void getKeyAt(unsigned char key[][4], int i);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
35
main.cpp
35
main.cpp
|
@ -6,7 +6,7 @@
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#include <Windows.h>
|
#include "platform.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "color.h"
|
#include "color.h"
|
||||||
|
@ -25,26 +25,26 @@ void processFile(const fs::path &filePath)
|
||||||
{
|
{
|
||||||
if (fs::exists(filePath) == false)
|
if (fs::exists(filePath) == false)
|
||||||
{
|
{
|
||||||
std::cerr << BOLDRED << "Error: " << RESET << "file '" << filePath.string() << "' does not exist." << std::endl;
|
std::cerr << BOLDRED << "Error: " << RESET << "file '" << filePath.u8string() << "' does not exist." << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NeteaseCrypt crypt(filePath.string());
|
NeteaseCrypt crypt(filePath.u8string());
|
||||||
crypt.Dump();
|
crypt.Dump();
|
||||||
crypt.FixMetadata();
|
crypt.FixMetadata();
|
||||||
|
|
||||||
std::cout << BOLDGREEN << "Done: '" << RESET << crypt.dumpFilepath().string() << "'" << std::endl;
|
std::cout << BOLDGREEN << "Done: " << RESET << "'" << 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.string() << "'" << std::endl;
|
std::cerr << BOLDRED << "Exception: " << RESET << RED << e.what() << RESET << " '" << filePath.u8string() << "'" << std::endl;
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
std::cerr << BOLDRED << "Unexpected exception while processing file: " << RESET << filePath.string() << std::endl;
|
|
||||||
}
|
}
|
||||||
|
// catch (...)
|
||||||
|
// {
|
||||||
|
// std::cerr << BOLDRED << "Unexpected exception while processing file: " << RESET << filePath.u8string() << std::endl;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
void processFilesInFolder(const fs::path &folderPath)
|
void processFilesInFolder(const fs::path &folderPath)
|
||||||
|
@ -58,22 +58,9 @@ void processFilesInFolder(const fs::path &folderPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(_WIN32)
|
|
||||||
int wmain(int argc, wchar_t *wideargv[])
|
|
||||||
#else
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
#if defined(_WIN32)
|
win32_utf8argv(&argc, &argv);
|
||||||
SetConsoleOutputCP(CP_UTF8);
|
|
||||||
char **argv = (char **)malloc(sizeof(char *) * argc);
|
|
||||||
for (int i = 0; i < argc; ++i)
|
|
||||||
{
|
|
||||||
int utf8_size = WideCharToMultiByte(CP_UTF8, 0, wideargv[i], -1, NULL, 0, NULL, NULL);
|
|
||||||
argv[i] = (char *)malloc(utf8_size);
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, wideargv[i], -1, argv[i], utf8_size, NULL, NULL);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (argc <= 1)
|
if (argc <= 1)
|
||||||
{
|
{
|
||||||
|
@ -115,7 +102,7 @@ int main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
fs::path path(argv[i]);
|
fs::path path = fs::u8path(argv[i]);
|
||||||
files.push_back(path);
|
files.push_back(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
528
ncmcrypt.cpp
528
ncmcrypt.cpp
|
@ -13,338 +13,394 @@
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
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};
|
||||||
|
|
||||||
const unsigned char NeteaseCrypt::mPng[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
|
const unsigned char NeteaseCrypt::mPng[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
|
||||||
|
|
||||||
static void aesEcbDecrypt(const unsigned char *key, std::string& src, std::string& dst) {
|
static void aesEcbDecrypt(const unsigned char *key, std::string &src, std::string &dst)
|
||||||
int n, i;
|
{
|
||||||
|
int n, i;
|
||||||
|
|
||||||
unsigned char out[16];
|
unsigned char out[16];
|
||||||
|
|
||||||
n = src.length() >> 4;
|
n = src.length() >> 4;
|
||||||
|
|
||||||
dst.clear();
|
dst.clear();
|
||||||
|
|
||||||
AES aes(key);
|
AES aes(key);
|
||||||
|
|
||||||
for (i = 0; i < n-1; i++) {
|
for (i = 0; i < n - 1; i++)
|
||||||
aes.decrypt((unsigned char*)src.c_str() + (i << 4), out);
|
{
|
||||||
dst += std::string((char*)out, 16);
|
aes.decrypt((unsigned char *)src.c_str() + (i << 4), out);
|
||||||
}
|
dst += std::string((char *)out, 16);
|
||||||
|
}
|
||||||
|
|
||||||
aes.decrypt((unsigned char*)src.c_str() + (i << 4), out);
|
aes.decrypt((unsigned char *)src.c_str() + (i << 4), out);
|
||||||
char pad = out[15];
|
char pad = out[15];
|
||||||
if (pad > 16) {
|
if (pad > 16)
|
||||||
pad = 0;
|
{
|
||||||
}
|
pad = 0;
|
||||||
dst += std::string((char*)out, 16-pad);
|
}
|
||||||
|
dst += std::string((char *)out, 16 - pad);
|
||||||
}
|
}
|
||||||
|
|
||||||
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())
|
{
|
||||||
return;
|
if (from.empty())
|
||||||
size_t start_pos = 0;
|
return;
|
||||||
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
size_t start_pos = 0;
|
||||||
str.replace(start_pos, from.length(), to);
|
while ((start_pos = str.find(from, start_pos)) != std::string::npos)
|
||||||
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
|
{
|
||||||
}
|
str.replace(start_pos, from.length(), to);
|
||||||
|
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NeteaseMusicMetadata::~NeteaseMusicMetadata() {
|
NeteaseMusicMetadata::~NeteaseMusicMetadata()
|
||||||
cJSON_Delete(mRaw);
|
{
|
||||||
|
cJSON_Delete(mRaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
NeteaseMusicMetadata::NeteaseMusicMetadata(cJSON* raw) {
|
NeteaseMusicMetadata::NeteaseMusicMetadata(cJSON *raw)
|
||||||
if (!raw) {
|
{
|
||||||
return;
|
if (!raw)
|
||||||
}
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
cJSON *swap;
|
cJSON *swap;
|
||||||
int artistLen, i;
|
int artistLen, i;
|
||||||
|
|
||||||
mRaw = raw;
|
mRaw = raw;
|
||||||
|
|
||||||
swap = cJSON_GetObjectItem(raw, "musicName");
|
swap = cJSON_GetObjectItem(raw, "musicName");
|
||||||
if (swap) {
|
if (swap)
|
||||||
mName = std::string(cJSON_GetStringValue(swap));
|
{
|
||||||
}
|
mName = std::string(cJSON_GetStringValue(swap));
|
||||||
|
}
|
||||||
|
|
||||||
swap = cJSON_GetObjectItem(raw, "album");
|
swap = cJSON_GetObjectItem(raw, "album");
|
||||||
if (swap) {
|
if (swap)
|
||||||
mAlbum = std::string(cJSON_GetStringValue(swap));
|
{
|
||||||
}
|
mAlbum = std::string(cJSON_GetStringValue(swap));
|
||||||
|
}
|
||||||
|
|
||||||
swap = cJSON_GetObjectItem(raw, "artist");
|
swap = cJSON_GetObjectItem(raw, "artist");
|
||||||
if (swap) {
|
if (swap)
|
||||||
artistLen = cJSON_GetArraySize(swap);
|
{
|
||||||
|
artistLen = cJSON_GetArraySize(swap);
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
for (i = 0; i < artistLen-1; i++) {
|
for (i = 0; i < artistLen - 1; i++)
|
||||||
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 += "/";
|
||||||
mArtist += std::string(cJSON_GetStringValue(cJSON_GetArrayItem(cJSON_GetArrayItem(swap, i), 0)));
|
}
|
||||||
}
|
mArtist += std::string(cJSON_GetStringValue(cJSON_GetArrayItem(cJSON_GetArrayItem(swap, i), 0)));
|
||||||
|
}
|
||||||
|
|
||||||
swap = cJSON_GetObjectItem(raw, "bitrate");
|
swap = cJSON_GetObjectItem(raw, "bitrate");
|
||||||
if (swap) {
|
if (swap)
|
||||||
mBitrate = swap->valueint;
|
{
|
||||||
}
|
mBitrate = swap->valueint;
|
||||||
|
}
|
||||||
|
|
||||||
swap = cJSON_GetObjectItem(raw, "duration");
|
swap = cJSON_GetObjectItem(raw, "duration");
|
||||||
if (swap) {
|
if (swap)
|
||||||
mDuration = swap->valueint;
|
{
|
||||||
}
|
mDuration = swap->valueint;
|
||||||
|
}
|
||||||
|
|
||||||
swap = cJSON_GetObjectItem(raw, "format");
|
swap = cJSON_GetObjectItem(raw, "format");
|
||||||
if (swap) {
|
if (swap)
|
||||||
mFormat = std::string(cJSON_GetStringValue(swap));
|
{
|
||||||
}
|
mFormat = std::string(cJSON_GetStringValue(swap));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeteaseCrypt::openFile(std::filesystem::path const& path) {
|
bool NeteaseCrypt::openFile(std::string const &path)
|
||||||
mFile.open(path, std::ios::in | std::ios::binary);
|
{
|
||||||
if (!mFile.is_open()) {
|
mFile.open(std::filesystem::u8path(path), std::ios::in | std::ios::binary);
|
||||||
return false;
|
if (!mFile.is_open())
|
||||||
} else {
|
{
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeteaseCrypt::isNcmFile() {
|
bool NeteaseCrypt::isNcmFile()
|
||||||
unsigned int header;
|
{
|
||||||
|
unsigned int header;
|
||||||
|
|
||||||
mFile.read(reinterpret_cast<char *>(&header), sizeof(header));
|
mFile.read(reinterpret_cast<char *>(&header), sizeof(header));
|
||||||
if (header != (unsigned int)0x4e455443) {
|
if (header != (unsigned int)0x4e455443)
|
||||||
return false;
|
{
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
mFile.read(reinterpret_cast<char *>(&header), sizeof(header));
|
mFile.read(reinterpret_cast<char *>(&header), sizeof(header));
|
||||||
if (header != (unsigned int)0x4d414446) {
|
if (header != (unsigned int)0x4d414446)
|
||||||
return false;
|
{
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int NeteaseCrypt::read(char *s, std::streamsize n) {
|
int NeteaseCrypt::read(char *s, std::streamsize n)
|
||||||
mFile.read(s, n);
|
{
|
||||||
|
mFile.read(s, 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeteaseCrypt::buildKeyBox(unsigned char *key, int keyLen) {
|
void NeteaseCrypt::buildKeyBox(unsigned char *key, int keyLen)
|
||||||
int i;
|
{
|
||||||
for (i = 0; i < 256; ++i) {
|
int i;
|
||||||
mKeyBox[i] = (unsigned char)i;
|
for (i = 0; i < 256; ++i)
|
||||||
}
|
{
|
||||||
|
mKeyBox[i] = (unsigned char)i;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned char swap = 0;
|
unsigned char swap = 0;
|
||||||
unsigned char c = 0;
|
unsigned char c = 0;
|
||||||
unsigned char last_byte = 0;
|
unsigned char last_byte = 0;
|
||||||
unsigned char key_offset = 0;
|
unsigned char key_offset = 0;
|
||||||
|
|
||||||
for (i = 0; i < 256; ++i)
|
for (i = 0; i < 256; ++i)
|
||||||
{
|
{
|
||||||
swap = mKeyBox[i];
|
swap = mKeyBox[i];
|
||||||
c = ((swap + last_byte + key[key_offset++]) & 0xff);
|
c = ((swap + last_byte + key[key_offset++]) & 0xff);
|
||||||
if (key_offset >= keyLen) key_offset = 0;
|
if (key_offset >= keyLen)
|
||||||
mKeyBox[i] = mKeyBox[c]; mKeyBox[c] = swap;
|
key_offset = 0;
|
||||||
last_byte = c;
|
mKeyBox[i] = mKeyBox[c];
|
||||||
}
|
mKeyBox[c] = swap;
|
||||||
|
last_byte = c;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string NeteaseCrypt::mimeType(std::string& data) {
|
std::string NeteaseCrypt::mimeType(std::string &data)
|
||||||
if (memcmp(data.c_str(), mPng, 8) == 0) {
|
{
|
||||||
return std::string("image/png");
|
if (memcmp(data.c_str(), mPng, 8) == 0)
|
||||||
}
|
{
|
||||||
|
return std::string("image/png");
|
||||||
|
}
|
||||||
|
|
||||||
return std::string("image/jpeg");
|
return std::string("image/jpeg");
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeteaseCrypt::FixMetadata() {
|
void NeteaseCrypt::FixMetadata()
|
||||||
if (mDumpFilepath.string().length() <= 0) {
|
{
|
||||||
throw std::invalid_argument("must dump before");
|
|
||||||
}
|
|
||||||
|
|
||||||
TagLib::File *audioFile;
|
TagLib::File *audioFile;
|
||||||
TagLib::Tag *tag;
|
TagLib::Tag *tag;
|
||||||
TagLib::ByteVector vector(mImageData.c_str(), mImageData.length());
|
TagLib::ByteVector vector(mImageData.c_str(), mImageData.length());
|
||||||
|
|
||||||
if (mFormat == NeteaseCrypt::MP3) {
|
if (mFormat == NeteaseCrypt::MP3)
|
||||||
audioFile = new TagLib::MPEG::File(mDumpFilepath.c_str());
|
{
|
||||||
tag = dynamic_cast<TagLib::MPEG::File*>(audioFile)->ID3v2Tag(true);
|
audioFile = new TagLib::MPEG::File(mDumpFilepath.c_str());
|
||||||
|
tag = dynamic_cast<TagLib::MPEG::File *>(audioFile)->ID3v2Tag(true);
|
||||||
|
|
||||||
if (mImageData.length() > 0) {
|
if (mImageData.length() > 0)
|
||||||
TagLib::ID3v2::AttachedPictureFrame *frame = new TagLib::ID3v2::AttachedPictureFrame;
|
{
|
||||||
|
TagLib::ID3v2::AttachedPictureFrame *frame = new TagLib::ID3v2::AttachedPictureFrame;
|
||||||
|
|
||||||
frame->setMimeType(mimeType(mImageData));
|
frame->setMimeType(mimeType(mImageData));
|
||||||
frame->setPicture(vector);
|
frame->setPicture(vector);
|
||||||
|
|
||||||
dynamic_cast<TagLib::ID3v2::Tag*>(tag)->addFrame(frame);
|
dynamic_cast<TagLib::ID3v2::Tag *>(tag)->addFrame(frame);
|
||||||
}
|
}
|
||||||
} else if (mFormat == NeteaseCrypt::FLAC) {
|
}
|
||||||
audioFile = new TagLib::FLAC::File(mDumpFilepath.c_str());
|
else if (mFormat == NeteaseCrypt::FLAC)
|
||||||
tag = audioFile->tag();
|
{
|
||||||
|
audioFile = new TagLib::FLAC::File(mDumpFilepath.c_str());
|
||||||
|
tag = audioFile->tag();
|
||||||
|
|
||||||
if (mImageData.length() > 0) {
|
if (mImageData.length() > 0)
|
||||||
TagLib::FLAC::Picture *cover = new TagLib::FLAC::Picture;
|
{
|
||||||
cover->setMimeType(mimeType(mImageData));
|
TagLib::FLAC::Picture *cover = new TagLib::FLAC::Picture;
|
||||||
cover->setType(TagLib::FLAC::Picture::FrontCover);
|
cover->setMimeType(mimeType(mImageData));
|
||||||
cover->setData(vector);
|
cover->setType(TagLib::FLAC::Picture::FrontCover);
|
||||||
|
cover->setData(vector);
|
||||||
|
|
||||||
dynamic_cast<TagLib::FLAC::File*>(audioFile)->addPicture(cover);
|
dynamic_cast<TagLib::FLAC::File *>(audioFile)->addPicture(cover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mMetaData != NULL) {
|
if (mMetaData != NULL)
|
||||||
tag->setTitle(TagLib::String(mMetaData->name(), TagLib::String::UTF8));
|
{
|
||||||
tag->setArtist(TagLib::String(mMetaData->artist(), TagLib::String::UTF8));
|
tag->setTitle(TagLib::String(mMetaData->name(), TagLib::String::UTF8));
|
||||||
tag->setAlbum(TagLib::String(mMetaData->album(), TagLib::String::UTF8));
|
tag->setArtist(TagLib::String(mMetaData->artist(), 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 netease copyright protected dump tool. author 5L", TagLib::String::UTF8));
|
||||||
|
|
||||||
audioFile->save();
|
audioFile->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeteaseCrypt::Dump() {
|
void NeteaseCrypt::Dump()
|
||||||
mDumpFilepath = mFilepath;
|
{
|
||||||
|
mDumpFilepath = std::filesystem::u8path(mFilepath);
|
||||||
|
|
||||||
std::vector<unsigned char> buffer(0x8000);
|
std::vector<unsigned char> buffer(0x8000);
|
||||||
|
|
||||||
std::ofstream output;
|
std::ofstream output;
|
||||||
|
|
||||||
while (!mFile.eof()) {
|
while (!mFile.eof())
|
||||||
int n = read((char*)buffer.data(), buffer.size());
|
{
|
||||||
|
int n = read((char *)buffer.data(), buffer.size());
|
||||||
|
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++)
|
||||||
int j = (i + 1) & 0xff;
|
{
|
||||||
buffer[i] ^= mKeyBox[(mKeyBox[j] + mKeyBox[(mKeyBox[j] + j) & 0xff]) & 0xff];
|
int j = (i + 1) & 0xff;
|
||||||
}
|
buffer[i] ^= mKeyBox[(mKeyBox[j] + mKeyBox[(mKeyBox[j] + j) & 0xff]) & 0xff];
|
||||||
|
}
|
||||||
|
|
||||||
if (!output.is_open()) {
|
if (!output.is_open())
|
||||||
// identify format
|
{
|
||||||
// ID3 format mp3
|
// identify format
|
||||||
if (buffer[0] == 0x49 && buffer[1] == 0x44 && buffer[2] == 0x33) {
|
// ID3 format mp3
|
||||||
mDumpFilepath.replace_extension(".mp3");
|
if (buffer[0] == 0x49 && buffer[1] == 0x44 && buffer[2] == 0x33)
|
||||||
mFormat = NeteaseCrypt::MP3;
|
{
|
||||||
} else {
|
mDumpFilepath = mDumpFilepath.replace_extension("mp3");
|
||||||
mDumpFilepath.replace_extension(".flac");
|
mFormat = NeteaseCrypt::MP3;
|
||||||
mFormat = NeteaseCrypt::FLAC;
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
mDumpFilepath = mDumpFilepath.replace_extension("flac");
|
||||||
|
mFormat = NeteaseCrypt::FLAC;
|
||||||
|
}
|
||||||
|
|
||||||
output.open(mDumpFilepath, output.out | output.binary);
|
output.open(mDumpFilepath, output.out | output.binary);
|
||||||
}
|
}
|
||||||
|
|
||||||
output.write((char*)buffer.data(), n);
|
output.write((char *)buffer.data(), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
output.flush();
|
output.flush();
|
||||||
output.close();
|
output.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
NeteaseCrypt::~NeteaseCrypt() {
|
NeteaseCrypt::~NeteaseCrypt()
|
||||||
if (mMetaData != NULL) {
|
{
|
||||||
delete mMetaData;
|
if (mMetaData != NULL)
|
||||||
}
|
{
|
||||||
|
delete mMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
mFile.close();
|
mFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
NeteaseCrypt::NeteaseCrypt(std::filesystem::path const& path) {
|
NeteaseCrypt::NeteaseCrypt(std::string const &path)
|
||||||
if (!openFile(path)) {
|
{
|
||||||
throw std::invalid_argument("Can't open file");
|
if (!openFile(path))
|
||||||
}
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
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");
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<char> keydata(n);
|
std::vector<char> keydata(n);
|
||||||
read(keydata.data(), n);
|
read(keydata.data(), n);
|
||||||
|
|
||||||
for (size_t i = 0; i < n; i++) {
|
for (size_t i = 0; i < n; i++)
|
||||||
keydata[i] ^= 0x64;
|
{
|
||||||
}
|
keydata[i] ^= 0x64;
|
||||||
|
}
|
||||||
|
|
||||||
std::string rawKeyData(keydata.begin(), keydata.end());
|
std::string rawKeyData(keydata.begin(), keydata.end());
|
||||||
std::string mKeyData;
|
std::string mKeyData;
|
||||||
|
|
||||||
aesEcbDecrypt(sCoreKey, rawKeyData, mKeyData);
|
aesEcbDecrypt(sCoreKey, rawKeyData, mKeyData);
|
||||||
|
|
||||||
buildKeyBox((unsigned char*)mKeyData.c_str()+17, mKeyData.length()-17);
|
buildKeyBox((unsigned char *)mKeyData.c_str() + 17, mKeyData.length() - 17);
|
||||||
|
|
||||||
read(reinterpret_cast<char *>(&n), sizeof(n));
|
read(reinterpret_cast<char *>(&n), sizeof(n));
|
||||||
|
|
||||||
if (n <= 0) {
|
if (n <= 0)
|
||||||
std::cout << "[Warn] " << path.string() << " missing metadata infomation can't fix some infomation!" << std::endl;
|
{
|
||||||
|
std::cout << "[Warn] " << path << " missing metadata infomation can't fix some infomation!" << std::endl;
|
||||||
|
|
||||||
mMetaData = NULL;
|
mMetaData = NULL;
|
||||||
} else {
|
}
|
||||||
std::vector<char> modifyData(n);
|
else
|
||||||
read(modifyData.data(), n);
|
{
|
||||||
|
std::vector<char> modifyData(n);
|
||||||
|
read(modifyData.data(), n);
|
||||||
|
|
||||||
for (size_t i = 0; i < n; i++) {
|
for (size_t i = 0; i < n; i++)
|
||||||
modifyData[i] ^= 0x63;
|
{
|
||||||
}
|
modifyData[i] ^= 0x63;
|
||||||
|
}
|
||||||
|
|
||||||
std::string swapModifyData;
|
std::string swapModifyData;
|
||||||
std::string modifyOutData;
|
std::string modifyOutData;
|
||||||
std::string modifyDecryptData;
|
std::string modifyDecryptData;
|
||||||
|
|
||||||
swapModifyData = std::string(modifyData.begin() + 22, modifyData.end());
|
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);
|
||||||
|
|
||||||
aesEcbDecrypt(sModifyKey, modifyOutData, modifyDecryptData);
|
aesEcbDecrypt(sModifyKey, modifyOutData, modifyDecryptData);
|
||||||
|
|
||||||
// escape `music:`
|
// escape `music:`
|
||||||
modifyDecryptData = std::string(modifyDecryptData.begin()+6, modifyDecryptData.end());
|
modifyDecryptData = std::string(modifyDecryptData.begin() + 6, modifyDecryptData.end());
|
||||||
|
|
||||||
// std::cout << modifyDecryptData << std::endl;
|
// std::cout << modifyDecryptData << std::endl;
|
||||||
|
|
||||||
mMetaData = new NeteaseMusicMetadata(cJSON_Parse(modifyDecryptData.c_str()));
|
mMetaData = new NeteaseMusicMetadata(cJSON_Parse(modifyDecryptData.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip crc32 & unuse charset
|
// skip crc32 & unuse charset
|
||||||
if (!mFile.seekg(9, mFile.cur)) {
|
if (!mFile.seekg(9, mFile.cur))
|
||||||
throw std::invalid_argument("can't seek file");
|
{
|
||||||
}
|
throw std::invalid_argument("can't seek file");
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
{
|
||||||
read(imageData, n);
|
char *imageData = (char *)malloc(n);
|
||||||
|
read(imageData, n);
|
||||||
|
|
||||||
mImageData = std::string(imageData, n);
|
mImageData = std::string(imageData, n);
|
||||||
} else {
|
}
|
||||||
std::cout << "[Warn] " << path.string() << " missing album can't fix album image!" << std::endl;
|
else
|
||||||
}
|
{
|
||||||
|
std::cout << "[Warn] " << path << " missing album can't fix album image!" << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
10
ncmcrypt.h
10
ncmcrypt.h
|
@ -42,7 +42,7 @@ private:
|
||||||
enum NcmFormat { MP3, FLAC };
|
enum NcmFormat { MP3, FLAC };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::filesystem::path mFilepath;
|
std::string mFilepath;
|
||||||
std::filesystem::path mDumpFilepath;
|
std::filesystem::path mDumpFilepath;
|
||||||
NcmFormat mFormat;
|
NcmFormat mFormat;
|
||||||
std::string mImageData;
|
std::string mImageData;
|
||||||
|
@ -52,17 +52,17 @@ private:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isNcmFile();
|
bool isNcmFile();
|
||||||
bool openFile(std::filesystem::path const&);
|
bool openFile(std::string const&);
|
||||||
int read(char *s, std::streamsize n);
|
int read(char *s, std::streamsize n);
|
||||||
void buildKeyBox(unsigned char *key, int keyLen);
|
void buildKeyBox(unsigned char *key, int keyLen);
|
||||||
std::string mimeType(std::string& data);
|
std::string mimeType(std::string& data);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const std::filesystem::path& filepath() const { return mFilepath; }
|
const std::string& filepath() const { return mFilepath; }
|
||||||
const std::filesystem::path& dumpFilepath() const { return mDumpFilepath; }
|
const std::filesystem::path dumpFilepath() const { return mDumpFilepath; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NeteaseCrypt(std::filesystem::path const&);
|
NeteaseCrypt(std::string const&);
|
||||||
~NeteaseCrypt();
|
~NeteaseCrypt();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# ifdef _WIN32
|
||||||
|
/*
|
||||||
|
* Win32-specific argv initialization that splits OS-supplied UNICODE
|
||||||
|
* command line string to array of UTF8-encoded strings.
|
||||||
|
*/
|
||||||
|
void win32_utf8argv(int *argc, char **argv[]);
|
||||||
|
# endif
|
|
@ -0,0 +1,296 @@
|
||||||
|
#include <windows.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
#if defined(CP_UTF8)
|
||||||
|
|
||||||
|
static UINT saved_cp;
|
||||||
|
static int newargc;
|
||||||
|
static char **newargv;
|
||||||
|
|
||||||
|
static void cleanup(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
SetConsoleOutputCP(saved_cp);
|
||||||
|
|
||||||
|
for (i = 0; i < newargc; i++)
|
||||||
|
free(newargv[i]);
|
||||||
|
|
||||||
|
free(newargv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Incrementally [re]allocate newargv and keep it NULL-terminated.
|
||||||
|
*/
|
||||||
|
static int validate_argv(int argc)
|
||||||
|
{
|
||||||
|
static int size = 0;
|
||||||
|
|
||||||
|
if (argc >= size) {
|
||||||
|
char **ptr;
|
||||||
|
|
||||||
|
while (argc >= size)
|
||||||
|
size += 64;
|
||||||
|
|
||||||
|
ptr = (char**)realloc(newargv, size * sizeof(newargv[0]));
|
||||||
|
if (ptr == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
(newargv = ptr)[argc] = NULL;
|
||||||
|
} else {
|
||||||
|
newargv[argc] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_glob(WCHAR *wstr, int wlen)
|
||||||
|
{
|
||||||
|
int i, slash, udlen;
|
||||||
|
WCHAR saved_char;
|
||||||
|
WIN32_FIND_DATAW data;
|
||||||
|
HANDLE h;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note that we support wildcard characters only in filename part
|
||||||
|
* of the path, and not in directories. Windows users are used to
|
||||||
|
* this, that's why recursive glob processing is not implemented.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Start by looking for last slash or backslash, ...
|
||||||
|
*/
|
||||||
|
for (slash = 0, i = 0; i < wlen; i++)
|
||||||
|
if (wstr[i] == L'/' || wstr[i] == L'\\')
|
||||||
|
slash = i + 1;
|
||||||
|
/*
|
||||||
|
* ... then look for asterisk or question mark in the file name.
|
||||||
|
*/
|
||||||
|
for (i = slash; i < wlen; i++)
|
||||||
|
if (wstr[i] == L'*' || wstr[i] == L'?')
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (i == wlen)
|
||||||
|
return 0; /* definitely not a glob */
|
||||||
|
|
||||||
|
saved_char = wstr[wlen];
|
||||||
|
wstr[wlen] = L'\0';
|
||||||
|
h = FindFirstFileW(wstr, &data);
|
||||||
|
wstr[wlen] = saved_char;
|
||||||
|
if (h == INVALID_HANDLE_VALUE)
|
||||||
|
return 0; /* not a valid glob, just pass... */
|
||||||
|
|
||||||
|
if (slash)
|
||||||
|
udlen = WideCharToMultiByte(CP_UTF8, 0, wstr, slash,
|
||||||
|
NULL, 0, NULL, NULL);
|
||||||
|
else
|
||||||
|
udlen = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
int uflen;
|
||||||
|
char *arg;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* skip over . and ..
|
||||||
|
*/
|
||||||
|
if (data.cFileName[0] == L'.') {
|
||||||
|
if ((data.cFileName[1] == L'\0') ||
|
||||||
|
(data.cFileName[1] == L'.' && data.cFileName[2] == L'\0'))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validate_argv(newargc + 1))
|
||||||
|
break;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* -1 below means "scan for trailing '\0' *and* count it",
|
||||||
|
* so that |uflen| covers even trailing '\0'.
|
||||||
|
*/
|
||||||
|
uflen = WideCharToMultiByte(CP_UTF8, 0, data.cFileName, -1,
|
||||||
|
NULL, 0, NULL, NULL);
|
||||||
|
|
||||||
|
arg = (char*)malloc(udlen + uflen);
|
||||||
|
if (arg == NULL)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (udlen)
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, wstr, slash,
|
||||||
|
arg, udlen, NULL, NULL);
|
||||||
|
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, data.cFileName, -1,
|
||||||
|
arg + udlen, uflen, NULL, NULL);
|
||||||
|
|
||||||
|
newargv[newargc++] = arg;
|
||||||
|
} while (FindNextFileW(h, &data));
|
||||||
|
|
||||||
|
CloseHandle(h);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void win32_utf8argv(int *argc, char **argv[])
|
||||||
|
{
|
||||||
|
const WCHAR *wcmdline;
|
||||||
|
WCHAR *warg, *wend, *p;
|
||||||
|
int wlen, ulen, valid = 1;
|
||||||
|
char *arg;
|
||||||
|
|
||||||
|
newargc = 0;
|
||||||
|
newargv = NULL;
|
||||||
|
if (!validate_argv(newargc))
|
||||||
|
return;
|
||||||
|
|
||||||
|
wcmdline = GetCommandLineW();
|
||||||
|
if (wcmdline == NULL) return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* make a copy of the command line, since we might have to modify it...
|
||||||
|
*/
|
||||||
|
wlen = wcslen(wcmdline);
|
||||||
|
p = (WCHAR*)_alloca((wlen + 1) * sizeof(WCHAR));
|
||||||
|
wcscpy(p, wcmdline);
|
||||||
|
|
||||||
|
while (*p != L'\0') {
|
||||||
|
int in_quote = 0;
|
||||||
|
|
||||||
|
if (*p == L' ' || *p == L'\t') {
|
||||||
|
p++; /* skip over whitespace */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note: because we may need to fiddle with the number of backslashes,
|
||||||
|
* the argument string is copied into itself. This is safe because
|
||||||
|
* the number of characters will never expand.
|
||||||
|
*/
|
||||||
|
warg = wend = p;
|
||||||
|
while (*p != L'\0'
|
||||||
|
&& (in_quote || (*p != L' ' && *p != L'\t'))) {
|
||||||
|
switch (*p) {
|
||||||
|
case L'\\':
|
||||||
|
/*
|
||||||
|
* Microsoft documentation on how backslashes are treated
|
||||||
|
* is:
|
||||||
|
*
|
||||||
|
* + Backslashes are interpreted literally, unless they
|
||||||
|
* immediately precede a double quotation mark.
|
||||||
|
* + If an even number of backslashes is followed by a double
|
||||||
|
* quotation mark, one backslash is placed in the argv array
|
||||||
|
* for every pair of backslashes, and the double quotation
|
||||||
|
* mark is interpreted as a string delimiter.
|
||||||
|
* + If an odd number of backslashes is followed by a double
|
||||||
|
* quotation mark, one backslash is placed in the argv array
|
||||||
|
* for every pair of backslashes, and the double quotation
|
||||||
|
* mark is "escaped" by the remaining backslash, causing a
|
||||||
|
* literal double quotation mark (") to be placed in argv.
|
||||||
|
*
|
||||||
|
* Ref: https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
|
||||||
|
*
|
||||||
|
* Though referred page doesn't mention it, multiple qouble
|
||||||
|
* quotes are also special. Pair of double quotes in quoted
|
||||||
|
* string is counted as single double quote.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
const WCHAR *q = p;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
while (*p == L'\\')
|
||||||
|
p++;
|
||||||
|
|
||||||
|
if (*p == L'"') {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = (p - q) / 2; i > 0; i--)
|
||||||
|
*wend++ = L'\\';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* if odd amount of backslashes before the quote,
|
||||||
|
* said quote is part of the argument, not a delimiter
|
||||||
|
*/
|
||||||
|
if ((p - q) % 2 == 1)
|
||||||
|
*wend++ = *p++;
|
||||||
|
} else {
|
||||||
|
for (i = p - q; i > 0; i--)
|
||||||
|
*wend++ = L'\\';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case L'"':
|
||||||
|
/*
|
||||||
|
* Without the preceding backslash (or when preceded with an
|
||||||
|
* even number of backslashes), the double quote is a simple
|
||||||
|
* string delimiter and just slightly change the parsing state
|
||||||
|
*/
|
||||||
|
if (in_quote && p[1] == L'"')
|
||||||
|
*wend++ = *p++;
|
||||||
|
else
|
||||||
|
in_quote = !in_quote;
|
||||||
|
p++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/*
|
||||||
|
* Any other non-delimiter character is just taken verbatim
|
||||||
|
*/
|
||||||
|
*wend++ = *p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wlen = wend - warg;
|
||||||
|
|
||||||
|
if (wlen == 0 || !process_glob(warg, wlen)) {
|
||||||
|
if (!validate_argv(newargc + 1)) {
|
||||||
|
valid = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulen = 0;
|
||||||
|
if (wlen > 0) {
|
||||||
|
ulen = WideCharToMultiByte(CP_UTF8, 0, warg, wlen,
|
||||||
|
NULL, 0, NULL, NULL);
|
||||||
|
if (ulen <= 0)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
arg = (char*)malloc(ulen + 1);
|
||||||
|
if (arg == NULL) {
|
||||||
|
valid = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wlen > 0)
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, warg, wlen,
|
||||||
|
arg, ulen, NULL, NULL);
|
||||||
|
arg[ulen] = '\0';
|
||||||
|
|
||||||
|
newargv[newargc++] = arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid) {
|
||||||
|
saved_cp = GetConsoleOutputCP();
|
||||||
|
SetConsoleOutputCP(CP_UTF8);
|
||||||
|
|
||||||
|
*argc = newargc;
|
||||||
|
*argv = newargv;
|
||||||
|
|
||||||
|
atexit(cleanup);
|
||||||
|
} else if (newargv != NULL) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < newargc; i++)
|
||||||
|
free(newargv[i]);
|
||||||
|
|
||||||
|
free(newargv);
|
||||||
|
|
||||||
|
newargc = 0;
|
||||||
|
newargv = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void win32_utf8argv(int *argc, char **argv[])
|
||||||
|
{ return; }
|
||||||
|
#endif
|
Loading…
Reference in New Issue