diff --git a/LICENSE b/LICENSE index 261eeb9..bab27c9 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2021 Ivan Gavryliuk Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 70e5798..dd365af 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,56 @@ # win-shim -**THIS IS AN EARLY ALPHA!** +> THIS IS AN EARLY ALPHA! -Lightweight and customisable shim executable for **Windows**. Shims supposed to work just like target executables they are shadowing. - -It's written in safe C++20. +Lightweight and customisable shim executable for **Windows** written in safe modern `C++ 20`. ## Features - Lightweight and Fast. Does not add to startup time, even for tiny utilities. - Self-sufficient and clean. Does not read config files or write anywhere. Does not need or require any external runtimes. - Mirrors: - - [ ] Executable name. - - [ ] Icon. - - [ ] Version information. - - [ ] Exit status (error code). -- Extras + - [x] Exit status (error code). + - [x] Original version information. + - [x] Original file details. + - [x] Original application icon. + - [ ] Application manifest. +- Supports: + - [x] Any command line, parametrised. +- Extras (on the roadmap) - [ ] Limit registry access. - [ ] Limit filesystem access. - [ ] Limit amount of available RAM. - [ ] Change clock. + - [ ] Freeze in background. ## How to Use -Download the latest release when ready. +Download the latest release when ready (still in dev!). + +Originally this shim was written because I wanted to associate a file extension in Windows with [vim](https://www.vim.org/), but opened as a tab in [windows terminal](https://github.com/microsoft/terminal) instead of an ugly terminal popup window. WT [does allow it](https://docs.microsoft.com/en-us/windows/terminal/command-line-arguments?tabs=windows), however I would need to build a command line like: + +```bash +wt -w 0 nt vim.exe +``` + +Unfortunately, you cannot associate a command line with an extension in Windows, only executable with an extension, so I though I'd build a shim executable for this. + +In order to achieve this, run `shmake` like following: + +```bash +shmake.exe -c "wt -w 0 nt vim.exe %s" -o vimwt.exe -m "c:\scoop\apps\vim\3.2\vim.exe" +``` + +which should generate a new executable `winwt.exe` that when called with an argument will open a tab in WT! `%s` is replaced by arguments passed to `wimwt.exe` when it executes. The last argument `-m` will also mirror vim application icon, version info and description so it looks just like vim everywhere. + ## Building -As this is **Windows exclusive**, you need Visual Studio 2019 with Windows SDK installed. Normally I would use CMake, but is considerably harder (not impossible) to use when you need access to OS specific tools, especially native resources (which I utilise heavily to do the magic). +As this is **Windows Exclusive**, you need Visual Studio 2019+ with Windows SDK installed. Normally I would use CMake, but it is considerably harder (not impossible) when you need access to OS specific tools, especially native resources (which I utilise heavily to do the magic). It is also very well integrated with `vcpkg`. `shmake` (but not shim) has dependency on: -- `boost::program_options`. +- `boost::program_options` to present you with a nice command line. +- todo... All the dependencies are installed via [vcpkg](https://github.com/microsoft/vcpkg). @@ -38,8 +58,8 @@ All the dependencies are installed via [vcpkg](https://github.com/microsoft/vcpk vcpkg install boost-program-options:x64-windows boost-program-options:x64-windows-static ``` -`shim` does not have any dependencies and is kept as small and light as possible. +`shim` **does not have any dependencies** and is kept as small and light as possible. ### Extra Oddness -`shmake` embeds `shim` inside it as a Windows native resource, so it's completely self-sufficient and can be distributed as a single `exe`. To do that, `smake`'s pre-build step copies `shim.exe` to `shim.bin` before build (as a pre-build step). Apparently `shim` is set as a project dependency of `shmake`, so it generates a full usable binary. \ No newline at end of file +`shmake` embeds `shim` inside it as a Windows native resource, so it's completely self-sufficient and can be distributed as a single `.exe`. To do that, `shmake`'s pre-build step copies `shim.exe` to `shim.bin` before build (as a pre-build step). Apparently `shim` is set as a project dependency of `shmake`, so it generates a full usable binary. diff --git a/shim/icon.ico b/shim/icon.ico index 8aa98e5..d51be00 100644 Binary files a/shim/icon.ico and b/shim/icon.ico differ diff --git a/shim/shim.aps b/shim/shim.aps index acb2afd..0d1804a 100644 Binary files a/shim/shim.aps and b/shim/shim.aps differ diff --git a/shim/shim.cpp b/shim/shim.cpp index e852677..9f7a2e2 100644 --- a/shim/shim.cpp +++ b/shim/shim.cpp @@ -56,11 +56,12 @@ int wmain(int argc, wchar_t* argv[]) wcout << L"waiting... "; ::WaitForSingleObject(pi.hProcess, INFINITE); - wcout << L"done." << endl; DWORD exit_code = 0; // if next line fails, code is still 0 ::GetExitCodeProcess(pi.hProcess, &exit_code); + wcout << L"done (code: " << exit_code << L")" << endl; + // free OS resources ::CloseHandle(pi.hProcess); diff --git a/shim/shim.rc b/shim/shim.rc index 5429134..2228f9b 100644 --- a/shim/shim.rc +++ b/shim/shim.rc @@ -34,46 +34,20 @@ END ///////////////////////////////////////////////////////////////////////////// -// English (United Kingdom) resources +// Neutral (Default) resources -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEUD) +LANGUAGE LANG_NEUTRAL, SUBLANG_DEFAULT #pragma code_page(1252) -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,0,0,1 - PRODUCTVERSION 1,0,0,1 + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -86,24 +60,60 @@ VS_VERSION_INFO VERSIONINFO BEGIN BLOCK "StringFileInfo" BEGIN - BLOCK "080904b0" + BLOCK "040004b0" BEGIN - VALUE "CompanyName", "TODO: " - VALUE "FileDescription", "TODO: " - VALUE "FileVersion", "1.0.0.1" + VALUE "CompanyName", "Ivan G" + VALUE "FileDescription", "Shim executable" + VALUE "FileVersion", "1.0.0.0" VALUE "InternalName", "shim.exe" VALUE "LegalCopyright", "Copyright (C) 2021" VALUE "OriginalFilename", "shim.exe" - VALUE "ProductName", "TODO: " - VALUE "ProductVersion", "1.0.0.1" + VALUE "ProductName", "Shim executable" + VALUE "ProductVersion", "1.0.0.0" END END BLOCK "VarFileInfo" BEGIN - VALUE "Translation", 0x809, 1200 + VALUE "Translation", 0x400, 1200 END END +#endif // Neutral (Default) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English (United Kingdom) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + ///////////////////////////////////////////////////////////////////////////// // diff --git a/shmake/data.aps b/shmake/data.aps index 6b96314..f7a0c24 100644 Binary files a/shmake/data.aps and b/shmake/data.aps differ diff --git a/shmake/data.rc b/shmake/data.rc index 30ae83e..4275ea9 100644 --- a/shmake/data.rc +++ b/shmake/data.rc @@ -52,6 +52,45 @@ END IDR_SHIM_EXE RCDATA "shim.bin" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080904b0" + BEGIN + VALUE "CompanyName", "Ivan G" + VALUE "FileDescription", "Shim maker" + VALUE "FileVersion", "1.0.0.0" + VALUE "InternalName", "shmake.exe" + VALUE "LegalCopyright", "Copyright (C) 2021" + VALUE "OriginalFilename", "shmake.exe" + VALUE "ProductName", "Shim maker" + VALUE "ProductVersion", "1.0.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x809, 1200 + END +END + #endif // English (United Kingdom) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/shmake/resources.cpp b/shmake/resources.cpp index 01b78a8..d50bf7a 100644 --- a/shmake/resources.cpp +++ b/shmake/resources.cpp @@ -7,16 +7,54 @@ using namespace std; +// this struct in not defined in any system header files, but is mentioned here: +// https://docs.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo +// note the remarks: +// +// This structure is not a true C-language structure because it contains variable-length members. +// This structure was created solely to depict the organization of data in a version resource and does not appear in any of the +// header files shipped with the Windows Software Development Kit (SDK). +typedef struct { + WORD wLength; + // The length, in bytes, of the VS_VERSIONINFO structure. This length does not include any padding that aligns any subsequent version resource data on a 32-bit boundary. + WORD wValueLength; + // The type of data in the version resource. This member is 1 if the version resource contains text data and 0 if the version resource contains binary data. + WORD wType; + // The Unicode string L"VS_VERSION_INFO". + WCHAR szKey; + WORD Padding1; + VS_FIXEDFILEINFO Value; + WORD Padding2; + // An array of zero or one StringFileInfo structures, and zero or one VarFileInfo structures that are children of the current VS_VERSIONINFO structure. + WORD Children; +} VS_VERSIONINFO; + +BOOL EnumResourceNamesFuncFindFirst(HMODULE hModule, LPCWSTR lpType, LPWSTR lpName, LONG_PTR lParam) +{ + LPWSTR* ret = (LPWSTR*)lParam; + *ret = lpName; + return FALSE; +} + +BOOL EnumResourceLanguagesFindFirst(HMODULE hModule, LPCWSTR lpType, LPCWSTR lpName, WORD wLanguage, LONG_PTR lParam) +{ + WORD* ret = (WORD*)lParam; + *ret = wLanguage; + return FALSE; +} + resources::resources(const std::wstring& file_path) - : file_path { file_path } + : file_path { file_path }, edit_made { false }, hEdit { 0 } { if (file_path.empty()) { - hInstance = ::GetModuleHandle(nullptr); + hInstance = ::GetModuleHandle(NULL); } else { - if (!::LoadLibraryEx(file_path.c_str(), NULL, LOAD_LIBRARY_AS_DATAFILE)) + hInstance = ::LoadLibraryEx(file_path.c_str(), NULL, LOAD_LIBRARY_AS_DATAFILE); + + if (!hInstance) throw_win32_le(); } } @@ -44,13 +82,12 @@ std::wstring resources::load_string(UINT id) return std::wstring(szs); } -void resources::copy_main_icon(const resources& source) +void resources::set_main_icon(const std::wstring& path) { - //HICON hSrcIcon = ::ExtractIconA(hInstance, file_path.c_str(), 0); + HMODULE hSrcModule = ::LoadLibraryEx(path.c_str(), NULL, LOAD_LIBRARY_AS_DATAFILE); + HRSRC hSrcIcon = ::FindResource(hSrcModule, MAKEINTRESOURCE(1), RT_ICON); - //::UpdateResource() - - //::DestroyIcon(hSrcIcon); + //::FreeLibrary(hSrcModule); } void resources::replace_string_table(int index, const std::vector& strings) @@ -65,6 +102,7 @@ void resources::replace_string_table(int index, const std::vector& std::copy(w.begin(), w.end(), std::back_inserter(buffer)); } + // You have to replace the entire string table, not just a single entry. Good performance wise. if (!::UpdateResource(hEdit, RT_STRING, MAKEINTRESOURCE(index), @@ -94,6 +132,16 @@ void resources::extract_binary_to_file(int res_id, const std::wstring& path) f.close(); } +void resources::replace_version_info(const resources& other) +{ + raw_copy(other, RT_VERSION); +} + +void resources::replace_icon(const resources& other) +{ + raw_copy(other, RT_ICON); +} + void resources::open_for_edit() { if (edit_made) return; @@ -107,3 +155,59 @@ void resources::open_for_edit() DWORD err = ::GetLastError(); } } + +bool resources::open_first_resource(LPCWSTR lpType, LPWSTR* lpName, WORD* wLanguage, DWORD* dataSize, LPVOID* data) const +{ + ::EnumResourceNames(hInstance, lpType, EnumResourceNamesFuncFindFirst, (LONG_PTR)lpName); + if (*lpName) + { + ::EnumResourceLanguages(hInstance, lpType, *lpName, EnumResourceLanguagesFindFirst, (LONG_PTR)wLanguage); + if (*wLanguage) + { + if (data) + { + HRSRC hResInfo = ::FindResourceEx(hInstance, lpType, *lpName, *wLanguage); + if (hResInfo) + { + *dataSize = ::SizeofResource(hInstance, hResInfo); + if (*dataSize) + { + HGLOBAL hResData = ::LoadResource(hInstance, hResInfo); + if (hResData) + { + *data = ::LockResource(hResData); + return true; + } + } + } + } + else + { + return true; + } + } + } + return false; +} + +bool resources::raw_copy(const resources& other, LPCWSTR lpType) +{ + // get first RT_VERSION and first language + LPWSTR lpName, lpNameOther; + WORD wLanguage, wLanguageOther; + DWORD dataSize; + LPVOID data; + if (other.open_first_resource(lpType, &lpNameOther, &wLanguageOther, &dataSize, &data) && + open_first_resource(lpType, &lpName, &wLanguage, nullptr, nullptr)) + { + open_for_edit(); + + if (!::UpdateResource(hEdit, lpType, lpName, wLanguage, data, dataSize)) + { + throw_win32_le(); + } + + return true; + } + return false; +} diff --git a/shmake/resources.h b/shmake/resources.h index bfdf950..2b34276 100644 --- a/shmake/resources.h +++ b/shmake/resources.h @@ -15,12 +15,16 @@ class resources std::wstring load_string(UINT id); - void copy_main_icon(const resources& source); + void set_main_icon(const std::wstring& path); void replace_string_table(int index, const std::vector& strings); void extract_binary_to_file(int res_id, const std::wstring& path); + void replace_version_info(const resources& other); + + void replace_icon(const resources& other); + void commit_changes(); private: @@ -30,5 +34,7 @@ class resources HANDLE hEdit; void open_for_edit(); + bool open_first_resource(LPCWSTR lpType, LPWSTR* lpName, WORD* wLanguage, DWORD* dataSize, LPVOID* data) const; + bool raw_copy(const resources& other, LPCWSTR lpType); }; diff --git a/shmake/shim.bin b/shmake/shim.bin index 7037ee7..ed5e0ca 100644 Binary files a/shmake/shim.bin and b/shmake/shim.bin differ diff --git a/shmake/shmake.cpp b/shmake/shmake.cpp index 69b8b07..9498ab2 100644 --- a/shmake/shmake.cpp +++ b/shmake/shmake.cpp @@ -1,4 +1,4 @@ -#include +#include #include "boost/program_options.hpp" #include "resource.h" #include "resources.h" @@ -11,12 +11,15 @@ namespace fs = std::filesystem; int main(int ac, char* av[]) { + wcout << L"Windows Shim Maker by @aloneguid (https://github.com/aloneguid/win-shim)" << endl << endl; + // tutorial: https://www.boost.org/doc/libs/1_77_0/doc/html/program_options/tutorial.html#id-1.3.32.4.3 po::options_description desc("allowed options"); desc.add_options() //("target,t", po::value(), "Path to target executable.") ("output,o", po::value()->default_value(""), "Path to output executable.") ("cmd,c", po::value()->default_value(""), "Command line to execute, use %s to capture the original. For instance 'vim.exe %s'. ") + ("mirror,m", po::value()->default_value(""), "Mirror data from another executable module (.exe or .dll). Mirrors version information, file description.") ("help,h", "Produce help message."); po::variables_map vm; @@ -34,6 +37,7 @@ int main(int ac, char* av[]) { //auto target = vm["target"].as(); auto output = vm["output"].as(); + auto mirror = str_to_wstr(vm["mirror"].as()); if (output.empty()) { @@ -60,6 +64,13 @@ int main(int ac, char* av[]) cout << "patching shim... " << endl; resources shim_res(ofile); + if (!mirror.empty()) + { + resources mirror_res(mirror); + shim_res.replace_version_info(mirror_res); + shim_res.replace_icon(mirror_res); + } + vector st; st.push_back(str_to_wstr(cmd)); shim_res.replace_string_table(1, st);