From bfdd203cf9296fcda7d201f38c5179f791c171e5 Mon Sep 17 00:00:00 2001 From: d4koon Date: Sat, 26 Sep 2020 17:17:26 +0200 Subject: [PATCH] Reworked hook - Hook-logging works now over WinSock - Better control over injection of hook - Moved injecteion-code completly into WhatsAppTray --- WhatsappTray.sln | 7 +- WhatsappTray/Helper.cpp | 26 ++ WhatsappTray/Helper.h | 2 + WhatsappTray/Hook.cpp | 428 +++++++++++----------- WhatsappTray/Hook.vcxproj | 2 + WhatsappTray/Hook.vcxproj.filters | 12 +- WhatsappTray/SharedDefines.h | 29 +- WhatsappTray/WhatsAppApi.cpp | 15 +- WhatsappTray/WhatsAppApi.h | 3 +- WhatsappTray/WhatsappTray.cpp | 149 ++++++-- WhatsappTray/WhatsappTray.vcxproj | 7 +- WhatsappTray/WhatsappTray.vcxproj.filters | 6 + WhatsappTray/WinSockClient.cpp | 175 +++++++++ WhatsappTray/WinSockClient.h | 10 + WhatsappTray/WinSockServer.cpp | 233 ++++++++++++ WhatsappTray/WinSockServer.h | 11 + 16 files changed, 840 insertions(+), 275 deletions(-) create mode 100644 WhatsappTray/WinSockClient.cpp create mode 100644 WhatsappTray/WinSockClient.h create mode 100644 WhatsappTray/WinSockServer.cpp create mode 100644 WhatsappTray/WinSockServer.h diff --git a/WhatsappTray.sln b/WhatsappTray.sln index 21bf6b8..05e023f 100644 --- a/WhatsappTray.sln +++ b/WhatsappTray.sln @@ -1,11 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2027 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30503.244 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Hook", "WhatsappTray\Hook.vcxproj", "{9CD8044A-759A-4F65-B9FE-6DD8DD188428}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WhatsappTray", "WhatsappTray\WhatsappTray.vcxproj", "{CDDAA887-EEE9-47FA-B4B5-929A92D71248}" + ProjectSection(ProjectDependencies) = postProject + {9CD8044A-759A-4F65-B9FE-6DD8DD188428} = {9CD8044A-759A-4F65-B9FE-6DD8DD188428} + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/WhatsappTray/Helper.cpp b/WhatsappTray/Helper.cpp index 1f822f4..afb5caf 100644 --- a/WhatsappTray/Helper.cpp +++ b/WhatsappTray/Helper.cpp @@ -27,6 +27,7 @@ #include #include #include +#include /* For .lnk resolver */ #include "shobjidl.h" @@ -340,3 +341,28 @@ std::string Helper::ResolveLnk(HWND hwnd, LPCSTR lpszLinkFile) return lnkPath; } + +/** + * @brief Get the path to the executable for the ProcessID + * + * @param processId The ProcessID from which the path to the executable should be fetched + * @return The path to the executable from the ProcessID +*/ +std::string Helper::GetFilepathFromProcessID(DWORD processId) +{ + HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId); + if (processHandle == NULL) { + Logger::Error(MODULE_NAME "::GetFilepathFromProcessID() - Failed to open process."); + return ""; + } + + wchar_t filepath[MAX_PATH]; + if (GetModuleFileNameExW(processHandle, NULL, filepath, MAX_PATH) == 0) { + CloseHandle(processHandle); + Logger::Error(MODULE_NAME "::GetFilepathFromProcessID() - Failed to get module filepath."); + return ""; + } + CloseHandle(processHandle); + + return Helper::WideToUtf8(filepath); +} \ No newline at end of file diff --git a/WhatsappTray/Helper.h b/WhatsappTray/Helper.h index 95a31d4..c53d130 100644 --- a/WhatsappTray/Helper.h +++ b/WhatsappTray/Helper.h @@ -24,6 +24,7 @@ // Include lib for GetFileVersionInfoSize() #pragma comment(lib,"Version.lib") +#include #include class Helper @@ -48,5 +49,6 @@ class Helper static std::string GetFilenameFromPath(std::string path); static std::wstring GetFilenameFromPath(std::wstring path); static std::string ResolveLnk(HWND hwnd, LPCSTR lpszLinkFile); + static std::string GetFilepathFromProcessID(DWORD processId); }; diff --git a/WhatsappTray/Hook.cpp b/WhatsappTray/Hook.cpp index e3c58c1..098b3b2 100644 --- a/WhatsappTray/Hook.cpp +++ b/WhatsappTray/Hook.cpp @@ -21,11 +21,13 @@ #include "SharedDefines.h" #include "WindowsMessage.h" +#include "WinSockClient.h" #include #include #include #include +#include // OpenProcess() // Problems with OutputDebugString: // Had problems that the messages from OutputDebugString in the hook-functions did not work anymore after some playing arround with old versions, suddenly it worked again. @@ -40,57 +42,91 @@ #undef MODULE_NAME #define MODULE_NAME "WhatsappTrayHook::" -static HHOOK _hWndProc = NULL; -static HHOOK _mouseProc = NULL; -static HHOOK _cbtProc = NULL; - -static DWORD _processIdWhatsapp; -static HWND _hwndWhatsapp; -static WNDPROC _originalWndProc; - -LRESULT APIENTRY EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); -BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam); -LRESULT CALLBACK CallWndRetProc(int nCode, WPARAM wParam, LPARAM lParam); -bool IsTopLevelWhatsApp(HWND hwnd); -std::string GetWindowTitle(HWND hwnd); -bool SendMessageToWhatsappTray(HWND hwnd, UINT message); -void TraceString(std::string traceString); -void TraceStream(std::ostringstream& traceBuffer); +#define DLLIMPORT __declspec(dllexport) + +static DWORD _processID = NULL; +static HWND _whatsAppWindowHandle = NULL; +static WNDPROC _originalWndProc = NULL; + +static HWND _tempWindowHandle = NULL; /* For GetTopLevelWindowhandleWithName() */ +static std::string _searchedWindowTitle; /* For GetTopLevelWindowhandleWithName() */ + +static LRESULT APIENTRY RedirectedWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +// NOTE: extern "C" is important to disable name-mangling, so that the functions can be found with GetProcAddress(), which is used in WhatsappTray.cpp +extern "C" DLLIMPORT LRESULT CALLBACK CallWndRetProc(int nCode, WPARAM wParam, LPARAM lParam); +extern "C" DLLIMPORT LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam); +static HWND GetTopLevelWindowhandleWithName(std::string searchedWindowTitle); +static BOOL CALLBACK FindTopLevelWindowhandleWithNameCallback(HWND hwnd, LPARAM lParam); +static bool WindowhandleIsToplevelWithTitle(HWND hwnd, std::string searchedWindowTitle); +static bool IsTopLevelWhatsApp(HWND hwnd); +static std::string GetWindowTitle(HWND hwnd); +static std::string GetFilepathFromProcessID(DWORD processId); +static std::string WideToUtf8(const std::wstring& inputString); +static std::string GetEnviromentVariable(const std::string& inputString); +static bool SendMessageToWhatsappTray(HWND hwnd, UINT message); +static void TraceString(std::string traceString); +static void TraceStream(std::ostringstream& traceBuffer); + +#define LogString(logString, ...) TraceString(string_format(logString, __VA_ARGS__)) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { - std::ostringstream traceBuffer; - switch (fdwReason) { - case DLL_PROCESS_ATTACH: - - OutputDebugString("Hook.dll ATTACHED!"); + case DLL_PROCESS_ATTACH: { + SocketInit(); // Find the WhatsApp window-handle that we need to replace the window-proc - _processIdWhatsapp = GetCurrentProcessId(); + _processID = GetCurrentProcessId(); - traceBuffer << "_processIdWhatsapp: 0x" << std::uppercase << std::hex << _processIdWhatsapp; - TraceStream(traceBuffer); + LogString("Attached hook.dll to ProcessID: 0x%08X", _processID); - EnumWindows(EnumWindowsCallback, NULL); + auto filepath = GetFilepathFromProcessID(_processID); - traceBuffer << "Attached in window '" << GetWindowTitle(_hwndWhatsapp) << "'" << "_hwndWhatsapp: 0x" << std::uppercase << std::hex << _hwndWhatsapp; - TraceStream(traceBuffer); + /* LoadLibrary()triggers DllMain with DLL_PROCESS_ATTACH. This is used in WhatsappTray.cpp + * to prevent tirggering the wndProc redirect for WhatsappTray we need to detect if this happend. + * the easiest/best way to detect that is by setting a enviroment variable before LoadLibrary() */ + auto envValue = GetEnviromentVariable(WHATSAPPTRAY_LOAD_LIBRARY_TEST_ENV_VAR); + + LogString("Filepath: '%s' " WHATSAPPTRAY_LOAD_LIBRARY_TEST_ENV_VAR ": '%s'", filepath.c_str(), envValue.c_str()); + + if (envValue.compare(WHATSAPPTRAY_LOAD_LIBRARY_TEST_ENV_VAR_VALUE) == 0) { + LogString("Detected that this Attache was triggered by LoadLibrary() => Cancel further processing"); + + // It is best to remove the variable here so we can be sure it is not removed before it was detected. + // Delete enviroment-variable by setting it to "". + _putenv_s(WHATSAPPTRAY_LOAD_LIBRARY_TEST_ENV_VAR, ""); + break; + } + + _whatsAppWindowHandle = GetTopLevelWindowhandleWithName(WHATSAPP_CLIENT_NAME); + auto windowTitle = GetWindowTitle(_whatsAppWindowHandle); + + LogString("Attached in window '%s' _whatsAppWindowHandle: 0x%08X", windowTitle.c_str(), _whatsAppWindowHandle); + + if (_whatsAppWindowHandle == NULL) { + LogString("Error, window-handle for '" WHATSAPP_CLIENT_NAME "' was not found"); + } // Replace the original window-proc with our own. This is called subclassing. // Our window-proc will call after the processing the original window-proc. - _originalWndProc = reinterpret_cast(SetWindowLongPtr(_hwndWhatsapp, GWLP_WNDPROC, (LONG_PTR)EditSubclassProc)); - + _originalWndProc = reinterpret_cast(SetWindowLongPtr(_whatsAppWindowHandle, GWLP_WNDPROC, (LONG_PTR)RedirectedWndProc)); + break; - case DLL_PROCESS_DETACH: - OutputDebugString("Hook.dll DETACHED!"); + } + case DLL_PROCESS_DETACH: { + LogString("Detach hook.dll from ProcessID: 0x%08X",_processID); // Remove our window-proc from the chain by setting the original window-proc. - SetWindowLongPtr(_hwndWhatsapp, GWLP_WNDPROC, (LONG_PTR)_originalWndProc); - break; + if (_originalWndProc != NULL) { + SetWindowLongPtr(_whatsAppWindowHandle, GWLP_WNDPROC, (LONG_PTR)_originalWndProc); + } + + SocketCleanup(); + } break; } - return TRUE; + + return true; } /** @@ -98,7 +134,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) * * This method has the advantage over SetWindowsHookEx(WH_CALLWNDPROC... that here we can skip or modifie messages. */ -LRESULT APIENTRY EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +static LRESULT APIENTRY RedirectedWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { std::ostringstream traceBuffer; @@ -107,7 +143,7 @@ LRESULT APIENTRY EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lP // Dont print WM_GETTEXT so there is not so much "spam" traceBuffer << MODULE_NAME << __func__ << ": " << WindowsMessage::GetString(uMsg) << "(0x" << std::uppercase << std::hex << uMsg << ") "; - traceBuffer << "windowName='" << GetWindowTitle(hwnd) << "' "; + traceBuffer << "windowTitle='" << GetWindowTitle(hwnd) << "' "; traceBuffer << "hwnd='" << std::uppercase << std::hex << hwnd << "' "; traceBuffer << "wParam='" << std::uppercase << std::hex << wParam << "' "; TraceStream(traceBuffer); @@ -117,7 +153,7 @@ LRESULT APIENTRY EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lP if (uMsg == WM_SYSCOMMAND) { // Description for WM_SYSCOMMAND: https://msdn.microsoft.com/de-de/library/windows/desktop/ms646360(v=vs.85).aspx if (wParam == SC_MINIMIZE) { - TraceString(MODULE_NAME "SC_MINIMIZE"); + LogString(MODULE_NAME "::%s: SC_MINIMIZE received", __func__); // Here i check if the windowtitle matches. Vorher hatte ich das Problem das sich Chrome auch minimiert hat. if (IsTopLevelWhatsApp(hwnd)) { @@ -125,10 +161,7 @@ LRESULT APIENTRY EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lP } } } else if (uMsg == WM_NCDESTROY) { - uintptr_t handle1 = reinterpret_cast(hwnd); - uintptr_t whatsappTrayWindowHandle = reinterpret_cast(FindWindow(NAME, NAME)); - traceBuffer << MODULE_NAME << __func__ << ": " << "WM_NCDESTROY whatsappTrayWindowHandle='" << whatsappTrayWindowHandle << "'"; - TraceStream(traceBuffer); + LogString(MODULE_NAME "::%s: WM_NCDESTROY received", __func__); if (IsTopLevelWhatsApp(hwnd)) { auto successfulSent = SendMessageToWhatsappTray(hwnd, WM_WHAHTSAPP_CLOSING); @@ -138,23 +171,21 @@ LRESULT APIENTRY EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lP } } else if (uMsg == WM_CLOSE) { // This happens when alt + f4 is pressed. - traceBuffer << MODULE_NAME << __func__ << ": WM_CLOSE received."; - TraceStream(traceBuffer); + LogString(MODULE_NAME "::%s: WM_CLOSE received", __func__); // Notify WhatsappTray and if it wants to close it can do so... SendMessageToWhatsappTray(hwnd, WM_WHATSAPP_TO_WHATSAPPTRAY_RECEIVED_WM_CLOSE); - traceBuffer << MODULE_NAME << __func__ << ": WM_CLOSE blocked."; + LogString(MODULE_NAME "::%s: WM_CLOSE blocked.", __func__); // Block WM_CLOSE return 0; } else if (uMsg == WM_WHATSAPPTRAY_TO_WHATSAPP_SEND_WM_CLOSE) { // This message is defined by me and should only come from WhatsappTray. // It more or less replaces WM_CLOSE which is now always blocked... // To have a way to still send WM_CLOSE this message was made. - traceBuffer << MODULE_NAME << __func__ << ": WM_WHATSAPPTRAY_TO_WHATSAPP_SEND_WM_CLOSE received."; - TraceStream(traceBuffer); + LogString(MODULE_NAME "::%s: WM_WHATSAPPTRAY_TO_WHATSAPP_SEND_WM_CLOSE received", __func__); - traceBuffer << MODULE_NAME << __func__ << ": Send WM_CLOSE to WhatsApp."; + LogString(MODULE_NAME "::%s: Send WM_CLOSE to WhatsApp.", __func__); // NOTE: lParam/wParam are not used in WM_CLOSE. return CallWindowProc(_originalWndProc, hwnd, WM_CLOSE, 0, 0); } @@ -164,32 +195,8 @@ LRESULT APIENTRY EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lP } /** - * Search for the main whatsapp window + * THIS IS CURRENTLY ONLY A DUMMY. BUT THE SetWindowsHookEx() IS STILL USED TO INJECT THE DLL INTO THE TARGET (WhatsApp) * - * Be carefull, one process can have multiple windows. - * Thats why the window-title also gets checked. - */ -BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) -{ - DWORD processId; - auto result = GetWindowThreadProcessId(hwnd, &processId); - - // Check processId and Title - // Already observerd an instance where the processId alone lead to an false match! - if (_processIdWhatsapp == processId && GetWindowTitle(hwnd).compare(WHATSAPP_CLIENT_NAME) == 0) - { - // Found the matching processId - _hwndWhatsapp = hwnd; - - // Stop enumeration - return false; - } - - // Continue enumeration - return true; -} - -/** * Only works for 32-bit apps or 64-bit apps depending on whether this is complied as 32-bit or 64-bit (Whatsapp is a 64Bit-App) * NOTE: This only caputers messages sent by SendMessage() and NOT PostMessage()! * NOTE: This function also receives messages from child-windows. @@ -201,140 +208,112 @@ BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) */ LRESULT CALLBACK CallWndRetProc(int nCode, WPARAM wParam, LPARAM lParam) { - if (nCode < HC_ACTION) { - return CallNextHookEx(NULL, nCode, wParam, lParam); - } - //CWPSTRUCT* msg = reinterpret_cast(lParam); - return CallNextHookEx(NULL, nCode, wParam, lParam); } -// This function is should not be active in normal distribution-version -// Only for debugging. The call to SetWindowsHookEx(WH_CALLWNDPROC, ... to use this function instead of the "normal" one. -LRESULT CALLBACK CallWndRetProcDebug(int nCode, WPARAM wParam, LPARAM lParam) +/** + * @brief Mouse-hook +*/ +LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) { - //if (nCode >= 0) - { - CWPSTRUCT *msg = (CWPSTRUCT*)lParam; - - //if (msg->hwnd == (HWND)0x00120A42) - if (msg->hwnd == FindWindow(NULL, WHATSAPP_CLIENT_NAME)) { - - if (msg->message == 0x2 || msg->message == 528 || msg->message == 70 || msg->message == 71 || msg->message == 28 || msg->message == 134 || msg->message == 6 || msg->message == 641 || msg->message == 642 || msg->message == 7 || msg->message == 533 || msg->message == 144 || msg->message == 70 || msg->message == 71 || msg->message == 134 || msg->message == 6 || msg->message == 28 || msg->message == 8 || msg->message == 641 || msg->message == 642 || msg->message == 626 || msg->message == 2) { - - // WM_DESTROY - - //std::ostringstream filename2; - //filename2 << "C:/hooktest/HWND_" << msg->hwnd << ".txt"; - - //std::ofstream myfile; - //myfile.open(filename2.str().c_str(), std::ios::app); - - ////LONG wndproc = GetWindowLong(msg->hwnd, -4); + if (nCode >= 0) { + if (wParam == WM_LBUTTONDOWN) { + //auto fileName = string_format("C:/hooktest/HWND_%d.txt", (HWND)wParam); - //myfile << "\nblocked (" << msg->message << ")" << msg->wParam; + //std::ofstream myfile; + //myfile.open(fileName.str().c_str(), std::ios::app); - //MSG msgr; - //PeekMessage(&msgr, msg->hwnd, 0, 0, PM_REMOVE); + LogString(MODULE_NAME "::%s: WM_LBUTTONDOWN received", __func__); - return 0; - } + MOUSEHOOKSTRUCT* mhs = (MOUSEHOOKSTRUCT*)lParam; + RECT rect; + GetWindowRect(mhs->hwnd, &rect); - //if (msg->message == WM_SYSCOMMAND) - { - std::ostringstream filename; - filename << "C:/hooktest/HWND_" << msg->hwnd << ".txt"; + // Modify rect to cover the X(close) button. + rect.left = rect.right - 46; + rect.bottom = rect.top + 35; - std::ofstream myfile; - myfile.open(filename.str().c_str(), std::ios::app); + bool mouseOnClosebutton = PtInRect(&rect, mhs->pt); - myfile << "\nWM_SYSCOMMAND (" << msg->message << ")" << msg->wParam; + if (mouseOnClosebutton) { + //TraceString(MODULE_NAME "Closebutton mousedown"); + //myfile << "\nMinimize"; + //myfile << "\nHWND to Hookwindow:" << FindWindow(NAME, NAME); - if (msg->wParam == 61472) { - myfile << "\nMinimize"; - myfile << "\nHWND to Hookwindow:" << FindWindow(NAME, NAME); + PostMessage(FindWindow(NAME, NAME), WM_ADDTRAY, 0, (LPARAM)mhs->hwnd); - PostMessage(FindWindow(NAME, NAME), WM_ADDTRAY, 0, reinterpret_cast(msg->hwnd)); - } - myfile.close(); + // Returning nozero blocks the message frome being sent to the application. + return 1; } - } - } return CallNextHookEx(NULL, nCode, wParam, lParam); } -LRESULT CALLBACK CBTProc(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam) +/** + * @brief Returns the window-handle for the window with the searched name in the current process + * + * https://stackoverflow.com/questions/31384004/can-several-windows-be-bound-to-the-same-process + * Windows are associated with threads. + * Threads are associated with processes. + * A thread can create as many top-level windows as it likes. + * Therefore you can perfectly well have multiple top-level windows associated with the same thread. + * @return +*/ +static HWND GetTopLevelWindowhandleWithName(std::string searchedWindowTitle) { - if (nCode == HCBT_DESTROYWND) { - std::ostringstream filename; - filename << "C:/hooktest/HWND_" << (HWND)wParam << ".txt"; - - std::ofstream myfile; - myfile.open(filename.str().c_str(), std::ios::app); - - myfile << "\nblocked ("; + _tempWindowHandle = NULL; + _searchedWindowTitle = searchedWindowTitle; + EnumWindows(FindTopLevelWindowhandleWithNameCallback, NULL); - return 1; - } - return CallNextHookEx(_cbtProc, nCode, wParam, lParam); + return _tempWindowHandle; } -LRESULT CALLBACK MouseProc(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam) +/** + * @brief Search the window-handle for the current process + * + * Be carefull, one process can have multiple windows. + * Thats why the window-title also gets checked. + */ +static BOOL CALLBACK FindTopLevelWindowhandleWithNameCallback(HWND hwnd, LPARAM lParam) { - if (nCode >= 0) { - if (wParam == WM_LBUTTONDOWN) { - //std::ostringstream filename2; - //filename2 << "C:/hooktest/HWND_" << (HWND)wParam << ".txt"; - - //std::ofstream myfile; - //myfile.open(filename2.str().c_str(), std::ios::app); - - MOUSEHOOKSTRUCT *mhs = (MOUSEHOOKSTRUCT*)lParam; - - RECT rect; - GetWindowRect(mhs->hwnd, &rect); - - // Modify rect to cover the X(close) button. - rect.left = rect.right - 46; - rect.bottom = rect.top + 35; + DWORD processId; + auto result = GetWindowThreadProcessId(hwnd, &processId); - bool mouseOnClosebutton = PtInRect(&rect, mhs->pt); + // Check processId and Title + // Already observerd an instance where the processId alone lead to an false match! + if (_processID == processId) { + // Found the matching processId - if (mouseOnClosebutton) { - //OutputDebugStringA(MODULE_NAME "Closebutton mousedown"); - //myfile << "\nMinimize"; - //myfile << "\nHWND to Hookwindow:" << FindWindow(NAME, NAME); + if (WindowhandleIsToplevelWithTitle(hwnd, _searchedWindowTitle)) { + // The window is the root-window - PostMessage(FindWindow(NAME, NAME), WM_ADDTRAY, 0, (LPARAM)mhs->hwnd); + _tempWindowHandle = hwnd; - // Returning nozero blocks the message frome being sent to the application. - return 1; - } + // Stop enumeration + return false; } } - return CallNextHookEx(_cbtProc, nCode, wParam, lParam); + // Continue enumeration + return true; } /** - * To verify that we found the correct window: - * 1. Check if its a toplevel-window (below desktop) - * 2. Match the window-name with whatsapp. + * @brief Returns true if the window-handle and the title match */ -bool IsTopLevelWhatsApp(HWND hwnd) +static bool WindowhandleIsToplevelWithTitle(HWND hwnd, std::string searchedWindowTitle) { if (hwnd != GetAncestor(hwnd, GA_ROOT)) { - TraceString(MODULE_NAME "IsTopLevelWhatsApp: Window is not a toplevel-window."); + TraceString(MODULE_NAME "WindowhandleIsToplevelWithTitle: Window is not a toplevel-window."); return false; } - auto windowName = GetWindowTitle(hwnd); - if (windowName.compare(WHATSAPP_CLIENT_NAME) != 0) { - TraceString(std::string(MODULE_NAME "IsTopLevelWhatsApp: windowName='") + windowName + "' does not match '" WHATSAPP_CLIENT_NAME "'." + windowName); + auto windowTitle = GetWindowTitle(hwnd); + if (windowTitle.compare(searchedWindowTitle) != 0) { + TraceString(std::string(MODULE_NAME "WindowhandleIsToplevelWithTitle: windowTitle='") + windowTitle + "' does not match '" WHATSAPP_CLIENT_NAME "'." + windowTitle); return false; } @@ -342,9 +321,19 @@ bool IsTopLevelWhatsApp(HWND hwnd) } /** - * Gets the text of a window. + * To verify that we found the correct window: + * 1. Check if its a toplevel-window (below desktop) + * 2. Match the window-name with whatsapp. + */ +static bool IsTopLevelWhatsApp(HWND hwnd) +{ + return WindowhandleIsToplevelWithTitle(hwnd, WHATSAPP_CLIENT_NAME); +} + +/** + * @brief Gets the text of a window. */ -std::string GetWindowTitle(HWND hwnd) +static std::string GetWindowTitle(HWND hwnd) { char windowNameBuffer[2000]; GetWindowTextA(hwnd, windowNameBuffer, sizeof(windowNameBuffer)); @@ -353,69 +342,78 @@ std::string GetWindowTitle(HWND hwnd) } /** - * Send message to WhatsappTray. - */ -bool SendMessageToWhatsappTray(HWND hwnd, UINT message) + * @brief Get the path to the executable for the ProcessID + * + * @param processId The ProcessID from which the path to the executable should be fetched + * @return The path to the executable from the ProcessID +*/ +static std::string GetFilepathFromProcessID(DWORD processId) { - return PostMessage(FindWindow(NAME, NAME), message, 0, reinterpret_cast(hwnd)); + HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId); + if (processHandle == NULL) { + return ""; + } + + wchar_t filepath[MAX_PATH]; + if (GetModuleFileNameExW(processHandle, NULL, filepath, MAX_PATH) == 0) { + CloseHandle(processHandle); + return ""; + } + CloseHandle(processHandle); + + return WideToUtf8(filepath); } -void TraceString(std::string traceString) +static std::string WideToUtf8(const std::wstring& inputString) { -#ifdef _DEBUG - OutputDebugStringA(traceString.c_str()); -#endif + int size_needed = WideCharToMultiByte(CP_UTF8, 0, inputString.c_str(), (int)inputString.size(), NULL, 0, NULL, NULL); + std::string strTo(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, inputString.c_str(), (int)inputString.size(), &strTo[0], size_needed, NULL, NULL); + return strTo; } -void TraceStream(std::ostringstream& traceBuffer) +static std::string GetEnviromentVariable(const std::string& inputString) { -#ifdef _DEBUG - OutputDebugStringA(traceBuffer.str().c_str()); - traceBuffer.clear(); - traceBuffer.str(std::string()); -#endif + size_t requiredSize; + const size_t varbufferSize = 2000; + char varbuffer[varbufferSize]; + + getenv_s( + &requiredSize, + varbuffer, + varbufferSize, + inputString.c_str() + ); + + return varbuffer; } /** - * Registers the hook. - * - * @param hLib - * @param threadId If threadId 0 is passed, it is a global Hook. - * @param closeToTray - * @return + * @brief Send message to WhatsappTray. */ -BOOL DLLIMPORT RegisterHook(HMODULE hLib, DWORD threadId, bool closeToTray) +static bool SendMessageToWhatsappTray(HWND hwnd, UINT message) { - if (!closeToTray) { - _hWndProc = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)CallWndRetProc, hLib, threadId); - if (_hWndProc == NULL) { - OutputDebugStringA(MODULE_NAME "RegisterHook() - Error Creation Hook _hWndProc\n"); - UnRegisterHook(); - return FALSE; - } - } + return PostMessage(FindWindow(NAME, NAME), message, 0, reinterpret_cast(hwnd)); +} - if (closeToTray) { - _mouseProc = SetWindowsHookEx(WH_MOUSE, (HOOKPROC)MouseProc, hLib, threadId); - if (_mouseProc == NULL) { - OutputDebugStringA(MODULE_NAME "RegisterHook() - Error Creation Hook _hWndProc\n"); - UnRegisterHook(); - return FALSE; - } - } +static void TraceString(std::string traceString) +{ + SocketSendMessage(LOGGER_IP, LOGGER_PORT, traceString.c_str()); - return TRUE; +//#ifdef _DEBUG +// OutputDebugStringA(traceString.c_str()); +//#endif } -void DLLIMPORT UnRegisterHook() +static void TraceStream(std::ostringstream& traceBuffer) { - if (_hWndProc) { - UnhookWindowsHookEx(_hWndProc); - _hWndProc = NULL; - } - if (_mouseProc) { - UnhookWindowsHookEx(_mouseProc); - _mouseProc = NULL; - } -} + SocketSendMessage(LOGGER_IP, LOGGER_PORT, traceBuffer.str().c_str()); + traceBuffer.clear(); + traceBuffer.str(std::string()); +//#ifdef _DEBUG +// OutputDebugStringA(traceBuffer.str().c_str()); +// traceBuffer.clear(); +// traceBuffer.str(std::string()); +//#endif +} diff --git a/WhatsappTray/Hook.vcxproj b/WhatsappTray/Hook.vcxproj index ce85f77..e8acb8c 100644 --- a/WhatsappTray/Hook.vcxproj +++ b/WhatsappTray/Hook.vcxproj @@ -108,10 +108,12 @@ + + diff --git a/WhatsappTray/Hook.vcxproj.filters b/WhatsappTray/Hook.vcxproj.filters index 9b29d6e..409c8f7 100644 --- a/WhatsappTray/Hook.vcxproj.filters +++ b/WhatsappTray/Hook.vcxproj.filters @@ -1,19 +1,25 @@  - + {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd - Header Files + Files + + + Files - Header Files + Files + + + Files \ No newline at end of file diff --git a/WhatsappTray/SharedDefines.h b/WhatsappTray/SharedDefines.h index ccb914a..cf20e14 100644 --- a/WhatsappTray/SharedDefines.h +++ b/WhatsappTray/SharedDefines.h @@ -23,6 +23,13 @@ #define NAME TEXT("WhatsappTray") #define WHATSAPP_CLIENT_NAME TEXT("WhatsApp") +#define WHATSAPPTRAY_LOAD_LIBRARY_TEST_ENV_VAR "WhatsappTrayLoadLibraryTest" /* The enviroment-variable used to test if the hook.dll was triggerd by WhatsappTray's LoadLibrary() */ +#define WHATSAPPTRAY_LOAD_LIBRARY_TEST_ENV_VAR_VALUE "TRUE" /* The value of the enviroment-variable used to test if the hook.dll was triggerd by WhatsappTray's LoadLibrary() */ + +#define LOGGER_IP "127.0.0.1" +// What port to use: https://stackoverflow.com/a/53667220/4870255 +// Ports 49152 - 65535 - Free to use these in client programs +#define LOGGER_PORT "52677" #define WM_ADDTRAY 0x0401 #define WM_TRAYCMD 0x0404 @@ -39,9 +46,21 @@ #define IDM_SETTING_START_MINIMIZED 0x1007 #define IDM_SETTING_SHOW_UNREAD_MESSAGES 0x1008 -#define DLLIMPORT __declspec(dllexport) - -#include +#include +#include +#include -BOOL DLLIMPORT RegisterHook(HMODULE hLib, DWORD threadId, bool closeToTray); -void DLLIMPORT UnRegisterHook(); +/** + * @brief + * + * https://stackoverflow.com/a/26221725/4870255 + */ +template +std::string string_format(const std::string& format, Args ... args) +{ + size_t size = snprintf(nullptr, 0, format.c_str(), args ...) + 1; // Extra space for '\0' + if (size <= 0) { throw std::runtime_error("Error during formatting."); } + std::unique_ptr buf(new char[size]); + snprintf(buf.get(), size, format.c_str(), args ...); + return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside +} \ No newline at end of file diff --git a/WhatsappTray/WhatsAppApi.cpp b/WhatsappTray/WhatsAppApi.cpp index 238f4cf..9b6f471 100644 --- a/WhatsappTray/WhatsAppApi.cpp +++ b/WhatsappTray/WhatsAppApi.cpp @@ -39,12 +39,18 @@ namespace fs = std::experimental::filesystem; #undef MODULE_NAME #define MODULE_NAME "WhatsAppApi::" +/* Public */ +bool WhatsAppApi::IsFullInit = false; + +/* Private */ std::unique_ptr WhatsAppApi::dirWatcher = std::unique_ptr(nullptr); std::function WhatsAppApi::receivedMessageEvent = NULL; std::function WhatsAppApi::receivedFullInitEvent = NULL; -/// Initialize the class. +/** + * @brief Initialize the class. +*/ void WhatsAppApi::Init() { std::wstring leveldbDirectory = Helper::Utf8ToWide(std::string(AppData::WhatsappRoamingDirectoryGet())); @@ -167,6 +173,7 @@ void WhatsAppApi::IndexedDbChanged(const DWORD dwAction, std::wstring strFilenam if (std::regex_search(lineBuffer.c_str(), std::regex("models:mute:cache"))) { Logger::Debug(MODULE_NAME "IndexedDbChanged() - Found match for fullInit."); + IsFullInit = true; if (receivedFullInitEvent) { receivedFullInitEvent(); } @@ -180,9 +187,9 @@ void WhatsAppApi::IndexedDbChanged(const DWORD dwAction, std::wstring strFilenam // Match: recv: [0-9a-f]{16}[.]--[0-9a-f]+\"\ttimestampN if (std::regex_search(lineBuffer.c_str(), std::regex("recv: [0-9a-f]{16}[.]--[0-9a-f]+\"\\ttimestampN"))) { - Logger::Info(MODULE_NAME "IndexedDbChanged() - Found match for receivedMessage."); - - if (receivedMessageEvent) { + //Logger::Info(MODULE_NAME "IndexedDbChanged() - Found match for receivedMessage."); + + if (WhatsAppApi::IsFullInit && receivedMessageEvent) { receivedMessageEvent(); } } diff --git a/WhatsappTray/WhatsAppApi.h b/WhatsappTray/WhatsAppApi.h index 10148ee..48c1298 100644 --- a/WhatsappTray/WhatsAppApi.h +++ b/WhatsappTray/WhatsAppApi.h @@ -28,6 +28,7 @@ class DirectoryWatcher; class WhatsAppApi { public: + static bool IsFullInit; static void WhatsAppApi::NotifyOnNewMessage(const std::function& newMessageHandler); static void WhatsAppApi::NotifyOnFullInit(const std::function& newMessageHandler); static void Init(); @@ -35,8 +36,8 @@ class WhatsAppApi WhatsAppApi() { } ~WhatsAppApi() { } static std::unique_ptr dirWatcher; - static void IndexedDbChanged(const DWORD dwAction, std::wstring strFilename); static std::function receivedMessageEvent; static std::function receivedFullInitEvent; + static void IndexedDbChanged(const DWORD dwAction, std::wstring strFilename); }; diff --git a/WhatsappTray/WhatsappTray.cpp b/WhatsappTray/WhatsappTray.cpp index fc8ca19..75f79ce 100644 --- a/WhatsappTray/WhatsappTray.cpp +++ b/WhatsappTray/WhatsappTray.cpp @@ -29,11 +29,12 @@ #include "WhatsAppApi.h" #include "TrayManager.h" #include "AboutDialog.h" +#include "WinSockServer.h" #include "Helper.h" #include "Logger.h" +#include #include -#include #include namespace fs = std::experimental::filesystem; @@ -47,6 +48,11 @@ constexpr auto CompileConfiguration = "Release"; #undef MODULE_NAME #define MODULE_NAME "WhatsappTray" +static char moduleName[] = MODULE_NAME; + +static HHOOK _hWndProc = NULL; +static HHOOK _mouseProc = NULL; + static HINSTANCE _hInstance; static HMODULE _hLib; static HWND _hwndWhatsappTray; @@ -55,15 +61,20 @@ static HWND _hwndWhatsapp; static int messagesSinceMinimize = 0; +static char loggerPort[] = LOGGER_PORT; +static std::thread winsockThread; + static std::unique_ptr trayManager; -LRESULT CALLBACK WhatsAppTrayWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); -HWND FindWhatsapp(); -HWND StartWhatsapp(); -void TryClosePreviousWhatsappTrayInstance(); -bool CreateWhatsappTrayWindow(); -bool SetHook(); -void SetLaunchOnWindowsStartupSetting(bool value); +static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); +static HWND FindWhatsapp(); +static HWND StartWhatsapp(); +static void TryClosePreviousWhatsappTrayInstance(); +static bool CreateWhatsappTrayWindow(); +static bool SetHook(); +static void SetLaunchOnWindowsStartupSetting(bool value); +static bool RegisterHook(HMODULE hLib, DWORD threadId, bool closeToTray); +static void UnRegisterHook(); int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd) { @@ -74,7 +85,14 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _ // For reading data from *.lnk files (shortcut files). See Helper::ResolveLnk() CoInitialize(nullptr); - Logger::Info(MODULE_NAME "::WinMain() - Starting WhatsappTray %s in %s CompileConfiguration.", Helper::GetProductAndVersion().c_str(), CompileConfiguration); + Logger::Info(MODULE_NAME "::WinMain(): Starting WhatsappTray %s in %s CompileConfiguration.", Helper::GetProductAndVersion().c_str(), CompileConfiguration); + + // Initialize WinSock-server, which is used to send log-messages from WhatsApp-hook to WhatsappTray + SocketNotifyOnNewMessage([](std::string message) { + Logger::Info("Hook> %s", message.c_str()); + }); + + winsockThread = std::thread(SocketStart, loggerPort); WhatsAppApi::Init(); @@ -116,12 +134,16 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _ }); } + /* LoadLibrary()triggers DllMain with DLL_PROCESS_ATTACH. This is used in WhatsappTray.cpp + * to prevent tirggering the wndProc redirect for WhatsappTray we need to detect if this happend. + * the easiest/best way to detect that is by setting a enviroment variable before LoadLibrary() */ + _putenv_s(WHATSAPPTRAY_LOAD_LIBRARY_TEST_ENV_VAR, WHATSAPPTRAY_LOAD_LIBRARY_TEST_ENV_VAR_VALUE); if (!(_hLib = LoadLibrary("Hook.dll"))) { Logger::Error(MODULE_NAME "::WinMain() - Error loading Hook.dll."); MessageBox(NULL, "Error loading Hook.dll.", "WhatsappTray", MB_OK | MB_ICONERROR); return 0; } - + if (StartWhatsapp() == nullptr) { MessageBoxA(NULL, (std::string("Error launching WhatsApp. Examine the logs for details")).c_str(), "WhatsappTray", MB_OK); return 0; @@ -176,7 +198,7 @@ HWND StartWhatsapp() if (waStartPathStringExtension.compare("lnk") == 0) { waStartPathString = Helper::ResolveLnk(_hwndWhatsappTray, waStartPathString.c_str()); - Logger::Info(MODULE_NAME "::StartWhatsapp() - Resolved .lnk (Shortcut) to :'" + waStartPathString + "'"); + Logger::Info(MODULE_NAME "::StartWhatsapp() - Resolved .lnk (Shortcut) to:'" + waStartPathString + "'"); } STARTUPINFO si; @@ -254,23 +276,11 @@ HWND FindWhatsapp() DWORD processId; DWORD threadId = GetWindowThreadProcessId(currentWindow, &processId); - HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId); - if (processHandle == NULL) { - Logger::Error(MODULE_NAME "::FindWhatsapp() - Failed to open process."); - continue; - } + auto filepath = Helper::GetFilepathFromProcessID(processId); - wchar_t filepath[MAX_PATH]; - if (GetModuleFileNameExW(processHandle, NULL, filepath, MAX_PATH) == 0) { - CloseHandle(processHandle); - Logger::Error(MODULE_NAME "::FindWhatsapp() - Failed to get module filepath."); - continue; - } - CloseHandle(processHandle); + Logger::Info(MODULE_NAME "::FindWhatsapp() - Filepath is: '%s'", filepath.c_str()); - Logger::Info(MODULE_NAME "::FindWhatsapp() - Filepath is: '%s'", Helper::WideToUtf8(filepath).c_str()); - - std::wstring filenameFromWindow = Helper::GetFilenameFromPath(filepath); + std::wstring filenameFromWindow = Helper::GetFilenameFromPath(Helper::Utf8ToWide(filepath)); std::wstring whatsappStartpathWide = Helper::Utf8ToWide(AppData::WhatsappStartpathGet()); std::wstring filenameFromSettings = Helper::GetFilenameFromPath(whatsappStartpathWide); @@ -319,7 +329,7 @@ bool CreateWhatsappTrayWindow() { WNDCLASS wc; wc.style = 0; - wc.lpfnWndProc = WhatsAppTrayWndProc; + wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = _hInstance; @@ -396,9 +406,9 @@ void ExecuteMenu() DestroyMenu(hMenu); } -LRESULT CALLBACK WhatsAppTrayWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { - Logger::Info(MODULE_NAME "::WhatsAppTrayWndProc() - Message Received msg='0x%X'", msg); + //Logger::Info(MODULE_NAME "::WndProc() - Message Received msg='0x%X'", msg); switch (msg) { case WM_COMMAND: @@ -406,15 +416,13 @@ LRESULT CALLBACK WhatsAppTrayWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM case IDM_ABOUT: AboutDialog::Create(_hInstance, _hwndWhatsappTray); break; - case IDM_SETTING_CLOSE_TO_TRAY: - { + case IDM_SETTING_CLOSE_TO_TRAY: { // Toggle the 'close to tray'-feature. // We have to first change the value and then unregister and register to set the ne value in the hook. AppData::CloseToTray.Set(!AppData::CloseToTray.Get()); SendMessage(_hwndWhatsappTray, WM_REAPPLY_HOOK, 0, 0); - break; - } + } break; case IDM_SETTING_LAUNCH_ON_WINDOWS_STARTUP: // Toggle the 'launch on windows startup'-feature. SetLaunchOnWindowsStartupSetting(!AppData::LaunchOnWindowsStartup.Get()); @@ -428,7 +436,7 @@ LRESULT CALLBACK WhatsAppTrayWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM AppData::ShowUnreadMessages.Set(!AppData::ShowUnreadMessages.Get()); break; case IDM_RESTORE: - Logger::Info(MODULE_NAME "::WhatsAppTrayWndProc() - IDM_RESTORE"); + Logger::Info(MODULE_NAME "::WndProc() - IDM_RESTORE"); trayManager->RestoreWindowFromTray(_hwndForMenu); break; case IDM_CLOSE: @@ -445,7 +453,7 @@ LRESULT CALLBACK WhatsAppTrayWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM SetHook(); break; case WM_ADDTRAY: - Logger::Info(MODULE_NAME "::WhatsAppTrayWndProc() - WM_ADDTRAY"); + Logger::Info(MODULE_NAME "::WndProc() - WM_ADDTRAY"); messagesSinceMinimize = 0; trayManager->MinimizeWindowToTray(_hwndWhatsapp); break; @@ -468,7 +476,7 @@ LRESULT CALLBACK WhatsAppTrayWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM case WM_WHAHTSAPP_CLOSING: // If Whatsapp is closing we want to close WhatsappTray as well. - Logger::Info(MODULE_NAME "::WhatsAppTrayWndProc() - WM_WHAHTSAPP_CLOSING"); + Logger::Info(MODULE_NAME "::WndProc() - WM_WHAHTSAPP_CLOSING"); DestroyWindow(_hwndWhatsappTray); case WM_WHATSAPP_TO_WHATSAPPTRAY_RECEIVED_WM_CLOSE: Logger::Info(MODULE_NAME "::" + std::string(__func__) + "() - WM_WHATSAPP_TO_WHATSAPPTRAY_RECEIVED_WM_CLOSE received"); @@ -484,24 +492,29 @@ LRESULT CALLBACK WhatsAppTrayWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM // NOTE: WM_WHATSAPPTRAY_TO_WHATSAPP_SEND_WM_CLOSE is a special message i made because WM_CLOSE is always blocked by the hook. PostMessage(_hwndWhatsapp, WM_WHATSAPPTRAY_TO_WHATSAPP_SEND_WM_CLOSE, 0, 0); - Logger::Info(MODULE_NAME "::WhatsAppTrayWndProc() - WM_WHATSAPPTRAY_TO_WHATSAPP_SEND_WM_CLOSE sent"); + Logger::Info(MODULE_NAME "::WndProc() - WM_WHATSAPPTRAY_TO_WHATSAPP_SEND_WM_CLOSE sent"); } break; case WM_DESTROY: - Logger::Info(MODULE_NAME "::WhatsAppTrayWndProc() - WM_DESTROY"); + Logger::Info(MODULE_NAME "::WndProc() - WM_DESTROY"); trayManager->RestoreAllWindowsFromTray(); trayManager->RemoveTrayIcon(_hwndWhatsapp); UnRegisterHook(); FreeLibrary(_hLib); + + // Stop winsock-server and wait for it to cleanup and finish + SocketStopServer(); + winsockThread.join(); + PostQuitMessage(0); - Logger::Info(MODULE_NAME "::WhatsAppTrayWndProc() - QuitMessage posted."); + Logger::Info(MODULE_NAME "::WndProc() - QuitMessage posted."); break; case WM_WHATSAPP_API_NEW_MESSAGE: - Logger::Info(MODULE_NAME "::WhatsAppTrayWndProc() - WM_WHATSAPP_API_NEW_MESSAGE"); + Logger::Info(MODULE_NAME "::WndProc() - WM_WHATSAPP_API_NEW_MESSAGE"); messagesSinceMinimize++; if (AppData::ShowUnreadMessages.Get()) { @@ -548,3 +561,59 @@ void SetLaunchOnWindowsStartupSetting(bool value) Registry::UnregisterProgram(); } } + +/** + * Registers the hook. + * + * @param hLib + * @param threadId If threadId 0 is passed, it is a global Hook. + * @param closeToTray + * @return + */ +bool RegisterHook(HMODULE hLib, DWORD threadId, bool closeToTray) +{ + // NOTE: To see if the functions are visible use 'dumpbin /EXPORTS \Hook.dll' in the debugger-console + auto CallWndRetProc = (HOOKPROC)GetProcAddress(_hLib, "CallWndRetProc"); + if (CallWndRetProc == NULL) { + Logger::Error("The function 'CallWndRetProc' was not found.\n"); + return false; + } + + auto MouseProc = (HOOKPROC)GetProcAddress(_hLib, "MouseProc"); + if (MouseProc == NULL) { + Logger::Error("The function 'MouseProc' was not found.\n"); + return false; + } + + if (!closeToTray) { + _hWndProc = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)CallWndRetProc, hLib, threadId); + if (_hWndProc == NULL) { + Logger::Error(MODULE_NAME "RegisterHook() - Error Creation Hook _hWndProc\n"); + UnRegisterHook(); + return false; + } + } + + if (closeToTray) { + _mouseProc = SetWindowsHookEx(WH_MOUSE, (HOOKPROC)MouseProc, hLib, threadId); + if (_mouseProc == NULL) { + Logger::Error(MODULE_NAME "RegisterHook() - Error Creation Hook _hWndProc\n"); + UnRegisterHook(); + return false; + } + } + + return true; +} + +void UnRegisterHook() +{ + if (_hWndProc) { + UnhookWindowsHookEx(_hWndProc); + _hWndProc = NULL; + } + if (_mouseProc) { + UnhookWindowsHookEx(_mouseProc); + _mouseProc = NULL; + } +} diff --git a/WhatsappTray/WhatsappTray.vcxproj b/WhatsappTray/WhatsappTray.vcxproj index 7084d2e..7ecb893 100644 --- a/WhatsappTray/WhatsappTray.vcxproj +++ b/WhatsappTray/WhatsappTray.vcxproj @@ -137,6 +137,7 @@ + @@ -155,6 +156,7 @@ + @@ -163,11 +165,6 @@ - - - {9cd8044a-759a-4f65-b9fe-6dd8dd188428} - - diff --git a/WhatsappTray/WhatsappTray.vcxproj.filters b/WhatsappTray/WhatsappTray.vcxproj.filters index c9b555e..8376088 100644 --- a/WhatsappTray/WhatsappTray.vcxproj.filters +++ b/WhatsappTray/WhatsappTray.vcxproj.filters @@ -74,6 +74,9 @@ Files\Helper + + Files\Logging + @@ -112,6 +115,9 @@ Files + + Files\Logging + diff --git a/WhatsappTray/WinSockClient.cpp b/WhatsappTray/WinSockClient.cpp new file mode 100644 index 0000000..6d7dd24 --- /dev/null +++ b/WhatsappTray/WinSockClient.cpp @@ -0,0 +1,175 @@ +// WinSockClient.cpp : Defines the entry point for the application. +// + +#include "WinSockClient.h" + +#include +#include + +#pragma comment(lib, "ws2_32.lib") + +#undef MODULE_NAME +#define MODULE_NAME "ClientSocket" + +// NOTE: For debugging OutputDebugStringA could be used... +#define LogDebug(message, ...) //printf(MODULE_NAME "::" __FUNCTION__ " - " message "\n", __VA_ARGS__) + +static bool SetupSocket(); +static bool CreateServerAddress(SOCKET clientSocket, const char ipString[], const char portString[], sockaddr_in& ServerAddress); +static int WaitForNewMesaageWithTimeout(FD_SET& readSet, long sec, long usec); + +static SOCKET clientSocket = INVALID_SOCKET; + +bool SocketInit() +{ + // Initialize Winsock + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2, 2), &wsaData); + + if (NO_ERROR != nResult) { + LogDebug("Error occurred while executing WSAStartup()."); + return false; + } + + LogDebug("WSAStartup() successful."); + + return true; +} + +void SocketCleanup() +{ + if (clientSocket != INVALID_SOCKET) { + closesocket(clientSocket); + } + + // Cleanup Winsock + WSACleanup(); +} + +bool SocketSendMessage(const char ipString[], const char portString[], const char message[]) +{ + if (SetupSocket() == false) { + LogDebug("Error occurred while SetupSocket()"); + closesocket(clientSocket); + } + + sockaddr_in ServerAddress; + if (CreateServerAddress(clientSocket, ipString, portString, ServerAddress) == false) { + return false; + } + + // Establish connection with the server + if (SOCKET_ERROR == connect(clientSocket, reinterpret_cast(&ServerAddress), sizeof(ServerAddress))) { + auto error = WSAGetLastError(); + LogDebug("Error occurred while connecting: %ld.", error); + } + LogDebug("connect() successful."); + + // Send the message to the server + int nBytesSent = send(clientSocket, message, (int)strlen(message), 0); + + if (SOCKET_ERROR == nBytesSent) { + LogDebug("Error occurred while writing to socket."); + return false; + } + LogDebug("send() successful."); + + FD_SET readSet; + // NOTE: selRet has the cout of sockets that are ready + auto selRet = WaitForNewMesaageWithTimeout(readSet, 10, 0); + if (selRet == SOCKET_ERROR) { + LogDebug("Error occurred while using select() on socket."); + return false; + } + else if (selRet == 0) { + LogDebug("Waiting for message timeout."); + return false; + } + + if (FD_ISSET(clientSocket, &readSet)) { + // Server sent message to client + + // Init szBuffer with 0 + const int bufferSize = 256; + char szBuffer[bufferSize]; + ZeroMemory(szBuffer, bufferSize); + + // Get the message from the server + // WARNING: MSG_WAITALL only because in this case the server will close the connection, wich will also stop the waiting. + int bytesRecv = recv(clientSocket, szBuffer, bufferSize - 1, MSG_WAITALL); + + if (SOCKET_ERROR == bytesRecv) { + LogDebug("Error occurred while reading from socket."); + return false; + } + LogDebug("recv() successful."); + + LogDebug("Server message: '%s'", szBuffer); + } + + closesocket(clientSocket); + + return true; +} + +/** + * @brief Create a socket + * + * @return True if socket was sucessfully created. +*/ +static bool SetupSocket() +{ + clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (INVALID_SOCKET == clientSocket) { + LogDebug("Error occurred while opening socket: %ld.", WSAGetLastError()); + return false; + } + LogDebug("socket() successful."); + + return true; +} + +static bool CreateServerAddress(SOCKET clientSocket, const char ipString[], const char portString[], sockaddr_in& serverAddress) +{ + // Server name will be supplied as a commandline argument + // Get the server details + struct hostent* server = gethostbyname(ipString); + + if (server == NULL) { + LogDebug("Error occurred no such host."); + return false; + } + + LogDebug("gethostbyname() successful."); + + // Port number will be supplied as a commandline argument + int portNumber = atoi(portString); + + // Cleanup and Init the serverAddress with 0 + ZeroMemory((char*)&serverAddress, sizeof(serverAddress)); + + serverAddress.sin_family = AF_INET; + + // Assign the information received from gethostbyname() + CopyMemory((char*)&serverAddress.sin_addr.s_addr, (char*)server->h_addr, server->h_length); + + serverAddress.sin_port = htons(portNumber); //comes from commandline + + return true; +} + +// Wait with timeout for new message. +static int WaitForNewMesaageWithTimeout(FD_SET& readSet, long sec, long usec) +{ + FD_ZERO(&readSet); + FD_SET(clientSocket, &readSet); + timeval timeoutTime; + timeoutTime.tv_sec = sec; + timeoutTime.tv_usec = usec; + + // NOTE: selRet has the cout of sockets that are ready + auto selRet = select(NULL, &readSet, NULL, NULL, &timeoutTime); + + return selRet; +} diff --git a/WhatsappTray/WinSockClient.h b/WhatsappTray/WinSockClient.h new file mode 100644 index 0000000..96310e8 --- /dev/null +++ b/WhatsappTray/WinSockClient.h @@ -0,0 +1,10 @@ +// WinSockClient.h : Include file for standard system include files, +// or project specific include files. + +#pragma once + +#include + +bool SocketInit(); +void SocketCleanup(); +bool SocketSendMessage(const char ipString[], const char portString[], const char message[]); diff --git a/WhatsappTray/WinSockServer.cpp b/WhatsappTray/WinSockServer.cpp new file mode 100644 index 0000000..76bf1a0 --- /dev/null +++ b/WhatsappTray/WinSockServer.cpp @@ -0,0 +1,233 @@ +// WinSockServer.cpp : Defines the entry point for the application. +// + +#include "stdafx.h" +#include "WinSockServer.h" + +#include +//#include +#include +#include "Logger.h" + +#pragma comment(lib, "ws2_32.lib") + +#undef MODULE_NAME +#define MODULE_NAME "ServerSocket" + +#define LogDebug(message, ...) //Logger::Info(MODULE_NAME "::" __FUNCTION__ " - " message "\n", __VA_ARGS__) + +static bool SocketDoProcessing(char portString[]); +static bool SetupSocket(char portString[]); +static bool ProcessClientSocket(SOCKET remoteSocket, std::string& messageFromClient); +static int WaitForNewMesaageWithTimeout(SOCKET clientSocket, FD_SET& readSet, long sec, long usec); + +static std::atomic isRunning = false; +static SOCKET listenSocket = INVALID_SOCKET; +static std::function socketMessageReceivedEvent = NULL; + +/** + * @brief Initialize socket + * + * @param portString The port the socket is running on +*/ +bool SocketStart(char portString[]) +{ + isRunning = true; + + // Initialize Winsock + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2, 2), &wsaData); + + if (NO_ERROR != nResult) { + LogDebug("Error occurred while executing WSAStartup()."); + return false; + } + + LogDebug("WSAStartup() successful."); + + SocketDoProcessing(portString); + + // Cleanup Winsock + WSACleanup(); + return true; +} + +static bool SocketDoProcessing(char portString[]) +{ + bool success = true; + + if (SetupSocket(portString) == false) { + return false; + } + + if (SOCKET_ERROR == listen(listenSocket, SOMAXCONN)) { + closesocket(listenSocket); + + LogDebug("Error occurred while listening."); + return false; + } + + LogDebug("Listen to port successful."); + + struct sockaddr_in clientAddress; + int clientSize = sizeof(clientAddress); + while (true) { + + LogDebug("Wait for a client to connect."); + SOCKET clientSocket = accept(listenSocket, (struct sockaddr*)&clientAddress, &clientSize); + + if (INVALID_SOCKET == clientSocket) { + if (isRunning == false) { + LogDebug("Listening on socket for new connection stopped."); + break; + } + + auto errorNo = WSAGetLastError(); + LogDebug("Error occurred while accepting socket: %ld.", errorNo); + + success = false; + break; + } + else { + LogDebug("Accept() successful."); + } + + LogDebug("Client connected from: %s", inet_ntoa(clientAddress.sin_addr)); + + std::string messageFromClient; + auto clientProcessingSuccessful = ProcessClientSocket(clientSocket, messageFromClient); + } + + if (INVALID_SOCKET != listenSocket) { + closesocket(listenSocket); + } + return success; +} + +void SocketNotifyOnNewMessage(const std::function& messageReceivedEvent) +{ + socketMessageReceivedEvent = messageReceivedEvent; +} + +void SocketStopServer() +{ + isRunning = false; + closesocket(listenSocket); + listenSocket = INVALID_SOCKET; +} + +/** + * @brief Create a socket +*/ +static bool SetupSocket(char portString[]) +{ + SOCKET listenSocketTemp = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (INVALID_SOCKET == listenSocketTemp) { + LogDebug("Error occurred while opening socket: %ld.", WSAGetLastError()); + return false; + } + + LogDebug("socket() successful."); + + struct sockaddr_in ServerAddress; + // Cleanup and Init with 0 the ServerAddress + ZeroMemory((char*)&ServerAddress, sizeof(ServerAddress)); + + // Port number will be supplied as a commandline argument + int nPortNo = atoi(portString); + + // Fill up the address structure + ServerAddress.sin_family = AF_INET; + ServerAddress.sin_addr.s_addr = INADDR_ANY; // WinSock will supply address + ServerAddress.sin_port = htons(nPortNo); //comes from commandline + + // Assign local address and port number + if (SOCKET_ERROR == bind(listenSocketTemp, (struct sockaddr*)&ServerAddress, sizeof(ServerAddress))) { + closesocket(listenSocketTemp); + + LogDebug("Error occurred while binding."); + return false; + } + else { + LogDebug("bind() successful."); + } + + listenSocket = listenSocketTemp; + + return true; +} + +static bool ProcessClientSocket(SOCKET clientSocket, std::string& messageFromClient) +{ + FD_SET readSet; + + auto selRet = WaitForNewMesaageWithTimeout(clientSocket, readSet, 10, 0); + if (selRet == SOCKET_ERROR) { + LogDebug("Error occurred while using select() on socket."); + return false; + } + else if (selRet == 0) { + LogDebug("Waiting for message timeout."); + return false; + } + + if (FD_ISSET(clientSocket, &readSet)) { + // Init messageBuffer with 0 + const int messageBufferSize = 2000; + char messageBuffer[messageBufferSize]; + ZeroMemory(messageBuffer, messageBufferSize); + + // Receive data from a connected or bound socket + int nBytesRecv = recv(clientSocket, messageBuffer, messageBufferSize - 1, 0); + + if (SOCKET_ERROR == nBytesRecv) { + LogDebug("Error occurred while receiving from socket."); + + closesocket(clientSocket); + return false; + } + LogDebug("recv() successful."); + + messageFromClient = std::string(messageBuffer); + + if (socketMessageReceivedEvent) { + socketMessageReceivedEvent(messageFromClient); + } + } + + // Send data to connected client + std::string messageToClient = "Message received successfully"; + int nBytesSent = send(clientSocket, messageToClient.c_str(), (int)messageToClient.length(), 0); + + if (SOCKET_ERROR == nBytesSent) { + LogDebug("Error occurred while writing message to Client to socket."); + + closesocket(clientSocket); + return false; + } + LogDebug("Send message to Client successful."); + + closesocket(clientSocket); + + return true; +} + +/** + * @brief Wait with timeout for new message. + * + * @return The cout of sockets that have data or SOCKET_ERROR +*/ +static int WaitForNewMesaageWithTimeout(SOCKET clientSocket, FD_SET& readSet, long sec, long usec) +{ + FD_ZERO(&readSet); + FD_SET(clientSocket, &readSet); + timeval timeoutTime; + timeoutTime.tv_sec = sec; + timeoutTime.tv_usec = usec; + + // NOTE: selRet has the cout of sockets that are ready + auto selRet = select(NULL, &readSet, NULL, NULL, &timeoutTime); + + return selRet; +} diff --git a/WhatsappTray/WinSockServer.h b/WhatsappTray/WinSockServer.h new file mode 100644 index 0000000..d3874c1 --- /dev/null +++ b/WhatsappTray/WinSockServer.h @@ -0,0 +1,11 @@ +// WinSockServer.h : Include file for standard system include files, +// or project specific include files. + +#pragma once + +#include +#include + +bool SocketStart(char portString[]); +void SocketNotifyOnNewMessage(const std::function& messageReceivedEvent); +void SocketStopServer();