diff --git a/example/csharp/NeteaseCrypt.cs b/example/csharp/NeteaseCrypt.cs
index f78439c..db5bb0e 100644
--- a/example/csharp/NeteaseCrypt.cs
+++ b/example/csharp/NeteaseCrypt.cs
@@ -14,7 +14,7 @@ namespace libncmdump_demo_cli
private static extern IntPtr CreateNeteaseCrypt(IntPtr path);
[DllImport(DLL_PATH, CallingConvention = CallingConvention.Cdecl)]
- private static extern int Dump(IntPtr NeteaseCrypt);
+ private static extern int Dump(IntPtr NeteaseCrypt, IntPtr outputPath);
[DllImport(DLL_PATH, CallingConvention = CallingConvention.Cdecl)]
private static extern void FixMetadata(IntPtr NeteaseCrypt);
@@ -42,10 +42,17 @@ namespace libncmdump_demo_cli
///
/// 启动转换过程。
///
+ /// 指定一个路径输出,如果为空,则输出到原路径
/// 返回一个整数,指示转储过程的结果。如果成功,返回0;如果失败,返回1。
- public int Dump()
+ public int Dump(string OutputPath)
{
- return Dump(NeteaseCryptClass);
+ byte[] bytes = Encoding.UTF8.GetBytes(OutputPath);
+
+ IntPtr outputPtr = Marshal.AllocHGlobal(bytes.Length + 1);
+ Marshal.Copy(bytes, 0, outputPtr, bytes.Length);
+ Marshal.WriteByte(outputPtr, bytes.Length, 0);
+
+ return Dump(NeteaseCryptClass, outputPtr);
}
///
diff --git a/example/csharp/Program.cs b/example/csharp/Program.cs
index 8665adb..b296eed 100644
--- a/example/csharp/Program.cs
+++ b/example/csharp/Program.cs
@@ -14,7 +14,7 @@ namespace libncmdump_demo_cli
NeteaseCrypt neteaseCrypt = new NeteaseCrypt(filePath);
// 启动转换过程
- int result = neteaseCrypt.Dump();
+ int result = neteaseCrypt.Dump(""); // 为空则输出到源
// 修复元数据
neteaseCrypt.FixMetadata();
diff --git a/src/include/cxxopts.hpp b/src/include/cxxopts.hpp
new file mode 100644
index 0000000..a2e71fa
--- /dev/null
+++ b/src/include/cxxopts.hpp
@@ -0,0 +1,2909 @@
+/*
+
+Copyright (c) 2014-2022 Jarryd Beck
+
+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.
+
+*/
+
+// vim: ts=2:sw=2:expandtab
+
+#ifndef CXXOPTS_HPP_INCLUDED
+#define CXXOPTS_HPP_INCLUDED
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef CXXOPTS_NO_EXCEPTIONS
+#include
+#endif
+
+#if defined(__GNUC__) && !defined(__clang__)
+# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49
+# define CXXOPTS_NO_REGEX true
+# endif
+#endif
+#if defined(_MSC_VER) && !defined(__clang__)
+#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern
+#define CXXOPTS_LINKONCE __declspec(selectany) extern
+#else
+#define CXXOPTS_LINKONCE_CONST
+#define CXXOPTS_LINKONCE
+#endif
+
+#ifndef CXXOPTS_NO_REGEX
+# include
+#endif // CXXOPTS_NO_REGEX
+
+// Nonstandard before C++17, which is coincidentally what we also need for
+#ifdef __has_include
+# if __has_include()
+# include
+# ifdef __cpp_lib_optional
+# define CXXOPTS_HAS_OPTIONAL
+# endif
+# endif
+#endif
+
+#define CXXOPTS_FALLTHROUGH
+#ifdef __has_cpp_attribute
+ #if __has_cpp_attribute(fallthrough)
+ #undef CXXOPTS_FALLTHROUGH
+ #define CXXOPTS_FALLTHROUGH [[fallthrough]]
+ #endif
+#endif
+
+#if __cplusplus >= 201603L
+#define CXXOPTS_NODISCARD [[nodiscard]]
+#else
+#define CXXOPTS_NODISCARD
+#endif
+
+#ifndef CXXOPTS_VECTOR_DELIMITER
+#define CXXOPTS_VECTOR_DELIMITER ','
+#endif
+
+#define CXXOPTS__VERSION_MAJOR 3
+#define CXXOPTS__VERSION_MINOR 2
+#define CXXOPTS__VERSION_PATCH 1
+
+#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6
+ #define CXXOPTS_NULL_DEREF_IGNORE
+#endif
+
+#if defined(__GNUC__)
+#define DO_PRAGMA(x) _Pragma(#x)
+#define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push)
+#define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop)
+#define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x)
+#else
+// define other compilers here if needed
+#define CXXOPTS_DIAGNOSTIC_PUSH
+#define CXXOPTS_DIAGNOSTIC_POP
+#define CXXOPTS_IGNORE_WARNING(x)
+#endif
+
+#ifdef CXXOPTS_NO_RTTI
+#define CXXOPTS_RTTI_CAST static_cast
+#else
+#define CXXOPTS_RTTI_CAST dynamic_cast
+#endif
+
+namespace cxxopts {
+static constexpr struct {
+ uint8_t major, minor, patch;
+} version = {
+ CXXOPTS__VERSION_MAJOR,
+ CXXOPTS__VERSION_MINOR,
+ CXXOPTS__VERSION_PATCH
+};
+} // namespace cxxopts
+
+//when we ask cxxopts to use Unicode, help strings are processed using ICU,
+//which results in the correct lengths being computed for strings when they
+//are formatted for the help output
+//it is necessary to make sure that can be found by the
+//compiler, and that icu-uc is linked in to the binary.
+
+#ifdef CXXOPTS_USE_UNICODE
+#include
+
+namespace cxxopts {
+
+using String = icu::UnicodeString;
+
+inline
+String
+toLocalString(std::string s)
+{
+ return icu::UnicodeString::fromUTF8(std::move(s));
+}
+
+// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it:
+// warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor
+CXXOPTS_DIAGNOSTIC_PUSH
+CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor")
+// This will be ignored under other compilers like LLVM clang.
+class UnicodeStringIterator
+{
+ public:
+
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = int32_t;
+ using difference_type = std::ptrdiff_t;
+ using pointer = value_type*;
+ using reference = value_type&;
+
+ UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos)
+ : s(string)
+ , i(pos)
+ {
+ }
+
+ value_type
+ operator*() const
+ {
+ return s->char32At(i);
+ }
+
+ bool
+ operator==(const UnicodeStringIterator& rhs) const
+ {
+ return s == rhs.s && i == rhs.i;
+ }
+
+ bool
+ operator!=(const UnicodeStringIterator& rhs) const
+ {
+ return !(*this == rhs);
+ }
+
+ UnicodeStringIterator&
+ operator++()
+ {
+ ++i;
+ return *this;
+ }
+
+ UnicodeStringIterator
+ operator+(int32_t v)
+ {
+ return UnicodeStringIterator(s, i + v);
+ }
+
+ private:
+ const icu::UnicodeString* s;
+ int32_t i;
+};
+CXXOPTS_DIAGNOSTIC_POP
+
+inline
+String&
+stringAppend(String&s, String a)
+{
+ return s.append(std::move(a));
+}
+
+inline
+String&
+stringAppend(String& s, std::size_t n, UChar32 c)
+{
+ for (std::size_t i = 0; i != n; ++i)
+ {
+ s.append(c);
+ }
+
+ return s;
+}
+
+template
+String&
+stringAppend(String& s, Iterator begin, Iterator end)
+{
+ while (begin != end)
+ {
+ s.append(*begin);
+ ++begin;
+ }
+
+ return s;
+}
+
+inline
+size_t
+stringLength(const String& s)
+{
+ return static_cast(s.length());
+}
+
+inline
+std::string
+toUTF8String(const String& s)
+{
+ std::string result;
+ s.toUTF8String(result);
+
+ return result;
+}
+
+inline
+bool
+empty(const String& s)
+{
+ return s.isEmpty();
+}
+
+} // namespace cxxopts
+
+namespace std {
+
+inline
+cxxopts::UnicodeStringIterator
+begin(const icu::UnicodeString& s)
+{
+ return cxxopts::UnicodeStringIterator(&s, 0);
+}
+
+inline
+cxxopts::UnicodeStringIterator
+end(const icu::UnicodeString& s)
+{
+ return cxxopts::UnicodeStringIterator(&s, s.length());
+}
+
+} // namespace std
+
+//ifdef CXXOPTS_USE_UNICODE
+#else
+
+namespace cxxopts {
+
+using String = std::string;
+
+template
+T
+toLocalString(T&& t)
+{
+ return std::forward(t);
+}
+
+inline
+std::size_t
+stringLength(const String& s)
+{
+ return s.length();
+}
+
+inline
+String&
+stringAppend(String&s, const String& a)
+{
+ return s.append(a);
+}
+
+inline
+String&
+stringAppend(String& s, std::size_t n, char c)
+{
+ return s.append(n, c);
+}
+
+template
+String&
+stringAppend(String& s, Iterator begin, Iterator end)
+{
+ return s.append(begin, end);
+}
+
+template
+std::string
+toUTF8String(T&& t)
+{
+ return std::forward(t);
+}
+
+inline
+bool
+empty(const std::string& s)
+{
+ return s.empty();
+}
+
+} // namespace cxxopts
+
+//ifdef CXXOPTS_USE_UNICODE
+#endif
+
+namespace cxxopts {
+
+namespace {
+CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'");
+CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'");
+} // namespace
+
+// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we
+// want to silence it: warning: base class 'class
+// std::enable_shared_from_this' has accessible non-virtual
+// destructor This will be ignored under other compilers like LLVM clang.
+CXXOPTS_DIAGNOSTIC_PUSH
+CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor")
+
+// some older versions of GCC warn under this warning
+CXXOPTS_IGNORE_WARNING("-Weffc++")
+class Value : public std::enable_shared_from_this
+{
+ public:
+
+ virtual ~Value() = default;
+
+ virtual
+ std::shared_ptr
+ clone() const = 0;
+
+ virtual void
+ add(const std::string& text) const = 0;
+
+ virtual void
+ parse(const std::string& text) const = 0;
+
+ virtual void
+ parse() const = 0;
+
+ virtual bool
+ has_default() const = 0;
+
+ virtual bool
+ is_container() const = 0;
+
+ virtual bool
+ has_implicit() const = 0;
+
+ virtual std::string
+ get_default_value() const = 0;
+
+ virtual std::string
+ get_implicit_value() const = 0;
+
+ virtual std::shared_ptr
+ default_value(const std::string& value) = 0;
+
+ virtual std::shared_ptr
+ implicit_value(const std::string& value) = 0;
+
+ virtual std::shared_ptr
+ no_implicit_value() = 0;
+
+ virtual bool
+ is_boolean() const = 0;
+};
+
+CXXOPTS_DIAGNOSTIC_POP
+
+namespace exceptions {
+
+class exception : public std::exception
+{
+ public:
+ explicit exception(std::string message)
+ : m_message(std::move(message))
+ {
+ }
+
+ CXXOPTS_NODISCARD
+ const char*
+ what() const noexcept override
+ {
+ return m_message.c_str();
+ }
+
+ private:
+ std::string m_message;
+};
+
+class specification : public exception
+{
+ public:
+
+ explicit specification(const std::string& message)
+ : exception(message)
+ {
+ }
+};
+
+class parsing : public exception
+{
+ public:
+ explicit parsing(const std::string& message)
+ : exception(message)
+ {
+ }
+};
+
+class option_already_exists : public specification
+{
+ public:
+ explicit option_already_exists(const std::string& option)
+ : specification("Option " + LQUOTE + option + RQUOTE + " already exists")
+ {
+ }
+};
+
+class invalid_option_format : public specification
+{
+ public:
+ explicit invalid_option_format(const std::string& format)
+ : specification("Invalid option format " + LQUOTE + format + RQUOTE)
+ {
+ }
+};
+
+class invalid_option_syntax : public parsing {
+ public:
+ explicit invalid_option_syntax(const std::string& text)
+ : parsing("Argument " + LQUOTE + text + RQUOTE +
+ " starts with a - but has incorrect syntax")
+ {
+ }
+};
+
+class no_such_option : public parsing
+{
+ public:
+ explicit no_such_option(const std::string& option)
+ : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist")
+ {
+ }
+};
+
+class missing_argument : public parsing
+{
+ public:
+ explicit missing_argument(const std::string& option)
+ : parsing(
+ "Option " + LQUOTE + option + RQUOTE + " is missing an argument"
+ )
+ {
+ }
+};
+
+class option_requires_argument : public parsing
+{
+ public:
+ explicit option_requires_argument(const std::string& option)
+ : parsing(
+ "Option " + LQUOTE + option + RQUOTE + " requires an argument"
+ )
+ {
+ }
+};
+
+class gratuitous_argument_for_option : public parsing
+{
+ public:
+ gratuitous_argument_for_option
+ (
+ const std::string& option,
+ const std::string& arg
+ )
+ : parsing(
+ "Option " + LQUOTE + option + RQUOTE +
+ " does not take an argument, but argument " +
+ LQUOTE + arg + RQUOTE + " given"
+ )
+ {
+ }
+};
+
+class requested_option_not_present : public parsing
+{
+ public:
+ explicit requested_option_not_present(const std::string& option)
+ : parsing("Option " + LQUOTE + option + RQUOTE + " not present")
+ {
+ }
+};
+
+class option_has_no_value : public exception
+{
+ public:
+ explicit option_has_no_value(const std::string& option)
+ : exception(
+ !option.empty() ?
+ ("Option " + LQUOTE + option + RQUOTE + " has no value") :
+ "Option has no value")
+ {
+ }
+};
+
+class incorrect_argument_type : public parsing
+{
+ public:
+ explicit incorrect_argument_type
+ (
+ const std::string& arg
+ )
+ : parsing(
+ "Argument " + LQUOTE + arg + RQUOTE + " failed to parse"
+ )
+ {
+ }
+};
+
+} // namespace exceptions
+
+
+template
+void throw_or_mimic(const std::string& text)
+{
+ static_assert(std::is_base_of::value,
+ "throw_or_mimic only works on std::exception and "
+ "deriving classes");
+
+#ifndef CXXOPTS_NO_EXCEPTIONS
+ // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw
+ throw T{text};
+#else
+ // Otherwise manually instantiate the exception, print what() to stderr,
+ // and exit
+ T exception{text};
+ std::cerr << exception.what() << std::endl;
+ std::exit(EXIT_FAILURE);
+#endif
+}
+
+using OptionNames = std::vector;
+
+namespace values {
+
+namespace parser_tool {
+
+struct IntegerDesc
+{
+ std::string negative = "";
+ std::string base = "";
+ std::string value = "";
+};
+struct ArguDesc {
+ std::string arg_name = "";
+ bool grouping = false;
+ bool set_value = false;
+ std::string value = "";
+};
+
+#ifdef CXXOPTS_NO_REGEX
+inline IntegerDesc SplitInteger(const std::string &text)
+{
+ if (text.empty())
+ {
+ throw_or_mimic(text);
+ }
+ IntegerDesc desc;
+ const char *pdata = text.c_str();
+ if (*pdata == '-')
+ {
+ pdata += 1;
+ desc.negative = "-";
+ }
+ if (strncmp(pdata, "0x", 2) == 0)
+ {
+ pdata += 2;
+ desc.base = "0x";
+ }
+ if (*pdata != '\0')
+ {
+ desc.value = std::string(pdata);
+ }
+ else
+ {
+ throw_or_mimic(text);
+ }
+ return desc;
+}
+
+inline bool IsTrueText(const std::string &text)
+{
+ const char *pdata = text.c_str();
+ if (*pdata == 't' || *pdata == 'T')
+ {
+ pdata += 1;
+ if (strncmp(pdata, "rue\0", 4) == 0)
+ {
+ return true;
+ }
+ }
+ else if (strncmp(pdata, "1\0", 2) == 0)
+ {
+ return true;
+ }
+ return false;
+}
+
+inline bool IsFalseText(const std::string &text)
+{
+ const char *pdata = text.c_str();
+ if (*pdata == 'f' || *pdata == 'F')
+ {
+ pdata += 1;
+ if (strncmp(pdata, "alse\0", 5) == 0)
+ {
+ return true;
+ }
+ }
+ else if (strncmp(pdata, "0\0", 2) == 0)
+ {
+ return true;
+ }
+ return false;
+}
+
+inline OptionNames split_option_names(const std::string &text)
+{
+ OptionNames split_names;
+
+ std::string::size_type token_start_pos = 0;
+ auto length = text.length();
+
+ if (length == 0)
+ {
+ throw_or_mimic(text);
+ }
+
+ while (token_start_pos < length) {
+ const auto &npos = std::string::npos;
+ auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos);
+ if (next_non_space_pos == npos) {
+ throw_or_mimic(text);
+ }
+ token_start_pos = next_non_space_pos;
+ auto next_delimiter_pos = text.find(',', token_start_pos);
+ if (next_delimiter_pos == token_start_pos) {
+ throw_or_mimic(text);
+ }
+ if (next_delimiter_pos == npos) {
+ next_delimiter_pos = length;
+ }
+ auto token_length = next_delimiter_pos - token_start_pos;
+ // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/
+ {
+ const char* option_name_valid_chars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "_-.?";
+
+ if (!std::isalnum(text[token_start_pos], std::locale::classic()) ||
+ text.find_first_not_of(option_name_valid_chars, token_start_pos) < next_delimiter_pos) {
+ throw_or_mimic(text);
+ }
+ }
+ split_names.emplace_back(text.substr(token_start_pos, token_length));
+ token_start_pos = next_delimiter_pos + 1;
+ }
+ return split_names;
+}
+
+inline ArguDesc ParseArgument(const char *arg, bool &matched)
+{
+ ArguDesc argu_desc;
+ const char *pdata = arg;
+ matched = false;
+ if (strncmp(pdata, "--", 2) == 0)
+ {
+ pdata += 2;
+ if (isalnum(*pdata, std::locale::classic()))
+ {
+ argu_desc.arg_name.push_back(*pdata);
+ pdata += 1;
+ while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || *pdata == '_')
+ {
+ argu_desc.arg_name.push_back(*pdata);
+ pdata += 1;
+ }
+ if (argu_desc.arg_name.length() > 1)
+ {
+ if (*pdata == '=')
+ {
+ argu_desc.set_value = true;
+ pdata += 1;
+ if (*pdata != '\0')
+ {
+ argu_desc.value = std::string(pdata);
+ }
+ matched = true;
+ }
+ else if (*pdata == '\0')
+ {
+ matched = true;
+ }
+ }
+ }
+ }
+ else if (strncmp(pdata, "-", 1) == 0)
+ {
+ pdata += 1;
+ argu_desc.grouping = true;
+ while (isalnum(*pdata, std::locale::classic()))
+ {
+ argu_desc.arg_name.push_back(*pdata);
+ pdata += 1;
+ }
+ matched = !argu_desc.arg_name.empty() && *pdata == '\0';
+ }
+ return argu_desc;
+}
+
+#else // CXXOPTS_NO_REGEX
+
+namespace {
+CXXOPTS_LINKONCE
+const char* const integer_pattern =
+ "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)";
+CXXOPTS_LINKONCE
+const char* const truthy_pattern =
+ "(t|T)(rue)?|1";
+CXXOPTS_LINKONCE
+const char* const falsy_pattern =
+ "(f|F)(alse)?|0";
+CXXOPTS_LINKONCE
+const char* const option_pattern =
+ "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)";
+CXXOPTS_LINKONCE
+const char* const option_specifier_pattern =
+ "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*";
+CXXOPTS_LINKONCE
+const char* const option_specifier_separator_pattern = ", *";
+
+} // namespace
+
+inline IntegerDesc SplitInteger(const std::string &text)
+{
+ static const std::basic_regex integer_matcher(integer_pattern);
+
+ std::smatch match;
+ std::regex_match(text, match, integer_matcher);
+
+ if (match.length() == 0)
+ {
+ throw_or_mimic(text);
+ }
+
+ IntegerDesc desc;
+ desc.negative = match[1];
+ desc.base = match[2];
+ desc.value = match[3];
+
+ if (match.length(4) > 0)
+ {
+ desc.base = match[5];
+ desc.value = "0";
+ return desc;
+ }
+
+ return desc;
+}
+
+inline bool IsTrueText(const std::string &text)
+{
+ static const std::basic_regex truthy_matcher(truthy_pattern);
+ std::smatch result;
+ std::regex_match(text, result, truthy_matcher);
+ return !result.empty();
+}
+
+inline bool IsFalseText(const std::string &text)
+{
+ static const std::basic_regex falsy_matcher(falsy_pattern);
+ std::smatch result;
+ std::regex_match(text, result, falsy_matcher);
+ return !result.empty();
+}
+
+// Gets the option names specified via a single, comma-separated string,
+// and returns the separate, space-discarded, non-empty names
+// (without considering which or how many are single-character)
+inline OptionNames split_option_names(const std::string &text)
+{
+ static const std::basic_regex option_specifier_matcher(option_specifier_pattern);
+ if (!std::regex_match(text.c_str(), option_specifier_matcher))
+ {
+ throw_or_mimic(text);
+ }
+
+ OptionNames split_names;
+
+ static const std::basic_regex option_specifier_separator_matcher(option_specifier_separator_pattern);
+ constexpr int use_non_matches { -1 };
+ auto token_iterator = std::sregex_token_iterator(
+ text.begin(), text.end(), option_specifier_separator_matcher, use_non_matches);
+ std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names));
+ return split_names;
+}
+
+inline ArguDesc ParseArgument(const char *arg, bool &matched)
+{
+ static const std::basic_regex option_matcher(option_pattern);
+ std::match_results result;
+ std::regex_match(arg, result, option_matcher);
+ matched = !result.empty();
+
+ ArguDesc argu_desc;
+ if (matched) {
+ argu_desc.arg_name = result[1].str();
+ argu_desc.set_value = result[2].length() > 0;
+ argu_desc.value = result[3].str();
+ if (result[4].length() > 0)
+ {
+ argu_desc.grouping = true;
+ argu_desc.arg_name = result[4].str();
+ }
+ }
+
+ return argu_desc;
+}
+
+#endif // CXXOPTS_NO_REGEX
+#undef CXXOPTS_NO_REGEX
+} // namespace parser_tool
+
+namespace detail {
+
+template
+struct SignedCheck;
+
+template
+struct SignedCheck
+{
+ template
+ void
+ operator()(bool negative, U u, const std::string& text)
+ {
+ if (negative)
+ {
+ if (u > static_cast((std::numeric_limits::min)()))
+ {
+ throw_or_mimic(text);
+ }
+ }
+ else
+ {
+ if (u > static_cast((std::numeric_limits::max)()))
+ {
+ throw_or_mimic(text);
+ }
+ }
+ }
+};
+
+template
+struct SignedCheck
+{
+ template
+ void
+ operator()(bool, U, const std::string&) const {}
+};
+
+template
+void
+check_signed_range(bool negative, U value, const std::string& text)
+{
+ SignedCheck::is_signed>()(negative, value, text);
+}
+
+} // namespace detail
+
+template
+void
+checked_negate(R& r, T&& t, const std::string&, std::true_type)
+{
+ // if we got to here, then `t` is a positive number that fits into
+ // `R`. So to avoid MSVC C4146, we first cast it to `R`.
+ // See https://github.com/jarro2783/cxxopts/issues/62 for more details.
+ r = static_cast(-static_cast(t-1)-1);
+}
+
+template
+void
+checked_negate(R&, T&&, const std::string& text, std::false_type)
+{
+ throw_or_mimic(text);
+}
+
+template
+void
+integer_parser(const std::string& text, T& value)
+{
+ parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text);
+
+ using US = typename std::make_unsigned::type;
+ constexpr bool is_signed = std::numeric_limits::is_signed;
+
+ const bool negative = int_desc.negative.length() > 0;
+ const uint8_t base = int_desc.base.length() > 0 ? 16 : 10;
+ const std::string & value_match = int_desc.value;
+
+ US result = 0;
+
+ for (char ch : value_match)
+ {
+ US digit = 0;
+
+ if (ch >= '0' && ch <= '9')
+ {
+ digit = static_cast(ch - '0');
+ }
+ else if (base == 16 && ch >= 'a' && ch <= 'f')
+ {
+ digit = static_cast(ch - 'a' + 10);
+ }
+ else if (base == 16 && ch >= 'A' && ch <= 'F')
+ {
+ digit = static_cast(ch - 'A' + 10);
+ }
+ else
+ {
+ throw_or_mimic(text);
+ }
+
+ US limit = 0;
+ if (negative)
+ {
+ limit = static_cast(std::abs(static_cast((std::numeric_limits::min)())));
+ }
+ else
+ {
+ limit = (std::numeric_limits::max)();
+ }
+
+ if (base != 0 && result > limit / base)
+ {
+ throw_or_mimic(text);
+ }
+ if (result * base > limit - digit)
+ {
+ throw_or_mimic(text);
+ }
+
+ result = static_cast(result * base + digit);
+ }
+
+ detail::check_signed_range(negative, result, text);
+
+ if (negative)
+ {
+ checked_negate(value, result, text, std::integral_constant());
+ }
+ else
+ {
+ value = static_cast(result);
+ }
+}
+
+template
+void stringstream_parser(const std::string& text, T& value)
+{
+ std::stringstream in(text);
+ in >> value;
+ if (!in) {
+ throw_or_mimic(text);
+ }
+}
+
+template ::value>::type* = nullptr
+ >
+void parse_value(const std::string& text, T& value)
+{
+ integer_parser(text, value);
+}
+
+inline
+void
+parse_value(const std::string& text, bool& value)
+{
+ if (parser_tool::IsTrueText(text))
+ {
+ value = true;
+ return;
+ }
+
+ if (parser_tool::IsFalseText(text))
+ {
+ value = false;
+ return;
+ }
+
+ throw_or_mimic(text);
+}
+
+inline
+void
+parse_value(const std::string& text, std::string& value)
+{
+ value = text;
+}
+
+// The fallback parser. It uses the stringstream parser to parse all types
+// that have not been overloaded explicitly. It has to be placed in the
+// source code before all other more specialized templates.
+template ::value>::type* = nullptr
+ >
+void
+parse_value(const std::string& text, T& value) {
+ stringstream_parser(text, value);
+}
+
+#ifdef CXXOPTS_HAS_OPTIONAL
+template
+void
+parse_value(const std::string& text, std::optional& value)
+{
+ T result;
+ parse_value(text, result);
+ value = std::move(result);
+}
+#endif
+
+inline
+void parse_value(const std::string& text, char& c)
+{
+ if (text.length() != 1)
+ {
+ throw_or_mimic(text);
+ }
+
+ c = text[0];
+}
+
+template
+void
+parse_value(const std::string& text, std::vector& value)
+{
+ if (text.empty()) {
+ T v;
+ parse_value(text, v);
+ value.emplace_back(std::move(v));
+ return;
+ }
+ std::stringstream in(text);
+ std::string token;
+ while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
+ T v;
+ parse_value(token, v);
+ value.emplace_back(std::move(v));
+ }
+}
+
+template
+void
+add_value(const std::string& text, T& value)
+{
+ parse_value(text, value);
+}
+
+template
+void
+add_value(const std::string& text, std::vector& value)
+{
+ T v;
+ add_value(text, v);
+ value.emplace_back(std::move(v));
+}
+
+template
+struct type_is_container
+{
+ static constexpr bool value = false;
+};
+
+template
+struct type_is_container>
+{
+ static constexpr bool value = true;
+};
+
+template
+class abstract_value : public Value
+{
+ using Self = abstract_value;
+
+ public:
+ abstract_value()
+ : m_result(std::make_shared())
+ , m_store(m_result.get())
+ {
+ }
+
+ explicit abstract_value(T* t)
+ : m_store(t)
+ {
+ }
+
+ ~abstract_value() override = default;
+
+ abstract_value& operator=(const abstract_value&) = default;
+
+ abstract_value(const abstract_value& rhs)
+ {
+ if (rhs.m_result)
+ {
+ m_result = std::make_shared();
+ m_store = m_result.get();
+ }
+ else
+ {
+ m_store = rhs.m_store;
+ }
+
+ m_default = rhs.m_default;
+ m_implicit = rhs.m_implicit;
+ m_default_value = rhs.m_default_value;
+ m_implicit_value = rhs.m_implicit_value;
+ }
+
+ void
+ add(const std::string& text) const override
+ {
+ add_value(text, *m_store);
+ }
+
+ void
+ parse(const std::string& text) const override
+ {
+ parse_value(text, *m_store);
+ }
+
+ bool
+ is_container() const override
+ {
+ return type_is_container::value;
+ }
+
+ void
+ parse() const override
+ {
+ parse_value(m_default_value, *m_store);
+ }
+
+ bool
+ has_default() const override
+ {
+ return m_default;
+ }
+
+ bool
+ has_implicit() const override
+ {
+ return m_implicit;
+ }
+
+ std::shared_ptr
+ default_value(const std::string& value) override
+ {
+ m_default = true;
+ m_default_value = value;
+ return shared_from_this();
+ }
+
+ std::shared_ptr
+ implicit_value(const std::string& value) override
+ {
+ m_implicit = true;
+ m_implicit_value = value;
+ return shared_from_this();
+ }
+
+ std::shared_ptr
+ no_implicit_value() override
+ {
+ m_implicit = false;
+ return shared_from_this();
+ }
+
+ std::string
+ get_default_value() const override
+ {
+ return m_default_value;
+ }
+
+ std::string
+ get_implicit_value() const override
+ {
+ return m_implicit_value;
+ }
+
+ bool
+ is_boolean() const override
+ {
+ return std::is_same::value;
+ }
+
+ const T&
+ get() const
+ {
+ if (m_store == nullptr)
+ {
+ return *m_result;
+ }
+ return *m_store;
+ }
+
+ protected:
+ std::shared_ptr m_result{};
+ T* m_store{};
+
+ bool m_default = false;
+ bool m_implicit = false;
+
+ std::string m_default_value{};
+ std::string m_implicit_value{};
+};
+
+template
+class standard_value : public abstract_value
+{
+ public:
+ using abstract_value::abstract_value;
+
+ CXXOPTS_NODISCARD
+ std::shared_ptr
+ clone() const override
+ {
+ return std::make_shared>(*this);
+ }
+};
+
+template <>
+class standard_value : public abstract_value
+{
+ public:
+ ~standard_value() override = default;
+
+ standard_value()
+ {
+ set_default_and_implicit();
+ }
+
+ explicit standard_value(bool* b)
+ : abstract_value(b)
+ {
+ m_implicit = true;
+ m_implicit_value = "true";
+ }
+
+ std::shared_ptr
+ clone() const override
+ {
+ return std::make_shared>(*this);
+ }
+
+ private:
+
+ void
+ set_default_and_implicit()
+ {
+ m_default = true;
+ m_default_value = "false";
+ m_implicit = true;
+ m_implicit_value = "true";
+ }
+};
+
+} // namespace values
+
+template
+std::shared_ptr
+value()
+{
+ return std::make_shared>();
+}
+
+template
+std::shared_ptr
+value(T& t)
+{
+ return std::make_shared>(&t);
+}
+
+class OptionAdder;
+
+CXXOPTS_NODISCARD
+inline
+const std::string&
+first_or_empty(const OptionNames& long_names)
+{
+ static const std::string empty{""};
+ return long_names.empty() ? empty : long_names.front();
+}
+
+class OptionDetails
+{
+ public:
+ OptionDetails
+ (
+ std::string short_,
+ OptionNames long_,
+ String desc,
+ std::shared_ptr val
+ )
+ : m_short(std::move(short_))
+ , m_long(std::move(long_))
+ , m_desc(std::move(desc))
+ , m_value(std::move(val))
+ , m_count(0)
+ {
+ m_hash = std::hash{}(first_long_name() + m_short);
+ }
+
+ OptionDetails(const OptionDetails& rhs)
+ : m_desc(rhs.m_desc)
+ , m_value(rhs.m_value->clone())
+ , m_count(rhs.m_count)
+ {
+ }
+
+ OptionDetails(OptionDetails&& rhs) = default;
+
+ CXXOPTS_NODISCARD
+ const String&
+ description() const
+ {
+ return m_desc;
+ }
+
+ CXXOPTS_NODISCARD
+ const Value&
+ value() const {
+ return *m_value;
+ }
+
+ CXXOPTS_NODISCARD
+ std::shared_ptr
+ make_storage() const
+ {
+ return m_value->clone();
+ }
+
+ CXXOPTS_NODISCARD
+ const std::string&
+ short_name() const
+ {
+ return m_short;
+ }
+
+ CXXOPTS_NODISCARD
+ const std::string&
+ first_long_name() const
+ {
+ return first_or_empty(m_long);
+ }
+
+ CXXOPTS_NODISCARD
+ const std::string&
+ essential_name() const
+ {
+ return m_long.empty() ? m_short : m_long.front();
+ }
+
+ CXXOPTS_NODISCARD
+ const OptionNames &
+ long_names() const
+ {
+ return m_long;
+ }
+
+ std::size_t
+ hash() const
+ {
+ return m_hash;
+ }
+
+ private:
+ std::string m_short{};
+ OptionNames m_long{};
+ String m_desc{};
+ std::shared_ptr m_value{};
+ int m_count;
+
+ std::size_t m_hash{};
+};
+
+struct HelpOptionDetails
+{
+ std::string s;
+ OptionNames l;
+ String desc;
+ bool has_default;
+ std::string default_value;
+ bool has_implicit;
+ std::string implicit_value;
+ std::string arg_help;
+ bool is_container;
+ bool is_boolean;
+};
+
+struct HelpGroupDetails
+{
+ std::string name{};
+ std::string description{};
+ std::vector options{};
+};
+
+class OptionValue
+{
+ public:
+ void
+ add
+ (
+ const std::shared_ptr& details,
+ const std::string& text
+ )
+ {
+ ensure_value(details);
+ ++m_count;
+ m_value->add(text);
+ m_long_names = &details->long_names();
+ }
+
+ void
+ parse
+ (
+ const std::shared_ptr& details,
+ const std::string& text
+ )
+ {
+ ensure_value(details);
+ ++m_count;
+ m_value->parse(text);
+ m_long_names = &details->long_names();
+ }
+
+ void
+ parse_default(const std::shared_ptr& details)
+ {
+ ensure_value(details);
+ m_default = true;
+ m_long_names = &details->long_names();
+ m_value->parse();
+ }
+
+ void
+ parse_no_value(const std::shared_ptr& details)
+ {
+ m_long_names = &details->long_names();
+ }
+
+#if defined(CXXOPTS_NULL_DEREF_IGNORE)
+CXXOPTS_DIAGNOSTIC_PUSH
+CXXOPTS_IGNORE_WARNING("-Wnull-dereference")
+#endif
+
+ CXXOPTS_NODISCARD
+ std::size_t
+ count() const noexcept
+ {
+ return m_count;
+ }
+
+#if defined(CXXOPTS_NULL_DEREF_IGNORE)
+CXXOPTS_DIAGNOSTIC_POP
+#endif
+
+ // TODO: maybe default options should count towards the number of arguments
+ CXXOPTS_NODISCARD
+ bool
+ has_default() const noexcept
+ {
+ return m_default;
+ }
+
+ template
+ const T&
+ as() const
+ {
+ if (m_value == nullptr) {
+ throw_or_mimic(
+ m_long_names == nullptr ? "" : first_or_empty(*m_long_names));
+ }
+
+ return CXXOPTS_RTTI_CAST&>(*m_value).get();
+ }
+
+#ifdef CXXOPTS_HAS_OPTIONAL
+ template
+ std::optional
+ as_optional() const
+ {
+ if (m_value == nullptr) {
+ return std::nullopt;
+ }
+ return as();
+ }
+#endif
+
+ private:
+ void
+ ensure_value(const std::shared_ptr& details)
+ {
+ if (m_value == nullptr)
+ {
+ m_value = details->make_storage();
+ }
+ }
+
+
+ const OptionNames * m_long_names = nullptr;
+ // Holding this pointer is safe, since OptionValue's only exist in key-value pairs,
+ // where the key has the string we point to.
+ std::shared_ptr m_value{};
+ std::size_t m_count = 0;
+ bool m_default = false;
+};
+
+class KeyValue
+{
+ public:
+ KeyValue(std::string key_, std::string value_) noexcept
+ : m_key(std::move(key_))
+ , m_value(std::move(value_))
+ {
+ }
+
+ CXXOPTS_NODISCARD
+ const std::string&
+ key() const
+ {
+ return m_key;
+ }
+
+ CXXOPTS_NODISCARD
+ const std::string&
+ value() const
+ {
+ return m_value;
+ }
+
+ template
+ T
+ as() const
+ {
+ T result;
+ values::parse_value(m_value, result);
+ return result;
+ }
+
+ private:
+ std::string m_key;
+ std::string m_value;
+};
+
+using ParsedHashMap = std::unordered_map;
+using NameHashMap = std::unordered_map;
+
+class ParseResult
+{
+ public:
+ class Iterator
+ {
+ public:
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = KeyValue;
+ using difference_type = void;
+ using pointer = const KeyValue*;
+ using reference = const KeyValue&;
+
+ Iterator() = default;
+ Iterator(const Iterator&) = default;
+
+// GCC complains about m_iter not being initialised in the member
+// initializer list
+CXXOPTS_DIAGNOSTIC_PUSH
+CXXOPTS_IGNORE_WARNING("-Weffc++")
+ Iterator(const ParseResult *pr, bool end=false)
+ : m_pr(pr)
+ {
+ if (end)
+ {
+ m_sequential = false;
+ m_iter = m_pr->m_defaults.end();
+ }
+ else
+ {
+ m_sequential = true;
+ m_iter = m_pr->m_sequential.begin();
+
+ if (m_iter == m_pr->m_sequential.end())
+ {
+ m_sequential = false;
+ m_iter = m_pr->m_defaults.begin();
+ }
+ }
+ }
+CXXOPTS_DIAGNOSTIC_POP
+
+ Iterator& operator++()
+ {
+ ++m_iter;
+ if(m_sequential && m_iter == m_pr->m_sequential.end())
+ {
+ m_sequential = false;
+ m_iter = m_pr->m_defaults.begin();
+ return *this;
+ }
+ return *this;
+ }
+
+ Iterator operator++(int)
+ {
+ Iterator retval = *this;
+ ++(*this);
+ return retval;
+ }
+
+ bool operator==(const Iterator& other) const
+ {
+ return (m_sequential == other.m_sequential) && (m_iter == other.m_iter);
+ }
+
+ bool operator!=(const Iterator& other) const
+ {
+ return !(*this == other);
+ }
+
+ const KeyValue& operator*()
+ {
+ return *m_iter;
+ }
+
+ const KeyValue* operator->()
+ {
+ return m_iter.operator->();
+ }
+
+ private:
+ const ParseResult* m_pr;
+ std::vector::const_iterator m_iter;
+ bool m_sequential = true;
+ };
+
+ ParseResult() = default;
+ ParseResult(const ParseResult&) = default;
+
+ ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential,
+ std::vector default_opts, std::vector&& unmatched_args)
+ : m_keys(std::move(keys))
+ , m_values(std::move(values))
+ , m_sequential(std::move(sequential))
+ , m_defaults(std::move(default_opts))
+ , m_unmatched(std::move(unmatched_args))
+ {
+ }
+
+ ParseResult& operator=(ParseResult&&) = default;
+ ParseResult& operator=(const ParseResult&) = default;
+
+ Iterator
+ begin() const
+ {
+ return Iterator(this);
+ }
+
+ Iterator
+ end() const
+ {
+ return Iterator(this, true);
+ }
+
+ std::size_t
+ count(const std::string& o) const
+ {
+ auto iter = m_keys.find(o);
+ if (iter == m_keys.end())
+ {
+ return 0;
+ }
+
+ auto viter = m_values.find(iter->second);
+
+ if (viter == m_values.end())
+ {
+ return 0;
+ }
+
+ return viter->second.count();
+ }
+
+ const OptionValue&
+ operator[](const std::string& option) const
+ {
+ auto iter = m_keys.find(option);
+
+ if (iter == m_keys.end())
+ {
+ throw_or_mimic(option);
+ }
+
+ auto viter = m_values.find(iter->second);
+
+ if (viter == m_values.end())
+ {
+ throw_or_mimic(option);
+ }
+
+ return viter->second;
+ }
+
+#ifdef CXXOPTS_HAS_OPTIONAL
+ template
+ std::optional
+ as_optional(const std::string& option) const
+ {
+ auto iter = m_keys.find(option);
+ if (iter != m_keys.end())
+ {
+ auto viter = m_values.find(iter->second);
+ if (viter != m_values.end())
+ {
+ return viter->second.as_optional();
+ }
+ }
+ return std::nullopt;
+ }
+#endif
+
+ const std::vector&
+ arguments() const
+ {
+ return m_sequential;
+ }
+
+ const std::vector&
+ unmatched() const
+ {
+ return m_unmatched;
+ }
+
+ const std::vector&
+ defaults() const
+ {
+ return m_defaults;
+ }
+
+ const std::string
+ arguments_string() const
+ {
+ std::string result;
+ for(const auto& kv: m_sequential)
+ {
+ result += kv.key() + " = " + kv.value() + "\n";
+ }
+ for(const auto& kv: m_defaults)
+ {
+ result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n";
+ }
+ return result;
+ }
+
+ private:
+ NameHashMap m_keys{};
+ ParsedHashMap m_values{};
+ std::vector m_sequential{};
+ std::vector m_defaults{};
+ std::vector m_unmatched{};
+};
+
+struct Option
+{
+ Option
+ (
+ std::string opts,
+ std::string desc,
+ std::shared_ptr value = ::cxxopts::value(),
+ std::string arg_help = ""
+ )
+ : opts_(std::move(opts))
+ , desc_(std::move(desc))
+ , value_(std::move(value))
+ , arg_help_(std::move(arg_help))
+ {
+ }
+
+ std::string opts_;
+ std::string desc_;
+ std::shared_ptr value_;
+ std::string arg_help_;
+};
+
+using OptionMap = std::unordered_map>;
+using PositionalList = std::vector;
+using PositionalListIterator = PositionalList::const_iterator;
+
+class OptionParser
+{
+ public:
+ OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised)
+ : m_options(options)
+ , m_positional(positional)
+ , m_allow_unrecognised(allow_unrecognised)
+ {
+ }
+
+ ParseResult
+ parse(int argc, const char* const* argv);
+
+ bool
+ consume_positional(const std::string& a, PositionalListIterator& next);
+
+ void
+ checked_parse_arg
+ (
+ int argc,
+ const char* const* argv,
+ int& current,
+ const std::shared_ptr& value,
+ const std::string& name
+ );
+
+ void
+ add_to_option(const std::shared_ptr& value, const std::string& arg);
+
+ void
+ parse_option
+ (
+ const std::shared_ptr& value,
+ const std::string& name,
+ const std::string& arg = ""
+ );
+
+ void
+ parse_default(const std::shared_ptr& details);
+
+ void
+ parse_no_value(const std::shared_ptr& details);
+
+ private:
+
+ void finalise_aliases();
+
+ const OptionMap& m_options;
+ const PositionalList& m_positional;
+
+ std::vector m_sequential{};
+ std::vector m_defaults{};
+ bool m_allow_unrecognised;
+
+ ParsedHashMap m_parsed{};
+ NameHashMap m_keys{};
+};
+
+class Options
+{
+ public:
+
+ explicit Options(std::string program_name, std::string help_string = "")
+ : m_program(std::move(program_name))
+ , m_help_string(toLocalString(std::move(help_string)))
+ , m_custom_help("[OPTION...]")
+ , m_positional_help("positional parameters")
+ , m_show_positional(false)
+ , m_allow_unrecognised(false)
+ , m_width(76)
+ , m_tab_expansion(false)
+ , m_options(std::make_shared())
+ {
+ }
+
+ Options&
+ positional_help(std::string help_text)
+ {
+ m_positional_help = std::move(help_text);
+ return *this;
+ }
+
+ Options&
+ custom_help(std::string help_text)
+ {
+ m_custom_help = std::move(help_text);
+ return *this;
+ }
+
+ Options&
+ show_positional_help()
+ {
+ m_show_positional = true;
+ return *this;
+ }
+
+ Options&
+ allow_unrecognised_options()
+ {
+ m_allow_unrecognised = true;
+ return *this;
+ }
+
+ Options&
+ set_width(std::size_t width)
+ {
+ m_width = width;
+ return *this;
+ }
+
+ Options&
+ set_tab_expansion(bool expansion=true)
+ {
+ m_tab_expansion = expansion;
+ return *this;
+ }
+
+ ParseResult
+ parse(int argc, const char* const* argv);
+
+ OptionAdder
+ add_options(std::string group = "");
+
+ void
+ add_options
+ (
+ const std::string& group,
+ std::initializer_list options
+ );
+
+ void
+ add_option
+ (
+ const std::string& group,
+ const Option& option
+ );
+
+ void
+ add_option
+ (
+ const std::string& group,
+ const std::string& s,
+ const OptionNames& l,
+ std::string desc,
+ const std::shared_ptr& value,
+ std::string arg_help
+ );
+
+ void
+ add_option
+ (
+ const std::string& group,
+ const std::string& short_name,
+ const std::string& single_long_name,
+ std::string desc,
+ const std::shared_ptr& value,
+ std::string arg_help
+ )
+ {
+ OptionNames long_names;
+ long_names.emplace_back(single_long_name);
+ add_option(group, short_name, long_names, desc, value, arg_help);
+ }
+
+ //parse positional arguments into the given option
+ void
+ parse_positional(std::string option);
+
+ void
+ parse_positional(std::vector options);
+
+ void
+ parse_positional(std::initializer_list options);
+
+ template
+ void
+ parse_positional(Iterator begin, Iterator end) {
+ parse_positional(std::vector{begin, end});
+ }
+
+ std::string
+ help(const std::vector& groups = {}, bool print_usage=true) const;
+
+ std::vector
+ groups() const;
+
+ const HelpGroupDetails&
+ group_help(const std::string& group) const;
+
+ const std::string& program() const
+ {
+ return m_program;
+ }
+
+ private:
+
+ void
+ add_one_option
+ (
+ const std::string& option,
+ const std::shared_ptr& details
+ );
+
+ String
+ help_one_group(const std::string& group) const;
+
+ void
+ generate_group_help
+ (
+ String& result,
+ const std::vector& groups
+ ) const;
+
+ void
+ generate_all_groups_help(String& result) const;
+
+ std::string m_program{};
+ String m_help_string{};
+ std::string m_custom_help{};
+ std::string m_positional_help{};
+ bool m_show_positional;
+ bool m_allow_unrecognised;
+ std::size_t m_width;
+ bool m_tab_expansion;
+
+ std::shared_ptr m_options;
+ std::vector m_positional{};
+ std::unordered_set m_positional_set{};
+
+ //mapping from groups to help options
+ std::vector m_group{};
+ std::map m_help{};
+};
+
+class OptionAdder
+{
+ public:
+
+ OptionAdder(Options& options, std::string group)
+ : m_options(options), m_group(std::move(group))
+ {
+ }
+
+ OptionAdder&
+ operator()
+ (
+ const std::string& opts,
+ const std::string& desc,
+ const std::shared_ptr& value
+ = ::cxxopts::value(),
+ std::string arg_help = ""
+ );
+
+ private:
+ Options& m_options;
+ std::string m_group;
+};
+
+namespace {
+constexpr std::size_t OPTION_LONGEST = 30;
+constexpr std::size_t OPTION_DESC_GAP = 2;
+
+String
+format_option
+(
+ const HelpOptionDetails& o
+)
+{
+ const auto& s = o.s;
+ const auto& l = first_or_empty(o.l);
+
+ String result = " ";
+
+ if (!s.empty())
+ {
+ result += "-" + toLocalString(s);
+ if (!l.empty())
+ {
+ result += ",";
+ }
+ }
+ else
+ {
+ result += " ";
+ }
+
+ if (!l.empty())
+ {
+ result += " --" + toLocalString(l);
+ }
+
+ auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : "arg";
+
+ if (!o.is_boolean)
+ {
+ if (o.has_implicit)
+ {
+ result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
+ }
+ else
+ {
+ result += " " + arg;
+ }
+ }
+
+ return result;
+}
+
+String
+format_description
+(
+ const HelpOptionDetails& o,
+ std::size_t start,
+ std::size_t allowed,
+ bool tab_expansion
+)
+{
+ auto desc = o.desc;
+
+ if (o.has_default && (!o.is_boolean || o.default_value != "false"))
+ {
+ if(!o.default_value.empty())
+ {
+ desc += toLocalString(" (default: " + o.default_value + ")");
+ }
+ else
+ {
+ desc += toLocalString(" (default: \"\")");
+ }
+ }
+
+ String result;
+
+ if (tab_expansion)
+ {
+ String desc2;
+ auto size = std::size_t{ 0 };
+ for (auto c = std::begin(desc); c != std::end(desc); ++c)
+ {
+ if (*c == '\n')
+ {
+ desc2 += *c;
+ size = 0;
+ }
+ else if (*c == '\t')
+ {
+ auto skip = 8 - size % 8;
+ stringAppend(desc2, skip, ' ');
+ size += skip;
+ }
+ else
+ {
+ desc2 += *c;
+ ++size;
+ }
+ }
+ desc = desc2;
+ }
+
+ desc += " ";
+
+ auto current = std::begin(desc);
+ auto previous = current;
+ auto startLine = current;
+ auto lastSpace = current;
+
+ auto size = std::size_t{};
+
+ bool appendNewLine;
+ bool onlyWhiteSpace = true;
+
+ while (current != std::end(desc))
+ {
+ appendNewLine = false;
+ if (*previous == ' ' || *previous == '\t')
+ {
+ lastSpace = current;
+ }
+ if (*current != ' ' && *current != '\t')
+ {
+ onlyWhiteSpace = false;
+ }
+
+ while (*current == '\n')
+ {
+ previous = current;
+ ++current;
+ appendNewLine = true;
+ }
+
+ if (!appendNewLine && size >= allowed)
+ {
+ if (lastSpace != startLine)
+ {
+ current = lastSpace;
+ previous = current;
+ }
+ appendNewLine = true;
+ }
+
+ if (appendNewLine)
+ {
+ stringAppend(result, startLine, current);
+ startLine = current;
+ lastSpace = current;
+
+ if (*previous != '\n')
+ {
+ stringAppend(result, "\n");
+ }
+
+ stringAppend(result, start, ' ');
+
+ if (*previous != '\n')
+ {
+ stringAppend(result, lastSpace, current);
+ }
+
+ onlyWhiteSpace = true;
+ size = 0;
+ }
+
+ previous = current;
+ ++current;
+ ++size;
+ }
+
+ //append whatever is left but ignore whitespace
+ if (!onlyWhiteSpace)
+ {
+ stringAppend(result, startLine, previous);
+ }
+
+ return result;
+}
+
+} // namespace
+
+inline
+void
+Options::add_options
+(
+ const std::string &group,
+ std::initializer_list options
+)
+{
+ OptionAdder option_adder(*this, group);
+ for (const auto &option: options)
+ {
+ option_adder(option.opts_, option.desc_, option.value_, option.arg_help_);
+ }
+}
+
+inline
+OptionAdder
+Options::add_options(std::string group)
+{
+ return OptionAdder(*this, std::move(group));
+}
+
+inline
+OptionAdder&
+OptionAdder::operator()
+(
+ const std::string& opts,
+ const std::string& desc,
+ const std::shared_ptr& value,
+ std::string arg_help
+)
+{
+ OptionNames option_names = values::parser_tool::split_option_names(opts);
+ // Note: All names will be non-empty; but we must separate the short
+ // (length-1) and longer names
+ std::string short_name {""};
+ auto first_short_name_iter =
+ std::partition(option_names.begin(), option_names.end(),
+ [&](const std::string& name) { return name.length() > 1; }
+ );
+ auto num_length_1_names = (option_names.end() - first_short_name_iter);
+ switch(num_length_1_names) {
+ case 1:
+ short_name = *first_short_name_iter;
+ option_names.erase(first_short_name_iter);
+ CXXOPTS_FALLTHROUGH;
+ case 0:
+ break;
+ default:
+ throw_or_mimic(opts);
+ };
+
+ m_options.add_option
+ (
+ m_group,
+ short_name,
+ option_names,
+ desc,
+ value,
+ std::move(arg_help)
+ );
+
+ return *this;
+}
+
+inline
+void
+OptionParser::parse_default(const std::shared_ptr& details)
+{
+ // TODO: remove the duplicate code here
+ auto& store = m_parsed[details->hash()];
+ store.parse_default(details);
+ m_defaults.emplace_back(details->essential_name(), details->value().get_default_value());
+}
+
+inline
+void
+OptionParser::parse_no_value(const std::shared_ptr& details)
+{
+ auto& store = m_parsed[details->hash()];
+ store.parse_no_value(details);
+}
+
+inline
+void
+OptionParser::parse_option
+(
+ const std::shared_ptr& value,
+ const std::string& /*name*/,
+ const std::string& arg
+)
+{
+ auto hash = value->hash();
+ auto& result = m_parsed[hash];
+ result.parse(value, arg);
+
+ m_sequential.emplace_back(value->essential_name(), arg);
+}
+
+inline
+void
+OptionParser::checked_parse_arg
+(
+ int argc,
+ const char* const* argv,
+ int& current,
+ const std::shared_ptr& value,
+ const std::string& name
+)
+{
+ if (current + 1 >= argc)
+ {
+ if (value->value().has_implicit())
+ {
+ parse_option(value, name, value->value().get_implicit_value());
+ }
+ else
+ {
+ throw_or_mimic(name);
+ }
+ }
+ else
+ {
+ if (value->value().has_implicit())
+ {
+ parse_option(value, name, value->value().get_implicit_value());
+ }
+ else
+ {
+ parse_option(value, name, argv[current + 1]);
+ ++current;
+ }
+ }
+}
+
+inline
+void
+OptionParser::add_to_option(const std::shared_ptr& value, const std::string& arg)
+{
+ auto hash = value->hash();
+ auto& result = m_parsed[hash];
+ result.add(value, arg);
+
+ m_sequential.emplace_back(value->essential_name(), arg);
+}
+
+inline
+bool
+OptionParser::consume_positional(const std::string& a, PositionalListIterator& next)
+{
+ while (next != m_positional.end())
+ {
+ auto iter = m_options.find(*next);
+ if (iter != m_options.end())
+ {
+ if (!iter->second->value().is_container())
+ {
+ auto& result = m_parsed[iter->second->hash()];
+ if (result.count() == 0)
+ {
+ add_to_option(iter->second, a);
+ ++next;
+ return true;
+ }
+ ++next;
+ continue;
+ }
+ add_to_option(iter->second, a);
+ return true;
+ }
+ throw_or_mimic(*next);
+ }
+
+ return false;
+}
+
+inline
+void
+Options::parse_positional(std::string option)
+{
+ parse_positional(std::vector{std::move(option)});
+}
+
+inline
+void
+Options::parse_positional(std::vector options)
+{
+ m_positional = std::move(options);
+
+ m_positional_set.insert(m_positional.begin(), m_positional.end());
+}
+
+inline
+void
+Options::parse_positional(std::initializer_list options)
+{
+ parse_positional(std::vector(options));
+}
+
+inline
+ParseResult
+Options::parse(int argc, const char* const* argv)
+{
+ OptionParser parser(*m_options, m_positional, m_allow_unrecognised);
+
+ return parser.parse(argc, argv);
+}
+
+inline ParseResult
+OptionParser::parse(int argc, const char* const* argv)
+{
+ int current = 1;
+ bool consume_remaining = false;
+ auto next_positional = m_positional.begin();
+
+ std::vector unmatched;
+
+ while (current != argc)
+ {
+ if (strcmp(argv[current], "--") == 0)
+ {
+ consume_remaining = true;
+ ++current;
+ break;
+ }
+ bool matched = false;
+ values::parser_tool::ArguDesc argu_desc =
+ values::parser_tool::ParseArgument(argv[current], matched);
+
+ if (!matched)
+ {
+ //not a flag
+
+ // but if it starts with a `-`, then it's an error
+ if (argv[current][0] == '-' && argv[current][1] != '\0') {
+ if (!m_allow_unrecognised) {
+ throw_or_mimic(argv[current]);
+ }
+ }
+
+ //if true is returned here then it was consumed, otherwise it is
+ //ignored
+ if (consume_positional(argv[current], next_positional))
+ {
+ }
+ else
+ {
+ unmatched.emplace_back(argv[current]);
+ }
+ //if we return from here then it was parsed successfully, so continue
+ }
+ else
+ {
+ //short or long option?
+ if (argu_desc.grouping)
+ {
+ const std::string& s = argu_desc.arg_name;
+
+ for (std::size_t i = 0; i != s.size(); ++i)
+ {
+ std::string name(1, s[i]);
+ auto iter = m_options.find(name);
+
+ if (iter == m_options.end())
+ {
+ if (m_allow_unrecognised)
+ {
+ unmatched.push_back(std::string("-") + s[i]);
+ continue;
+ }
+ //error
+ throw_or_mimic(name);
+ }
+
+ auto value = iter->second;
+
+ if (i + 1 == s.size())
+ {
+ //it must be the last argument
+ checked_parse_arg(argc, argv, current, value, name);
+ }
+ else if (value->value().has_implicit())
+ {
+ parse_option(value, name, value->value().get_implicit_value());
+ }
+ else if (i + 1 < s.size())
+ {
+ std::string arg_value = s.substr(i + 1);
+ parse_option(value, name, arg_value);
+ break;
+ }
+ else
+ {
+ //error
+ throw_or_mimic(name);
+ }
+ }
+ }
+ else if (argu_desc.arg_name.length() != 0)
+ {
+ const std::string& name = argu_desc.arg_name;
+
+ auto iter = m_options.find(name);
+
+ if (iter == m_options.end())
+ {
+ if (m_allow_unrecognised)
+ {
+ // keep unrecognised options in argument list, skip to next argument
+ unmatched.emplace_back(argv[current]);
+ ++current;
+ continue;
+ }
+ //error
+ throw_or_mimic(name);
+ }
+
+ auto opt = iter->second;
+
+ //equals provided for long option?
+ if (argu_desc.set_value)
+ {
+ //parse the option given
+
+ parse_option(opt, name, argu_desc.value);
+ }
+ else
+ {
+ //parse the next argument
+ checked_parse_arg(argc, argv, current, opt, name);
+ }
+ }
+
+ }
+
+ ++current;
+ }
+
+ for (auto& opt : m_options)
+ {
+ auto& detail = opt.second;
+ const auto& value = detail->value();
+
+ auto& store = m_parsed[detail->hash()];
+
+ if (value.has_default()) {
+ if (!store.count() && !store.has_default()) {
+ parse_default(detail);
+ }
+ }
+ else {
+ parse_no_value(detail);
+ }
+ }
+
+ if (consume_remaining)
+ {
+ while (current < argc)
+ {
+ if (!consume_positional(argv[current], next_positional)) {
+ break;
+ }
+ ++current;
+ }
+
+ //adjust argv for any that couldn't be swallowed
+ while (current != argc) {
+ unmatched.emplace_back(argv[current]);
+ ++current;
+ }
+ }
+
+ finalise_aliases();
+
+ ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(m_defaults), std::move(unmatched));
+ return parsed;
+}
+
+inline
+void
+OptionParser::finalise_aliases()
+{
+ for (auto& option: m_options)
+ {
+ auto& detail = *option.second;
+ auto hash = detail.hash();
+ m_keys[detail.short_name()] = hash;
+ for(const auto& long_name : detail.long_names()) {
+ m_keys[long_name] = hash;
+ }
+
+ m_parsed.emplace(hash, OptionValue());
+ }
+}
+
+inline
+void
+Options::add_option
+(
+ const std::string& group,
+ const Option& option
+)
+{
+ add_options(group, {option});
+}
+
+inline
+void
+Options::add_option
+(
+ const std::string& group,
+ const std::string& s,
+ const OptionNames& l,
+ std::string desc,
+ const std::shared_ptr& value,
+ std::string arg_help
+)
+{
+ auto stringDesc = toLocalString(std::move(desc));
+ auto option = std::make_shared(s, l, stringDesc, value);
+
+ if (!s.empty())
+ {
+ add_one_option(s, option);
+ }
+
+ for(const auto& long_name : l) {
+ add_one_option(long_name, option);
+ }
+
+ //add the help details
+
+ if (m_help.find(group) == m_help.end())
+ {
+ m_group.push_back(group);
+ }
+
+ auto& options = m_help[group];
+
+ options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
+ value->has_default(), value->get_default_value(),
+ value->has_implicit(), value->get_implicit_value(),
+ std::move(arg_help),
+ value->is_container(),
+ value->is_boolean()});
+}
+
+inline
+void
+Options::add_one_option
+(
+ const std::string& option,
+ const std::shared_ptr& details
+)
+{
+ auto in = m_options->emplace(option, details);
+
+ if (!in.second)
+ {
+ throw_or_mimic(option);
+ }
+}
+
+inline
+String
+Options::help_one_group(const std::string& g) const
+{
+ using OptionHelp = std::vector>;
+
+ auto group = m_help.find(g);
+ if (group == m_help.end())
+ {
+ return "";
+ }
+
+ OptionHelp format;
+
+ std::size_t longest = 0;
+
+ String result;
+
+ if (!g.empty())
+ {
+ result += toLocalString(" " + g + " options:\n");
+ }
+
+ for (const auto& o : group->second.options)
+ {
+ if (o.l.size() &&
+ m_positional_set.find(o.l.front()) != m_positional_set.end() &&
+ !m_show_positional)
+ {
+ continue;
+ }
+
+ auto s = format_option(o);
+ longest = (std::max)(longest, stringLength(s));
+ format.push_back(std::make_pair(s, String()));
+ }
+ longest = (std::min)(longest, OPTION_LONGEST);
+
+ //widest allowed description -- min 10 chars for helptext/line
+ std::size_t allowed = 10;
+ if (m_width > allowed + longest + OPTION_DESC_GAP)
+ {
+ allowed = m_width - longest - OPTION_DESC_GAP;
+ }
+
+ auto fiter = format.begin();
+ for (const auto& o : group->second.options)
+ {
+ if (o.l.size() &&
+ m_positional_set.find(o.l.front()) != m_positional_set.end() &&
+ !m_show_positional)
+ {
+ continue;
+ }
+
+ auto d = format_description(o, longest + OPTION_DESC_GAP, allowed, m_tab_expansion);
+
+ result += fiter->first;
+ if (stringLength(fiter->first) > longest)
+ {
+ result += '\n';
+ result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' '));
+ }
+ else
+ {
+ result += toLocalString(std::string(longest + OPTION_DESC_GAP -
+ stringLength(fiter->first),
+ ' '));
+ }
+ result += d;
+ result += '\n';
+
+ ++fiter;
+ }
+
+ return result;
+}
+
+inline
+void
+Options::generate_group_help
+(
+ String& result,
+ const std::vector& print_groups
+) const
+{
+ for (std::size_t i = 0; i != print_groups.size(); ++i)
+ {
+ const String& group_help_text = help_one_group(print_groups[i]);
+ if (empty(group_help_text))
+ {
+ continue;
+ }
+ result += group_help_text;
+ if (i < print_groups.size() - 1)
+ {
+ result += '\n';
+ }
+ }
+}
+
+inline
+void
+Options::generate_all_groups_help(String& result) const
+{
+ generate_group_help(result, m_group);
+}
+
+inline
+std::string
+Options::help(const std::vector& help_groups, bool print_usage) const
+{
+ String result = m_help_string;
+ if(print_usage)
+ {
+ result+= "\nUsage:\n " + toLocalString(m_program);
+ }
+
+ if (!m_custom_help.empty())
+ {
+ result += " " + toLocalString(m_custom_help);
+ }
+
+ if (!m_positional.empty() && !m_positional_help.empty()) {
+ result += " " + toLocalString(m_positional_help);
+ }
+
+ result += "\n\n";
+
+ if (help_groups.empty())
+ {
+ generate_all_groups_help(result);
+ }
+ else
+ {
+ generate_group_help(result, help_groups);
+ }
+
+ return toUTF8String(result);
+}
+
+inline
+std::vector
+Options::groups() const
+{
+ return m_group;
+}
+
+inline
+const HelpGroupDetails&
+Options::group_help(const std::string& group) const
+{
+ return m_help.at(group);
+}
+
+} // namespace cxxopts
+
+#endif //CXXOPTS_HPP_INCLUDED
diff --git a/src/include/ncmcrypt.h b/src/include/ncmcrypt.h
index d126dbe..7cbebd6 100644
--- a/src/include/ncmcrypt.h
+++ b/src/include/ncmcrypt.h
@@ -66,6 +66,6 @@ public:
~NeteaseCrypt();
public:
- void Dump();
+ void Dump(std::string const&);
void FixMetadata();
};
\ No newline at end of file
diff --git a/src/include/version.h b/src/include/version.h
new file mode 100644
index 0000000..ac671cc
--- /dev/null
+++ b/src/include/version.h
@@ -0,0 +1,3 @@
+#define VERSION_MAJOR 1
+#define VERSION_MINOR 5
+#define VERSION_PATCH 0
\ No newline at end of file
diff --git a/src/lib/libncmdump.cpp b/src/lib/libncmdump.cpp
index ad5c94f..2783f7b 100644
--- a/src/lib/libncmdump.cpp
+++ b/src/lib/libncmdump.cpp
@@ -9,10 +9,10 @@ extern "C" {
return new NeteaseCrypt(fPath.u8string());
}
- API int Dump(NeteaseCrypt* neteaseCrypt) {
+ API int Dump(NeteaseCrypt* neteaseCrypt, const char* outputPath) {
try
{
- neteaseCrypt->Dump();
+ neteaseCrypt->Dump(outputPath);
}
catch (const std::invalid_argument& e)
{
diff --git a/src/lib/libncmdump.h b/src/lib/libncmdump.h
index 29ce74e..dd8e1a3 100644
--- a/src/lib/libncmdump.h
+++ b/src/lib/libncmdump.h
@@ -8,7 +8,7 @@
extern "C" {
API NeteaseCrypt* CreateNeteaseCrypt(const char* path);
- API int Dump(NeteaseCrypt* neteaseCrypt);
+ API int Dump(NeteaseCrypt* neteaseCrypt, const char* outputPath);
API void FixMetadata(NeteaseCrypt* neteaseCrypt);
API void DestroyNeteaseCrypt(NeteaseCrypt* neteaseCrypt);
}
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 5909bd3..1777176 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -10,115 +10,190 @@
#endif
#include "color.h"
+#include "version.h"
+#include "cxxopts.hpp"
namespace fs = std::filesystem;
-void displayHelp()
-{
- std::cout << "Usage: ncmdump [-d] [-h] file1 file2 ..." << std::endl;
- std::cout << "Options:" << std::endl;
- std::cout << " -d Process files in a folder (requires folder path)" << std::endl;
- std::cout << " -h, --help Display this help message" << std::endl;
-}
-
-void processFile(const fs::path &filePath)
+void processFile(const fs::path &filePath, const fs::path &outputFolder)
{
if (fs::exists(filePath) == false)
{
- std::cerr << BOLDRED << "Error: " << RESET << "file '" << filePath.u8string() << "' does not exist." << std::endl;
+ std::cerr << BOLDRED << "[Error] " << RESET << "file '" << filePath.u8string() << "' does not exist." << std::endl;
+ return;
+ }
+
+ // skip if not ending with ".ncm"
+ if (!filePath.has_extension() || filePath.extension().u8string()!= ".ncm")
+ {
return;
}
try
{
NeteaseCrypt crypt(filePath.u8string());
- crypt.Dump();
+ crypt.Dump(outputFolder.u8string());
crypt.FixMetadata();
std::cout << BOLDGREEN << "[Done] " << RESET << "'" << filePath.u8string() << "' -> '" << crypt.dumpFilepath().u8string() << "'" << std::endl;
}
catch (const std::invalid_argument &e)
{
- std::cerr << BOLDRED << "Exception: " << RESET << RED << e.what() << RESET << " '" << filePath.u8string() << "'" << 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.u8string() << std::endl;
- }
-}
-
-void processFilesInFolder(const fs::path &folderPath)
-{
- for (const auto &entry : fs::directory_iterator(folderPath))
- {
- if (fs::is_regular_file(entry.status()))
- {
- processFile(entry.path());
- }
+ std::cerr << BOLDRED << "[Error] Unexpected exception while processing file: " << RESET << filePath.u8string() << std::endl;
}
}
int main(int argc, char **argv)
{
#if defined(_WIN32)
- win32_utf8argv(&argc, &argv);
+ win32_utf8argv(&argc, &argv); // Convert command line arguments to UTF-8 under Windows
#endif
- if (argc <= 1)
+
+ cxxopts::Options options("ncmdump");
+
+ options.add_options()
+ ("h,help", "Print usage")
+ ("d,directory", "Process files in a folder (requires folder path)", cxxopts::value())
+ ("r,recursive", "Process files recursively (requires -d option)", cxxopts::value()->default_value("false"))
+ ("o,output", "Output folder (default: original file folder)", cxxopts::value())
+ ("v,version", "Print version information", cxxopts::value()->default_value("false"))
+ ("filenames", "Input files", cxxopts::value>());
+
+ options.parse_positional({"filenames"});
+
+ // Parse options the usual way
+ auto result = options.parse(argc, argv);
+
+ // display help message
+ if (result.count("help"))
{
- displayHelp();
+ std::cout << options.help() << std::endl;
+ return 0;
+ }
+
+ // display version information
+ if (result.count("version"))
+ {
+ std::cout << "ncmdump version " << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_PATCH << std::endl;
+ return 0;
+ }
+
+ // no input files or folder provided
+ if (result.count("directory") == 0 && result.count("filenames") == 0)
+ {
+ std::cout << options.help() << std::endl;
return 1;
}
- std::vector files;
- bool processFolders = false;
-
- bool folderProvided = false;
-
-#define COMPARE_STR(s1, s2) (strcmp(s1, s2) == 0)
-#define HELP_SHORT "-h"
-#define HELP_LONG "--help"
-#define PROCESS_FOLDER "-d"
- for (int i = 1; i < argc; ++i)
+ // only -r option without -d option
+ if (result.count("recursive") && result.count("directory") == 0)
{
- if (COMPARE_STR(argv[i], HELP_SHORT) || COMPARE_STR(argv[i], HELP_LONG))
+ std::cerr << BOLDRED << "[Error] " << RESET << "-r option requires -d option." << std::endl;
+ return 1;
+ }
+
+ // check output folder
+ fs::path outputDir = fs::u8path("");
+ bool outputDirSpecified = result.count("output") > 0;
+
+ if (outputDirSpecified)
+ {
+ outputDir = fs::u8path(result["output"].as());
+ if (fs::exists(outputDir))
{
- displayHelp();
- return 0;
- }
- else if (COMPARE_STR(argv[i], PROCESS_FOLDER))
- {
- processFolders = true;
- if (i + 1 < argc && argv[i + 1][0] != '-')
+ if (!fs::is_directory(outputDir))
{
- folderProvided = true;
- processFilesInFolder(fs::u8path(argv[i + 1]));
- // Skip the folder name
- ++i;
- }
- else
- {
- std::cerr << "Error: -d option requires a folder path." << std::endl;
+ std::cerr << BOLDRED << "[Error] " << RESET << "'" << outputDir.u8string() << "' is not a valid directory." << std::endl;
return 1;
}
}
- else
- {
- fs::path path = fs::u8path(argv[i]);
- files.push_back(path);
- }
+ fs::create_directories(outputDir);
}
- for (const auto &file : files)
+ // process files in a folder
+ if (result.count("directory"))
{
- if (processFolders && fs::is_directory(file))
+ fs::path sourceDir = fs::u8path(result["directory"].as());
+ if (!fs::is_directory(sourceDir))
{
- processFilesInFolder(file);
+ std::cerr << BOLDRED << "[Error] " << RESET << "'" << sourceDir.u8string() << "' is not a valid directory." << std::endl;
+ return 1;
+ }
+
+ bool recursive = result["recursive"].as();
+
+ if (recursive)
+ {
+ // 递归遍历源目录
+ for (const auto &entry : fs::recursive_directory_iterator(sourceDir))
+ {
+ // 没有指定输出目录,则使用源目录作为输出目录
+ if (!outputDirSpecified)
+ {
+ outputDir = sourceDir;
+ }
+
+ // 获得递归遍历的相对路径
+ const auto &path = fs::u8path(entry.path().u8string());
+ auto relativePath = fs::relative(path, sourceDir);
+ fs::path destinationPath = outputDir / relativePath;
+
+ if (fs::is_regular_file(path))
+ {
+ // 确保输出文件的目录存在
+ fs::create_directories(destinationPath.parent_path());
+
+ // 处理文件
+ processFile(path, destinationPath.parent_path());
+ }
+ }
}
else
{
- processFile(file);
+ for (const auto &entry : fs::directory_iterator(sourceDir))
+ {
+ const auto &path = fs::u8path(entry.path().u8string());
+ if (entry.is_regular_file())
+ {
+ if (outputDirSpecified)
+ {
+ processFile(path, outputDir);
+ }
+ else
+ {
+ processFile(path, "");
+ }
+ }
+ }
}
+ return 0;
}
+ // process individual files
+ if (result.count("filenames"))
+ {
+ for (const auto &filePath : result["filenames"].as>())
+ {
+ fs::path filePathU8 = fs::u8path(filePath);
+ if (!fs::is_regular_file(filePathU8))
+ {
+ std::cerr << BOLDRED << "[Error] " << RESET << "'" << filePathU8.u8string() << "' is not a valid file." << std::endl;
+ continue;
+ }
+
+ if (outputDirSpecified)
+ {
+ processFile(filePathU8, outputDir);
+ }
+ else
+ {
+ processFile(filePathU8, "");
+ }
+ }
+ }
return 0;
}
diff --git a/src/ncmcrypt.cpp b/src/ncmcrypt.cpp
index 09f7dbc..00f760e 100644
--- a/src/ncmcrypt.cpp
+++ b/src/ncmcrypt.cpp
@@ -16,6 +16,9 @@
#include
#include
+#pragma warning(disable:4267)
+#pragma warning(disable:4244)
+
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};
@@ -260,9 +263,14 @@ void NeteaseCrypt::FixMetadata()
audioFile->~File();
}
-void NeteaseCrypt::Dump()
+void NeteaseCrypt::Dump(std::string const &outputDir = "")
{
- mDumpFilepath = std::filesystem::u8path(mFilepath);
+ if (outputDir.empty())
+ {
+ mDumpFilepath = std::filesystem::u8path(mFilepath);
+ } else {
+ mDumpFilepath = std::filesystem::u8path(outputDir) / std::filesystem::u8path(mFilepath).filename();
+ }
std::vector buffer(0x8000);