chore: initial commit (extracted from Launchers monorepo)
Plugin: UniversalShellExt v1.0.0 Architectures: x64 License: MIT
This commit is contained in:
@@ -0,0 +1,700 @@
|
||||
// UniversalShellExt.cpp - Generic Shell Extension
|
||||
// Configurable via Registry
|
||||
// Version: 1.1.0
|
||||
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#include <shlwapi.h>
|
||||
#include <strsafe.h>
|
||||
#include <new>
|
||||
|
||||
#pragma comment(lib, "shlwapi.lib")
|
||||
|
||||
// DLL Version
|
||||
#define DLL_VERSION L"1.1.0"
|
||||
|
||||
// GUID for UniversalShellExt
|
||||
// {5D849F86-F2A9-41FA-8A24-E01CD6D6696C}
|
||||
static const CLSID CLSID_UniversalShellExt =
|
||||
{ 0x5d849f86, 0xf2a9, 0x41fa, { 0x8a, 0x24, 0xe0, 0x1c, 0xd6, 0xd6, 0x69, 0x6c } };
|
||||
|
||||
// CLSID as string
|
||||
#define CLSID_STRING L"{5D849F86-F2A9-41FA-8A24-E01CD6D6696C}"
|
||||
|
||||
// Registry Configuration - now per-CLSID
|
||||
#define REG_KEY_BASE L"Software\\UniversalShellExt\\"
|
||||
#define REG_VALUE_COMMAND L"Command"
|
||||
#define REG_VALUE_ARGS L"Arguments"
|
||||
#define REG_VALUE_ICON L"Icon"
|
||||
#define REG_VALUE_TEXT L"MenuText"
|
||||
#define REG_VALUE_ENABLE_LOG L"EnableLog"
|
||||
|
||||
// Global Variables
|
||||
HINSTANCE g_hInst = NULL;
|
||||
LONG g_cDllRef = 0;
|
||||
|
||||
// Internal Helper for File Logging
|
||||
void WriteLogFile(const wchar_t* buffer) {
|
||||
FILE* f;
|
||||
if (_wfopen_s(&f, L"C:\\Users\\Simone\\.gemini\\shellext_debug.log", L"a") == 0) {
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
fwprintf(f, L"[%02d:%02d:%02d.%03d] %s\n", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, buffer);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
// Global Logger (Legacy/Global scope)
|
||||
void WriteLog(const wchar_t* format, ...) {
|
||||
// Global logging is now MINIMAL or DISABLED by default unless needed.
|
||||
// Uncomment for deep debugging of DLL loading.
|
||||
// wchar_t buffer[1024];
|
||||
// va_list args;
|
||||
// va_start(args, format);
|
||||
// _vswprintf_p(buffer, 1024, format, args);
|
||||
// va_end(args);
|
||||
// WriteLogFile(buffer);
|
||||
}
|
||||
|
||||
// Configuration Cache globals removed.
|
||||
// They are now member variables of the class.
|
||||
|
||||
//==============================================================================
|
||||
// Helper: Icon to Bitmap
|
||||
//==============================================================================
|
||||
|
||||
//==============================================================================
|
||||
// Helper: Icon to Bitmap
|
||||
//==============================================================================
|
||||
HBITMAP IconToBitmap(HICON hIcon)
|
||||
{
|
||||
if (!hIcon) return NULL;
|
||||
ICONINFO iconInfo;
|
||||
if (!GetIconInfo(hIcon, &iconInfo)) return NULL;
|
||||
|
||||
BITMAP bm;
|
||||
GetObject(iconInfo.hbmColor ? iconInfo.hbmColor : iconInfo.hbmMask, sizeof(bm), &bm);
|
||||
|
||||
int width = bm.bmWidth;
|
||||
int height = bm.bmHeight;
|
||||
|
||||
if (iconInfo.hbmColor) DeleteObject(iconInfo.hbmColor);
|
||||
if (iconInfo.hbmMask) DeleteObject(iconInfo.hbmMask);
|
||||
|
||||
HDC hdcScreen = GetDC(NULL);
|
||||
HDC hdcMem = CreateCompatibleDC(hdcScreen);
|
||||
|
||||
BITMAPINFO bmi = {0};
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = width;
|
||||
bmi.bmiHeader.biHeight = -height;
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
void* pBits;
|
||||
HBITMAP hBitmap = CreateDIBSection(hdcScreen, &bmi, DIB_RGB_COLORS, &pBits, NULL, 0);
|
||||
|
||||
if (hBitmap)
|
||||
{
|
||||
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hdcMem, hBitmap);
|
||||
RECT rc = {0, 0, width, height};
|
||||
FillRect(hdcMem, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH)); // Transparent assumes 32bit alpha
|
||||
DrawIconEx(hdcMem, 0, 0, hIcon, width, height, 0, NULL, DI_NORMAL);
|
||||
SelectObject(hdcMem, hOldBitmap);
|
||||
}
|
||||
|
||||
DeleteDC(hdcMem);
|
||||
ReleaseDC(NULL, hdcScreen);
|
||||
return hBitmap;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// Class UniversalShellExt
|
||||
//==============================================================================
|
||||
class UniversalShellExt : public IShellExtInit, public IContextMenu
|
||||
{
|
||||
public:
|
||||
// IUnknown
|
||||
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
|
||||
{
|
||||
static const QITAB qit[] =
|
||||
{
|
||||
QITABENT(UniversalShellExt, IContextMenu),
|
||||
QITABENT(UniversalShellExt, IShellExtInit),
|
||||
{ 0 },
|
||||
};
|
||||
return QISearch(this, qit, riid, ppv);
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(ULONG) AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&m_cRef);
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(ULONG) Release()
|
||||
{
|
||||
ULONG cRef = InterlockedDecrement(&m_cRef);
|
||||
if (cRef == 0) delete this;
|
||||
return cRef;
|
||||
}
|
||||
|
||||
// IShellExtInit
|
||||
IFACEMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID)
|
||||
{
|
||||
LPOLESTR szCLSID = NULL;
|
||||
StringFromCLSID(m_clsid, &szCLSID);
|
||||
Log(L"UniversalShellExt::Initialize called. CLSID: %s", szCLSID ? szCLSID : L"Unknown");
|
||||
if (szCLSID) CoTaskMemFree(szCLSID);
|
||||
|
||||
if (pDataObj == NULL) {
|
||||
Log(L"Initialize - Error: pDataObj is NULL");
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
||||
STGMEDIUM stm;
|
||||
|
||||
if (SUCCEEDED(pDataObj->GetData(&fe, &stm)))
|
||||
{
|
||||
UINT nFiles = DragQueryFile((HDROP)stm.hGlobal, 0xFFFFFFFF, NULL, 0);
|
||||
Log(L"Initialize - Files selected: %d", nFiles);
|
||||
if (nFiles > 0)
|
||||
{
|
||||
DragQueryFile((HDROP)stm.hGlobal, 0, m_szSelectedPath, MAX_PATH);
|
||||
Log(L"Initialize - Selected Path: %s", m_szSelectedPath);
|
||||
}
|
||||
ReleaseStgMedium(&stm);
|
||||
}
|
||||
else if (pidlFolder != NULL)
|
||||
{
|
||||
SHGetPathFromIDList(pidlFolder, m_szSelectedPath);
|
||||
Log(L"Initialize - From PIDL: %s", m_szSelectedPath);
|
||||
}
|
||||
else {
|
||||
Log(L"Initialize - Failed to get path data.");
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// IContextMenu
|
||||
IFACEMETHODIMP QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
|
||||
{
|
||||
LPOLESTR szCLSID = NULL;
|
||||
StringFromCLSID(m_clsid, &szCLSID);
|
||||
Log(L"UniversalShellExt::QueryContextMenu called for CLSID: %s", szCLSID ? szCLSID : L"Unknown");
|
||||
Log(L"QueryContextMenu - indexMenu: %d, idCmdFirst: %d, idCmdLast: %d, uFlags: 0x%X", indexMenu, idCmdFirst, idCmdLast, uFlags);
|
||||
if (szCLSID) CoTaskMemFree(szCLSID);
|
||||
|
||||
if (uFlags & CMF_DEFAULTONLY) return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0);
|
||||
|
||||
LoadConfiguration(); // Load config into member variables
|
||||
|
||||
// Fixed position at top
|
||||
UINT targetPosition = 0;
|
||||
|
||||
Log(L"QueryContextMenu - indexMenu: %d, idCmdFirst: %d, idCmdLast: %d", indexMenu, idCmdFirst, idCmdLast);
|
||||
Log(L"QueryContextMenu - Using fixed position: %d", targetPosition);
|
||||
Log(L"QueryContextMenu called. MenuText: %s", m_szMenuText);
|
||||
|
||||
// Dont show if no command configured
|
||||
if (m_szCommand[0] == L'\0') {
|
||||
Log(L"QueryContextMenu - Error: Command is empty!");
|
||||
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0);
|
||||
}
|
||||
|
||||
HICON hIcon = NULL;
|
||||
HBITMAP hBitmap = NULL;
|
||||
|
||||
// Extract icon
|
||||
if (m_szIcon[0] != L'\0')
|
||||
{
|
||||
Log(L"QueryContextMenu - Icon configured: %s", m_szIcon);
|
||||
// Try to find index if comma exists e.g. "dll,1"
|
||||
// Simple extraction for now
|
||||
ExtractIconEx(m_szIcon, 0, NULL, &hIcon, 1);
|
||||
}
|
||||
|
||||
if (hIcon)
|
||||
{
|
||||
hBitmap = IconToBitmap(hIcon);
|
||||
DestroyIcon(hIcon);
|
||||
}
|
||||
|
||||
if (m_hMenuBitmap) { DeleteObject(m_hMenuBitmap); m_hMenuBitmap = NULL; }
|
||||
|
||||
MENUITEMINFO mii = { sizeof(mii) };
|
||||
mii.fMask = MIIM_STRING | MIIM_ID | MIIM_STATE | MIIM_BITMAP;
|
||||
mii.wID = idCmdFirst;
|
||||
mii.dwTypeData = m_szMenuText;
|
||||
mii.fState = MFS_ENABLED;
|
||||
mii.hbmpItem = hBitmap;
|
||||
|
||||
if (!InsertMenuItem(hMenu, targetPosition, TRUE, &mii))
|
||||
{
|
||||
if (hBitmap) DeleteObject(hBitmap);
|
||||
Log(L"QueryContextMenu - InsertMenuItem FAILED. Error: %d", GetLastError());
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(L"QueryContextMenu - InsertMenuItem Success. ID: %d", idCmdFirst);
|
||||
}
|
||||
|
||||
m_hMenuBitmap = hBitmap;
|
||||
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 1);
|
||||
}
|
||||
|
||||
IFACEMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pici)
|
||||
{
|
||||
LPOLESTR szCLSID = NULL;
|
||||
StringFromCLSID(m_clsid, &szCLSID);
|
||||
Log(L"UniversalShellExt::InvokeCommand called for CLSID: %s", szCLSID ? szCLSID : L"Unknown");
|
||||
|
||||
// Debug MessageBox removed for production
|
||||
|
||||
|
||||
// Debug Box (Temporary - UNCOMMENTED FOR DEBUG)
|
||||
// MessageBox(NULL, L"InvokeCommand Called!", L"Debug", MB_OK);
|
||||
|
||||
bool isMatch = false;
|
||||
|
||||
// 1. Check for Integer Offset (Standard Right-Click)
|
||||
if (HIWORD(pici->lpVerb) == 0)
|
||||
{
|
||||
// We only add 1 item, so offset 0 is the only valid one.
|
||||
if (LOWORD(pici->lpVerb) == 0) isMatch = true;
|
||||
}
|
||||
// 2. Check for String Verb (Programmatic/Windows 11)
|
||||
else
|
||||
{
|
||||
LPCSTR pszVerb = pici->lpVerb;
|
||||
// Compare with our CLSID string (which we return in GetCommandString)
|
||||
// Need to convert our WCHAR CLSID to MultiByte for comparison
|
||||
if (szCLSID) {
|
||||
// Construct EXPECTED verb: Verb_XXXX (without braces)
|
||||
WCHAR szVerbW[128];
|
||||
StringCchPrintf(szVerbW, 128, L"Verb_%s", szCLSID + 1); // Skip '{'
|
||||
size_t lenW = 0;
|
||||
StringCchLength(szVerbW, 128, &lenW);
|
||||
if (lenW > 0 && szVerbW[lenW-1] == L'}') szVerbW[lenW-1] = L'\0';
|
||||
|
||||
// Convert to ANSI for comparison
|
||||
int lenA = WideCharToMultiByte(CP_ACP, 0, szVerbW, -1, NULL, 0, NULL, NULL);
|
||||
char* pszVerbA = new (std::nothrow) char[lenA];
|
||||
if (pszVerbA) {
|
||||
WideCharToMultiByte(CP_ACP, 0, szVerbW, -1, pszVerbA, lenA, NULL, NULL);
|
||||
|
||||
if (lstrcmpiA(pszVerb, pszVerbA) == 0) isMatch = true;
|
||||
else if (lstrcmpiA(pszVerb, "UniversalShellExt") == 0) isMatch = true; // Fallback
|
||||
|
||||
delete[] pszVerbA;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (szCLSID) CoTaskMemFree(szCLSID);
|
||||
|
||||
if (!isMatch) {
|
||||
Log(L"InvokeCommand - Verb mismatch. Rejecting.");
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
// If command is empty, try reloading config (fallback)
|
||||
if (m_szCommand[0] == L'\0') {
|
||||
Log(L"InvokeCommand - Command empty, reloading...");
|
||||
LoadConfiguration();
|
||||
}
|
||||
|
||||
// Check if command file exists
|
||||
if (m_szCommand[0] == L'\0' || GetFileAttributes(m_szCommand) == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
WCHAR szMsg[MAX_PATH + 128];
|
||||
if (m_szCommand[0] == L'\0')
|
||||
{
|
||||
StringCchCopy(szMsg, ARRAYSIZE(szMsg), L"Nessun comando configurato.\n\nConfigurare la chiave di registro:\nHKLM\\SOFTWARE\\UniversalShellExt\\Command");
|
||||
}
|
||||
else
|
||||
{
|
||||
StringCchPrintf(szMsg, ARRAYSIZE(szMsg), L"File non trovato:\n\n%s\n\nVerificare la configurazione nel registro.", m_szCommand);
|
||||
}
|
||||
Log(L"InvokeCommand - Error: %s", szMsg);
|
||||
MessageBox(NULL, szMsg, L"UniversalShellExt - Errore", MB_OK | MB_ICONERROR);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// Prepare Parameters
|
||||
// Replace %1 in m_szArguments with m_szSelectedPath (quoted)
|
||||
WCHAR szArgsExpanded[MAX_PATH * 2];
|
||||
WCHAR szPathQuoted[MAX_PATH + 2];
|
||||
StringCchPrintf(szPathQuoted, MAX_PATH+2, L"\"%s\"", m_szSelectedPath);
|
||||
|
||||
// Simple Replace of %1
|
||||
// Note: This is a basic implementation.
|
||||
// It searches for %1 and replaces it.
|
||||
|
||||
// Copy arguments to buffer
|
||||
WCHAR *pFound = wcsstr(m_szArguments, L"%1");
|
||||
if (pFound)
|
||||
{
|
||||
size_t prefixLen = pFound - m_szArguments;
|
||||
wcsncpy_s(szArgsExpanded, ARRAYSIZE(szArgsExpanded), m_szArguments, prefixLen);
|
||||
szArgsExpanded[prefixLen] = L'\0';
|
||||
StringCchCat(szArgsExpanded, ARRAYSIZE(szArgsExpanded), szPathQuoted); // Insert Path (quoted)
|
||||
StringCchCat(szArgsExpanded, ARRAYSIZE(szArgsExpanded), pFound + 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No %1 found, just use arguments as is.
|
||||
// The provided snippet had a different logic here, appending the path if no %1.
|
||||
// Sticking to the original logic for now, which means if no %1, path is not appended.
|
||||
StringCchCopy(szArgsExpanded, ARRAYSIZE(szArgsExpanded), m_szArguments);
|
||||
}
|
||||
|
||||
SHELLEXECUTEINFO sei = { sizeof(sei) };
|
||||
sei.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
sei.lpVerb = L"open";
|
||||
sei.lpFile = m_szCommand;
|
||||
sei.lpParameters = szArgsExpanded;
|
||||
sei.nShow = SW_SHOWNORMAL;
|
||||
|
||||
// Set Working Directory to the folder containing the executable
|
||||
WCHAR szDir[MAX_PATH];
|
||||
StringCchCopy(szDir, MAX_PATH, m_szCommand);
|
||||
PathRemoveFileSpec(szDir); // Requires shlwapi.h and linking shlwapi.lib
|
||||
sei.lpDirectory = szDir;
|
||||
|
||||
Log(L"InvokeCommand - Executing: %s Params: %s Dir: %s", sei.lpFile, sei.lpParameters, sei.lpDirectory);
|
||||
|
||||
if (!ShellExecuteEx(&sei))
|
||||
{
|
||||
DWORD lastError = GetLastError();
|
||||
Log(L"InvokeCommand - ShellExecuteEx failed with error: %d", lastError);
|
||||
if (lastError == ERROR_ELEVATION_REQUIRED)
|
||||
{
|
||||
sei.lpVerb = L"runas";
|
||||
Log(L"InvokeCommand - Retrying with 'runas' verb.");
|
||||
ShellExecuteEx(&sei);
|
||||
} else {
|
||||
MessageBox(NULL, L"Failed to launch command.", L"Error", MB_ICONERROR);
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
if (sei.hProcess) CloseHandle(sei.hProcess);
|
||||
Log(L"InvokeCommand - Success");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pReserved, CHAR *pszName, UINT cchMax)
|
||||
{
|
||||
Log(L"GetCommandString called. uType: %d", uType);
|
||||
if (idCmd != 0) return E_INVALIDARG;
|
||||
|
||||
LoadConfiguration(); // Reload config
|
||||
|
||||
switch (uType)
|
||||
{
|
||||
case GCS_HELPTEXTW:
|
||||
StringCchCopyW((LPWSTR)pszName, cchMax, m_szMenuText);
|
||||
return S_OK;
|
||||
|
||||
case GCS_HELPTEXTA:
|
||||
{
|
||||
int len = WideCharToMultiByte(CP_ACP, 0, m_szMenuText, -1, NULL, 0, NULL, NULL);
|
||||
if (len <= cchMax) WideCharToMultiByte(CP_ACP, 0, m_szMenuText, -1, pszName, cchMax, NULL, NULL);
|
||||
}
|
||||
return S_OK;
|
||||
|
||||
case GCS_VERBW:
|
||||
case GCS_VALIDATEW:
|
||||
{
|
||||
LPOLESTR szCLSID = NULL;
|
||||
StringFromCLSID(m_clsid, &szCLSID);
|
||||
if (szCLSID)
|
||||
{
|
||||
// Sanitize Verb: replace {CLSID} with Verb_CLSID (no braces)
|
||||
WCHAR szVerb[128];
|
||||
StringCchPrintf(szVerb, 128, L"Verb_%s", szCLSID + 1); // Skip '{'
|
||||
size_t len = 0;
|
||||
StringCchLength(szVerb, 128, &len);
|
||||
if (len > 0 && szVerb[len-1] == L'}') szVerb[len-1] = L'\0';
|
||||
|
||||
Log(L"GetCommandString - Returning VERB: %s", szVerb);
|
||||
|
||||
if (uType == GCS_VERBW && pszName) {
|
||||
StringCchCopyW((LPWSTR)pszName, cchMax, szVerb);
|
||||
}
|
||||
CoTaskMemFree(szCLSID);
|
||||
return S_OK;
|
||||
}
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
case GCS_VERBA:
|
||||
case GCS_VALIDATEA:
|
||||
// Return "UniversalShellExt" for legacy ANSI, but maybe we should return Verb_GUID here too if possible?
|
||||
// Sticking to simplified "Valid" response for now to minimize charset risk.
|
||||
// If Verb is requested (ANSI), we return the generic string.
|
||||
if (uType == GCS_VERBA && pszName) {
|
||||
StringCchCopyA(pszName, cchMax, "UniversalShellExt");
|
||||
}
|
||||
return S_OK;
|
||||
|
||||
default:
|
||||
Log(L"GetCommandString - Unknown uType: %d", uType);
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
}
|
||||
|
||||
UniversalShellExt(REFCLSID clsid) : m_cRef(1), m_hMenuBitmap(NULL), m_clsid(clsid)
|
||||
{
|
||||
InterlockedIncrement(&g_cDllRef);
|
||||
|
||||
// Stringify CLSID for log
|
||||
LPOLESTR szCLSID = NULL;
|
||||
StringFromCLSID(m_clsid, &szCLSID);
|
||||
Log(L"UniversalShellExt::Constructor - CLSID: %s", szCLSID ? szCLSID : L"Unknown");
|
||||
if (szCLSID) CoTaskMemFree(szCLSID);
|
||||
|
||||
m_szSelectedPath[0] = L'\0';
|
||||
m_szCommand[0] = L'\0';
|
||||
m_szArguments[0] = L'\0';
|
||||
m_szIcon[0] = L'\0';
|
||||
m_szMenuText[0] = L'\0';
|
||||
m_bEnableLog = FALSE;
|
||||
}
|
||||
|
||||
protected:
|
||||
~UniversalShellExt()
|
||||
{
|
||||
LPOLESTR szCLSID = NULL;
|
||||
StringFromCLSID(m_clsid, &szCLSID);
|
||||
Log(L"UniversalShellExt::Destructor - CLSID: %s", szCLSID ? szCLSID : L"Unknown");
|
||||
if (szCLSID) CoTaskMemFree(szCLSID);
|
||||
|
||||
if (m_hMenuBitmap) DeleteObject(m_hMenuBitmap);
|
||||
InterlockedDecrement(&g_cDllRef);
|
||||
}
|
||||
|
||||
private:
|
||||
LONG m_cRef;
|
||||
WCHAR m_szSelectedPath[MAX_PATH];
|
||||
HBITMAP m_hMenuBitmap;
|
||||
CLSID m_clsid;
|
||||
// Member variables to store configuration for this instance
|
||||
WCHAR m_szCommand[MAX_PATH];
|
||||
WCHAR m_szArguments[MAX_PATH];
|
||||
WCHAR m_szIcon[MAX_PATH];
|
||||
WCHAR m_szMenuText[128];
|
||||
BOOL m_bEnableLog;
|
||||
|
||||
// Member helper for logging
|
||||
void Log(const wchar_t* format, ...) {
|
||||
if (!m_bEnableLog) return;
|
||||
|
||||
wchar_t buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
_vswprintf_p(buffer, 1024, format, args);
|
||||
va_end(args);
|
||||
|
||||
WriteLogFile(buffer);
|
||||
}
|
||||
|
||||
// Internal LoadConfiguration method
|
||||
void LoadConfiguration()
|
||||
{
|
||||
m_szCommand[0] = L'\0';
|
||||
m_szArguments[0] = L'\0';
|
||||
m_szIcon[0] = L'\0';
|
||||
m_szMenuText[0] = L'\0';
|
||||
|
||||
// Build registry path: Software\UniversalShellExt\{CLSID}
|
||||
WCHAR szKeyPath[256];
|
||||
WCHAR szClsid[64];
|
||||
StringFromGUID2(m_clsid, szClsid, ARRAYSIZE(szClsid));
|
||||
StringCchPrintf(szKeyPath, ARRAYSIZE(szKeyPath), L"%s%s", REG_KEY_BASE, szClsid);
|
||||
|
||||
HKEY hKey;
|
||||
DWORD dwSize;
|
||||
DWORD dwType = REG_SZ;
|
||||
|
||||
// Try to open key from HKLM (64-bit view)
|
||||
LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKeyPath, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
|
||||
if (status != ERROR_SUCCESS)
|
||||
{
|
||||
status = RegOpenKeyEx(HKEY_CURRENT_USER, szKeyPath, 0, KEY_READ, &hKey);
|
||||
}
|
||||
|
||||
if (status == ERROR_SUCCESS)
|
||||
{
|
||||
// Read Command
|
||||
dwSize = sizeof(m_szCommand);
|
||||
RegQueryValueEx(hKey, REG_VALUE_COMMAND, NULL, &dwType, (LPBYTE)m_szCommand, &dwSize);
|
||||
|
||||
// Read Arguments
|
||||
dwSize = sizeof(m_szArguments);
|
||||
RegQueryValueEx(hKey, REG_VALUE_ARGS, NULL, &dwType, (LPBYTE)m_szArguments, &dwSize);
|
||||
|
||||
// Read Icon
|
||||
dwSize = sizeof(m_szIcon);
|
||||
RegQueryValueEx(hKey, REG_VALUE_ICON, NULL, &dwType, (LPBYTE)m_szIcon, &dwSize);
|
||||
|
||||
// Read MenuText
|
||||
dwSize = sizeof(m_szMenuText);
|
||||
dwSize = sizeof(m_szMenuText);
|
||||
RegQueryValueEx(hKey, REG_VALUE_TEXT, NULL, &dwType, (LPBYTE)m_szMenuText, &dwSize);
|
||||
|
||||
// Read EnableLog
|
||||
DWORD dwLog = 0;
|
||||
dwSize = sizeof(dwLog);
|
||||
DWORD dwTypeDw = REG_DWORD;
|
||||
if (RegQueryValueEx(hKey, REG_VALUE_ENABLE_LOG, NULL, &dwTypeDw, (LPBYTE)&dwLog, &dwSize) == ERROR_SUCCESS) {
|
||||
m_bEnableLog = (dwLog != 0);
|
||||
}
|
||||
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
// Defaults
|
||||
if (m_szCommand[0] == L'\0') {
|
||||
// Nothing to run?
|
||||
}
|
||||
|
||||
if (m_szArguments[0] == L'\0') {
|
||||
StringCchCopy(m_szArguments, MAX_PATH, L"\"%1\"");
|
||||
}
|
||||
|
||||
if (m_szIcon[0] == L'\0') {
|
||||
// Default icon to command exe
|
||||
StringCchCopy(m_szIcon, MAX_PATH, m_szCommand);
|
||||
}
|
||||
|
||||
if (m_szMenuText[0] == L'\0') {
|
||||
StringCchCopy(m_szMenuText, 128, L"Open with Application");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Class Factory
|
||||
//==============================================================================
|
||||
class ClassFactory : public IClassFactory
|
||||
{
|
||||
public:
|
||||
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
|
||||
{
|
||||
static const QITAB qit[] = { QITABENT(ClassFactory, IClassFactory), { 0 } };
|
||||
return QISearch(this, qit, riid, ppv);
|
||||
}
|
||||
IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_cRef); }
|
||||
IFACEMETHODIMP_(ULONG) Release()
|
||||
{
|
||||
ULONG cRef = InterlockedDecrement(&m_cRef);
|
||||
if (cRef == 0) delete this;
|
||||
return cRef;
|
||||
}
|
||||
IFACEMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv)
|
||||
{
|
||||
if (pUnkOuter != NULL) return CLASS_E_NOAGGREGATION;
|
||||
UniversalShellExt *pExt = new (std::nothrow) UniversalShellExt(m_clsid);
|
||||
if (pExt == NULL) return E_OUTOFMEMORY;
|
||||
HRESULT hr = pExt->QueryInterface(riid, ppv);
|
||||
pExt->Release();
|
||||
return hr;
|
||||
}
|
||||
IFACEMETHODIMP LockServer(BOOL fLock)
|
||||
{
|
||||
if (fLock) InterlockedIncrement(&g_cDllRef);
|
||||
else InterlockedDecrement(&g_cDllRef);
|
||||
return S_OK;
|
||||
}
|
||||
ClassFactory(const CLSID& clsid) : m_cRef(1), m_clsid(clsid) { }
|
||||
protected:
|
||||
~ClassFactory() { }
|
||||
private:
|
||||
LONG m_cRef;
|
||||
CLSID m_clsid;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// DLL Exports
|
||||
//==============================================================================
|
||||
|
||||
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
|
||||
{
|
||||
LPOLESTR szCLSID = NULL;
|
||||
StringFromCLSID(rclsid, &szCLSID);
|
||||
WriteLog(L"DllGetClassObject - Requesting CLSID: %s", szCLSID ? szCLSID : L"Unknown");
|
||||
if (szCLSID) CoTaskMemFree(szCLSID);
|
||||
|
||||
*ppv = NULL;
|
||||
if (!IsEqualIID(riid, IID_IClassFactory))
|
||||
return CLASS_E_CLASSNOTAVAILABLE;
|
||||
|
||||
ClassFactory* pFactory = new (std::nothrow) ClassFactory(rclsid); // Pass CLSID to factory
|
||||
if (!pFactory)
|
||||
return E_OUTOFMEMORY;
|
||||
|
||||
HRESULT hr = pFactory->QueryInterface(riid, ppv);
|
||||
pFactory->Release();
|
||||
return hr;
|
||||
}
|
||||
|
||||
STDAPI DllCanUnloadNow(void) { return g_cDllRef > 0 ? S_FALSE : S_OK; }
|
||||
|
||||
HRESULT SetRegistryValue(HKEY hKeyRoot, LPCWSTR pszSubKey, LPCWSTR pszValueName, LPCWSTR pszValue)
|
||||
{
|
||||
HKEY hKey;
|
||||
HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyEx(hKeyRoot, pszSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL));
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
if (pszValue != NULL)
|
||||
{
|
||||
DWORD cbData = (lstrlen(pszValue) + 1) * sizeof(WCHAR);
|
||||
hr = HRESULT_FROM_WIN32(RegSetValueEx(hKey, pszValueName, 0, REG_SZ, (const BYTE*)pszValue, cbData));
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
STDAPI DllRegisterServer(void)
|
||||
{
|
||||
WCHAR szModulePath[MAX_PATH];
|
||||
if (!GetModuleFileName(g_hInst, szModulePath, ARRAYSIZE(szModulePath))) return HRESULT_FROM_WIN32(GetLastError());
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
hr = SetRegistryValue(HKEY_CLASSES_ROOT, L"CLSID\\" CLSID_STRING, NULL, L"Universal Portable Shell Extension");
|
||||
if (FAILED(hr)) return hr;
|
||||
hr = SetRegistryValue(HKEY_CLASSES_ROOT, L"CLSID\\" CLSID_STRING L"\\InprocServer32", NULL, szModulePath);
|
||||
if (FAILED(hr)) return hr;
|
||||
hr = SetRegistryValue(HKEY_CLASSES_ROOT, L"CLSID\\" CLSID_STRING L"\\InprocServer32", L"ThreadingModel", L"Apartment");
|
||||
if (FAILED(hr)) return hr;
|
||||
|
||||
// Register Context Menu Handler
|
||||
hr = SetRegistryValue(HKEY_CLASSES_ROOT, L"Directory\\shellex\\ContextMenuHandlers\\UniversalShellExt", NULL, CLSID_STRING);
|
||||
if (FAILED(hr)) return hr;
|
||||
hr = SetRegistryValue(HKEY_CLASSES_ROOT, L"Directory\\Background\\shellex\\ContextMenuHandlers\\UniversalShellExt", NULL, CLSID_STRING);
|
||||
if (FAILED(hr)) return hr;
|
||||
|
||||
// Approve
|
||||
hr = SetRegistryValue(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", CLSID_STRING, L"Universal Shell Extension");
|
||||
return hr;
|
||||
}
|
||||
|
||||
STDAPI DllUnregisterServer(void)
|
||||
{
|
||||
RegDeleteTree(HKEY_CLASSES_ROOT, L"CLSID\\" CLSID_STRING);
|
||||
RegDeleteTree(HKEY_CLASSES_ROOT, L"Directory\\shellex\\ContextMenuHandlers\\UniversalShellExt");
|
||||
RegDeleteTree(HKEY_CLASSES_ROOT, L"Directory\\Background\\shellex\\ContextMenuHandlers\\UniversalShellExt");
|
||||
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", 0, KEY_WRITE, &hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
RegDeleteValue(hKey, CLSID_STRING);
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user