From 4f5f8566a8cd0e751dc7ff7cf460c7c535d6d8c7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 25 Mar 2015 20:20:31 +0100 Subject: [PATCH 1/5] fixup! Let the Git wrapper serve as a drop-in replacement for builtins Prepare to prefix the command-line with non-builtins. We are now using the Git wrapper to call non-Git programs, too. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 0266617b15..39045764a9 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -97,7 +97,7 @@ static void setup_environment(LPWSTR exepath) * untouched. */ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, - LPWSTR builtin, int builtin_len) + LPWSTR prefix_args, int prefix_args_len) { int wargc = 0, gui = 0; LPWSTR cmd = NULL, cmdline = NULL; @@ -106,7 +106,7 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, cmdline = GetCommandLine(); wargv = CommandLineToArgvW(cmdline, &wargc); cmd = (LPWSTR)malloc(sizeof(WCHAR) * - (wcslen(cmdline) + builtin_len + 1 + MAX_PATH)); + (wcslen(cmdline) + prefix_args_len + 1 + MAX_PATH)); if (wargc > 1 && wcsicmp(L"gui", wargv[1]) == 0) { *wait = 0; if (wargc > 2 && wcsicmp(L"citool", wargv[2]) == 0) { @@ -127,9 +127,9 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, *exep = NULL; } } - else if (builtin) + else if (prefix_args) _swprintf(cmd, L"%s\\%s %.*s", - exepath, L"git.exe", builtin_len, builtin); + exepath, L"git.exe", prefix_args_len, prefix_args); else wcscpy(cmd, L"git.exe"); @@ -151,9 +151,9 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, int main(void) { - int r = 1, wait = 1, builtin_len = -1; + int r = 1, wait = 1, prefix_args_len = -1; WCHAR exepath[MAX_PATH], exe[MAX_PATH]; - LPWSTR cmd = NULL, exep = exe, builtin = NULL, basename; + LPWSTR cmd = NULL, exep = exe, prefix_args = NULL, basename; UINT codepage = 0; /* get the installation location */ @@ -165,10 +165,10 @@ int main(void) basename = exepath + wcslen(exepath) + 1; if (!wcsncmp(basename, L"git-", 4)) { /* Call a builtin */ - builtin = basename + 4; - builtin_len = wcslen(builtin); - if (!wcscmp(builtin + builtin_len - 4, L".exe")) - builtin_len -= 4; + prefix_args = basename + 4; + prefix_args_len = wcslen(prefix_args); + if (!wcscmp(prefix_args + prefix_args_len - 4, L".exe")) + prefix_args_len -= 4; /* set the default exe module */ wcscpy(exe, exepath); @@ -186,9 +186,10 @@ int main(void) PathAppend(exe, L"bin\\git.exe"); } - if (!builtin) + if (!prefix_args) setup_environment(exepath); - cmd = fixup_commandline(exepath, &exep, &wait, builtin, builtin_len); + cmd = fixup_commandline(exepath, &exep, &wait, + prefix_args, prefix_args_len); /* set the console to ANSI/GUI codepage */ codepage = GetConsoleCP(); From 9dbe2649f7565ff2c5b4a91a13da6d44fd5127bb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 23 Mar 2015 14:31:42 +0100 Subject: [PATCH 2/5] git-wrapper: support MSys2 The original purpose of the Git wrapper is to run from inside Git for Windows' /cmd/ directory, to allow setting up some environment variables before Git is allowed to take over. Due to differences in the file system layout, MSys2 requires some changes for that to work. In addition, we must take care to set the `MSYSTEM` environment variable to `MINGW32` or `MINGW64`, respectively, to allow MSys2 to be configured correctly in case Git launches a shell or Perl script. We also need to change the `TERM` variable to `cygwin` instead of `msys`, otherwise the pager `less.exe` (spawned e.g. by `git log`) will simply crash with a message similar to this one: 1 [main] less 9832 cygwin_exception::open_stackdumpfile: Dumping stack trace to less.exe.stackdump Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 48 ++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 39045764a9..ffae0bc554 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -12,6 +12,9 @@ #include #include #include +#include + +static WCHAR msystem_bin[64]; static void print_error(LPCWSTR prefix, DWORD error_number) { @@ -36,12 +39,18 @@ static void print_error(LPCWSTR prefix, DWORD error_number) static void setup_environment(LPWSTR exepath) { - int len; + WCHAR msystem[64]; LPWSTR path2 = NULL; + int len; - /* if not set, set TERM to msys */ + /* Set MSYSTEM */ + swprintf(msystem, sizeof(msystem), + L"MINGW%d", (int) sizeof(void *) * 8); + SetEnvironmentVariable(L"MSYSTEM", msystem); + + /* if not set, set TERM to cygwin */ if (!GetEnvironmentVariable(L"TERM", NULL, 0)) - SetEnvironmentVariable(L"TERM", L"msys"); + SetEnvironmentVariable(L"TERM", L"cygwin"); /* if not set, set PLINK_PROTOCOL to ssh */ if (!GetEnvironmentVariable(L"PLINK_PROTOCOL", NULL, 0)) @@ -79,12 +88,22 @@ static void setup_environment(LPWSTR exepath) len = sizeof(WCHAR) * (len + 2 * MAX_PATH); path2 = (LPWSTR)malloc(len); wcscpy(path2, exepath); - PathAppend(path2, L"bin;"); - /* should do this only if it exists */ - wcscat(path2, exepath); - PathAppend(path2, L"mingw\\bin;"); - GetEnvironmentVariable(L"PATH", &path2[wcslen(path2)], - (len/sizeof(WCHAR))-wcslen(path2)); + PathAppend(path2, msystem_bin); + if (_waccess(path2, 0) != -1) { + /* We are in an MSys2-based setup */ + wcscat(path2, L";"); + wcscat(path2, exepath); + PathAppend(path2, L"usr\\bin;"); + } + else { + /* Fall back to MSys1 paths */ + wcscpy(path2, exepath); + PathAppend(path2, L"bin;"); + wcscat(path2, exepath); + PathAppend(path2, L"mingw\\bin;"); + } + GetEnvironmentVariable(L"PATH", path2 + wcslen(path2), + (len / sizeof(WCHAR)) - wcslen(path2)); SetEnvironmentVariable(L"PATH", path2); free(path2); @@ -156,6 +175,10 @@ int main(void) LPWSTR cmd = NULL, exep = exe, prefix_args = NULL, basename; UINT codepage = 0; + /* Determine MSys2-based Git path. */ + swprintf(msystem_bin, sizeof(msystem_bin), + L"mingw%d\\bin", (int) sizeof(void *) * 8); + /* get the installation location */ GetModuleFileName(NULL, exepath, MAX_PATH); if (!PathRemoveFileSpec(exepath)) { @@ -183,7 +206,12 @@ int main(void) /* set the default exe module */ wcscpy(exe, exepath); - PathAppend(exe, L"bin\\git.exe"); + PathAppend(exe, msystem_bin); + PathAppend(exe, L"git.exe"); + if (_waccess(exe, 0) == -1) { + wcscpy(exe, exepath); + PathAppend(exe, L"bin\\git.exe"); + } } if (!prefix_args) From d7ff2b2b169284f735b1b761034178b38b5d00c3 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 25 Mar 2015 16:34:48 +0100 Subject: [PATCH 3/5] git-wrapper: prepare for executing configurable command-lines We are about to use the Git wrapper to call the Git Bash of Git for Windows. All the wrapper needs to do for that is to set up the environment variables, use the home directory as working directory and then hand off to a user-specified command-line. We prepare the existing code for this change by introducing flags to set up the environment variables, to launch a non-Git program, and to use the home directory as working directory. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index ffae0bc554..d82d4657ae 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -116,7 +116,7 @@ static void setup_environment(LPWSTR exepath) * untouched. */ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, - LPWSTR prefix_args, int prefix_args_len) + LPWSTR prefix_args, int prefix_args_len, int is_git_command) { int wargc = 0, gui = 0; LPWSTR cmd = NULL, cmdline = NULL; @@ -146,9 +146,14 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, *exep = NULL; } } - else if (prefix_args) - _swprintf(cmd, L"%s\\%s %.*s", - exepath, L"git.exe", prefix_args_len, prefix_args); + else if (prefix_args) { + if (is_git_command) + _swprintf(cmd, L"%s\\%s %.*s", exepath, L"git.exe", + prefix_args_len, prefix_args); + else + _swprintf(cmd, L"%.*s", prefix_args_len, prefix_args); + + } else wcscpy(cmd, L"git.exe"); @@ -170,9 +175,10 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, int main(void) { - int r = 1, wait = 1, prefix_args_len = -1; + int r = 1, wait = 1, prefix_args_len = -1, needs_env_setup = 1, + is_git_command = 1, start_in_home = 0; WCHAR exepath[MAX_PATH], exe[MAX_PATH]; - LPWSTR cmd = NULL, exep = exe, prefix_args = NULL, basename; + LPWSTR cmd = NULL, dir = NULL, exep = exe, prefix_args = NULL, basename; UINT codepage = 0; /* Determine MSys2-based Git path. */ @@ -187,6 +193,8 @@ int main(void) } basename = exepath + wcslen(exepath) + 1; if (!wcsncmp(basename, L"git-", 4)) { + needs_env_setup = 0; + /* Call a builtin */ prefix_args = basename + 4; prefix_args_len = wcslen(prefix_args); @@ -214,10 +222,19 @@ int main(void) } } - if (!prefix_args) + if (needs_env_setup) setup_environment(exepath); cmd = fixup_commandline(exepath, &exep, &wait, - prefix_args, prefix_args_len); + prefix_args, prefix_args_len, is_git_command); + + if (start_in_home) { + int len = GetEnvironmentVariable(L"HOME", NULL, 0); + + if (len) { + dir = malloc(sizeof(WCHAR) * len); + GetEnvironmentVariable(L"HOME", dir, len); + } + } /* set the console to ANSI/GUI codepage */ codepage = GetConsoleCP(); @@ -238,7 +255,7 @@ int main(void) TRUE, /* handles inheritable? */ CREATE_UNICODE_ENVIRONMENT, NULL, /* environment: use parent */ - NULL, /* starting directory: use parent */ + dir, /* starting directory: use parent */ &si, &pi); if (br) { if (wait) From 304d2974633a9da9da701fc1fd66d126c41051f4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 25 Mar 2015 16:39:56 +0100 Subject: [PATCH 4/5] git-wrapper: add code to configure command-lines to be launched To allow the Git wrapper to replace the `git-bash.bat` script (which would always open the Win32 console, even if the user wants to use a different terminal emulator), we need to offer a way to configure *which* command-line to run. We need to recompile the git-wrapper with the `-mwindows` flag to declare that this is a GUI program (not a console program). Having to recompile it anyway, let's put the new code in a conditionally compiled section so that the builtins (for which we use the git-wrapper, too) do not need to carry around that code. To configure the command-line, we use a way that is very typical for Windows: resources. Windows resources are data that are stored inside .exe files, but can be changed *after* compilation. Therefore, this facility is *exactly* what we want: we can easily copy the .exe to the new name `git-bash.exe`, configure that executable to run the Bash, and then copy it again to the new name `git-cmd.exe` and configure that executable to run `cmd.exe` instead. For even more flexibility, we expand environment variables specified as `@@@@`, and for convenience `@@EXEPATH@@` expands into the directory in which the executable resides. Sadly, an executable cannot configure itself: the `.exe` file is locked while the process is running. This means we have to have a separate executable to edit the resources anyway, so let's just enhance the Git wrapper *itself*: when copied to the new name `edit-res.exe`, it can edit other copies of the Git wrapper like so: edit-res.exe git-cmd.exe command '@@COMSPEC@@ /K' Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 191 +++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index d82d4657ae..09dd256243 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -173,6 +173,189 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, return cmd; } +#ifdef MAGIC_RESOURCE + +static int wsuffixcmp(LPWSTR text, LPWSTR suffix) +{ + int text_len = wcslen(text), suffix_len = wcslen(suffix); + + if (text_len < suffix_len) + return -1; + + return wcscmp(text + (text_len - suffix_len), suffix); +} + +static int edit_resources(LPWSTR exe_path, + LPWSTR *commands, int command_count) +{ + WORD language = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); + HANDLE handle; + int i; + + if (command_count > 16) { + fwprintf(stderr, L"Cannot handle more than 16 commands\n"); + return -1; + } + + if (wsuffixcmp(exe_path, L".exe")) { + fwprintf(stderr, L"Not an .exe file: '%s'", exe_path); + return -1; + } + if (_waccess(exe_path, 0) == -1) { + fwprintf(stderr, L"File not found: '%s'", exe_path); + return -1; + } + + handle = BeginUpdateResource(exe_path, FALSE); + if (!handle) { + fwprintf(stderr, + L"Could not update resources of '%s'", exe_path); + return -1; + } + + if (command_count >= 0) { + LPWSTR buffer, p; + int alloc = 16; /* 16 words with string lengths, for sure... */ + + for (i = 0; i < command_count; i++) { + int len = wcslen(commands[i]); + if (len > 0xffff) { + fwprintf(stderr, L"Too long command: %s\n", + commands[i]); + return -1; + } + alloc += len; + } + + p = buffer = calloc(alloc, sizeof(WCHAR)); + + for (i = 0; i < command_count; i++) + p += swprintf(p, alloc - (p - buffer), L"%c%s", + (WCHAR) wcslen(commands[i]), commands[i]); + + UpdateResource(handle, RT_STRING, MAKEINTRESOURCE(1), + language, buffer, sizeof(WCHAR) * alloc); + } + + if (EndUpdateResource(handle, FALSE)) + return 0; + + fwprintf(stderr, L"Error %d updating resources\n", + (int) GetLastError()); + return -1; +} + +static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, + LPWSTR *prefix_args, int *prefix_args_len, + int *is_git_command, int *start_in_home) +{ + int id = 0, wargc; + LPWSTR *wargv; + +#define BUFSIZE 65536 + static WCHAR buf[BUFSIZE]; + int len; + + if (!wcscmp(basename, L"edit-res.exe")) { + LPWSTR cmdline = GetCommandLine(); + + wargv = CommandLineToArgvW(cmdline, &wargc); + + if (wargv[1]) { + if (wargc > 1 && !wcscmp(wargv[1], L"command")) + exit(edit_resources(wargv[2], + wargv + 3, wargc - 3)); + } + fwprintf(stderr, + L"Usage: %s command ...\n", + basename); + exit(1); + } + + SetEnvironmentVariable(L"EXEPATH", exepath); + for (id = 0; ; id++) { + len = LoadString(NULL, id, buf, BUFSIZE); + + if (!len) { + fwprintf(stderr, L"Need a valid command-line; " + L"Copy %s to edit-res.exe and call\n" + L"\n\tedit-res.exe command %s " + L"\"\"\n", + basename, basename); + exit(1); + } + + if (len >= BUFSIZE) { + fwprintf(stderr, + L"Could not read resource (too large)\n"); + exit(1); + } + + buf[len] = L'\0'; + + for (;;) { + LPWSTR atat = wcsstr(buf, L"@@"), atat2; + WCHAR save; + int env_len, delta; + + if (!atat) + break; + + atat2 = wcsstr(atat + 2, L"@@"); + if (!atat2) + break; + + *atat2 = L'\0'; + env_len = GetEnvironmentVariable(atat + 2, NULL, 0); + delta = env_len - 1 - (atat2 + 2 - atat); + if (len + delta >= BUFSIZE) { + fwprintf(stderr, + L"Substituting '%s' results in too " + L"large a command-line\n", atat + 2); + exit(1); + } + if (delta) + memmove(atat2 + 2 + delta, atat2 + 2, + sizeof(WCHAR) * (len + 1 + - (atat2 + 2 - buf))); + len += delta; + save = atat[env_len - 1]; + GetEnvironmentVariable(atat + 2, atat, env_len); + atat[env_len - 1] = save; + } + + /* parse first argument */ + wargv = CommandLineToArgvW(buf, &wargc); + if (wargc < 1) { + fwprintf(stderr, L"Invalid command-line: '%s'\n", buf); + exit(1); + } + if (*wargv[0] == L'\\' || + (isalpha(*wargv[0]) && wargv[0][1] == L':')) + wcscpy(exep, wargv[0]); + else { + wcscpy(exep, exepath); + PathAppend(exep, wargv[0]); + } + + if (_waccess(exep, 0) != -1) + break; + fwprintf(stderr, + L"Skipping command-line '%s'\n('%s' not found)\n", + buf, exep); + } + + *prefix_args = buf; + *prefix_args_len = wcslen(buf); + + *is_git_command = 0; + *start_in_home = 1; + + return 1; +} + +#endif + int main(void) { int r = 1, wait = 1, prefix_args_len = -1, needs_env_setup = 1, @@ -192,6 +375,14 @@ int main(void) ExitProcess(1); } basename = exepath + wcslen(exepath) + 1; +#ifdef MAGIC_RESOURCE + if (configure_via_resource(basename, exepath, exep, + &prefix_args, &prefix_args_len, + &is_git_command, &start_in_home)) { + /* do nothing */ + } + else +#endif if (!wcsncmp(basename, L"git-", 4)) { needs_env_setup = 0; From 35e219dff2ab0225e13a2bdd4360f39c8b7dfcaa Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 25 Mar 2015 16:51:55 +0100 Subject: [PATCH 5/5] git-wrapper: allow the `edit-res.exe` copy to change icons, too As we already allow changing text resources, it is a very small step to allow changing the `.exe` icon, too. So let's just do it. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 132 ++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 3 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 09dd256243..43aa83e665 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -175,6 +175,100 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, #ifdef MAGIC_RESOURCE +#include + +#pragma pack(2) +struct resource_directory +{ + int8_t width; + int8_t height; + int8_t color_count; + int8_t reserved; + int16_t planes; + int16_t bit_count; + int32_t bytes_in_resource; + int16_t id; +}; + +struct header +{ + int16_t reserved; + int16_t type; + int16_t count; +}; + +struct icon_header +{ + int8_t width; + int8_t height; + int8_t color_count; + int8_t reserved; + int16_t planes; + int16_t bit_count; + int32_t bytes_in_resource; + int32_t image_offset; +}; + +struct icon_image +{ + BITMAPINFOHEADER header; + RGBQUAD colors; + int8_t xors[1]; + int8_t ands[1]; +}; + +struct icon +{ + int count; + struct header *header; + struct resource_directory *items; + struct icon_image **images; +}; + +static int parse_ico_file(LPWSTR ico_path, struct icon *result) +{ + struct header file_header; + FILE *file = _wfopen(ico_path, L"rb"); + int i; + + if (!file) { + fwprintf(stderr, L"could not open icon file '%s'", ico_path); + return 1; + } + + fread(&file_header, sizeof(struct header), 1, file); + result->count = file_header.count; + + result->header = malloc(sizeof(struct header) + result->count + * sizeof(struct resource_directory)); + result->header->reserved = 0; + result->header->type = 1; + result->header->count = result->count; + result->items = (struct resource_directory *)(result->header + 1); + struct icon_header *icon_headers = malloc(result->count + * sizeof(struct icon_header)); + fread(icon_headers, result->count * sizeof(struct icon_header), + 1, file); + result->images = malloc(result->count * sizeof(struct icon_image *)); + + for (i = 0; i < result->count; i++) { + struct icon_image** image = result->images + i; + struct icon_header* icon_header = icon_headers + i; + struct resource_directory *item = result->items + i; + + *image = malloc(icon_header->bytes_in_resource); + fseek(file, icon_header->image_offset, SEEK_SET); + fread(*image, icon_header->bytes_in_resource, 1, file); + + memcpy(item, icon_header, sizeof(struct resource_directory)); + item->id = (int16_t)(i + 1); + } + + fclose(file); + + return 0; +} + static int wsuffixcmp(LPWSTR text, LPWSTR suffix) { int text_len = wcslen(text), suffix_len = wcslen(suffix); @@ -186,9 +280,10 @@ static int wsuffixcmp(LPWSTR text, LPWSTR suffix) } static int edit_resources(LPWSTR exe_path, - LPWSTR *commands, int command_count) + LPWSTR ico_path, LPWSTR *commands, int command_count) { WORD language = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); + struct icon icon; HANDLE handle; int i; @@ -206,6 +301,20 @@ static int edit_resources(LPWSTR exe_path, return -1; } + if (ico_path) { + if (wsuffixcmp(ico_path, L".ico")) { + fwprintf(stderr, L"Not an .ico file: '%s'", ico_path); + return -1; + } + if (_waccess(ico_path, 0) == -1) { + fwprintf(stderr, L"File not found: '%s'", ico_path); + return -1; + } + + if (parse_ico_file(ico_path, &icon)) + return -1; + } + handle = BeginUpdateResource(exe_path, FALSE); if (!handle) { fwprintf(stderr, @@ -213,6 +322,20 @@ static int edit_resources(LPWSTR exe_path, return -1; } + if (ico_path) { + int id = 1; + UpdateResource(handle, RT_GROUP_ICON, + L"MAINICON", language, + icon.header, sizeof(struct header) + icon.count + * sizeof(struct resource_directory)); + for (i = 0; i < icon.count; i++) { + UpdateResource(handle, RT_ICON, + MAKEINTRESOURCE(id++), language, + icon.images[i], + icon.items[i].bytes_in_resource); + } + } + if (command_count >= 0) { LPWSTR buffer, p; int alloc = 16; /* 16 words with string lengths, for sure... */ @@ -262,12 +385,15 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, wargv = CommandLineToArgvW(cmdline, &wargc); if (wargv[1]) { + if (wargc == 4 && !wcscmp(wargv[1], L"icon")) + exit(edit_resources(wargv[2], wargv[3], + NULL, -1)); if (wargc > 1 && !wcscmp(wargv[1], L"command")) - exit(edit_resources(wargv[2], + exit(edit_resources(wargv[2], NULL, wargv + 3, wargc - 3)); } fwprintf(stderr, - L"Usage: %s command ...\n", + L"Usage: %s (icon | command) ...\n", basename); exit(1); }