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);