41#include <system_error>
43#include <unordered_map>
65concept StringType = std::is_class_v<T> &&
requires(T type) {
70 typename T::value_type;
71 typename T::size_type;
72 { type.length() } -> std::same_as<typename T::size_type>;
73 { type[0] } -> std::same_as<typename T::value_type &>;
74 { type += T{} } -> std::same_as<T &>;
75 { type = T{} } -> std::same_as<T &>;
80 std::vector<char> buffer(32768);
81 const DWORD length = GetModuleFileNameA(
nullptr, buffer.data(),
static_cast<DWORD
>(buffer.size()));
82 if (length > 0 && length < buffer.size()) {
83 return std::filesystem::path(std::string(buffer.data(), length)).parent_path().lexically_normal().string();
85#elif defined(__linux__)
86 std::vector<char> buffer(4096);
87 const ssize_t length = readlink(
"/proc/self/exe", buffer.data(), buffer.size());
88 if (length > 0 &&
static_cast<std::size_t
>(length) < buffer.size()) {
89 return std::filesystem::path(std::string(buffer.data(),
static_cast<std::size_t
>(length))).parent_path().lexically_normal().string();
93 std::error_code error;
94 const std::filesystem::path absolute_path = std::filesystem::absolute(executable ==
nullptr ?
"." : executable, error);
95 if (!error && absolute_path.has_parent_path()) {
96 return absolute_path.parent_path().lexically_normal().string();
158 args = std::move(a.args);
173 String
text()
const {
return value; }
187template <StringType String>
class Argz {
234 arg_data.args.reserve(
static_cast<size_t>(argc - 1));
236 if constexpr (std::is_same<typename String::value_type, char>::value) {
237 for (
int i = 1; i < argc; ++i) {
238 const char *a = (argv !=
nullptr) ? argv[i] :
nullptr;
239 arg_data.args.emplace_back(a !=
nullptr ? a :
"");
244 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
245 for (
int i = 1; i < argc; ++i) {
246 const char *a = (argv !=
nullptr) ? argv[i] :
nullptr;
249 for (
size_t z = 0; a[z] != 0; ++z) {
250 data +=
static_cast<typename String::value_type
>(a[z]);
278 a.
desc = description;
293 a.
desc = description;
309 a.
desc = description;
326 a.
desc = description;
339 if (i.second.arg_name == value) {
340 return i.second.arg_letter;
353 if (index <
static_cast<int>(
arg_data.args.size())) {
354 const String &type{
arg_data.args[index]};
355 if (type.length() > 2 && type[0] ==
'-' && type[1] ==
'-') {
357 String inline_value{};
358 const auto eq_pos = type.find(
static_cast<typename String::value_type
>(
'='));
359 const bool has_inline_value = (eq_pos != String::npos);
360 for (
size_t z = 2; z < type.length(); ++z)
361 if (!has_inline_value || z < eq_pos) {
363 }
else if (z > eq_pos) {
364 inline_value += type[z];
371 if (has_inline_value) {
372 if constexpr (std::is_same<typename String::value_type, char>::value) {
375 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
386 if (has_inline_value) {
387 if (inline_value.empty()) {
388 if constexpr (std::is_same<typename String::value_type, char>::value) {
391 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
399 if (++index <
static_cast<int>(
arg_data.args.size())) {
400 const String &s{
arg_data.args[index]};
401 if (canUseAsOptionValue(s)) {
408 if constexpr (std::is_same<typename String::value_type, char>::value) {
411 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
417 throwUnknownOption(type);
419 }
else if (type.length() == 1 && type[0] ==
'-') {
420 if constexpr (std::is_same<typename String::value_type, char>::value) {
423 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
426 }
else if (type.length() > 1 && (type[0] ==
'-')) {
427 const int c{type[cindex]};
430 if (cindex >=
static_cast<int>(type.length())) {
435 name_val +=
static_cast<typename String::value_type
>(c);
442 if (index <
static_cast<int>(
arg_data.args.size())) {
443 const String &s{
arg_data.args[index]};
444 if (!canUseAsOptionValue(s)) {
445 if constexpr (std::is_same<typename String::value_type, char>::value) {
448 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
452 if (s.length() > 0) {
460 if constexpr (std::is_same<typename String::value_type, char>::value) {
463 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
468 if constexpr (std::is_same<typename String::value_type, char>::value) {
471 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
476 throwUnknownOption(type);
495 template <
typename T>
void help(T &cout) {
496 using char_type =
typename std::decay<decltype(*std::declval<T>().rdbuf())>::type::char_type;
497 const bool use_color = supportsColor(cout);
498 auto write_ansi = [&](
const char *seq) {
502 if constexpr (std::is_same_v<char_type, char>) {
504 }
else if constexpr (std::is_same_v<char_type, wchar_t>) {
505 for (
const char *p = seq; *p !=
'\0'; ++p) {
506 cout << static_cast<char_type>(*p);
510 auto write_padding = [&](
size_t count) {
511 for (
size_t i = 0; i <
count; ++i) {
512 cout << static_cast<char_type>(
' ');
520 std::vector<Argument<String>> v;
521 std::vector<Argument<String>> v2;
524 v.push_back(i.second);
526 v2.push_back(i.second);
528 std::ranges::sort(v);
529 std::ranges::sort(v2);
530 std::vector<HelpRow> rows;
531 rows.reserve(v.size() + v2.size());
532 for (
const auto &a : v) {
534 token +=
static_cast<char_type
>(
'-');
535 token +=
static_cast<char_type
>(a.arg_letter);
536 rows.push_back(HelpRow{std::move(token), a.desc,
true});
538 for (
const auto &a : v2) {
540 token +=
static_cast<char_type
>(
'-');
541 token +=
static_cast<char_type
>(
'-');
543 rows.push_back(HelpRow{std::move(token), a.desc,
false});
545 size_t token_width = 0;
546 for (
const auto &row : rows) {
547 token_width = std::max(token_width,
static_cast<size_t>(row.token.length()));
549 const size_t desc_column = token_width + 2;
550 for (
const auto &row : rows) {
551 write_ansi(row.is_short ?
"\x1b[1;36m" :
"\x1b[1;35m");
553 write_ansi(
"\x1b[0m");
554 if (desc_column > row.token.length()) {
555 write_padding(desc_column - row.token.length());
559 write_ansi(
"\x1b[90m");
561 write_ansi(
"\x1b[0m");
566 const size_t count()
const {
return index; }
570 std::unordered_map<int, Argument<String>>
arg_info;
573 template <
typename Stream>
static bool supportsColor(
const Stream &stream) {
574 if constexpr (std::is_same_v<Stream, std::ostream>) {
575 if (&stream != &std::cout) {
579 return _isatty(_fileno(stdout));
581 return isatty(fileno(stdout));
583 }
else if constexpr (std::is_same_v<Stream, std::wostream>) {
584 if (&stream != &std::wcout) {
588 return _isatty(_fileno(stdout));
590 return isatty(fileno(stdout));
597 [[noreturn]]
void throwUnknownOption(
const String &token)
const {
598 if constexpr (std::is_same<typename String::value_type, char>::value) {
599 String value =
"Error argument: ";
601 value +=
" switch not found";
604 if constexpr (std::is_same<typename String::value_type, wchar_t>::value) {
605 String value = L
"Error argument: ";
607 value += L
" switch not found";
612 bool isSignedNumericToken(
const String &s)
const {
617 const auto ch_minus =
static_cast<typename String::value_type
>(
'-');
618 const auto ch_plus =
static_cast<typename String::value_type
>(
'+');
619 const auto ch_dot =
static_cast<typename String::value_type
>(
'.');
620 const auto ch_e =
static_cast<typename String::value_type
>(
'e');
621 const auto ch_E =
static_cast<typename String::value_type
>(
'E');
622 const auto ch_0 =
static_cast<typename String::value_type
>(
'0');
623 const auto ch_9 =
static_cast<typename String::value_type
>(
'9');
626 if (s[i] == ch_minus || s[i] == ch_plus) {
629 if (i >= s.length()) {
633 bool has_digit =
false;
634 bool has_dot =
false;
635 for (; i < s.length(); ++i) {
636 const auto ch = s[i];
637 if (ch >= ch_0 && ch <= ch_9) {
641 if (ch == ch_dot && !has_dot) {
645 if ((ch == ch_e || ch == ch_E) && has_digit) {
647 if (j < s.length() && (s[j] == ch_minus || s[j] == ch_plus)) {
650 if (j >= s.length()) {
653 bool exp_digit =
false;
654 for (; j < s.length(); ++j) {
655 if (s[j] >= ch_0 && s[j] <= ch_9) {
668 bool isRecognizedOptionToken(
const String &s)
const {
669 if (s.length() <= 1 || s[0] !=
static_cast<typename String::value_type
>(
'-')) {
673 if (s.length() > 2 && s[1] ==
static_cast<typename String::value_type
>(
'-')) {
675 for (
size_t z = 2; z < s.length(); ++z) {
681 for (
size_t z = 1; z < s.length(); ++z) {
689 bool canUseAsOptionValue(
const String &s)
const {
693 if (s[0] !=
static_cast<typename String::value_type
>(
'-')) {
696 if (isSignedNumericToken(s)) {
699 return !isRecognizedOptionToken(s);
702 int index = 0, cindex = 1;
762[[nodiscard]]
inline int parse_arg_int(
const std::string &text,
const std::string &option_name) {
764 const char *begin = text.data();
765 const char *end = begin + text.size();
766 const std::from_chars_result result = std::from_chars(begin, end, value);
767 if (result.ec != std::errc{} || result.ptr != end) {
773[[nodiscard]]
inline double parse_arg_double(
const std::string &text,
const std::string &option_name) {
775 const char *begin = text.data();
776 const char *end = begin + text.size();
777 const std::from_chars_result result = std::from_chars(begin, end, value);
778 if (result.ec != std::errc{} || result.ptr != end || !std::isfinite(value)) {
785 if (argv0 ==
nullptr || argv0[0] ==
'\0') {
789 std::string name = argv0;
790 const std::string::size_type separator = name.find_last_of(
"/\\");
791 if (separator != std::string::npos) {
792 name.erase(0, separator + 1);
795 constexpr std::string_view exe_extension =
".exe";
796 if (name.size() >= exe_extension.size()) {
797 const std::string::size_type extension_pos = name.size() - exe_extension.size();
798 const bool has_exe_extension = std::ranges::equal(name.begin() +
static_cast<std::ptrdiff_t
>(extension_pos), name.end(), exe_extension.begin(), exe_extension.end(), [](
char left,
char right) { return std::tolower(static_cast<unsigned char>(left)) == std::tolower(static_cast<unsigned char>(right)); });
799 if (has_exe_extension) {
800 name.erase(extension_pos);
804 return name.empty() ?
"mxvk" : name;
859 .addOptionSingle(
'v',
"Print version")
860 .addOptionSingleValue(
'p',
"assets path")
861 .addOptionDoubleValue(
'P',
"path",
"assets path")
862 .addOptionSingleValue(
'r',
"Resolution WidthxHeight")
863 .addOptionDoubleValue(
'R',
"resolution",
"Resolution WidthxHeight")
864 .addOptionSingle(
'f',
"fullscreen")
865 .addOptionDouble(
'F',
"fullscreen",
"fullscreen")
866 .addOptionDouble(301,
"fast",
"fast")
867 .addOptionDoubleValue(256,
"filename",
"input filename")
868 .addOptionDoubleValue(324,
"model",
"model filename (.obj, .mxmod, or .mxmod.z)")
869 .addOptionSingleValue(
'o',
"output filename")
870 .addOptionDoubleValue(304,
"output",
"output filename")
871 .addOptionSingleValue(
'c',
"crf value")
872 .addOptionDoubleValue(305,
"crf",
"crf value")
873 .addOptionDoubleValue(306,
"encode-preset",
"encoder preset")
874 .addOptionDoubleValue(307,
"encode-tune",
"encoder tune")
875 .addOptionDoubleValue(308,
"encode-codec",
"encoder codec policy or name")
876 .addOptionDouble(309,
"encode-realtime",
"encoder realtime mode")
877 .addOptionDouble(314,
"mxwrite-block",
"block MXWrite when its queue is full")
878 .addOptionDouble(310,
"repeat",
"repeat playback or wrapped textures")
879 .addOptionDouble(315,
"binary",
"use binary glyphs only")
880 .addOptionDouble(319,
"enable-crt",
"enable CRT post-processing at startup")
881 .addOptionDouble(320,
"enable-vsync",
"enable FIFO present mode / v-sync")
882 .addOptionDouble(322,
"enable-screenshot",
"enable F10 screenshot capture")
883 .addOptionDouble(325,
"disable-sound",
"disable background music")
884 .addOptionDouble(330,
"benchmark",
"enable application benchmark mode")
885 .addOptionDouble(331,
"wireframe",
"render supported 3D models as wireframes")
886 .addOptionDouble(326,
"nowarpfix",
"3dmath - disable perspective-correct texture mapping")
887 .addOptionDoubleValue(327,
"framebuffer",
"3dmath - software framebuffer size WidthxHeight")
888 .addOptionDouble(328,
"disable-mipmap",
"3dmath - disable mipmap generation and selection")
889 .addOptionDoubleValue(329,
"mip-bias",
"3dmath - mipmap level-of-detail bias (-16 to 16)")
890 .addOptionDoubleValue(321,
"fps",
"capture FPS override")
891 .addOptionSingleValue(
'z',
"matrix rain font size")
892 .addOptionDoubleValue(316,
"font-size",
"matrix rain font size")
893 .addOptionSingleValue(
'j',
"matrix rain font file path")
894 .addOptionDoubleValue(317,
"font-path",
"matrix rain font file path")
895 .addOptionSingleValue(
'C',
"matrix rain RGB tint (#RRGGBB or R,G,B)")
896 .addOptionDoubleValue(318,
"color",
"matrix rain RGB tint (#RRGGBB or R,G,B)")
897 .addOptionDoubleValue(302,
"resource",
"resource file")
898 .addOptionDoubleValue(303,
"resource_path",
"resource data path")
899 .addOptionDoubleValue(257,
"texture",
"texture file (.png or .tex)")
900 .addOptionDoubleValue(323,
"textures",
"texture file (.png or .tex)")
901 .addOptionSingleValue(
'S',
"shader SPV folder path (contains index.txt)")
902 .addOptionDoubleValue(258,
"shader-path",
"shader SPV folder path (contains index.txt)")
903 .addOptionDoubleValue(313,
"fragment",
"fragment shader SPV path")
904 .addOptionSingleValue(
'i',
"acidcam filter mode index")
905 .addOptionDoubleValue(311,
"index",
"acidcam filter mode index")
906 .addOptionDoubleValue(312,
"shader-index",
"initial shader entry index")
907 .addOptionDoubleValue(300,
"camera",
"camera index");
912 int tw = 1280, th = 720;
913 bool fullscreen =
false;
915 bool resolutionSpecified =
false;
916 std::string filename;
920 std::string encodePreset;
921 std::string encodeTune;
922 std::string encodeCodec;
923 bool encodeRealtime =
false;
924 bool mxwriteBlockWhenFull =
false;
927 bool enable_crt =
false;
928 bool enable_vsync =
false;
929 bool enable_screenshot =
false;
930 bool disable_sound =
false;
931 bool benchmark =
false;
932 bool wireframe =
false;
933 bool nowarpfix =
false;
934 bool disable_mipmap =
false;
935 float mip_bias = 0.0f;
937 bool framebufferSpecified =
false;
940 std::string font_path;
943 std::string shaderPath;
944 std::string fragmentPath;
945 int camera_index = 0;
947 int shader_index = 0;
948 std::string resource;
949 std::string resource_path;
950 while ((value = parser.
proc(arg)) != -1) {
982 encodeRealtime =
true;
985 mxwriteBlockWhenFull =
true;
1000 enable_screenshot =
true;
1003 disable_sound =
true;
1015 const std::string::size_type separator = arg.
arg_value.find(
'x');
1016 if (separator == std::string::npos) {
1021 if (framebuffer.
width <= 0 || framebuffer.
height <= 0) {
1024 framebufferSpecified =
true;
1027 disable_mipmap =
true;
1031 if (parsed_mip_bias < -16.0 || parsed_mip_bias > 16.0) {
1034 mip_bias =
static_cast<float>(parsed_mip_bias);
1045 if (font_size <= 0) {
1070 parser.
help(std::cout);
1080 if (pos == std::string::npos) {
1083 std::string left, right;
1088 if (tw <= 0 || th <= 0) {
1091 resolutionSpecified =
true;
1114 std::cerr <<
"mx: No path provided; using executable directory: " << path <<
'\n';
1116 std::error_code error;
1117 const std::filesystem::path absolute_path = std::filesystem::absolute(path, error);
1119 path = absolute_path.lexically_normal().string();
std::string executable_directory(const char *executable)
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
double parse_arg_double(const std::string &text, const std::string &option_name)
ArgType
Discriminator for option kind used inside Argument.
int parse_arg_int(const std::string &text, const std::string &option_name)
std::string parse_executable_name(const char *argv0)
Exception thrown by Argz::proc() on unrecognised or malformed options.
ArgException(const String &s)
Template command-line argument parser.
void help(T &cout)
Print all registered options to any ostream-like object.
std::unordered_map< int, Argument< String > > arg_info
Argz< String > & addOptionSingleValue(const int &c, const String &description)
Register a short option that requires a value argument (e.g.
ArgumentData< String > arg_data
int lookUpCode(const String &value) const
Look up the integer code registered for a long option name.
Argz< String > & addOptionDouble(const int &code, const String &value, const String &description)
Register a flag-only long option (e.g.
const size_t count() const
Argz< String > & initArgs(int argc, char **argv)
Ingest argc/argv, converting to the target String type.
Argz< String > & addOptionDoubleValue(const int &code, const String &value, const String &description)
Register a long option that requires a value argument (e.g.
int proc(Argument< String > &a)
Advance the cursor and parse the next argument.
Argz< String > & operator=(Argz< String > &&a)
Argz< String > & addOptionSingle(const int &c, const String &description)
Register a flag-only short option (e.g.
Argz< String > & operator=(const Argz< String > &a)
Argz(const Argz< String > &a)
void reset()
Reset the internal parse cursor to the beginning of argv.
Argz(int argc, char **argv)
Construct and immediately ingest argv.
Models a string-like class with indexing, concatenation, length, and value_type.
void setDefaultEnableScreenshot(bool enabled)
void setDefaultExecutableName(const std::string &name)
Holds the raw argv strings converted to the target String type.
ArgumentData(ArgumentData< String > &&a)
ArgumentData< String > & operator=(ArgumentData< String > &&a)
ArgumentData(const ArgumentData< String > &a)
ArgumentData & operator=(const ArgumentData< String > &a)
std::vector< String > args
Converted argv entries (argv[1] … argv[argc-1]).
int argc
Original argc value.
Describes a single parsed argument entry returned by Argz::proc().
int arg_letter
Short option code (e.g. 'o') or unique integer for long-only options.
Argument & operator=(const Argument< String > &a)
Argument(const Argument &a)
ArgType arg_type
Which ArgType variant this argument represents.
String arg_name
Long option name (e.g. "output").
auto operator<=>(const Argument< String > &a) const
String arg_value
Value string supplied after the option, if any.
bool operator==(const Argument< String > &a) const
String desc
Human-readable description used in help output.
Plain data structure returned by proc_args() with all common libmx2 CLI options.
FramebufferDimensions framebuffer
Software framebuffer size requested by --framebuffer.
std::string shaderPath
Optional SPV shader folder path (-S / --shader-path).
bool framebufferSpecified
Whether --framebuffer was provided.
std::string output
Optional output filename (--output).
std::string font_path
Optional font file path (--font-path).
bool mxwriteBlockWhenFull
Make MXWrite block instead of dropping frames (--mxwrite-block).
bool fast
Whether fast mode was requested (--fast).
int camera_index
Optional camera index.
bool enable_screenshot
Enable F10 screenshot capture (--enable-screenshot).
std::string encodeTune
Optional encoder tune (--encode-tune).
bool fullscreen
Whether fullscreen mode was requested.
int index
Optional acidcam filter mode index.
bool encodeRealtime
Enable low-latency encoder settings (--encode-realtime).
bool resolutionSpecified
Whether -r/–resolution was provided.
float mip_bias
Mipmap level-of-detail bias requested by --mip-bias.
std::string model
Optional model filename (--model).
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
std::string crf
Optional CRF value (--crf).
bool nowarpfix
Disable perspective-correct texture mapping (--nowarpfix).
std::string texture
Optional texture file path (--texture).
int height
Viewport height in pixels (default: 720).
double fps
Optional FPS override (--fps); non-positive means unspecified.
std::string encodeCodec
Optional encoder codec policy/name (--encode-codec).
std::string color
Optional rain RGB tint (--color).
std::string resource_path
Resource path.
bool repeat
Enable repeat behavior such as playback looping or wrapped textures.
std::string filename
Optional input filename (--filename).
std::string encodePreset
Optional encoder preset (--encode-preset).
std::string path
Asset root; proc_args() defaults it to the executable directory.
std::string fragmentPath
Optional fragment shader SPV path (--fragment).
std::string executable_name
Executable basename derived from argv[0].
bool benchmark
Enable application benchmark mode (--benchmark).
bool wireframe
Render supported 3D models as wireframes (--wireframe).
int width
Viewport width in pixels (default: 1280).
std::string resource
Resource file.
bool enable_crt
Enable CRT post-processing at startup (--enable-crt).
int shader_index
Optional initial shader entry index.
bool disable_mipmap
Disable mipmap generation and selection (--disable-mipmap).
int font_size
Matrix rain font size in pixels (--font-size).
bool binary
Use binary glyphs only (--binary).
bool disable_sound
Disable application background music (--disable-sound).
Parsed software framebuffer dimensions.
int width
Software framebuffer width in pixels.
int height
Software framebuffer height in pixels.