186a661d1e
Credit goes to @myfreeer https://github.com/myfreeer/chrome-pak-customizer
235 lines
6.7 KiB
C
235 lines
6.7 KiB
C
#pragma once
|
|
#ifndef COMMANDLINETOARGVA_H
|
|
#define COMMANDLINETOARGVA_H
|
|
|
|
#include <string.h>
|
|
#include <windows.h>
|
|
// https://github.com/wine-mirror/wine/blob/5946973021285dd6ecb8df224956fea4817f8fed/dlls/shell32/shell32_main.c
|
|
// https://github.com/futurist/CommandLineToArgvA/blob/09765ffef215f0a066d6b0ea5c1edda88b6739af/CommandLineToArgvA.c
|
|
/*************************************************************************
|
|
* CommandLineToArgvA [SHELL32.@]
|
|
*
|
|
* We must interpret the quotes in the command line to rebuild the argv
|
|
* array correctly:
|
|
* - arguments are separated by spaces or tabs
|
|
* - quotes serve as optional argument delimiters
|
|
* '"a b"' -> 'a b'
|
|
* - escaped quotes must be converted back to '"'
|
|
* '\"' -> '"'
|
|
* - consecutive backslashes preceding a quote see their number halved with
|
|
* the remainder escaping the quote:
|
|
* 2n backslashes + quote -> n backslashes + quote as an argument delimiter
|
|
* 2n+1 backslashes + quote -> n backslashes + literal quote
|
|
* - backslashes that are not followed by a quote are copied literally:
|
|
* 'a\b' -> 'a\b'
|
|
* 'a\\b' -> 'a\\b'
|
|
* - in quoted strings, consecutive quotes see their number divided by three
|
|
* with the remainder modulo 3 deciding whether to close the string or not.
|
|
* Note that the opening quote must be counted in the consecutive quotes,
|
|
* that's the (1+) below:
|
|
* (1+) 3n quotes -> n quotes
|
|
* (1+) 3n+1 quotes -> n quotes plus closes the quoted string
|
|
* (1+) 3n+2 quotes -> n+1 quotes plus closes the quoted string
|
|
* - in unquoted strings, the first quote opens the quoted string and the
|
|
* remaining consecutive quotes follow the above rule.
|
|
*/
|
|
LPSTR WINAPI *CommandLineToArgvA(LPCSTR lpCmdline, int *numargs) {
|
|
DWORD argc;
|
|
LPSTR *argv;
|
|
LPCSTR s;
|
|
LPSTR d;
|
|
LPSTR cmdline;
|
|
int qcount, bcount;
|
|
|
|
if (!numargs) {
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return NULL;
|
|
}
|
|
|
|
if (lpCmdline == NULL || *lpCmdline == 0) {
|
|
/* Return the path to the executable */
|
|
DWORD len, deslen = MAX_PATH, size;
|
|
|
|
size = sizeof(LPSTR) * 2 + deslen * sizeof(char);
|
|
for (;;) {
|
|
if (!(argv = LocalAlloc(LMEM_FIXED, size)))
|
|
return NULL;
|
|
len = GetModuleFileNameA(0, (LPSTR)(argv + 2), deslen);
|
|
if (!len) {
|
|
LocalFree(argv);
|
|
return NULL;
|
|
}
|
|
if (len < deslen)
|
|
break;
|
|
deslen *= 2;
|
|
size = sizeof(LPSTR) * 2 + deslen * sizeof(char);
|
|
LocalFree(argv);
|
|
}
|
|
argv[0] = (LPSTR)(argv + 2);
|
|
argv[1] = NULL;
|
|
*numargs = 1;
|
|
|
|
return argv;
|
|
}
|
|
|
|
/* --- First count the arguments */
|
|
argc = 1;
|
|
s = lpCmdline;
|
|
/* The first argument, the executable path, follows special rules */
|
|
if (*s == '"') {
|
|
/* The executable path ends at the next quote, no matter what */
|
|
s++;
|
|
while (*s)
|
|
if (*s++ == '"')
|
|
break;
|
|
} else {
|
|
/* The executable path ends at the next space, no matter what */
|
|
while (*s && *s != ' ' && *s != '\t')
|
|
s++;
|
|
}
|
|
/* skip to the first argument, if any */
|
|
while (*s == ' ' || *s == '\t')
|
|
s++;
|
|
if (*s)
|
|
argc++;
|
|
|
|
/* Analyze the remaining arguments */
|
|
qcount = bcount = 0;
|
|
while (*s) {
|
|
if ((*s == ' ' || *s == '\t') && qcount == 0) {
|
|
/* skip to the next argument and count it if any */
|
|
while (*s == ' ' || *s == '\t')
|
|
s++;
|
|
if (*s)
|
|
argc++;
|
|
bcount = 0;
|
|
} else if (*s == '\\') {
|
|
/* '\', count them */
|
|
bcount++;
|
|
s++;
|
|
} else if (*s == '"') {
|
|
/* '"' */
|
|
if ((bcount & 1) == 0)
|
|
qcount++; /* unescaped '"' */
|
|
s++;
|
|
bcount = 0;
|
|
/* consecutive quotes, see comment in copying code below */
|
|
while (*s == '"') {
|
|
qcount++;
|
|
s++;
|
|
}
|
|
qcount = qcount % 3;
|
|
if (qcount == 2)
|
|
qcount = 0;
|
|
} else {
|
|
/* a regular character */
|
|
bcount = 0;
|
|
s++;
|
|
}
|
|
}
|
|
|
|
/* Allocate in a single lump, the string array, and the strings that go
|
|
* with it. This way the caller can make a single LocalFree() call to free
|
|
* both, as per MSDN.
|
|
*/
|
|
argv = LocalAlloc(LMEM_FIXED, (argc + 1) * sizeof(LPSTR) +
|
|
(strlen(lpCmdline) + 1) * sizeof(char));
|
|
if (!argv)
|
|
return NULL;
|
|
cmdline = (LPSTR)(argv + argc + 1);
|
|
strcpy(cmdline, lpCmdline);
|
|
|
|
/* --- Then split and copy the arguments */
|
|
argv[0] = d = cmdline;
|
|
argc = 1;
|
|
/* The first argument, the executable path, follows special rules */
|
|
if (*d == '"') {
|
|
/* The executable path ends at the next quote, no matter what */
|
|
s = d + 1;
|
|
while (*s) {
|
|
if (*s == '"') {
|
|
s++;
|
|
break;
|
|
}
|
|
*d++ = *s++;
|
|
}
|
|
} else {
|
|
/* The executable path ends at the next space, no matter what */
|
|
while (*d && *d != ' ' && *d != '\t')
|
|
d++;
|
|
s = d;
|
|
if (*s)
|
|
s++;
|
|
}
|
|
/* close the executable path */
|
|
*d++ = 0;
|
|
/* skip to the first argument and initialize it if any */
|
|
while (*s == ' ' || *s == '\t')
|
|
s++;
|
|
if (!*s) {
|
|
/* There are no parameters so we are all done */
|
|
argv[argc] = NULL;
|
|
*numargs = (int) argc;
|
|
return argv;
|
|
}
|
|
|
|
/* Split and copy the remaining arguments */
|
|
argv[argc++] = d;
|
|
qcount = bcount = 0;
|
|
while (*s) {
|
|
if ((*s == ' ' || *s == '\t') && qcount == 0) {
|
|
/* close the argument */
|
|
*d++ = 0;
|
|
bcount = 0;
|
|
|
|
/* skip to the next one and initialize it if any */
|
|
do {
|
|
s++;
|
|
} while (*s == ' ' || *s == '\t');
|
|
if (*s)
|
|
argv[argc++] = d;
|
|
} else if (*s == '\\') {
|
|
*d++ = *s++;
|
|
bcount++;
|
|
} else if (*s == '"') {
|
|
if ((bcount & 1) == 0) {
|
|
/* Preceded by an even number of '\', this is half that
|
|
* number of '\', plus a quote which we erase.
|
|
*/
|
|
d -= bcount / 2;
|
|
qcount++;
|
|
} else {
|
|
/* Preceded by an odd number of '\', this is half that
|
|
* number of '\' followed by a '"'
|
|
*/
|
|
d = d - bcount / 2 - 1;
|
|
*d++ = '"';
|
|
}
|
|
s++;
|
|
bcount = 0;
|
|
/* Now count the number of consecutive quotes. Note that qcount
|
|
* already takes into account the opening quote if any, as well as
|
|
* the quote that lead us here.
|
|
*/
|
|
while (*s == '"') {
|
|
if (++qcount == 3) {
|
|
*d++ = '"';
|
|
qcount = 0;
|
|
}
|
|
s++;
|
|
}
|
|
if (qcount == 2)
|
|
qcount = 0;
|
|
} else {
|
|
/* a regular character */
|
|
*d++ = *s++;
|
|
bcount = 0;
|
|
}
|
|
}
|
|
*d = '\0';
|
|
argv[argc] = NULL;
|
|
*numargs = (int) argc;
|
|
|
|
return argv;
|
|
}
|
|
#endif
|