NSIS/Contrib/Makensisw/lookup.cpp
2021-08-18 23:48:20 +00:00

365 lines
No EOL
13 KiB
C++

// Copyright (C) 2020-2021 Anders Kjersem
//
// This file is a part of NSIS.
//
// Licensed under the zlib/libpng license (the "License");
// you may not use this file except in compliance with the License.
//
// Licence details can be found in the file COPYING.
//
// This software is provided 'as-is', without any express or implied
// warranty.
#include "makensisw.h"
#include <windows.h>
#include <windowsx.h>
#include <shlobj.h>
#include <shlwapi.h>
#include "utils.h"
#include "resource.h"
enum { reg_mui_string_truncate = 0x01 };
static void StrTToW(LPCTSTR Src, LPWSTR Dst, UINT cch)
{
if (sizeof(*Src) == sizeof(*Dst))
lstrcpynW(Dst, (LPCWSTR) Src, cch);
else
MultiByteToWideChar(CP_ACP, 0, (LPCSTR) Src, -1, Dst, cch);
}
template<class T> static LPWSTR SmartStrTToW(T*Src, LPWSTR Dst, UINT cch)
{
if (sizeof(T) == sizeof(*Dst)) return (LPWSTR) Src;
return (StrTToW(Src, Dst, cch), Dst);
}
#define StrBase10ToSInt(s) StrToSInt((s), 10)
template<class T> static int WINAPI StrToSInt(const T*Str, UINT Base = 0)
{
if (Base && !(*Str >= '0' && *Str <= '9') && *Str != '-') return 0; // Don't allow leading space
int v, succ;
if (sizeof(*Str) > 1)
succ = StrToIntExW((WCHAR*) Str, Base != 10 ? STIF_SUPPORT_HEX : STIF_DEFAULT, &v);
else
succ = StrToIntExA((CHAR *) Str, Base != 10 ? STIF_SUPPORT_HEX : STIF_DEFAULT, &v);
return succ ? v : 0; // Not full base support, we only need 10 and 16
}
template<class T> static ULARGE_INTEGER PathParseIconLocationEx(T*Path)
{
ULARGE_INTEGER li;
int idx = 0;
SIZE_T i, comma = 0;
for (i = 0; Path[i]; ++i)
{
if (Path[i] == ',') comma = i;
if (FSPath::IsAgnosticSeparator(Path[i])) comma = 0;
}
if (comma)
{
Path[comma] = T('\0');
idx = StrBase10ToSInt(Path + comma + 1);
}
return (li.HighPart = (UINT) comma, li.LowPart = idx, li);
}
#ifndef _WIN64
static int WINAPI PathParseIconLocationFallback(LPSTR Path)
{
ULARGE_INTEGER li = PathParseIconLocationEx(Path);
return li.LowPart;
}
#endif
static HRESULT GetSpecialFolderPath(HWND hWnd, LPTSTR Buf, UINT csidl)
{
UINT create = csidl & CSIDL_FLAG_CREATE, succ, ec;
csidl &= ~CSIDL_FLAG_CREATE;
if (SupportsWNT4() || SupportsW95())
{
LPITEMIDLIST pidl;
HRESULT hr = SHGetSpecialFolderLocation(hWnd, csidl, &pidl);
if (SUCCEEDED(hr))
{
hr = SHGetPathFromIDList(pidl, Buf) ? S_OK : E_FAIL, CoTaskMemFree(pidl);
if (SUCCEEDED(hr) && create)
{
succ = CreateDirectory(Buf, NULL);
hr = (succ || (ec = GetLastError()) == ERROR_ALREADY_EXISTS) ? S_OK : HRESULT_FROM_WIN32(ec);
}
}
return hr;
}
succ = SHGetSpecialFolderPath(hWnd, Buf, csidl, false);
if (!succ && create) succ = SHGetSpecialFolderPath(hWnd, Buf, csidl, create);
return succ ? S_OK : E_FAIL;
}
static HICON LoadIconFromLocation(LPCTSTR Path, int Idx)
{
// web.archive.org/web/201901/https://blogs.msdn.microsoft.com/oldnewthing/20100505-00/?p=14153# -1 is special
HICON hIco = ExtractIcon(NULL, Path, Idx == -1 ? 0 : Idx);
if (!hIco && !GetLastError()) SetLastError(ERROR_NOT_FOUND);
return hIco;
}
template<class T> static void AppendText(HWND hEdit, const T*Str)
{
SNDMSG(hEdit, EM_SETSEL, 0, -1), SNDMSG(hEdit, EM_SETSEL, -1, -1);
if (sizeof(T) > 1)
SendMessageW(hEdit, EM_REPLACESEL, 0, (SIZE_T) Str);
else
SendMessageA(hEdit, EM_REPLACESEL, 0, (SIZE_T) Str);
}
static int AddComboStringWithData(HWND hWnd, LPCSTR Str, SIZE_T Data)
{
int idx = (int) SendMessageA(hWnd, CB_INSERTSTRING, (INT_PTR) -1, (SIZE_T) const_cast<LPCSTR>(Str));
if (idx >= 0) SendMessage(hWnd, CB_SETITEMDATA, idx, Data);
return idx;
}
template<class I, class O> static HKEY ParseRegPath(I*In, O*&Path, O*&Name)
{
HKEY hKey = GetRegRootKey(In);
I*pp = 0, *pn = 0;
if (hKey)
{
SIZE_T i;
for (i = 0; In[i]; ++i)
if (In[i] == I('\\'))
(!pp ? pp : pn) = &In[i + 1]; // First separator after HK* or last separator
if (pn) pn[-1] = I('\0'); // Split path and name
}
return (Path = const_cast<O*>(pp), Name = const_cast<O*>(pn), hKey);
}
HRESULT (WINAPI*g_RLMSOld)(HKEY hKey, LPCWSTR pszValue, LPWSTR pszOutBuf, DWORD cbOutBuf);
static LONG WINAPI RegLoadMUIStringFallbackW(HKEY hKey, LPCWSTR Name, LPWSTR Out, DWORD cbOutBuf, LPDWORD pcbData, DWORD Flags, LPCSTR pszDirectory)
{
if ((Flags & reg_mui_string_truncate) && pcbData) return ERROR_INVALID_PARAMETER;
if (g_RLMSOld)
{
HRESULT hr = g_RLMSOld(hKey, Name, Out, cbOutBuf);
if (SUCCEEDED(hr))
{
if (pcbData) *pcbData = (StrLenT(Out) + 1) * sizeof(*Out);
return ERROR_SUCCESS;
}
if (pcbData) *pcbData = (cbOutBuf|1) * 2; // We have no real value to set, caller just has to loop until success
return hr; // Note: Not converting E_* here, FormatMessage knows what to do.
}
return ERROR_NOT_SUPPORTED;
}
enum { LM_SHGLN, LM_SHLIS, LM_RLMS, LM_PPIL, LM_FMTMSG };
LPCSTR g_ModeLbl[] = { "Path:", "Path:", "Registry:", "Path:", "[Path,]Number:" };
struct DIALOGDATA {
HRESULT (WINAPI*SHLIS)(LPCWSTR s, LPWSTR o, UINT cch, PVOID*ppvReserved);
HRESULT (WINAPI*SHGLN)(PCWSTR p, PWSTR m, UINT cch, int*rid);
LONG (WINAPI*RLMS)(HKEY hKey, LPCWSTR pszValue, LPWSTR pszOutBuf, DWORD cbOutBuf, LPDWORD pcbData, DWORD Flags, LPCSTR pszDirectory);
int (WINAPI*PPIL)(LPTSTR p);
void*OrgParentDlgData;
HWND hMode, hExtra, hOutTxt, hOutIco;
DIALOGDATA() { extern void* g_ModalDlgData; OrgParentDlgData = g_ModalDlgData, g_ModalDlgData = this; }
~DIALOGDATA() { extern void* g_ModalDlgData; g_ModalDlgData = OrgParentDlgData; }
static DIALOGDATA* Get(HWND hDlg) { return (DIALOGDATA*) g_ModalDlgData; }
static inline void Set(HWND hDlg, DIALOGDATA*p) { }
};
static INT_PTR CALLBACK LookupDlgProc(HWND hDlg, UINT Msg, WPARAM WParam, LPARAM LParam)
{
DIALOGDATA*pDD = DIALOGDATA::Get(hDlg);
int idx = pDD ? (int) SendMessage(pDD->hMode, CB_GETCURSEL, 0, 0) : 0;
UINT mode = pDD ? (UINT) SendMessage(pDD->hMode, CB_GETITEMDATA, idx, 0) : 0;
HRESULT hr;
TCHAR buf[MAX_PATH + 1 + 6]; // Path + ',' + resid
switch(Msg)
{
case WM_SIZE:
if (WParam == SIZE_MAXIMIZED) ShowWindow(hDlg, SW_SHOWNOACTIVATE); // Disallow STARTF_USESHOWWINDOW+SW_MAXIMIZE
break;
case WM_INITDIALOG:
DIALOGDATA::Set(hDlg, (pDD = (DIALOGDATA*) LParam));
CenterOnParent(hDlg);
pDD->hMode = GetDlgItem(hDlg, IDC_LUMODE);
pDD->hExtra = GetDlgItem(hDlg, IDC_LUEXTRATEXT);
pDD->hOutTxt = GetDlgItem(hDlg, IDC_LUOUTPUTTEXT);
pDD->hOutIco = CreateWindowEx(WS_EX_TRANSPARENT, _T("STATIC"), 0, WS_CHILD|WS_VISIBLE|SS_ICON|SS_CENTERIMAGE|SS_REALSIZECONTROL, 0, 0, 0, 0, pDD->hOutTxt, 0, 0, 0);
if (pDD->SHLIS) AddComboStringWithData(pDD->hMode, "SHLoadIndirectString", LM_SHLIS);
if (pDD->RLMS) AddComboStringWithData(pDD->hMode, "RegLoadMUIString", LM_RLMS);
if (pDD->SHGLN) AddComboStringWithData(pDD->hMode, "SHGetLocalizedName", LM_SHGLN);
if (pDD->PPIL) AddComboStringWithData(pDD->hMode, "PathParseIconLocation", LM_PPIL);
AddComboStringWithData(pDD->hMode, "FormatMessage", LM_FMTMSG);
SNDMSG(pDD->hExtra, EM_LIMITTEXT, COUNTOF(buf), 0);
SNDMSG(pDD->hMode, CB_SETCURSEL, (SIZE_T) 0, 0), SNDMSG(hDlg, WM_COMMAND, MAKELONG(IDC_LUMODE, CBN_SELENDOK), (SIZE_T) pDD->hMode);
return TRUE;
case WM_CLOSE: close:
return EndDialog(hDlg, 0);
case WM_DESTROY:
{
HICON hOld = (HICON) SNDMSG(pDD->hOutIco, STM_SETICON, (SIZE_T) NULL, 0);
if (hOld) DestroyIcon(hOld);
}
break;
case WM_COMMAND:
switch((UINT) WParam)
{
case IDCANCEL:
goto close;
case MAKELONG(IDC_LUMODE, CBN_SELENDOK):
{
SendMessageA(GetDlgItem(hDlg, IDC_LUEXTRALABEL), WM_SETTEXT, 0, (SIZE_T) const_cast<LPCSTR>(g_ModeLbl[mode]));
LPCTSTR extra = buf;
*buf = '\0';
switch(mode)
{
case LM_SHGLN:
GetSpecialFolderPath(NULL, buf, CSIDL_COMMON_DESKTOPDIRECTORY);
break;
case LM_SHLIS:
extra = _T("@shell32,-4097");
break;
case LM_RLMS:
extra = _T("HKCR\\AudioCD\\shell\\play\\MUIVerb");
break;
case LM_PPIL:
extra = _T("%WINDIR%\\Explorer.exe,-101");
break;
case LM_FMTMSG:
extra = _T("wininet.dll,12005");
break;
}
ShowWindow(pDD->hOutIco, SW_HIDE);
SetWindowText(pDD->hExtra, extra);
}
break;
case MAKELONG(IDC_LUEXTRATEXT, EN_UPDATE):
SendMessage(pDD->hExtra, WM_GETTEXT, COUNTOF(buf), (SIZE_T) buf);
SetWindowText(pDD->hOutTxt, _T(""));
switch(mode)
{
case LM_SHGLN:
{
int resid;
WCHAR path[COUNTOF(buf)], mod[MAX_PATH], *pis;
pis = SmartStrTToW(buf, path, COUNTOF(path));
hr = pDD->SHGLN(pis, mod, COUNTOF(mod), &resid);
if (SUCCEEDED(hr))
{
AppendText(pDD->hOutTxt, mod);
AppendText(pDD->hOutTxt, (wsprintfW(path, L",%d\r\n\r\n", resid), path));
if (ExpandEnvironmentStringsW(mod, path, COUNTOF(path)))
{
if (HMODULE hMod = LoadLibraryExW(path, NULL, LOAD_LIBRARY_AS_DATAFILE))
{
if (LoadStringW(hMod, resid, mod, COUNTOF(mod)))
{
AppendText(pDD->hOutTxt, mod);
}
FreeLibrary(hMod);
}
}
}
else die_hr:
{
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, NULL, hr, 0, buf, COUNTOF(buf), NULL);
AppendText(pDD->hOutTxt, (wsprintfW(path, L"%#x: ", hr), path));
AppendText(pDD->hOutTxt, buf);
}
}
break;
case LM_SHLIS:
{
WCHAR is[COUNTOF(buf)], os[MAX_PATH], *pis;
pis = SmartStrTToW(buf, is, COUNTOF(is));
hr = pDD->SHLIS(pis, os, COUNTOF(os), NULL);
if (FAILED(hr)) goto die_hr;
AppendText(pDD->hOutTxt, os);
}
break;
case LM_RLMS:
{
UINT ec;
TCHAR *pkey, *pname;
if (HKEY hKey = ParseRegPath(buf, pkey, pname))
{
if (!(ec = RegOpenKeyForReading(hKey, pkey, &hKey)))
{
WCHAR namebuf[255+!0], outbuf[MAX_PATH], *pwn = SmartStrTToW(pname, namebuf, COUNTOF(namebuf));
ec = pDD->RLMS(hKey, pwn, outbuf, sizeof(outbuf), NULL, reg_mui_string_truncate, NULL);
if (!ec) AppendText(pDD->hOutTxt, outbuf);
RegCloseKey(hKey);
}
if (FAILED(hr = HRESULT_FROM_WIN32(ec))) goto die_hr;
}
}
break;
case LM_PPIL:
{
RECT r;
int idx = pDD->PPIL(buf);
HICON hIco = LoadIconFromLocation(buf, idx), hOld;
hr = GetLastError();
GetClientRect(pDD->hOutTxt, &r);
SetWindowPos(pDD->hOutIco, HWND_TOP, 0, 0, r.right, r.bottom, SWP_NOACTIVATE|(hIco ? SWP_SHOWWINDOW : SWP_HIDEWINDOW));
hOld = (HICON) SNDMSG(pDD->hOutIco, STM_SETICON, (SIZE_T) hIco, 0);
if (hOld) DestroyIcon(hOld);
if (!hIco) goto die_hr;
}
break;
case LM_FMTMSG:
{
UINT flags = FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, succ = false;
ULARGE_INTEGER li = PathParseIconLocationEx(buf);
LPCTSTR numstr = buf;
HMODULE hMod = 0;
if (li.HighPart)
{
numstr = buf + ++li.HighPart;
hMod = LoadLibraryEx(buf, 0, LOAD_LIBRARY_AS_DATAFILE), flags |= FORMAT_MESSAGE_FROM_HMODULE;
if (!hMod) goto badmsgmod;
}
hr = StrToSInt(numstr);
succ = FormatMessage(flags, hMod, hr, 0, buf, COUNTOF(buf), NULL) != 0;
badmsgmod:
hr = GetLastError();
if (hMod) FreeLibrary(hMod);
if (!succ) goto die_hr;
SetWindowText(pDD->hOutTxt, buf);
}
break;
}
break;
}
break;
}
return FALSE;
}
INT_PTR ShowLookupDialog(HWND hOwner)
{
DIALOGDATA dd;
(FARPROC&) dd.SHLIS = GetSysProcAddr("SHLWAPI", "SHLoadIndirectString"); // WXP+
(FARPROC&) dd.SHGLN = GetSysProcAddr("SHELL32", "SHGetLocalizedName"); // WVista+
(FARPROC&) dd.RLMS = GetSysProcAddr("ADVAPI32", "RegLoadMUIStringW"); // WVista+ Note: RegLoadMUIStringA always returns ERROR_CALL_NOT_IMPLEMENTED
if (!dd.RLMS && ((FARPROC&) g_RLMSOld = GetSysProcAddr("SHLWAPI", (LPCSTR) 439))) dd.RLMS = RegLoadMUIStringFallbackW; // W98SE+,IE5+
(FARPROC&) dd.PPIL =
#ifdef _WIN64
(FARPROC) PathParseIconLocation;
#else
GetSysProcAddr("SHLWAPI", sizeof(TCHAR) == 1 ? "PathParseIconLocationA" : "PathParseIconLocationW"); // W95OSR2+,IE3.1+
if (!dd.PPIL || (SupportsWNT4() || IsWin9598ME()))
{
(FARPROC&) dd.PPIL = GetSysProcAddr("SHELL32", (LPCSTR) 249); // WNT4+ PathParseIconLocationT
if (sizeof(TCHAR) == 1) (FARPROC&) dd.PPIL = (FARPROC) PathParseIconLocationFallback;
}
#endif
return DialogBoxParam(HINST_APPLICATION, MAKEINTRESOURCE(DLG_LOOKUP), hOwner, LookupDlgProc, (LPARAM) &dd);
}