From 2fc40896aefd93384b618784838f5eba71b3637e Mon Sep 17 00:00:00 2001 From: pnck Date: Thu, 4 Mar 2021 08:53:52 +0800 Subject: [PATCH] feat: extract album image from ncm file --- foo_input_ncm/album_art_extractor.cpp | 27 ++++ foo_input_ncm/album_art_extractor.h | 18 +++ foo_input_ncm/cipher/abnormal_RC4.cpp | 16 +- foo_input_ncm/cipher/abnormal_RC4.h | 11 +- foo_input_ncm/common/define.h | 15 +- foo_input_ncm/foo_input_ncm.vcxproj | 2 + foo_input_ncm/foo_input_ncm.vcxproj.filters | 6 + foo_input_ncm/input_ncm.cpp | 46 +++--- foo_input_ncm/input_ncm.h | 1 + foo_input_ncm/ncm_file.cpp | 158 +++++++++++--------- foo_input_ncm/ncm_file.h | 17 ++- 11 files changed, 210 insertions(+), 107 deletions(-) create mode 100644 foo_input_ncm/album_art_extractor.cpp create mode 100644 foo_input_ncm/album_art_extractor.h diff --git a/foo_input_ncm/album_art_extractor.cpp b/foo_input_ncm/album_art_extractor.cpp new file mode 100644 index 0000000..e33586a --- /dev/null +++ b/foo_input_ncm/album_art_extractor.cpp @@ -0,0 +1,27 @@ +#include "stdafx.h" +#include "album_art_extractor.h" +#include "ncm_file.h" + +using namespace fb2k_ncm; + +bool ncm_album_art_extractor::is_our_path(const char *p_path, const char *p_extension) { + return stricmp_utf8(p_extension, "ncm") == 0; +} + +album_art_extractor_instance_ptr ncm_album_art_extractor::open(file_ptr p_filehint, const char *p_path, + abort_callback &p_abort) { + if (p_filehint.is_empty()) { + if (is_our_path(p_path, pfc::string_extension(p_path).toString())) { + filesystem::g_open_read(p_filehint, p_path, p_abort); + } + } + auto _ncm_file = fb2k::service_new(p_filehint, p_path); + _ncm_file->parse(ncm_file::parse_contents::NCM_PARSE_ALBUM); + auto album_arts = fb2k::service_new(); + auto image = album_art_data_impl::g_create(_ncm_file->image_data.data(), _ncm_file->image_data.size()); + album_arts->set(album_art_ids::cover_front, image); + // album_arts->set(album_art_ids::disc, image); + return album_arts; +} + +static service_factory_single_t g_ncm_album_art_extractor; \ No newline at end of file diff --git a/foo_input_ncm/album_art_extractor.h b/foo_input_ncm/album_art_extractor.h new file mode 100644 index 0000000..2c31e82 --- /dev/null +++ b/foo_input_ncm/album_art_extractor.h @@ -0,0 +1,18 @@ +#pragma once + +#include "stdafx.h" +#include "ncm_file.h" +#include "common/define.h" + +namespace fb2k_ncm +{ + + class ncm_album_art_extractor : public album_art_extractor { + public: + // GUID get_guid() override; + bool is_our_path(const char *p_path, const char *p_extension) override; + album_art_extractor_instance_ptr open(file_ptr p_filehint, const char *p_path, + abort_callback &p_abort) override; + }; + +} // namespace fb2k_ncm \ No newline at end of file diff --git a/foo_input_ncm/cipher/abnormal_RC4.cpp b/foo_input_ncm/cipher/abnormal_RC4.cpp index c67b179..436e58d 100644 --- a/foo_input_ncm/cipher/abnormal_RC4.cpp +++ b/foo_input_ncm/cipher/abnormal_RC4.cpp @@ -6,7 +6,7 @@ using namespace fb2k_ncm::cipher; -fb2k_ncm::cipher::abnormal_RC4::abnormal_RC4(const uint8_t *seed, size_t len) { +fb2k_ncm::cipher::abnormal_RC4::abnormal_RC4(const uint8_t *seed, size_t len) : abnormal_RC4() { key_seed_.assign(seed, seed + len); len &= 0xff; uint8_t key_box[256]; @@ -34,4 +34,18 @@ std::function fb2k_ncm::cipher::abnormal_RC4::get_tran bool fb2k_ncm::cipher::abnormal_RC4::is_valid() const { return key_seed_.size() && key_box_; +} + +abnormal_RC4::abnormal_RC4(abnormal_RC4 &c) : key_seed(key_seed_), key_seed_(c.key_seed_), key_box_(c.key_box_) {} +abnormal_RC4 &abnormal_RC4::operator=(abnormal_RC4 &c) { + key_seed_ = c.key_seed_; + key_box_ = c.key_box_; + return *this; +} +abnormal_RC4::abnormal_RC4(abnormal_RC4 &&c) + : key_seed(key_seed_), key_seed_(std::move(c.key_seed_)), key_box_(std::move(c.key_box_)) {} +abnormal_RC4 &abnormal_RC4::operator=(abnormal_RC4 &&c) { + key_seed_ = std::move(c.key_seed_); + key_box_ = std::move(c.key_box_); + return *this; } \ No newline at end of file diff --git a/foo_input_ncm/cipher/abnormal_RC4.h b/foo_input_ncm/cipher/abnormal_RC4.h index 0f5a206..5e66b78 100644 --- a/foo_input_ncm/cipher/abnormal_RC4.h +++ b/foo_input_ncm/cipher/abnormal_RC4.h @@ -9,13 +9,20 @@ namespace fb2k_ncm::cipher // special rc4-like BLOCK CIPHER used for ncm files class abnormal_RC4 { public: - abnormal_RC4() = default; + abnormal_RC4() : key_seed(key_seed_) {} abnormal_RC4(const uint8_t *seed, size_t len); abnormal_RC4(const std::vector &seed) : abnormal_RC4(seed.data(), seed.size()) {} - std::function get_transform() const; + abnormal_RC4(abnormal_RC4 &c); + abnormal_RC4 &operator=(abnormal_RC4 &c); + abnormal_RC4(abnormal_RC4 &&c); + abnormal_RC4 &operator=(abnormal_RC4 &&c); public: bool is_valid() const; + std::function get_transform() const; + + public: + const std::vector &key_seed; private: std::vector key_seed_; diff --git a/foo_input_ncm/common/define.h b/foo_input_ncm/common/define.h index ef0483b..56e004e 100644 --- a/foo_input_ncm/common/define.h +++ b/foo_input_ncm/common/define.h @@ -4,8 +4,8 @@ constexpr inline GUID guid_candidates[] = { {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}}, + {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}}, }; @@ -31,12 +31,15 @@ namespace fb2k_ncm uint64_t magic = ncm_magic; uint8_t unknown_padding[2]; uint32_t rc4_seed_len; - uint8_t *rc4_seed_; + const uint8_t *rc4_seed_; uint32_t meta_len; - uint8_t *meta_content; + const uint8_t *meta_content; uint8_t unknown_data[5]; uint32_t album_image_size[2]; // there are 2 exactly same field - uint8_t *album_image; - uint8_t *audio_file; + const uint8_t *album_image; + const uint8_t *audio_content; + // end of original file structure + uint64_t album_image_offset; + uint64_t audio_content_offset; }; } // namespace fb2k_ncm \ No newline at end of file diff --git a/foo_input_ncm/foo_input_ncm.vcxproj b/foo_input_ncm/foo_input_ncm.vcxproj index 994b672..3e3bd08 100644 --- a/foo_input_ncm/foo_input_ncm.vcxproj +++ b/foo_input_ncm/foo_input_ncm.vcxproj @@ -122,6 +122,7 @@ copy $(TargetPath) $(SolutionDir)test\foobar2000\components + @@ -133,6 +134,7 @@ copy $(TargetPath) $(SolutionDir)test\foobar2000\components + NotUsing diff --git a/foo_input_ncm/foo_input_ncm.vcxproj.filters b/foo_input_ncm/foo_input_ncm.vcxproj.filters index bd9b2e5..4082a54 100644 --- a/foo_input_ncm/foo_input_ncm.vcxproj.filters +++ b/foo_input_ncm/foo_input_ncm.vcxproj.filters @@ -48,6 +48,9 @@ Header File + + Header File + @@ -65,5 +68,8 @@ Source File + + Source File + \ No newline at end of file diff --git a/foo_input_ncm/input_ncm.cpp b/foo_input_ncm/input_ncm.cpp index c36687c..8b15ad9 100644 --- a/foo_input_ncm/input_ncm.cpp +++ b/foo_input_ncm/input_ncm.cpp @@ -23,7 +23,10 @@ inline bool fb2k_ncm::input_ncm::g_is_our_content_type(const char *p_content_typ } inline void fb2k_ncm::input_ncm::retag(const file_info &p_info, abort_callback &p_abort) { - throw exception_io_unsupported_format("Modification on ncm file not supported."); + if (source_info_writer_.is_valid()) { + source_info_writer_->set_info(0, p_info, p_abort); + } + // throw exception_io_unsupported_format("Modification on ncm file not supported."); } inline bool fb2k_ncm::input_ncm::decode_can_seek() { @@ -41,17 +44,17 @@ void input_ncm::open(service_ptr_t p_filehint, const char *p_path, t_input } // walk through the file structure - ncm_file_->parse(); + ncm_file_->parse(ncm_file::parse_contents::NCM_PARSE_META | ncm_file::parse_contents::NCM_PARSE_AUDIO); // find decoder and info readers service_list_t input_services; do { // there is format hint, so we don't have to find_input twice or more - if (ncm_file_->meta().IsObject() && ncm_file_->meta().HasMember("format")) { - if (ncm_file_->meta()["format"] == "flac") { + if (ncm_file_->meta_info.IsObject() && ncm_file_->meta_info.HasMember("format")) { + if (ncm_file_->meta_info["format"] == "flac") { input_entry::g_find_inputs_by_content_type(input_services, "audio/flac", false); break; - } else if (ncm_file_->meta()["format"] == "mp3") { + } else if (ncm_file_->meta_info["format"] == "mp3") { input_entry::g_find_inputs_by_content_type(input_services, "audio/mpeg", false); break; } @@ -105,6 +108,8 @@ void input_ncm::open(service_ptr_t p_filehint, const char *p_path, t_input throw exception_service_not_found("Failed to find proper audio decoder."); } input_ptr->open_for_info_read(source_info_reader_, ncm_file_, /*file_path_*/ "", p_abort); + // TODO: support of retagging on audio content (with ncm wrapper and meta info not touched) + // input_ptr->open_for_info_write(source_info_writer_, ncm_file_, /*file_path_*/ "", p_abort); } void input_ncm::decode_seek(double p_seconds, abort_callback &p_abort) { @@ -117,6 +122,7 @@ 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) { + // initialize should always follow open ncm_file_->ensure_audio_offset(); decoder_->initialize(0, p_flags, p_abort); // WTF MAGIC HACK here: @@ -133,35 +139,35 @@ void input_ncm::get_info(file_info &p_info, abort_callback &p_abort) { if (!source_info_reader_.is_valid()) { return; } - if (ncm_file_->meta().IsNull()) { + if (ncm_file_->meta_info.IsNull()) { return; } source_info_reader_->get_info(0, p_info, p_abort); // p_info.set_length(meta()["duration"].GetInt() / 1000.0); // p_info.info_set_bitrate(meta()["bitrate"].GetUint64() / 1000); - if (ncm_file_->meta().HasMember("artist")) { + if (ncm_file_->meta_info.HasMember("artist")) { p_info.meta_remove_field("Artist"); - for (auto &v : ncm_file_->meta()["artist"].GetArray()) { + for (auto &v : ncm_file_->meta_info["artist"].GetArray()) { p_info.meta_add("Artist", v[0].GetString()); } } - if (ncm_file_->meta().HasMember("album")) { - p_info.meta_set("Album", ncm_file_->meta()["album"].GetString()); + if (ncm_file_->meta_info.HasMember("album")) { + p_info.meta_set("Album", ncm_file_->meta_info["album"].GetString()); } - if (ncm_file_->meta().HasMember("musicName")) { - p_info.meta_set("Title", ncm_file_->meta()["musicName"].GetString()); + if (ncm_file_->meta_info.HasMember("musicName")) { + p_info.meta_set("Title", ncm_file_->meta_info["musicName"].GetString()); } - if (ncm_file_->meta().HasMember("musicId")) { - p_info.info_set("Music ID", PFC_string_formatter() << ncm_file_->meta()["musicId"].GetUint64()); + if (ncm_file_->meta_info.HasMember("musicId")) { + p_info.info_set("Music ID", PFC_string_formatter() << ncm_file_->meta_info["musicId"].GetUint64()); } - if (ncm_file_->meta().HasMember("albumId")) { - p_info.info_set("Album ID", PFC_string_formatter() << ncm_file_->meta()["albumId"].GetUint64()); + if (ncm_file_->meta_info.HasMember("albumId")) { + p_info.info_set("Album ID", PFC_string_formatter() << ncm_file_->meta_info["albumId"].GetUint64()); } - if (ncm_file_->meta().HasMember("albumPic")) { - p_info.info_set("Album Artwork", ncm_file_->meta()["albumPic"].GetString()); + if (ncm_file_->meta_info.HasMember("albumPic")) { + p_info.info_set("Album Artwork", ncm_file_->meta_info["albumPic"].GetString()); } - if (ncm_file_->meta().HasMember("alias")) { - for (auto &v : ncm_file_->meta()["alias"].GetArray()) { + if (ncm_file_->meta_info.HasMember("alias")) { + for (auto &v : ncm_file_->meta_info["alias"].GetArray()) { p_info.meta_add("Alias", v.GetString()); } } diff --git a/foo_input_ncm/input_ncm.h b/foo_input_ncm/input_ncm.h index 3a36c1a..e35436e 100644 --- a/foo_input_ncm/input_ncm.h +++ b/foo_input_ncm/input_ncm.h @@ -33,5 +33,6 @@ namespace fb2k_ncm service_ptr_t ncm_file_; service_ptr_t decoder_; service_ptr_t source_info_reader_; + service_ptr_t source_info_writer_; }; } // namespace fb2k_ncm \ No newline at end of file diff --git a/foo_input_ncm/ncm_file.cpp b/foo_input_ncm/ncm_file.cpp index a548ee5..d8be693 100644 --- a/foo_input_ncm/ncm_file.cpp +++ b/foo_input_ncm/ncm_file.cpp @@ -13,7 +13,7 @@ t_size fb2k_ncm::ncm_file::read(void *p_buffer, t_size p_bytes, abort_callback & throw exception_io_data(); } else { auto source_pos = source_->get_position(p_abort); - auto read_offset = source_pos - audio_content_offset_; + auto read_offset = source_pos - parsed_file_.audio_content_offset; auto _tmp = std::make_unique(p_bytes); t_size total = 0; total = source_->read(_tmp.get(), p_bytes, p_abort); @@ -26,31 +26,31 @@ t_size fb2k_ncm::ncm_file::read(void *p_buffer, t_size p_bytes, abort_callback & void fb2k_ncm::ncm_file::write(const void *p_buffer, t_size p_bytes, abort_callback &p_abort) { auto source_pos = source_->get_position(p_abort); - if (source_pos < audio_content_offset_) { - throw exception_io_write_protected("Modifying ncm file not supported"); + if (source_pos < parsed_file_.audio_content_offset) { + throw exception_io_write_protected("Modification on ncm file not supported."); } - auto write_offset = source_pos - audio_content_offset_; + auto write_offset = source_pos - parsed_file_.audio_content_offset; auto _tmp = std::make_unique(p_bytes); for (size_t i = 0; i < p_bytes; ++i) { - _tmp[i] = rc4_decrypter_.get_transform()(static_cast(p_buffer)[i], (write_offset + i) & 0xff); + _tmp[i] = rc4_decrypter_.get_transform()(static_cast(p_buffer)[i], (write_offset & 0xff) + i); } return source_->write(_tmp.get(), p_bytes, p_abort); } t_filesize fb2k_ncm::ncm_file::get_size(abort_callback &p_abort) { - return source_->get_size(p_abort) - audio_content_offset_; + return source_->get_size(p_abort) - parsed_file_.audio_content_offset; } t_filesize fb2k_ncm::ncm_file::get_position(abort_callback &p_abort) { - return source_->get_position(p_abort) - audio_content_offset_; + return source_->get_position(p_abort) - parsed_file_.audio_content_offset; } void fb2k_ncm::ncm_file::resize(t_filesize p_size, abort_callback &p_abort) { - throw exception_io_write_protected("Modifying ncm file not supported"); + throw exception_io_write_protected("Modification on ncm file not supported."); } void fb2k_ncm::ncm_file::seek(t_filesize p_position, abort_callback &p_abort) { - return source_->seek(audio_content_offset_ + p_position, p_abort); + return source_->seek(parsed_file_.audio_content_offset + p_position, p_abort); } bool fb2k_ncm::ncm_file::can_seek() { @@ -64,7 +64,7 @@ bool fb2k_ncm::ncm_file::get_content_type(pfc::string_base &p_out) { void fb2k_ncm::ncm_file::reopen(abort_callback &p_abort) { source_->reopen(p_abort); - source_->seek(audio_content_offset_, p_abort); + source_->seek(parsed_file_.audio_content_offset, p_abort); } bool fb2k_ncm::ncm_file::is_remote() { @@ -75,9 +75,8 @@ t_filetimestamp fb2k_ncm::ncm_file::get_timestamp(abort_callback &p_abort) { return source_->get_timestamp(p_abort); } -PFC_NORETURN void ncm_file::ensure_audio_offset() { - if (!audio_content_offset_) { - // initialize should always follow open +void ncm_file::ensure_audio_offset() { + if (!parsed_file_.audio_content_offset) { uBugCheck(); } } @@ -86,7 +85,7 @@ inline void ncm_file::throw_format_error() { throw exception_io_unsupported_format("Unsupported format or corrupted file"); } -void ncm_file::parse() { +void ncm_file::parse(uint16_t to_parse /* = 0xff*/) { abort_callback_dummy p_abort; source_->reopen(p_abort); uint64_t magic = 0; @@ -99,74 +98,87 @@ void ncm_file::parse() { // extract rc4 key for audio content decoding 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(data_size); - 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(data.get()), "neteasecloudmusic", 17)) { - throw_format_error(); - } - 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 - 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: " << this_path_; - goto NOMETA; - } - [this, &p_abort] { - auto meta_b64 = std::make_unique(parsed_file_.meta_len + 1); - 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)) { + if (to_parse & parse_contents::NCM_PARSE_AUDIO) { + if (0 == parsed_file_.rc4_seed_len || parsed_file_.rc4_seed_len > 256) { throw_format_error(); } - auto meta_decrypt_buffer_size = pfc::base64_decode_estimate(meta_b64.get() + 22); - auto meta_raw = std::make_unique(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(meta_raw.get()), "music:", 6)) { + auto data_size = cipher::aligned(parsed_file_.rc4_seed_len); + auto data = std::make_unique(data_size); + 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(data.get()), "neteasecloudmusic", 17)) { throw_format_error(); } - // skip heading `music:` - meta_str_.assign(reinterpret_cast(meta_raw.get()) + 6); - parsed_file_.meta_content = reinterpret_cast(meta_str_.data()); - meta_json_.Parse(meta_str_.c_str()); - if (!meta_json_.IsObject()) { - FB2K_console_formatter() << "[WARN] Failed to parse meta info of ncm file: " << this_path_; + 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)}); + parsed_file_.rc4_seed_ = rc4_decrypter_.key_seed.data(); + } else { + source_->seek_ex(parsed_file_.rc4_seed_len, file::t_seek_mode::seek_from_current, p_abort); + } + // get meta info json + source_->read(&parsed_file_.meta_len, sizeof(parsed_file_.meta_len), p_abort); + if (to_parse & parse_contents::NCM_PARSE_META) { + if (0 == parsed_file_.meta_len) { + FB2K_console_formatter() << "[WARN] No meta data found in ncm file: " << this_path_; + goto ENDMETA; + } else { + auto meta_b64 = std::make_unique(parsed_file_.meta_len + 1); + 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(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(meta_raw.get()), "music:", 6)) { + throw_format_error(); + } + // skip heading `music:` + meta_str_.assign(reinterpret_cast(meta_raw.get()) + 6); + parsed_file_.meta_content = reinterpret_cast(meta_str_.data()); + meta_json_.Parse(meta_str_.c_str()); + if (!meta_json_.IsObject()) { + FB2K_console_formatter() << "[WARN] Failed to parse meta info of ncm file: " << this_path_; + } } - }(); -NOMETA: + } else { + source_->seek_ex(parsed_file_.meta_len, file::t_seek_mode::seek_from_current, p_abort); + } + +ENDMETA: // skip gap source_->seek_ex(sizeof(parsed_file_.unknown_data), file::t_seek_mode::seek_from_current, p_abort); // get album image 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: " << this_path_; - goto NOALBUMIMG; + if (to_parse & parse_contents::NCM_PARSE_ALBUM) { + if (!parsed_file_.album_image_size[0]) { + FB2K_console_formatter() << "[WARN] No album image found in ncm file: " << this_path_; + goto ENDIMG; + } + parsed_file_.album_image_offset = source_->get_position(p_abort); + image_data_.resize(parsed_file_.album_image_size[0]); + source_->read(image_data_.data(), image_data_.size(), p_abort); + parsed_file_.album_image = image_data_.data(); + } else { + source_->seek_ex(parsed_file_.album_image_size[0], file::t_seek_mode::seek_from_current, p_abort); } - image_data_.resize(parsed_file_.album_image_size[0]); - source_->read(image_data_.data(), image_data_.size(), p_abort); - parsed_file_.album_image = image_data_.data(); -NOALBUMIMG: +ENDIMG: // remember where audio content starts - audio_content_offset_ = source_->get_position(p_abort); + parsed_file_.audio_content_offset = source_->get_position(p_abort); } \ No newline at end of file diff --git a/foo_input_ncm/ncm_file.h b/foo_input_ncm/ncm_file.h index 6f0b944..297fb05 100644 --- a/foo_input_ncm/ncm_file.h +++ b/foo_input_ncm/ncm_file.h @@ -8,6 +8,13 @@ namespace fb2k_ncm { class ncm_file : public file { + public: + enum parse_contents { + NCM_PARSE_META = 0b1, + NCM_PARSE_ALBUM = 0b10, + NCM_PARSE_AUDIO = 0b100, + }; + public: t_size read(void *p_buffer, t_size p_bytes, abort_callback &p_abort) override; void write(const void *p_buffer, t_size p_bytes, abort_callback &p_abort) override; @@ -23,22 +30,22 @@ namespace fb2k_ncm public: explicit ncm_file(file_ptr &source, const char *path) - : source_(source), this_path_(path), audio_content_offset_(0) {} + : source_(source), this_path_(path), meta_info(meta_json_), image_data(image_data_) {} const char *path() const { return this_path_; } - PFC_NORETURN void ensure_audio_offset(); - void parse(); - inline const rapidjson::Document &meta() const { return meta_json_; } + void ensure_audio_offset(); + void parse(uint16_t to_parse = 0xffff); private: inline void throw_format_error(); public: file_ptr source_; + const rapidjson::Document &meta_info; + const std::vector &image_data; private: const char *this_path_; ncm_file_st parsed_file_; - t_filesize audio_content_offset_; std::string meta_str_; rapidjson::Document meta_json_; cipher::abnormal_RC4 rc4_decrypter_;