Skip to content

Commit

Permalink
move file parsing proccess to ncm_file class
Browse files Browse the repository at this point in the history
  • Loading branch information
pnck committed Mar 3, 2021
1 parent d3a8ef8 commit 174c66b
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 147 deletions.
6 changes: 3 additions & 3 deletions foo_input_ncm/common/define.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
#include <stdint.h>

constexpr inline GUID guid_candidates[] = {
{0x0ef1cb99, 0x91ad, 0x486c, {0x82, 0x82, 0xda, 0x70, 0x98, 0x4e, 0xa8, 0x51}}, // input_ncm::g_get_guid
{0x9c99d51e, 0x1228, 0x4f25, {0x91, 0x63, 0xf1, 0x56, 0x1e, 0x57, 0x5b, 0x13}}, // ncm_file
{0xdb2c5ae1, 0x1a4c, 0x4c67, {0xb4, 0x13, 0xc9, 0xd9, 0x46, 0x34, 0xe2, 0xaf}},
{0x0ef1cb99, 0x91ad, 0x486c, {0x82, 0x82, 0xda, 0x70, 0x98, 0x4e, 0xa8, 0x51}}, // input_ncm
{0x9c99d51e, 0x1228, 0x4f25, {0x91, 0x63, 0xf1, 0x56, 0x1e, 0x57, 0x5b, 0x13}},
{0xdb2c5ae1, 0x1a4c, 0x4c67, {0xb4, 0x13, 0xc9, 0xd9, 0x46, 0x34, 0xe2, 0xaf}},
{0xc2cb5fa6, 0x9d9f, 0x47ec, {0xae, 0x3a, 0x18, 0x5f, 0xc7, 0x98, 0xd6, 0x2c}},
};

Expand Down
156 changes: 32 additions & 124 deletions foo_input_ncm/input_ncm.cpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
#include "stdafx.h"
#include "input_ncm.h"
#include "cipher/cipher.h"

#include "rapidjson/include/rapidjson/stringbuffer.h"
#include "rapidjson/include/rapidjson/prettywriter.h"

#include <string>
#include <algorithm>

using namespace fb2k_ncm;

inline void fb2k_ncm::input_ncm::throw_format_error() {
throw exception_io_unsupported_format("Unsupported format or corrupted file");
}

