diff --git a/client/deps/cliparser/argtable3.c b/client/deps/cliparser/argtable3.c index f0b6a4d37..134c82306 100644 --- a/client/deps/cliparser/argtable3.c +++ b/client/deps/cliparser/argtable3.c @@ -4270,10 +4270,15 @@ static void arg_cat_optionv(char *dest, } if (datatype) { - if (longopts) +/* if (longopts) arg_cat(&dest, "=", &ndest); else if (shortopts) arg_cat(&dest, " ", &ndest); +*/ + if (longopts) + arg_cat(&dest, " ", &ndest); + else if (shortopts) + arg_cat(&dest, " ", &ndest); if (optvalue) { arg_cat(&dest, "[", &ndest); diff --git a/client/deps/cliparser/cliparser.c b/client/deps/cliparser/cliparser.c index 042dae99d..4ec4eb303 100644 --- a/client/deps/cliparser/cliparser.c +++ b/client/deps/cliparser/cliparser.c @@ -69,8 +69,8 @@ int CLIParserParseArg(CLIParserContext *ctx, int argc, char **argv, void *vargta PrintAndLogEx(NORMAL, "\n"_SectionTagColor_("usage:")); PrintAndLogEx(NORMAL, " "_CommandColor_("%s")NOLF, ctx->programName); - arg_print_syntaxv(stdout, ctx->argtable, "\n\n"); - + arg_print_syntax(stdout, ctx->argtable, "\n\n"); + PrintAndLogEx(NORMAL, _SectionTagColor_("options:")); arg_print_glossary(stdout, ctx->argtable, " "_ArgColor_("%-30s")" "_ArgHelpColor_("%s")"\n"); diff --git a/client/src/cmdhfst.c b/client/src/cmdhfst.c index 8308df9b1..ff2b682bc 100644 --- a/client/src/cmdhfst.c +++ b/client/src/cmdhfst.c @@ -14,6 +14,7 @@ #include "cmdparser.h" // command_t #include "comms.h" // clearCommandBuffer #include "cmdtrace.h" +#include "cliparser.h" #include "crc16.h" #include "cmdhf14a.h" #include "protocols.h" // definitions of ISO14A/7816 protocol @@ -21,65 +22,9 @@ #include "mifare/ndef.h" // NDEFRecordsDecodeAndPrint #define TIMEOUT 2000 + static int CmdHelp(const char *Cmd); -static int usage_hf_st_info(void) { - PrintAndLogEx(NORMAL, "Usage: hf st info [h]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h this help"); - PrintAndLogEx(NORMAL, "Example:"); - PrintAndLogEx(NORMAL, _YELLOW_(" hf st info")); - return PM3_SUCCESS; -} -static int usage_hf_st_sim(void) { - PrintAndLogEx(NORMAL, "\n Emulating ST25TA512B tag with 7 byte UID\n"); - PrintAndLogEx(NORMAL, "Usage: hf st sim [h] u "); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h : This help"); - PrintAndLogEx(NORMAL, " u : 7 byte UID"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" hf st sim u 02E2007D0FCA4C")); - return PM3_SUCCESS; -} -static int usage_hf_st_ndef(void) { - PrintAndLogEx(NORMAL, "\n Print NFC Data Exchange Format (NDEF)\n"); - PrintAndLogEx(NORMAL, "Usage: hf st ndef [h] p "); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h : This help"); - PrintAndLogEx(NORMAL, " p : 16 byte password"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" hf st ndef p 82E80053D4CA5C0B656D852CC696C8A1")); - return PM3_SUCCESS; -} - -static int usage_hf_st_protect(void) { - PrintAndLogEx(NORMAL, "\n Change R/W protection for NFC Data Exchange Format (NDEF)\n"); - PrintAndLogEx(NORMAL, "Usage: hf st protect [h] p r|w [0|1]"); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h : This help"); - PrintAndLogEx(NORMAL, " p : 16 byte write password"); - PrintAndLogEx(NORMAL, " r|w : Change (r)ead or (w)rite protection"); - PrintAndLogEx(NORMAL, " [0|1] : Enable / Disable protection"); - PrintAndLogEx(NORMAL, " 0 = Disable (default)"); - PrintAndLogEx(NORMAL, " 1 = Enable"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" hf st protect p 82E80053D4CA5C0B656D852CC696C8A1 r 0")); - return PM3_SUCCESS; -} - -static int usage_hf_st_pwd(void) { - PrintAndLogEx(NORMAL, "\n Change R/W password for NFC Data Exchange Format (NDEF)\n"); - PrintAndLogEx(NORMAL, "Usage: hf st pwd [h] p r|w n "); - PrintAndLogEx(NORMAL, "Options:"); - PrintAndLogEx(NORMAL, " h : This help"); - PrintAndLogEx(NORMAL, " p : 16 byte write password"); - PrintAndLogEx(NORMAL, " r|w : Change (r)ead or (w)rite password"); - PrintAndLogEx(NORMAL, " n : New 16 byte password"); - PrintAndLogEx(NORMAL, "Examples:"); - PrintAndLogEx(NORMAL, _YELLOW_(" hf st pwd p 82E80053D4CA5C0B656D852CC696C8A1 r n 00000000000000000000000000000000")); - return PM3_SUCCESS; -} - // get ST Microelectronics chip model (from UID) static char *get_st_chip_model(uint8_t pc) { static char model[40]; @@ -348,26 +293,44 @@ int infoHF_ST(void) { return PM3_SUCCESS; } -// menu command to get and print all info known about any known 14b tag +// menu command to get and print all info known about any known ST25TA tag static int cmd_hf_st_info(const char *Cmd) { - char c = tolower(param_getchar(Cmd, 0)); - if (c == 'h') return usage_hf_st_info(); + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf st info", + "Get info about ST25TA tag", + "hf st info" + ); + + void *argtable[] = { + arg_param_begin, + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParserFree(ctx); return infoHF_ST(); } static int cmd_hf_st_sim(const char *Cmd) { - char c = tolower(param_getchar(Cmd, 0)); - if (c == 'h' || c == 0x00) return usage_hf_st_sim(); - int uidlen = 0; - uint8_t cmdp = 0; uint8_t uid[7] = {0}; - if (c == 'u') { - param_gethex_ex(Cmd, cmdp + 1, uid, &uidlen); - uidlen >>= 1; - if (uidlen != 7) { - return usage_hf_st_sim(); - } + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf st sim", + "Emulating ST25TA512B tag with 7 byte UID", + "hf st sim -u 02E2007D0FCA4C\n"); + + void *argtable[] = { + arg_param_begin, + arg_str1("u", "uid", "", "7 byte UID"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + CLIGetHexWithReturn(ctx, 1, uid, &uidlen); + + if (uidlen != 7) { + PrintAndLogEx(ERR, "UID must be 7 hex bytes"); + return PM3_EINVARG; } char param[40]; @@ -376,18 +339,32 @@ static int cmd_hf_st_sim(const char *Cmd) { } static int cmd_hf_st_ndef(const char *Cmd) { - char c = tolower(param_getchar(Cmd, 0)); - if (c == 'h' || c == 0x00) return usage_hf_st_ndef(); - int pwdlen = 0; - uint8_t cmdp = 0; uint8_t pwd[16] = {0}; - if (c == 'p') { - param_gethex_ex(Cmd, cmdp + 1, pwd, &pwdlen); - pwdlen >>= 1; + bool with_pwd = false; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf st ndef", + "Read NFC Data Exchange Format (NDEF) file on ST25TA", + "hf st ndef -p 82E80053D4CA5C0B656D852CC696C8A1\n"); + + void *argtable[] = { + arg_param_begin, + arg_str0("p", "password", "", "16 byte read password"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + CLIGetHexWithReturn(ctx, 1, pwd, &pwdlen); + + if (pwdlen == 0) { + with_pwd = false; + } else { if (pwdlen != 16) { - return usage_hf_st_ndef(); + PrintAndLogEx(ERR, "Password must be 16 hex bytes"); + return PM3_EINVARG; } + with_pwd = true; } bool activate_field = true; @@ -429,27 +406,29 @@ static int cmd_hf_st_ndef(const char *Cmd) { return PM3_ESOFT; } - // --------------- VERIFY ---------------- - uint8_t aVERIFY[30]; - int aVERIFY_n = 0; - param_gethex_to_eol("0020000100", 0, aVERIFY, sizeof(aVERIFY), &aVERIFY_n); - res = ExchangeAPDU14a(aVERIFY, aVERIFY_n, activate_field, keep_field_on, response, sizeof(response), &resplen); - if (res) - return res; - - sw = get_sw(response, resplen); - if (sw == 0x6300) { - // need to provide 16byte password - param_gethex_to_eol("0020000110", 0, aVERIFY, sizeof(aVERIFY), &aVERIFY_n); - memcpy(aVERIFY + aVERIFY_n, pwd, pwdlen); - res = ExchangeAPDU14a(aVERIFY, aVERIFY_n + pwdlen, activate_field, keep_field_on, response, sizeof(response), &resplen); + if (with_pwd) { + // --------------- VERIFY ---------------- + uint8_t aVERIFY[30]; + int aVERIFY_n = 0; + param_gethex_to_eol("0020000100", 0, aVERIFY, sizeof(aVERIFY), &aVERIFY_n); + res = ExchangeAPDU14a(aVERIFY, aVERIFY_n, activate_field, keep_field_on, response, sizeof(response), &resplen); if (res) return res; sw = get_sw(response, resplen); - if (sw != 0x9000) { - PrintAndLogEx(ERR, "Verify password failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); - return PM3_ESOFT; + if (sw == 0x6300) { + // need to provide 16byte password + param_gethex_to_eol("0020000110", 0, aVERIFY, sizeof(aVERIFY), &aVERIFY_n); + memcpy(aVERIFY + aVERIFY_n, pwd, pwdlen); + res = ExchangeAPDU14a(aVERIFY, aVERIFY_n + pwdlen, activate_field, keep_field_on, response, sizeof(response), &resplen); + if (res) + return res; + + sw = get_sw(response, resplen); + if (sw != 0x9000) { + PrintAndLogEx(ERR, "Verify password failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + return PM3_ESOFT; + } } } @@ -473,57 +452,72 @@ static int cmd_hf_st_ndef(const char *Cmd) { static int cmd_hf_st_protect(const char *Cmd) { - uint8_t cmdp = 0; - bool errors = false; int pwdlen = 0; uint8_t pwd[16] = {0}; int statelen = 3; - uint8_t state[3] = {0x26, 0, 0}; + uint8_t state[3] = {0x26, 0, 0x02}; - while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { - switch (tolower(param_getchar(Cmd, cmdp))) { - case 'h': - return usage_hf_st_protect(); - case '0': - state[0] = 0x26; //Disable protection - cmdp++; - break; - case '1': - state[0] = 0x28; //Enable protection - cmdp++; - break; - case 'r': - state[2] = 0x01; - cmdp++; - break; - case 'w': - state[2] = 0x02; - cmdp++; - break; - case 'p': - param_gethex_ex(Cmd, cmdp + 1, pwd, &pwdlen); - pwdlen >>= 1; - cmdp += 2; - break; - default: - PrintAndLogEx(WARNING, "Unknown parameter '%c'", param_getchar(Cmd, cmdp)); - errors = true; - break; - } - } + bool disable_protection = false; + bool enable_protection = false; + bool read_protection = false; + bool write_protection = false; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf st protect", + "Change read or write protection for NFC Data Exchange Format (NDEF) file on ST25TA", + "hf st protect -p 82E80053D4CA5C0B656D852CC696C8A1 -r -e -> enable read protection\n" + "hf st protect -p 82E80053D4CA5C0B656D852CC696C8A1 -w -d -> disable write protection\n"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("e", "enable", "enable protection"), + arg_lit0("d", "disable", "disable protection (default)"), + arg_lit0("r", "read", "change read protection"), + arg_lit0("w", "write", "change write protection (default)"), + arg_str1("p", "password", "", "16 byte write password"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + enable_protection = arg_get_lit(ctx, 1); + disable_protection = arg_get_lit(ctx, 2); + read_protection = arg_get_lit(ctx, 3); + write_protection = arg_get_lit(ctx, 4); + + CLIGetHexWithReturn(ctx, 5, pwd, &pwdlen); + + CLIParserFree(ctx); //Validations - if (state[2] == 0x00) { - PrintAndLogEx(WARNING, "Missing action (r)ead or (w)rite"); - errors = true; - } - if (pwdlen != 16) { - PrintAndLogEx(WARNING, "Missing 16 byte password"); - errors = true; + if (enable_protection && disable_protection) { + PrintAndLogEx(ERR, "Must specify either enable or disable protection, not both"); + return PM3_EINVARG; + } else { + if (enable_protection) { + state[0] = 0x28; + } + if (disable_protection) { + state[0] = 0x26; + } } - if (errors || cmdp == 0) return usage_hf_st_protect(); + if (read_protection && write_protection) { + PrintAndLogEx(ERR, "Must specify either read or write protection, not both"); + return PM3_EINVARG; + } else { + if (read_protection) { + state[2] = 0x01; + } + if (write_protection) { + state[2] = 0x02; + } + } + + if (pwdlen != 16) { + PrintAndLogEx(ERR, "Missing 16 byte password"); + return PM3_EINVARG; + } bool activate_field = true; bool keep_field_on = true; @@ -603,63 +597,63 @@ static int cmd_hf_st_protect(const char *Cmd) { } static int cmd_hf_st_pwd(const char *Cmd) { - char c = tolower(param_getchar(Cmd, 0)); - if (c == 'h' || c == 0x00) return usage_hf_st_pwd(); - uint8_t cmdp = 0; - bool errors = false; int pwdlen = 0; uint8_t pwd[16] = {0}; int newpwdlen = 0; uint8_t newpwd[16] = {0}; int changePwdlen = 4; - uint8_t changePwd[4] = {0x24, 0x00, 0x00, 0x10}; + uint8_t changePwd[4] = {0x24, 0x00, 0x01, 0x10}; + bool change_read_password = false; + bool change_write_password = false; - while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { - switch (tolower(param_getchar(Cmd, cmdp))) { - case 'h': - return usage_hf_st_pwd(); - case 'r': - changePwd[2] = 0x01; - cmdp++; - break; - case 'w': - changePwd[2] = 0x02; - cmdp++; - break; - case 'p': - param_gethex_ex(Cmd, cmdp + 1, pwd, &pwdlen); - pwdlen >>= 1; - cmdp += 2; - break; - case 'n': - param_gethex_ex(Cmd, cmdp + 1, newpwd, &newpwdlen); - newpwdlen >>= 1; - cmdp += 2; - break; - default: - PrintAndLogEx(WARNING, "Unknown parameter '%c'", param_getchar(Cmd, cmdp)); - errors = true; - break; + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf st pwd", + "Change read or write password for NFC Data Exchange Format (NDEF) file on ST25TA", + "hf st pwd -p 82E80053D4CA5C0B656D852CC696C8A1 -r -n 00000000000000000000000000000000 -> change read password\n" + "hf st pwd -p 82E80053D4CA5C0B656D852CC696C8A1 -w -n 00000000000000000000000000000000 -> change write password\n"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("r", "read", "change the read password (default)"), + arg_lit0("w", "write", "change the write password"), + arg_str1("p", "password", "", "current 16 byte write password"), + arg_str1("n", "new", "", "new 16 byte password"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + change_read_password = arg_get_lit(ctx, 1); + change_write_password = arg_get_lit(ctx, 2); + + CLIGetHexWithReturn(ctx, 3, pwd, &pwdlen); + + CLIGetHexWithReturn(ctx, 4, newpwd, &newpwdlen); + + if (change_read_password && change_write_password) { + PrintAndLogEx(ERR, "Must specify either read or write, not both"); + CLIParserFree(ctx); + return PM3_EINVARG; + } else { + if (change_read_password) { + changePwd[2] = 0x01; + } + if (change_write_password) { + changePwd[2] = 0x02; } } - //Validations + CLIParserFree(ctx); - if (changePwd[2] == 0x00) { - PrintAndLogEx(WARNING, "Missing password specification: (r)ead or (w)rite"); - errors = true; - } if (pwdlen != 16) { - PrintAndLogEx(WARNING, "Missing original 16 byte password"); - errors = true; + PrintAndLogEx(ERR, "Original write password must be 16 hex bytes"); + return PM3_EINVARG; } if (newpwdlen != 16) { - PrintAndLogEx(WARNING, "Missing new 16 byte password"); - errors = true; + PrintAndLogEx(ERR, "New password must be 16 hex bytes"); + return PM3_EINVARG; } - if (errors || cmdp == 0) return usage_hf_st_pwd(); - + bool activate_field = true; bool keep_field_on = true; uint8_t response[PM3_CMD_DATA_SIZE]; @@ -745,13 +739,13 @@ static int cmd_hf_st_list(const char *Cmd) { } static command_t CommandTable[] = { - {"help", CmdHelp, AlwaysAvailable, "This help"}, - {"info", cmd_hf_st_info, IfPm3Iso14443a, "Tag information"}, - {"list", cmd_hf_st_list, AlwaysAvailable, "List ISO 14443A/7816 history"}, - {"ndef", cmd_hf_st_ndef, AlwaysAvailable, "read NDEF file on tag"}, + {"help", CmdHelp, AlwaysAvailable, "This help"}, + {"info", cmd_hf_st_info, IfPm3Iso14443a, "Tag information"}, + {"list", cmd_hf_st_list, AlwaysAvailable, "List ISO 14443A/7816 history"}, + {"ndef", cmd_hf_st_ndef, AlwaysAvailable, "read NDEF file on tag"}, {"protect", cmd_hf_st_protect, IfPm3Iso14443a, "change protection on tag"}, - {"pwd", cmd_hf_st_pwd, IfPm3Iso14443a, "change password on tag"}, - {"sim", cmd_hf_st_sim, IfPm3Iso14443a, "Fake ISO 14443A/ST tag"}, + {"pwd", cmd_hf_st_pwd, IfPm3Iso14443a, "change password on tag"}, + {"sim", cmd_hf_st_sim, IfPm3Iso14443a, "Fake ISO 14443A/ST tag"}, {NULL, NULL, NULL, NULL} }; diff --git a/doc/cliparser.md b/doc/cliparser.md index bbc6c6b42..e4cc8e062 100644 --- a/doc/cliparser.md +++ b/doc/cliparser.md @@ -10,16 +10,30 @@ hf 14b raw -ss lf search 1 lf config h H ``` -In order to counter this and unify it, there was discussion over at the official repository a few years ago (link to issue) and there it became clear a change is needed. Among the different solutions suggested @merlokk's idea of using the lib cliparser was agreed upon. The lib was adapted and implemented for commands like +even the external tools which we collected into this repo, under folder */tools/* folder uses their own argument parsing. + + +In order to counter this and unify it, there was discussion over at the official repository a few years ago [link to issue](https://github.com/Proxmark/proxmark3/issues/467) and there it became clear a change is needed. Among the different solutions suggested @merlokk's idea of using the lib cliparser was agreed upon. The lib was adapted and implemented for commands like + ``` -emv -hf fido +[usb] pm3 --> emv +[usb] pm3 --> hf fido ``` -And then it fell into silence since it wasn't well documented how to use the cliparser. Looking at source code wasn't very efficient. However the need of a better cli parsing was still there. Fast forward today, where more commands has used the cliparser but it still wasn't the natural way when adding a new client command to the Proxmark3 client. After more discussions among @doegox, @iceman1001 and @mrwalker the concept became more clear on how to use the cliparser lib in the _preferred_ way. The aftermath was a design and layout specfied which lead to a simpler implemtentation of the cliparser in the client source code while still unfiy all helptexts with the new colours support and a defined layout. As seen below, the simplicity and clearness. + +And then it fell into silence since it wasn't well documented how to use the cliparser. Looking at source code wasn't very efficient. However the need of a better cli parsing was still there. + +Fast forward today, where more commands has used the cliparser but it still wasn't the natural way when adding a new client command to the Proxmark3 client. +After more discussions among @doegox, @iceman1001 and @mrwalker the concept became more clear on how to use the cliparser lib in the _preferred_ way. + +The aftermath was a design and layout specfied which lead to a simpler implemtentation of the cliparser in the client source code while still unfiy all helptexts with the new colours support and a defined layout. As seen below, the simplicity and clearness. ![sample of new style helptext](http://www.icedev.se/proxmark3/helptext.png) +Furthermore @mrwalker offered to take notes and thus this document was created. + +This is the _new_ and _prefered_ way to implement _helptext_ and _cli parsing_ for Proxmark3 client commands and it's external tools. + ## cliparser setup and use @@ -28,11 +42,11 @@ It will also add the `-h --help` option automatic. ## design comments -* where possiable all options should be lowercase. -* extended options preceeded with -- should be short +* where possible all options should be lowercase. +* extended options preceded with -- should be short * options provided directly (without an option identifier) should be avoided. -* -vv for extra verbos should be avoided; use of debug level is prefered. -* with --options the equal is not needed (will work with and without) so dont use '=' +* -vv for extra verbos should be avoided; use of debug level is preferred. +* with --options the equal is not needed (will work with and without) so don't use '=' e.g. cmd --cn 12345 @@ -62,10 +76,10 @@ In the command function, setup the context CLIParserContext *ctx; -### define the text +### define the context CLIParserInit (\, \, \); -use -> to seperate example and example comment and \\n to seperate examples. +use -> to separate example and example comment and \\n to separate examples. e.g. lf indala clone -r a0000000a0002021 -> this uses ..... CLIParserInit(&ctx, "lf indala clone", @@ -92,21 +106,28 @@ _All options has a parameter index, since `-h --help` is added automatic, it wi Hence all options you add will start at index 1 and upwards._ **Notes:** -booleen : arg_lit0 ("\", "\", \["\",\] \<"description"\>) +**bool option. true if supplied** +bool : arg_lit0 ("\", "\", \["\",\] \<"description"\>) -**integer** - optional integer : arg_int0 ("\", "\", \["\",\] \<"description"\>)\ +**integer that is optional** + optional integer : arg_int0 ("\", "\", \["\",\] \<"description"\>) + +**integer that is required** required integer : arg_int1 ("\", "\", \["\",\] \<"description"\>) -**Strings 0 or 1** - optional string : arg_str0("\", "\", \["\",\] \<"description"\>)\ +**String option that is optional and only one instance can be provided** + optional string : arg_str0("\", "\", \["\",\] \<"description"\>) + +**String option that is required and only one instance can be provided** required string : arg_str1("\", "\", \["\",\] \<"description"\>) -**Strings x to 250** - optional string : arg_strx0 ("\", "\", \["\",\] \<"description"\>)\ +**String option that is optional and can have up to 250 instances provided** + optional string : arg_strx0 ("\", "\", \["\",\] \<"description"\>) + +**String option that is required/at least one instance and can have up to 250 instances** required string : arg_strx1 ("\", "\", \["\",\] \<"description"\>) -**if an option does not have a short or long option, use NULL in its place** +**if an option does not have a short or long option, use NULL in its place** ### show the menu CLIExecWithReturn(\, \, \, \); @@ -119,6 +140,12 @@ Once you have extracted the options, cleanup the context. CLIParserFree(ctx); ### retreiving options + + +The parser will format and color and layout as needed. +It will also add the `-h --help` option automatic. + + **bool option** arg_get_lit(\, \);