Skip to content

Commit bc44726

Browse files
authored
Resource Redirect v1.3 (#4471)
* Added an option to to disable thumbnails in Explorer folders, making folders use a generic folder icon instead of showing a preview of their contents. Some icon themes may look better with this option enabled. * Added the following themes: PaveVista, Pane8.1, Minecraft Axolotle, and 7 other Minecraft themes.
1 parent 5e4d697 commit bc44726

1 file changed

Lines changed: 241 additions & 24 deletions

File tree

mods/icon-resource-redirect.wh.cpp

Lines changed: 241 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// @id icon-resource-redirect
33
// @name Resource Redirect
44
// @description Define alternative files for loading various resources (e.g. icons in imageres.dll) for simple theming without having to modify system files
5-
// @version 1.2.5
5+
// @version 1.3
66
// @author m417z
77
// @github https://github.com/m417z
88
// @twitter https://twitter.com/m417z
@@ -26,14 +26,13 @@
2626
Define alternative files for loading various resources (e.g. icons in
2727
imageres.dll) for simple theming without having to modify system files.
2828
29-
**Note**: This mod requires Windhawk v1.6 or newer.
30-
3129
## Icon themes
3230
33-
A collection of community contributed icon theme packs can be found in the
34-
[Resource Redirect icon
35-
themes](https://github.com/ramensoftware/resource-redirect-icon-themes)
36-
repository. An icon theme can be selected in the mod's settings.
31+
An icon theme from the collection of community-contributed icon theme packs can
32+
be selected in the mod's settings. Screenshots of some of the icon themes can be
33+
found in the [Resource Redirect icon
34+
themes](https://github.com/ramensoftware/resource-redirect-icon-themes#themes)
35+
repository.
3736
3837
An icon theme can also be installed manually by downloading it and specifying
3938
its path in the mod's settings. For details, refer to the guide in the
@@ -42,10 +41,24 @@ repository.
4241
A short demonstration can be found [here on
4342
YouTube](https://youtu.be/irzVmKHB83E).
4443
44+
## Disable folder thumbnails
45+
46+
The mod has an option to disable thumbnails in Explorer folders, making folders
47+
use a generic folder icon instead of showing a preview of their contents. Some
48+
icon themes may look better with this option enabled, while other themes have
49+
proper support for styling folder thumbnails. Folder thumbnails are only
50+
replaced when the "Redirect all loaded resources" option is enabled (see below).
51+
52+
## Redirect all loaded resources (experimental)
53+
54+
The option to redirect all resources can be enabled in the settings. In this
55+
case, redirection won't be limited to the resource types and loading methods
56+
listed above. This option might become the default in the future.
57+
4558
## Theme paths
4659
4760
Theme paths can be set in the settings. A theme path is a folder with
48-
alternative resource files, and the `theme.ini` file that contains redirection
61+
alternative resource files and a `theme.ini` file that contains redirection
4962
rules. For example, the `theme.ini` file may contain the following:
5063
5164
```
@@ -54,8 +67,8 @@ rules. For example, the `theme.ini` file may contain the following:
5467
%SystemRoot%\System32\imageres.dll=imageres.dll
5568
```
5669
57-
In this case, the folder must also contain the `explorer.exe`, `imageres.dll`
58-
files which will be used as the redirection resource files.
70+
In this case, the folder must also contain the `explorer.exe` and `imageres.dll`
71+
files, which will be used as the redirection resource files.
5972
6073
Alternatively, the theme path can be the `.ini` file itself.
6174
@@ -69,19 +82,13 @@ The mod supports the following resource types and loading methods:
6982
* Cursors loaded with the `LoadCursorW` function.
7083
* Bitmaps loaded with the `LoadBitmapW` function.
7184
* Menus loaded with the `LoadMenuW` function.
72-
* Dialogs loaded with the `DialogBoxParamW`, `CreateDialogParamW` functions.
85+
* Dialogs loaded with the `DialogBoxParamW` and `CreateDialogParamW` functions.
7386
* Strings loaded with the `LoadStringW` function.
7487
* GDI+ images (e.g. PNGs) loaded with the `SHCreateStreamOnModuleResourceW`
7588
function.
7689
* DirectUI resources (usually `UIFILE` and `XML`) loaded with the
7790
`SetXMLFromResource` function.
7891
79-
## Redirect all loaded resources (experimental)
80-
81-
The option to redirect all resources can be enabled in the settings. In this
82-
case, redirection won't be limited to the resource types and loading methods
83-
listed above. This option might become the default in the future.
84-
8592
## Choosing the redirected resource file
8693
8794
For some files, Windows has additional .mui and/or .mun resource files. For
@@ -279,7 +286,27 @@ The resource lookup order then becomes:
279286
- Windows 11 New Folders Purple|themes/icons/niivu/Windows%2011%20New%20Folders%20Purple.zip: Windows 11 New Folders Purple (by niivu)
280287
- Windows 11 New Folders Slate|themes/icons/niivu/Windows%2011%20New%20Folders%20Slate.zip: Windows 11 New Folders Slate (by niivu)
281288
- Windows 11 New Folders Yellow|themes/icons/niivu/Windows%2011%20New%20Folders%20Yellow.zip: Windows 11 New Folders Yellow (by niivu)
289+
- PaneVista|themes/icons/SoftwareType/PaneVista.zip: 'PaneVista (by SoftwareType, credit: ImSwordQueen)'
282290
- Pane7|themes/icons/ImSwordQueen/Pane7.zip: Pane7 (by ImSwordQueen)
291+
- Pane8.1|themes/icons/NicSonic/Pane8.1.zip: Pane8.1 (by NicSonic)
292+
- Minecraft Axolotle|themes/icons/Wasiabbas4pk/Minecraft%20Axolotle.zip: 'Minecraft Axolotle (by WasiXGamer, credit: dalps/minecraft11)'
293+
- Minecraft Fox|themes/icons/Wasiabbas4pk/Minecraft%20Fox.zip: 'Minecraft Fox (by WasiXGamer, credit: dalps/minecraft11)'
294+
- Minecraft TNT|themes/icons/Wasiabbas4pk/Minecraft%20TNT.zip: 'Minecraft TNT (by WasiXGamer, credit: dalps/minecraft11)'
295+
- Minecraft Panda|themes/icons/Wasiabbas4pk/Minecraft%20Panda.zip: 'Minecraft Panda (by WasiXGamer, credit: dalps/minecraft11)'
296+
- Minecraft Creeper|themes/icons/Wasiabbas4pk/Minecraft%20Creeper.zip: 'Minecraft Creeper (by WasiXGamer, credit: dalps/minecraft11)'
297+
- Minecraft Ender Dragon|themes/icons/Wasiabbas4pk/Minecraft%20Ender%20Dragon.zip: 'Minecraft Ender Dragon (by WasiXGamer, credit: dalps/minecraft11)'
298+
- Minecraft Shulker (Blue)|themes/icons/Wasiabbas4pk/Minecraft%20Shulker%28Blue%29.zip: 'Minecraft Shulker (Blue) (by WasiXGamer, credit: dalps/minecraft11)'
299+
- Minecraft Shulker (Black)|themes/icons/Wasiabbas4pk/Minecraft%20Shulker%28Black%29.zip: 'Minecraft Shulker (Black) (by WasiXGamer, credit: dalps/minecraft11)'
300+
- disableThumbnails: false
301+
$name: Disable folder thumbnails
302+
$description: >-
303+
Make Explorer folders use a generic folder icon instead of showing
304+
thumbnails of their contents. This works better with some icon themes.
305+
- allResourceRedirect: false
306+
$name: Redirect all loaded resources (experimental)
307+
$description: >-
308+
Try to redirect all loaded resources, not only the supported resources
309+
that are listed in the description.
283310
- themePaths: [""]
284311
$name: Theme paths
285312
$description: >-
@@ -296,17 +323,12 @@ The resource lookup order then becomes:
296323
$name: The redirection resource file
297324
$description: The custom resource file that will be used instead.
298325
$name: Redirection resource paths
299-
- allResourceRedirect: false
300-
$name: Redirect all loaded resources (experimental)
301-
$description: >-
302-
Try to redirect all loaded resources, not only the supported resources
303-
that are listed in the description.
304326
- themeFolder: ""
305327
$name: Theme folder (deprecated)
306328
$description: >-
307329
A folder with alternative resource files and theme.ini.
308330
309-
This option will be removed in the future, please use the new "Theme paths"
331+
This option will be removed in the future. Please use the new "Theme paths"
310332
option above.
311333
*/
312334
// ==/WindhawkModSettings==
@@ -332,12 +354,23 @@ The resource lookup order then becomes:
332354
#include <unordered_map>
333355
#include <vector>
334356

357+
using namespace std::string_view_literals;
358+
335359
#ifndef LR_EXACTSIZEONLY
336360
#define LR_EXACTSIZEONLY 0x10000
337361
#endif
338362

363+
#ifndef STATUS_SUCCESS
364+
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
365+
#endif
366+
367+
#ifndef STATUS_BUFFER_TOO_SMALL
368+
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
369+
#endif
370+
339371
struct {
340372
WindhawkUtils::StringSetting iconTheme;
373+
std::atomic<bool> disableThumbnails;
341374
bool allResourceRedirect;
342375
} g_settings;
343376

@@ -2553,8 +2586,180 @@ std::wstring GetIconThemePath(std::wstring_view iconTheme) {
25532586
return targetPath;
25542587
}
25552588

2589+
bool StartsWithCaseInsensitive(std::wstring_view str,
2590+
std::wstring_view prefix) {
2591+
return str.size() >= prefix.size() &&
2592+
_wcsnicmp(str.data(), prefix.data(), prefix.size()) == 0;
2593+
}
2594+
2595+
// https://github.com/valinet/wh-mods/blob/61319815c7e018e392a08077dc364559548ade02/mods/valinet-unserver.wh.cpp#L95
2596+
// https://stackoverflow.com/questions/937044/determine-path-to-registry-key-from-hkey-handle-in-c
2597+
std::wstring GetPathFromHKEY(HKEY key) {
2598+
if (!key) {
2599+
return {};
2600+
}
2601+
2602+
using NtQueryKey_t = NTSTATUS(NTAPI*)(
2603+
HANDLE KeyHandle, int KeyInformationClass, PVOID KeyInformation,
2604+
ULONG Length, PULONG ResultLength);
2605+
static NtQueryKey_t pNtQueryKey = []() {
2606+
HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
2607+
if (hNtdll) {
2608+
return (NtQueryKey_t)GetProcAddress(hNtdll, "NtQueryKey");
2609+
}
2610+
return (NtQueryKey_t) nullptr;
2611+
}();
2612+
2613+
if (!pNtQueryKey) {
2614+
return {};
2615+
}
2616+
2617+
constexpr int kKeyNameInformation = 3;
2618+
2619+
ULONG size = 0;
2620+
NTSTATUS result = pNtQueryKey(key, kKeyNameInformation, nullptr, 0, &size);
2621+
if (result != STATUS_BUFFER_TOO_SMALL) {
2622+
return {};
2623+
}
2624+
2625+
std::vector<BYTE> buffer(size);
2626+
result = pNtQueryKey(key, kKeyNameInformation, buffer.data(), size, &size);
2627+
if (result != STATUS_SUCCESS || size < sizeof(ULONG)) {
2628+
return {};
2629+
}
2630+
2631+
// The buffer contains a KEY_NAME_INFORMATION structure:
2632+
// ULONG NameLength (4 bytes) + WCHAR Name[1].
2633+
ULONG nameLength = *reinterpret_cast<ULONG*>(buffer.data());
2634+
if (size < sizeof(ULONG) + nameLength) {
2635+
return {};
2636+
}
2637+
2638+
PCWSTR name = reinterpret_cast<PCWSTR>(buffer.data() + sizeof(ULONG));
2639+
return std::wstring(name, nameLength / sizeof(WCHAR));
2640+
}
2641+
2642+
bool MatchesClassSubkey(HKEY hKey, std::wstring_view classSubKey) {
2643+
std::wstring keyPath = GetPathFromHKEY(hKey);
2644+
std::wstring_view keyPathSuffix = keyPath;
2645+
2646+
constexpr std::wstring_view kRegistryMachinePrefix =
2647+
L"\\REGISTRY\\MACHINE\\SOFTWARE\\Classes\\"sv;
2648+
constexpr std::wstring_view kRegistryUserPrefix = L"\\REGISTRY\\USER\\"sv;
2649+
2650+
if (StartsWithCaseInsensitive(keyPathSuffix, kRegistryMachinePrefix)) {
2651+
// Remove "\REGISTRY\MACHINE\SOFTWARE\Classes\" prefix.
2652+
keyPathSuffix.remove_prefix(kRegistryMachinePrefix.size());
2653+
} else if (StartsWithCaseInsensitive(keyPathSuffix, kRegistryUserPrefix)) {
2654+
// Remove "\REGISTRY\USER\" prefix.
2655+
keyPathSuffix.remove_prefix(kRegistryUserPrefix.size());
2656+
2657+
// Remove "<SID>_Classes\" prefix for non-empty SID.
2658+
size_t firstBackslash = keyPathSuffix.find(L'\\');
2659+
if (firstBackslash == std::wstring_view::npos) {
2660+
return false;
2661+
}
2662+
2663+
constexpr std::wstring_view kClassesSuffix = L"_Classes"sv;
2664+
if (firstBackslash > kClassesSuffix.size() &&
2665+
_wcsnicmp(
2666+
keyPathSuffix.data() + firstBackslash - kClassesSuffix.size(),
2667+
kClassesSuffix.data(), kClassesSuffix.size()) == 0) {
2668+
keyPathSuffix.remove_prefix(firstBackslash + 1);
2669+
} else {
2670+
return false;
2671+
}
2672+
} else {
2673+
return false;
2674+
}
2675+
2676+
return keyPathSuffix.size() == classSubKey.size() &&
2677+
_wcsnicmp(keyPathSuffix.data(), classSubKey.data(),
2678+
classSubKey.size()) == 0;
2679+
}
2680+
2681+
using RegQueryValueExW_t = decltype(&RegQueryValueExW);
2682+
RegQueryValueExW_t RegQueryValueExW_Original;
2683+
LSTATUS WINAPI RegQueryValueExW_Hook(HKEY hKey,
2684+
LPCWSTR lpValueName,
2685+
LPDWORD lpReserved,
2686+
LPDWORD lpType,
2687+
LPBYTE lpData,
2688+
LPDWORD lpcbData) {
2689+
// Disable thumbnails in Explorer folders by providing the "Logo" value for
2690+
// the "AllFolders\Shell" key. When this value is present, Explorer uses a
2691+
// generic folder icon instead of showing thumbnails of the folder contents.
2692+
// This is equivalent to setting the "Logo" value below to "imageres.dll,-3"
2693+
// (REG_SZ), but is applied dynamically by the mod:
2694+
//
2695+
// HKCU\Software\Classes\Local
2696+
// Settings\Software\Microsoft\Windows\Shell\Bags\AllFolders\Shell
2697+
if (!g_settings.disableThumbnails) {
2698+
return RegQueryValueExW_Original(hKey, lpValueName, lpReserved, lpType,
2699+
lpData, lpcbData);
2700+
}
2701+
2702+
constexpr WCHAR kValueName[] = L"Logo";
2703+
constexpr std::wstring_view kClassSubkey =
2704+
L"Local Settings\\Software\\Microsoft\\Windows\\Shell\\Bags\\AllFolders\\Shell"sv;
2705+
constexpr std::wstring_view kReplacementData = L"imageres.dll,-3"sv;
2706+
2707+
// Save the original buffer size before calling the original function, as
2708+
// lpcbData is overwritten with the actual data size on output.
2709+
DWORD dataBufferSize = (lpData && lpcbData) ? *lpcbData : 0;
2710+
2711+
LSTATUS ret = RegQueryValueExW_Original(hKey, lpValueName, lpReserved,
2712+
lpType, lpData, lpcbData);
2713+
if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA &&
2714+
ret != ERROR_FILE_NOT_FOUND) {
2715+
return ret;
2716+
}
2717+
2718+
if (!lpValueName || _wcsicmp(lpValueName, kValueName) != 0) {
2719+
return ret;
2720+
}
2721+
2722+
if (!MatchesClassSubkey(hKey, kClassSubkey)) {
2723+
return ret;
2724+
}
2725+
2726+
Wh_Log(L"Providing %s value to disable folder thumbnails", kValueName);
2727+
2728+
DWORD requiredSize = (kReplacementData.size() + 1) * sizeof(WCHAR);
2729+
2730+
if (lpType) {
2731+
*lpType = REG_SZ;
2732+
}
2733+
2734+
if (!lpData || !lpcbData) {
2735+
if (lpData && !lpcbData) {
2736+
// Shouldn't happen per documentation.
2737+
return ret;
2738+
}
2739+
if (lpcbData) {
2740+
*lpcbData = requiredSize;
2741+
}
2742+
return ERROR_SUCCESS;
2743+
}
2744+
2745+
if (dataBufferSize < requiredSize) {
2746+
Wh_Log(L"Not enough space for %s, available=%u", kValueName,
2747+
dataBufferSize);
2748+
*lpcbData = requiredSize;
2749+
return ERROR_MORE_DATA;
2750+
}
2751+
2752+
memcpy(lpData, kReplacementData.data(),
2753+
kReplacementData.size() * sizeof(WCHAR));
2754+
reinterpret_cast<PWSTR>(lpData)[kReplacementData.size()] = L'\0';
2755+
*lpcbData = requiredSize;
2756+
2757+
return ERROR_SUCCESS;
2758+
}
2759+
25562760
void LoadSettings() {
25572761
g_settings.iconTheme = WindhawkUtils::StringSetting::make(L"iconTheme");
2762+
g_settings.disableThumbnails = Wh_GetIntSetting(L"disableThumbnails");
25582763
g_settings.allResourceRedirect = Wh_GetIntSetting(L"allResourceRedirect");
25592764

25602765
std::unordered_map<std::wstring, std::vector<std::wstring>> paths;
@@ -2831,6 +3036,16 @@ BOOL Wh_ModInit() {
28313036
}
28323037
}
28333038

3039+
if (kernelBaseModule) {
3040+
auto pRegQueryValueExW = (RegQueryValueExW_t)GetProcAddress(
3041+
kernelBaseModule, "RegQueryValueExW");
3042+
if (pRegQueryValueExW) {
3043+
WindhawkUtils::SetFunctionHook(pRegQueryValueExW,
3044+
RegQueryValueExW_Hook,
3045+
&RegQueryValueExW_Original);
3046+
}
3047+
}
3048+
28343049
// The functions below use FindResourceEx, LoadResource, SizeofResource.
28353050
HMODULE shcoreModule =
28363051
LoadLibraryEx(L"shcore.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
@@ -2925,6 +3140,7 @@ BOOL Wh_ModSettingsChanged(BOOL* bReload) {
29253140
Wh_Log(L">");
29263141

29273142
auto prevIconTheme = std::move(g_settings.iconTheme);
3143+
bool prevDisableThumbnails = g_settings.disableThumbnails;
29283144
int prevAllResourceRedirect = g_settings.allResourceRedirect;
29293145

29303146
LoadSettings();
@@ -2937,7 +3153,8 @@ BOOL Wh_ModSettingsChanged(BOOL* bReload) {
29373153
FreeAndClearRedirectedModules();
29383154

29393155
if (DoesCurrentProcessOwnTaskbar()) {
2940-
if (wcscmp(g_settings.iconTheme, prevIconTheme) != 0) {
3156+
if (wcscmp(g_settings.iconTheme, prevIconTheme) != 0 ||
3157+
g_settings.disableThumbnails != prevDisableThumbnails) {
29413158
PromptToClearCache();
29423159
}
29433160

0 commit comments

Comments
 (0)