inline const char *fb2k_ncm::input_ncm::g_get_name() {
return "Netease Music Specific Format (*.ncm) Decoder";
}
Expand Down Expand Up @@ -43,102 +35,23 @@ void input_ncm::open(service_ptr_t<file> p_filehint, const char *p_path, t_input
if (p_reason == t_input_open_reason::input_open_info_write) {
throw exception_io_unsupported_format("Modification on ncm file not supported.");
}
if (!file_ptr_.is_valid()) {
if (!ncm_file_.is_valid()) {
input_open_file_helper(p_filehint, p_path, p_reason, p_abort);
file_ptr_ = fb2k::service_new<ncm_file>(p_filehint);
file_path_ = p_path;
}
file_ptr_->source_->reopen(p_abort);
uint64_t magic = 0;
file_ptr_->source_->read(&magic, sizeof(uint64_t), p_abort);
if (magic != parsed_file_.magic) {
throw_format_error();
ncm_file_ = fb2k::service_new<ncm_file>(p_filehint, p_path);
}
// skip gap
file_ptr_->source_->seek_ex(2, file::t_seek_mode::seek_from_current, p_abort);

// extract rc4 key for audio content decoding
file_ptr_->source_->read(&parsed_file_.rc4_seed_len, sizeof(parsed_file_.rc4_seed_len), p_abort);
if (0 == parsed_file_.rc4_seed_len || parsed_file_.rc4_seed_len > 256) {
throw_format_error();
}
auto data_size = cipher::aligned(parsed_file_.rc4_seed_len);
auto data = std::make_unique<uint8_t[]>(data_size);
file_ptr_->source_->read(data.get(), parsed_file_.rc4_seed_len, p_abort);
std::for_each_n(data.get(), parsed_file_.rc4_seed_len, [](uint8_t &_b) { _b ^= 0x64; });
cipher::make_AES_context_with_key(ncm_rc4_seed_aes_key)
.set_chain_mode(cipher::aes_chain_mode::ECB)
.set_input(data.get(), parsed_file_.rc4_seed_len)
.set_output(data.get(), data_size)
.decrypt_all();
if (strncmp(reinterpret_cast<char *>(data.get()), "neteasecloudmusic", 17)) {
throw_format_error();
}
file_ptr_->rc4_decrypter =
cipher::abnormal_RC4({data.get() + 17, data.get() + parsed_file_.rc4_seed_len -
cipher::guess_padding(data.get() + parsed_file_.rc4_seed_len)});

// get meta info json
file_ptr_->source_->read(&parsed_file_.meta_len, sizeof(parsed_file_.meta_len), p_abort);
if (0 == parsed_file_.meta_len) {
FB2K_console_formatter() << "[WARN] No meta data found in ncm file: " << file_path_;
goto NOMETA;
}
[this, &p_abort] {
auto meta_b64 = std::make_unique<char[]>(parsed_file_.meta_len + 1);
file_ptr_->source_->read(meta_b64.get(), parsed_file_.meta_len, p_abort);
std::for_each_n(meta_b64.get(), parsed_file_.meta_len, [](auto &b) { b ^= 0x63; });
meta_b64[parsed_file_.meta_len] = '\0';
if (strncmp(meta_b64.get(), "163 key(Don't modify):", 22)) {
throw_format_error();
}
auto meta_decrypt_buffer_size = pfc::base64_decode_estimate(meta_b64.get() + 22);
auto meta_raw = std::make_unique<uint8_t[]>(meta_decrypt_buffer_size);
pfc::base64_decode(meta_b64.get() + 22, meta_raw.get());
auto total = cipher::make_AES_context_with_key(ncm_meta_aes_key)
.set_chain_mode(cipher::aes_chain_mode::ECB)
.set_input(meta_raw.get(), meta_decrypt_buffer_size)
.set_output(meta_raw.get(), meta_decrypt_buffer_size)
.decrypt_all()
.outputted_len();
total -= cipher::guess_padding(meta_raw.get() + meta_decrypt_buffer_size);
meta_raw[total] = '\0';
if (strncmp(reinterpret_cast<char *>(meta_raw.get()), "music:", 6)) {
throw_format_error();
}
// skip heading `music:`
meta_str_.assign(reinterpret_cast<char *>(meta_raw.get()) + 6);
meta_json_.Parse(meta_str_.c_str());
if (!meta_json_.IsObject()) {
FB2K_console_formatter() << "[WARN] Failed to parse meta info of ncm file: " << file_path_;
}
}();
NOMETA:
// skip gap
file_ptr_->source_->seek_ex(sizeof(parsed_file_.unknown_data), file::t_seek_mode::seek_from_current, p_abort);
// get album image
file_ptr_->source_->read(&parsed_file_.album_image_size, sizeof(parsed_file_.album_image_size), p_abort);
if (!parsed_file_.album_image_size[0]) {
FB2K_console_formatter() << "[WARN] No album image found in ncm file: " << file_path_;
goto NOALBUMIMG;
}
[this, &p_abort] {
image_data_.resize(parsed_file_.album_image_size[0]);
file_ptr_->source_->read(image_data_.data(), image_data_.size(), p_abort);
}();
NOALBUMIMG:
// remember where audio content starts
file_ptr_->audio_content_offset_ = file_ptr_->source_->get_position(p_abort);
// walk through the file structure
ncm_file_->parse();

// find decoder and info readers
service_list_t<input_entry> input_services;
do {
// there is format hint, so we don't have to find_input twice or more
if (meta_json_.IsObject() && meta_json_.HasMember("format")) {
if (meta_json_["format"] == "flac") {
if (ncm_file_->meta().IsObject() && ncm_file_->meta().HasMember("format")) {
if (ncm_file_->meta()["format"] == "flac") {
input_entry::g_find_inputs_by_content_type(input_services, "audio/flac", false);
break;
} else if (meta_json_["format"] == "mp3") {
} else if (ncm_file_->meta()["format"] == "mp3") {
input_entry::g_find_inputs_by_content_type(input_services, "audio/mpeg", false);
break;
}
Expand All @@ -156,10 +69,10 @@ void input_ncm::open(service_ptr_t<file> p_filehint, const char *p_path, t_input
input_services[i]->cast(input_ptr);
try {
if (input_ptr.is_valid()) {
input_ptr->open_for_decoding(decoder_, file_ptr_, /*file_path_*/ "", p_abort);
input_ptr->open_for_decoding(decoder_, ncm_file_, /*file_path_*/ "", p_abort);
if (decoder_.is_valid()) {
// decoder_->initialize(0, p_flags, p_abort);
FB2K_console_formatter() << "Found decoder for ncm file " << file_path_ << " : \n"
FB2K_console_formatter() << "Found decoder for ncm file " << ncm_file_->path() << " : \n"
<< input_ptr->get_name();
break;
}
Expand All @@ -174,10 +87,10 @@ void input_ncm::open(service_ptr_t<file> p_filehint, const char *p_path, t_input
while (input_enum.next(input_ptr)) {
try {
if (input_ptr.is_valid()) {
input_ptr->open_for_decoding(decoder_, file_ptr_, /*file_path_*/ "", p_abort);
input_ptr->open_for_decoding(decoder_, ncm_file_, /*file_path_*/ "", p_abort);
if (decoder_.is_valid()) {
// decoder_->initialize(0, p_flags, p_abort);
FB2K_console_formatter() << "Found decoder for ncm file " << file_path_ << " : \n"
FB2K_console_formatter() << "Found decoder for ncm file " << ncm_file_->path() << " : \n"
<< input_ptr->get_name();
break;
}
Expand All @@ -191,7 +104,7 @@ void input_ncm::open(service_ptr_t<file> p_filehint, const char *p_path, t_input
if (decoder_.is_empty()) {
throw exception_service_not_found("Failed to find proper audio decoder.");
}
input_ptr->open_for_info_read(source_info_reader_, file_ptr_, /*file_path_*/ "", p_abort);
input_ptr->open_for_info_read(source_info_reader_, ncm_file_, /*file_path_*/ "", p_abort);
}

void input_ncm::decode_seek(double p_seconds, abort_callback &p_abort) {
Expand All @@ -204,59 +117,54 @@ bool input_ncm::decode_run(audio_chunk &p_chunk, abort_callback &p_abort) {
}

void input_ncm::decode_initialize(unsigned p_flags, abort_callback &p_abort) {
if (!file_ptr_->audio_content_offset_) {
// initialize should always follow open
uBugCheck();
}
ncm_file_->ensure_audio_offset();
decoder_->initialize(0, p_flags, p_abort);
// WTF MAGIC HACK here:
// flac decoder keeps complaining format error without this seek
decoder_->seek(1.0 / 100'000'00, p_abort);
decoder_->seek(1.0 / 10'000'000, p_abort);
}

t_filestats input_ncm::get_file_stats(abort_callback &p_abort) {
// return real file stats, which would be displayed on Properties/Location
return file_ptr_->source_->get_stats(p_abort);
return ncm_file_->source_->get_stats(p_abort);
}

void input_ncm::get_info(file_info &p_info, abort_callback &p_abort) {
if (!source_info_reader_.is_valid()) {
return;
}
if (meta_json_.IsNull()) {
if (ncm_file_->meta().IsNull()) {
return;
}
source_info_reader_->get_info(0, p_info, p_abort);
// p_info.set_length(meta_json_["duration"].GetInt() / 1000.0);
// p_info.info_set_bitrate(meta_json_["bitrate"].GetUint64() / 1000);
if (meta_json_.HasMember("artist")) {
// p_info.set_length(meta()["duration"].GetInt() / 1000.0);
// p_info.info_set_bitrate(meta()["bitrate"].GetUint64() / 1000);
if (ncm_file_->meta().HasMember("artist")) {
p_info.meta_remove_field("Artist");
for (auto &v : meta_json_["artist"].GetArray()) {
for (auto &v : ncm_file_->meta()["artist"].GetArray()) {
p_info.meta_add("Artist", v[0].GetString());
}
}
if (meta_json_.HasMember("album")) {
p_info.meta_set("Album", meta_json_["album"].GetString());
if (ncm_file_->meta().HasMember("album")) {
p_info.meta_set("Album", ncm_file_->meta()["album"].GetString());
}
if (meta_json_.HasMember("musicName")) {
p_info.meta_set("Title", meta_json_["musicName"].GetString());
if (ncm_file_->meta().HasMember("musicName")) {
p_info.meta_set("Title", ncm_file_->meta()["musicName"].GetString());
}
if (meta_json_.HasMember("musicId")) {
p_info.info_set("Music ID", PFC_string_formatter() << meta_json_["musicId"].GetUint64());
if (ncm_file_->meta().HasMember("musicId")) {
p_info.info_set("Music ID", PFC_string_formatter() << ncm_file_->meta()["musicId"].GetUint64());
}
if (meta_json_.HasMember("albumId")) {
p_info.info_set("Album ID", PFC_string_formatter() << meta_json_["albumId"].GetUint64());
if (ncm_file_->meta().HasMember("albumId")) {
p_info.info_set("Album ID", PFC_string_formatter() << ncm_file_->meta()["albumId"].GetUint64());
}
if (meta_json_.HasMember("albumPic")) {
p_info.info_set("Album Artwork", meta_json_["albumPic"].GetString());
if (ncm_file_->meta().HasMember("albumPic")) {
p_info.info_set("Album Artwork", ncm_file_->meta()["albumPic"].GetString());
}
if (meta_json_.HasMember("alias")) {
for (auto &v : meta_json_["alias"].GetArray()) {
if (ncm_file_->meta().HasMember("alias")) {
for (auto &v : ncm_file_->meta()["alias"].GetArray()) {
p_info.meta_add("Alias", v.GetString());
}
}
// TODO: find proper way to load album image
//auto album_image = album_art_data_impl::g_create(image_data_.data(), image_data_.size());
}

static input_singletrack_factory_t<input_ncm> g_input_ncm_factory;
14 changes: 2 additions & 12 deletions foo_input_ncm/input_ncm.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#include "common/define.h"
#include "ncm_file.h"

#include "rapidjson/include/rapidjson/document.h"
#include <vector>

namespace fb2k_ncm
Expand All @@ -12,7 +11,7 @@ namespace fb2k_ncm
class input_ncm : public input_stubs {

public:
input_ncm() : file_path_(nullptr) {}
input_ncm() = default;
virtual ~input_ncm(){};

public:
Expand All @@ -21,7 +20,6 @@ namespace fb2k_ncm
static const char *g_get_name();
static bool g_is_our_path(const char *p_path, const char *p_extension);
static const GUID g_get_guid();
;
static bool g_is_our_content_type(const char *p_content_type);
void retag(const file_info &p_info, abort_callback &p_abort);
bool decode_can_seek();
Expand All @@ -32,16 +30,8 @@ namespace fb2k_ncm
void get_info(file_info &p_info, abort_callback &p_abort);

private:
void throw_format_error();

private:
service_ptr_t<ncm_file> file_ptr_;
std::string meta_str_;
const char *file_path_;
ncm_file_st parsed_file_;
service_ptr_t<ncm_file> ncm_file_;
service_ptr_t<input_decoder> decoder_;
service_ptr_t<input_info_reader> source_info_reader_;
rapidjson::Document meta_json_;
std::vector<uint8_t> image_data_;
};
} // namespace fb2k_ncm
Loading

0 comments on commit 174c66b

Please sign in to comment.