diff --git a/.github/workflows/Build-WeChatFerry.yml b/.github/workflows/Build-WeChatFerry.yml index 1991b84..1305750 100644 --- a/.github/workflows/Build-WeChatFerry.yml +++ b/.github/workflows/Build-WeChatFerry.yml @@ -92,7 +92,7 @@ jobs: - name: 打包输出文件及下载 WeChat 安装包 run: | New-Item -ItemType Directory -Force -Path "WeChatFerry/tmp" - Compress-Archive -Path "WeChatFerry/Out/sdk.dll", "WeChatFerry/Out/spy.dll", "WeChatFerry/Out/spy_debug.dll" -DestinationPath "WeChatFerry/tmp/v${{ env.version }}.zip" + Compress-Archive -Path "WeChatFerry/Out/sdk.dll", "WeChatFerry/Out/spy.dll", "WeChatFerry/Out/spy_debug.dll", "WeChatFerry/Out/DISCLAIMER.md" -DestinationPath "WeChatFerry/tmp/v${{ env.version }}.zip" Invoke-WebRequest -Uri "https://github.com/tom-snow/wechat-windows-versions/releases/download/v${{ env.wechat_version }}/WeChatSetup-${{ env.wechat_version }}.exe" -OutFile "WeChatFerry/tmp/WeChatSetup-${{ env.wechat_version }}.exe" shell: pwsh diff --git a/README.MD b/README.MD index f2f79fa..741c9fa 100644 --- a/README.MD +++ b/README.MD @@ -5,7 +5,7 @@
⚠️ 免责声明【必读】⚠️ -请阅读完整的免责声明:[点击查看](DISCLAIMER.md) +请阅读完整的免责声明:[点击查看](WeChatFerry/DISCLAIMER.md)
@@ -32,7 +32,6 @@ * 发送图片消息 * 发送文件消息 * 发送卡片消息 -* 发送 XML 消息 * 发送 GIF 消息 * 拍一拍群友 * 转发消息 @@ -168,7 +167,7 @@ sdk.WxDestroySDK() ### 调试日志 ```c - DbgMsg("ListenMessage"); // 封装的 OutputDebugString + util::dbg_msg("ListenMessage"); // 封装的 OutputDebugString OutputDebugString(L"ListenMessage\n"); MessageBox(NULL, L"ListenMessage", L"ListenMessage", 0); ``` @@ -205,9 +204,9 @@ WeChatFerry ## 版本更新 -### v39.3.5 +### v39.4.1 -* 代码优化 +* 修复乱码问题。
点击查看更多 @@ -219,6 +218,14 @@ WeChatFerry * `y` 是 `WeChatFerry` 的版本,从 0 开始 * `z` 是各客户端的版本,从 0 开始 +### v39.4.0 + +* 重构代码,适配 `3.9.12.17`。 + +### v39.3.5 + +* 代码优化 + ### v39.3.4 * 实现获取登录二维码 diff --git a/WeChatFerry/com/log.hpp b/WeChatFerry/com/log.hpp index 4f30b27..4a08c8b 100644 --- a/WeChatFerry/com/log.hpp +++ b/WeChatFerry/com/log.hpp @@ -1,15 +1,18 @@ #pragma once +#ifdef ENABLE_DEBUG_LOG +#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG +#endif + #include #include #include -#include -#include -#include #include #include -#include "util.h" +#include +#include +#include #define LOG_DEBUG(...) SPDLOG_DEBUG(__VA_ARGS__) #define LOG_INFO(...) SPDLOG_INFO(__VA_ARGS__) @@ -48,7 +51,7 @@ inline void InitLogger(const std::string &path) logger = spdlog::rotating_logger_mt(DEFAULT_LOGGER_NAME, filename.string(), DEFAULT_LOGGER_MAX_SIZE, DEFAULT_LOGGER_MAX_FILES); } catch (const spdlog::spdlog_ex &ex) { - MessageBox(NULL, String2Wstring(ex.what()).c_str(), L"Init LOGGER ERROR", MB_ICONERROR); + MessageBoxA(NULL, ex.what(), "Init LOGGER ERROR", MB_ICONERROR); return; } @@ -59,11 +62,10 @@ inline void InitLogger(const std::string &path) spdlog::set_level(spdlog::level::debug); logger->flush_on(spdlog::level::debug); #else - spdlog::set_level(spdlog::level::info); logger->flush_on(spdlog::level::info); #endif - SPDLOG_DEBUG("Logger initialized with default settings."); + LOG_DEBUG("InitLogger with debug level"); } #ifdef ENABLE_DEBUG_LOG @@ -80,7 +82,7 @@ inline void log_buffer(uint8_t *buffer, size_t len) } } - SPDLOG_DEBUG(oss.str()); + LOG_DEBUG(oss.str()); } #endif diff --git a/WeChatFerry/com/util.cpp b/WeChatFerry/com/util.cpp index 0134592..b82e82f 100644 --- a/WeChatFerry/com/util.cpp +++ b/WeChatFerry/com/util.cpp @@ -1,177 +1,72 @@ -#include "Shlwapi.h" -#include "framework.h" +#include "util.h" + #include +#include #include -#include +#include #include -#include -#include #include +#include "framework.h" +#include +#include + #include "log.hpp" -#include "util.h" #pragma comment(lib, "shlwapi") #pragma comment(lib, "Version.lib") -using namespace std; +namespace fs = std::filesystem; -wstring String2Wstring(string s) +namespace util { - if (s.empty()) - return wstring(); - int size_needed = MultiByteToWideChar(CP_UTF8, 0, &s[0], (int)s.size(), NULL, 0); - wstring ws(size_needed, 0); - MultiByteToWideChar(CP_UTF8, 0, &s[0], (int)s.size(), &ws[0], size_needed); + +constexpr char WECHATEXE[] = "WeChat.exe"; +constexpr char WECHATWINDLL[] = "WeChatWin.dll"; + +std::wstring s2w(const std::string &s) +{ + if (s.empty()) return std::wstring(); + int size_needed = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), nullptr, 0); + std::wstring ws(size_needed, 0); + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), &ws[0], size_needed); return ws; } -string Wstring2String(wstring ws) +std::string w2s(const std::wstring &ws) { - if (ws.empty()) - return string(); - int size_needed = WideCharToMultiByte(CP_UTF8, 0, &ws[0], (int)ws.size(), NULL, 0, NULL, NULL); - string s(size_needed, 0); - WideCharToMultiByte(CP_UTF8, 0, &ws[0], (int)ws.size(), &s[0], size_needed, NULL, NULL); + if (ws.empty()) return std::string(); + int size_needed + = WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), static_cast(ws.size()), nullptr, 0, nullptr, nullptr); + std::string s(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), static_cast(ws.size()), &s[0], size_needed, nullptr, nullptr); return s; } -string GB2312ToUtf8(const char *gb2312) +std::string gb2312_to_utf8(const char *gb2312) { - int size_needed = 0; + if (!gb2312) return ""; - size_needed = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, NULL, 0); - wstring ws(size_needed, 0); + int size_needed = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, nullptr, 0); + std::wstring ws(size_needed, 0); MultiByteToWideChar(CP_ACP, 0, gb2312, -1, &ws[0], size_needed); - size_needed = WideCharToMultiByte(CP_UTF8, 0, &ws[0], -1, NULL, 0, NULL, NULL); - string s(size_needed, 0); - WideCharToMultiByte(CP_UTF8, 0, &ws[0], -1, &s[0], size_needed, NULL, NULL); + size_needed = WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, nullptr, 0, nullptr, nullptr); + std::string s(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, &s[0], size_needed, nullptr, nullptr); return s; } -static int GetWeChatPath(wchar_t *path) +static DWORD get_wechat_pid() { - int ret = -1; - HKEY hKey = NULL; - // HKEY_CURRENT_USER\Software\Tencent\WeChat InstallPath = xx - if (ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, L"Software\\Tencent\\WeChat", &hKey)) { - ret = GetLastError(); - return ret; - } + DWORD pid = 0; + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) return 0; - DWORD Type = REG_SZ; - DWORD cbData = MAX_PATH * sizeof(WCHAR); - if (ERROR_SUCCESS != RegQueryValueEx(hKey, L"InstallPath", 0, &Type, (LPBYTE)path, &cbData)) { - ret = GetLastError(); - goto __exit; - } - - if (path != NULL) { - PathAppend(path, WECHAREXE); - } - -__exit: - if (hKey) { - RegCloseKey(hKey); - } - - return ERROR_SUCCESS; -} - -static int GetWeChatWinDLLPath(wchar_t *path) -{ - int ret = GetWeChatPath(path); - if (ret != ERROR_SUCCESS) { - return ret; - } - - PathRemoveFileSpecW(path); - PathAppendW(path, WECHATWINDLL); - if (!PathFileExists(path)) { - // 微信从(大约)3.7开始,增加了一层版本目录: [3.7.0.29] - PathRemoveFileSpec(path); - _wfinddata_t findData; - wstring dir = wstring(path) + L"\\[*.*"; - intptr_t handle = _wfindfirst(dir.c_str(), &findData); - if (handle == -1) { // 检查是否成功 - return -1; - } - wstring dllPath = wstring(path) + L"\\" + findData.name; - wcscpy_s(path, MAX_PATH, dllPath.c_str()); - PathAppend(path, WECHATWINDLL); - } - - return ret; -} - -static bool GetFileVersion(const wchar_t *filePath, wchar_t *version) -{ - if (wcslen(filePath) > 0 && PathFileExists(filePath)) { - VS_FIXEDFILEINFO *pVerInfo = NULL; - DWORD dwTemp, dwSize; - BYTE *pData = NULL; - UINT uLen; - - dwSize = GetFileVersionInfoSize(filePath, &dwTemp); - if (dwSize == 0) { - return false; - } - - pData = new BYTE[dwSize + 1]; - if (pData == NULL) { - return false; - } - - if (!GetFileVersionInfo(filePath, 0, dwSize, pData)) { - delete[] pData; - return false; - } - - if (!VerQueryValue(pData, TEXT("\\"), (void **)&pVerInfo, &uLen)) { - delete[] pData; - return false; - } - - UINT64 verMS = pVerInfo->dwFileVersionMS; - UINT64 verLS = pVerInfo->dwFileVersionLS; - UINT64 major = HIWORD(verMS); - UINT64 minor = LOWORD(verMS); - UINT64 build = HIWORD(verLS); - UINT64 revision = LOWORD(verLS); - delete[] pData; - - StringCbPrintf(version, 0x20, TEXT("%d.%d.%d.%d"), major, minor, build, revision); - - return true; - } - - return false; -} - -int GetWeChatVersion(wchar_t *version) -{ - WCHAR Path[MAX_PATH] = { 0 }; - - int ret = GetWeChatWinDLLPath(Path); - if (ret != ERROR_SUCCESS) { - return ret; - } - - ret = GetFileVersion(Path, version); - - return ret; -} - -DWORD GetWeChatPid() -{ - DWORD pid = 0; - HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) }; while (Process32Next(hSnapshot, &pe32)) { - wstring strProcess = pe32.szExeFile; - if (strProcess == WECHAREXE) { + if (pe32.szExeFile == s2w(WECHATEXE)) { pid = pe32.th32ProcessID; break; } @@ -180,168 +75,237 @@ DWORD GetWeChatPid() return pid; } -enum class WindowsArchiture { x32, x64 }; -static WindowsArchiture GetWindowsArchitecture() +static std::optional get_wechat_path() { -#ifdef _WIN64 - return WindowsArchiture::x64; -#else - return WindowsArchiture::x32; -#endif + HKEY hKey; + if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Tencent\\WeChat", 0, KEY_READ, &hKey) != ERROR_SUCCESS) { + LOG_ERROR("无法打开注册表项"); + return std::nullopt; + } + + char path[MAX_PATH] = { 0 }; + DWORD type = REG_SZ; + DWORD size = sizeof(path); + if (RegQueryValueExA(hKey, "InstallPath", nullptr, &type, reinterpret_cast(path), &size) != ERROR_SUCCESS) { + RegCloseKey(hKey); + LOG_ERROR("无法读取注册表中的 InstallPath"); + return std::nullopt; + } + RegCloseKey(hKey); + + PathAppendA(path, WECHATEXE); + return std::string(path); } -BOOL IsProcessX64(DWORD pid) +static std::optional get_wechat_win_dll_path() { - BOOL isWow64 = false; - HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid); - if (!hProcess) - return false; - BOOL result = IsWow64Process(hProcess, &isWow64); - CloseHandle(hProcess); - if (!result) - return false; - if (isWow64) - return false; - else if (GetWindowsArchitecture() == WindowsArchiture::x32) - return false; - else - return true; + auto wechat_path = get_wechat_path(); + if (!wechat_path) { + return std::nullopt; + } + + fs::path dll_path = *wechat_path; + dll_path = dll_path.parent_path(); + + fs::path wechat_dll_path = dll_path / WECHATWINDLL; + if (fs::exists(wechat_dll_path)) { // 尝试直接查找 WeChatWin.dll + return wechat_dll_path.string(); + } + + // 微信从(大约)3.7开始,增加了一层版本目录: [3.7.0.29] + std::optional found_path; + for (const auto &entry : fs::directory_iterator(dll_path)) { + if (entry.is_directory()) { + fs::path possible_dll = entry.path() / WECHATWINDLL; + if (fs::exists(possible_dll)) { + found_path = possible_dll.string(); + break; // 取第一个找到的版本号文件夹 + } + } + } + + if (!found_path) { + LOG_ERROR("未找到 WeChatWin.dll"); + } + + return found_path; } -int OpenWeChat(DWORD *pid) +static std::optional get_file_version(const std::string &path) { - *pid = GetWeChatPid(); - if (*pid) { + if (!PathFileExistsA(path.c_str())) { + LOG_ERROR("文件不存在: {}", path); + return std::nullopt; + } + + DWORD dummy = 0; + DWORD size = GetFileVersionInfoSizeA(path.c_str(), &dummy); + if (size == 0) { + LOG_ERROR("无法获取文件版本信息大小: {}", path); + return std::nullopt; + } + + std::vector buffer(size); + if (!GetFileVersionInfoA(path.c_str(), 0, size, buffer.data())) { + LOG_ERROR("无法获取文件版本信息: {}", path); + return std::nullopt; + } + + VS_FIXEDFILEINFO *ver_info = nullptr; + UINT ver_size = 0; + if (!VerQueryValueA(buffer.data(), "\\", reinterpret_cast(&ver_info), &ver_size)) { + LOG_ERROR("无法获取文件版本信息: {}", path); + return std::nullopt; + } + + return fmt::format("{}.{}.{}.{}", HIWORD(ver_info->dwFileVersionMS), LOWORD(ver_info->dwFileVersionMS), + HIWORD(ver_info->dwFileVersionLS), LOWORD(ver_info->dwFileVersionLS)); +} + +std::string get_wechat_version() +{ + auto dll_path = get_wechat_win_dll_path(); + if (!dll_path) { + LOG_ERROR("无法获取 WeChatWin.dll 路径"); + return ""; + } + + auto version = get_file_version(*dll_path); + if (!version) { + LOG_ERROR("无法获取 WeChat 版本信息"); + return ""; + } + + return *version; +} + +int open_wechat(DWORD &pid) +{ + pid = get_wechat_pid(); + if (pid != 0) { return ERROR_SUCCESS; } - int ret = -1; - STARTUPINFO si = { sizeof(si) }; - WCHAR Path[MAX_PATH] = { 0 }; - PROCESS_INFORMATION pi = { 0 }; - - ret = GetWeChatPath(Path); - if (ERROR_SUCCESS != ret) { - return ret; + auto wechat_path = util::get_wechat_path(); + if (!wechat_path) { + LOG_ERROR("获取 WeChat 安装路径失败"); + return ERROR_FILE_NOT_FOUND; } - if (!CreateProcess(NULL, Path, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { + STARTUPINFOA si = { sizeof(si) }; + PROCESS_INFORMATION pi = {}; + + std::string command_line = *wechat_path; + if (!CreateProcessA(nullptr, command_line.data(), nullptr, nullptr, FALSE, CREATE_NEW_CONSOLE, nullptr, nullptr, + &si, &pi)) { return GetLastError(); } CloseHandle(pi.hThread); CloseHandle(pi.hProcess); - *pid = pi.dwProcessId; - + pid = pi.dwProcessId; return ERROR_SUCCESS; } -size_t GetWstringByAddress(UINT64 addr, wchar_t *buffer, UINT64 buffer_size) +uint32_t get_memory_int_by_address(HANDLE hProcess, uint64_t addr) { - size_t strLength = GET_DWORD(addr + 8); - if (strLength == 0) { - return 0; - } else if (strLength > buffer_size) { - strLength = buffer_size - 1; - } + uint32_t value = 0; + if (!addr || !hProcess) return value; - wmemcpy_s(buffer, strLength + 1, GET_WSTRING(addr), strLength + 1); - - return strLength; -} - -string GetStringByAddress(UINT64 addr) -{ - size_t strLength = GET_DWORD(addr + 8); - return Wstring2String(wstring(GET_WSTRING(addr), strLength)); -} - -string GetStringByStrAddr(UINT64 addr) -{ - size_t strLength = GET_DWORD(addr + 8); - return strLength ? string(GET_STRING(addr), strLength) : string(); -} - -string GetStringByWstrAddr(UINT64 addr) -{ - size_t strLength = GET_DWORD(addr + 8); - return strLength ? Wstring2String(wstring(GET_WSTRING(addr), strLength)) : string(); -} - -UINT32 GetMemoryIntByAddress(HANDLE hProcess, UINT64 addr) -{ - UINT32 value = 0; - - unsigned char data[4] = { 0 }; - if (ReadProcessMemory(hProcess, (LPVOID)addr, data, 4, 0)) { - value = data[0] & 0xFF; - value |= ((data[1] << 8) & 0xFF00); - value |= ((data[2] << 16) & 0xFF0000); - value |= ((data[3] << 24) & 0xFF000000); - } + ReadProcessMemory(hProcess, reinterpret_cast(addr), &value, sizeof(value), nullptr); return value; } -wstring GetUnicodeInfoByAddress(HANDLE hProcess, UINT64 address) +std::wstring get_unicode_info_by_address(HANDLE hProcess, uint64_t address) { - wstring value = L""; + if (!hProcess || !address) return L""; - UINT64 strAddress = GetMemoryIntByAddress(hProcess, address); - UINT64 strLen = GetMemoryIntByAddress(hProcess, address + 0x4); - if (strLen > 500) - return value; + uint64_t str_address = get_memory_int_by_address(hProcess, address); + uint64_t str_len = get_memory_int_by_address(hProcess, address + 0x4); + if (str_len > 500) return L""; wchar_t cValue[500] = { 0 }; - memset(cValue, 0, sizeof(cValue) / sizeof(wchar_t)); - if (ReadProcessMemory(hProcess, (LPVOID)strAddress, cValue, (strLen + 1) * 2, 0)) { - value = wstring(cValue); + if (ReadProcessMemory(hProcess, reinterpret_cast(str_address), cValue, (str_len + 1) * sizeof(wchar_t), + nullptr)) { + return std::wstring(cValue); } - return value; + return L""; } -void DbgMsg(const char *zcFormat, ...) +void dbg_msg(const char *format, ...) { - // initialize use of the variable argument array - va_list vaArgs; - va_start(vaArgs, zcFormat); + if (!format) return; - // reliably acquire the size - // from a copy of the variable argument array - // and a functionally reliable call to mock the formatting - va_list vaArgsCopy; - va_copy(vaArgsCopy, vaArgs); - const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaArgsCopy); - va_end(vaArgsCopy); + va_list args; + va_start(args, format); - // return a formatted string without risking memory mismanagement - // and without assuming any compiler or platform specific behavior - std::vector zc(iLen + 1); - std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs); - va_end(vaArgs); - std::string strText(zc.data(), iLen); + va_list args_copy; + va_copy(args_copy, args); + int len = vsnprintf(nullptr, 0, format, args_copy); + va_end(args_copy); - OutputDebugStringA(strText.c_str()); + std::vector buffer(len + 1); + vsnprintf(buffer.data(), buffer.size(), format, args); + va_end(args); + + OutputDebugStringW(s2w(buffer.data()).c_str()); } -WxString *NewWxStringFromStr(const string &str) { return NewWxStringFromWstr(String2Wstring(str)); } - -WxString *NewWxStringFromWstr(const wstring &ws) +std::unique_ptr new_wx_string(const char *str) { - WxString *p = (WxString *)HeapAlloc(GetProcessHeap(), 0, sizeof(WxString)); - wchar_t *pWstring = (wchar_t *)HeapAlloc(GetProcessHeap(), 0, (ws.size() + 1) * 2); - if (p == NULL || pWstring == NULL) { - LOG_ERROR("Out of Memory..."); - return NULL; + return new_wx_string(str ? std::string(str) : std::string()); +} + +std::unique_ptr new_wx_string(const std::string &str) { return std::make_unique(s2w(str)); } + +std::unique_ptr new_wx_string(const wchar_t *wstr) +{ + return new_wx_string(wstr ? std::wstring(wstr) : std::wstring()); +} + +std::unique_ptr new_wx_string(const std::wstring &wstr) { return std::make_unique(wstr); } + +AtWxidSplitResult<> parse_wxids(const std::string &atWxids) +{ + AtWxidSplitResult<> result; + if (!atWxids.empty()) { + std::wstringstream wss(util::s2w(atWxids)); + for (std::wstring wxid; std::getline(wss, wxid, L',');) { + result.wxids.push_back(wxid); + result.wxWxids.emplace_back(result.wxids.back()); + } } - - wmemcpy(pWstring, ws.c_str(), ws.size() + 1); - p->wptr = pWstring; - p->size = (DWORD)ws.size(); - p->capacity = (DWORD)ws.size(); - p->ptr = 0; - p->clen = 0; - return p; + return result; } + +WxString *CreateWxString(const std::string &s) +{ + std::wstring ws = util::s2w(s); + WxString *wxStr = reinterpret_cast(HeapAlloc(GetProcessHeap(), 8, sizeof(WxString))); + if (!wxStr) return nullptr; + size_t len = ws.length(); + wchar_t *ptr = reinterpret_cast(HeapAlloc(GetProcessHeap(), 8, (len + 1) * sizeof(wchar_t))); + if (!ptr) { + HeapFree(GetProcessHeap(), 8, wxStr); + return nullptr; + } + wmemcpy(ptr, ws.c_str(), len + 1); + wxStr->wptr = ptr; + wxStr->size = static_cast(ws.size()); + wxStr->length = static_cast(ws.length()); + wxStr->clen = 0; + wxStr->ptr = nullptr; + return wxStr; +} + +void FreeWxString(WxString *wxStr) +{ + if (wxStr) { + if (wxStr->wptr) HeapFree(GetProcessHeap(), 8, const_cast(wxStr->wptr)); + HeapFree(GetProcessHeap(), 8, wxStr); + } +} +} // namespace util diff --git a/WeChatFerry/com/util.h b/WeChatFerry/com/util.h index 8f252df..1998fbb 100644 --- a/WeChatFerry/com/util.h +++ b/WeChatFerry/com/util.h @@ -1,41 +1,105 @@ #pragma once +#include +#include #include +#include #include "spy_types.h" -#define WECHAREXE L"WeChat.exe" -#define WECHATWINDLL L"WeChatWin.dll" -#define WCFSDKDLL L"sdk.dll" -#define WCFSPYDLL L"spy.dll" -#define WCFSPYDLL_DEBUG L"spy_debug.dll" - -#define GET_UINT64(addr) ((UINT64) * (UINT64 *)(addr)) -#define GET_DWORD(addr) ((DWORD) * (UINT64 *)(addr)) -#define GET_QWORD(addr) ((UINT64) * (UINT64 *)(addr)) -#define GET_STRING(addr) ((CHAR *)(*(UINT64 *)(addr))) -#define GET_WSTRING(addr) ((WCHAR *)(*(UINT64 *)(addr))) -#define GET_STRING_FROM_P(addr) ((CHAR *)(addr)) -#define GET_WSTRING_FROM_P(addr) ((WCHAR *)(addr)) - -typedef struct PortPath { +namespace util +{ +struct PortPath { int port; char path[MAX_PATH]; -} PortPath_t; +}; -DWORD GetWeChatPid(); -BOOL IsProcessX64(DWORD pid); -int OpenWeChat(DWORD *pid); -int GetWeChatVersion(wchar_t *version); -size_t GetWstringByAddress(UINT64 address, wchar_t *buffer, UINT64 buffer_size); -UINT32 GetMemoryIntByAddress(HANDLE hProcess, UINT64 address); -std::wstring GetUnicodeInfoByAddress(HANDLE hProcess, UINT64 address); -std::wstring String2Wstring(std::string s); -std::string Wstring2String(std::wstring ws); -std::string GB2312ToUtf8(const char *gb2312); -std::string GetStringByAddress(UINT64 address); -std::string GetStringByStrAddr(UINT64 addr); -std::string GetStringByWstrAddr(UINT64 addr); -void DbgMsg(const char *zcFormat, ...); -WxString *NewWxStringFromStr(const std::string &str); -WxString *NewWxStringFromWstr(const std::wstring &ws); +DWORD get_wechat_pid(); +int open_wechat(DWORD &pid); +std::string get_wechat_version(); +uint32_t get_memory_int_by_address(HANDLE hProcess, uint64_t addr); +std::wstring get_unicode_info_by_address(HANDLE hProcess, uint64_t addr); +std::wstring s2w(const std::string &s); +std::string w2s(const std::wstring &ws); +std::string gb2312_to_utf8(const char *gb2312); +void dbg_msg(const char *format, ...); + +inline DWORD get_dword(uint64_t addr) { return addr ? *reinterpret_cast(addr) : 0; } +inline QWORD get_qword(uint64_t addr) { return addr ? *reinterpret_cast(addr) : 0; } +inline uint64_t get_uint64(uint64_t addr) { return addr ? *reinterpret_cast(addr) : 0; } +inline std::string get_p_string(uint64_t addr) { return addr ? std::string(reinterpret_cast(addr)) : ""; } +inline std::string get_p_string(uint64_t addr, size_t len) +{ + return addr ? std::string(reinterpret_cast(addr), len) : ""; +} +inline std::wstring get_p_wstring(uint64_t addr) +{ + return addr ? std::wstring(reinterpret_cast(addr)) : L""; +} +inline std::wstring get_p_wstring(uint64_t addr, size_t len) +{ + return addr ? std::wstring(reinterpret_cast(addr), len) : L""; +} +inline std::string get_pp_string(uint64_t addr) +{ + if (!addr) return ""; + + const char *ptr = *reinterpret_cast(addr); + return (ptr && *ptr) ? std::string(ptr) : ""; +} +inline std::wstring get_pp_wstring(uint64_t addr) +{ + if (!addr) return L""; + + const wchar_t *ptr = *reinterpret_cast(addr); + return (ptr && *ptr) ? std::wstring(ptr) : L""; +} +inline std::string get_pp_len_string(uint64_t addr) +{ + size_t len = get_dword(addr + 8); + return (addr && len) ? std::string(*reinterpret_cast(addr), len) : ""; +} +inline std::wstring get_pp_len_wstring(uint64_t addr) +{ + size_t len = get_dword(addr + 8); + return (addr && len) ? std::wstring(*reinterpret_cast(addr), len) : L""; +} +inline std::string get_str_by_wstr_addr(uint64_t addr) { return w2s(get_pp_len_wstring(addr)); } +inline void *AllocFromHeap(size_t size) { return HeapAlloc(GetProcessHeap(), 8, size); } +inline void FreeBuffer(void *buffer) +{ + if (buffer) HeapFree(GetProcessHeap(), 8, buffer); +} +inline int MsgBox(HWND hWnd, const std::string &text, const std::string &caption = "WCF", UINT uType = MB_OK) +{ + std::wstring wText = s2w(text); + std::wstring wCaption = s2w(caption); + return MessageBoxW(nullptr, wText.c_str(), wCaption.c_str(), uType); +} + +template static T *AllocBuffer(size_t count) +{ + return reinterpret_cast(HeapAlloc(GetProcessHeap(), 8, sizeof(T) * count)); +} + +template struct WxStringHolder { + std::wstring ws; + WxString wx; + explicit WxStringHolder(const T &str) : ws(util::s2w(str)), wx(ws) { } +}; + +template struct AtWxidSplitResult { + std::vector wxids; + std::vector wxWxids; +}; + +WxString *CreateWxString(const std::string &s); +void FreeWxString(WxString *wxStr); +AtWxidSplitResult<> parse_wxids(const std::string &atWxids); + +std::unique_ptr new_wx_string(const char *str); +std::unique_ptr new_wx_string(const wchar_t *wstr); +std::unique_ptr new_wx_string(const std::string &str); +std::unique_ptr new_wx_string(const std::wstring &wstr); + +} // namespace util diff --git a/WeChatFerry/rpc/pb_util.cpp b/WeChatFerry/rpc/pb_util.cpp index 5f8c4bd..5bde0f5 100644 --- a/WeChatFerry/rpc/pb_util.cpp +++ b/WeChatFerry/rpc/pb_util.cpp @@ -68,7 +68,6 @@ bool encode_contacts(pb_ostream_t *stream, const pb_field_t *field, void *const vector *v = (vector *)*arg; RpcContact message = RpcContact_init_default; - LOG_DEBUG("encode_contacts[{}]", v->size()); for (auto it = v->begin(); it != v->end(); it++) { message.wxid.funcs.encode = &encode_string; message.wxid.arg = (void *)(*it).wxid.c_str(); diff --git a/WeChatFerry/sdk/SDK.vcxproj b/WeChatFerry/sdk/SDK.vcxproj index 5f549ad..cb0fd9d 100644 --- a/WeChatFerry/sdk/SDK.vcxproj +++ b/WeChatFerry/sdk/SDK.vcxproj @@ -141,7 +141,6 @@ xcopy /y $(OutDir)$(TargetFileName) $(SolutionDir)..\clients\python\wcferry - diff --git a/WeChatFerry/sdk/SDK.vcxproj.filters b/WeChatFerry/sdk/SDK.vcxproj.filters index 323d545..5adfeab 100644 --- a/WeChatFerry/sdk/SDK.vcxproj.filters +++ b/WeChatFerry/sdk/SDK.vcxproj.filters @@ -27,9 +27,6 @@ 头文件 - - 头文件 - diff --git a/WeChatFerry/sdk/injector.cpp b/WeChatFerry/sdk/injector.cpp index cc06298..913ba87 100644 --- a/WeChatFerry/sdk/injector.cpp +++ b/WeChatFerry/sdk/injector.cpp @@ -1,112 +1,110 @@ -#include "framework.h" -#include "psapi.h" -#include -#include +#include "injector.h" + +#include + +#include "psapi.h" -#include "injector.h" #include "util.h" using namespace std; -HMODULE GetTargetModuleBase(HANDLE process, string dll) +static void handle_injection_error(HANDLE process, LPVOID remote_address, const std::string &error_msg) { - DWORD cbNeeded; - HMODULE moduleHandleList[512]; - BOOL ret = EnumProcessModulesEx(process, moduleHandleList, sizeof(moduleHandleList), &cbNeeded, LIST_MODULES_64BIT); - if (!ret) { - MessageBox(NULL, L"获取模块失败", L"GetTargetModuleBase", 0); + util::MsgBox(NULL, error_msg.c_str(), "Error", MB_ICONERROR); + if (remote_address) { + VirtualFreeEx(process, remote_address, 0, MEM_RELEASE); + } + if (process) { + CloseHandle(process); + } +} + +HMODULE get_target_module_base(HANDLE process, const string &dll) +{ + DWORD needed; + HMODULE modules[512]; + if (!EnumProcessModulesEx(process, modules, sizeof(modules), &needed, LIST_MODULES_64BIT)) { + util::MsgBox(NULL, "获取模块失败", "get_target_module_base", 0); return NULL; } - if (cbNeeded > sizeof(moduleHandleList)) { - MessageBox(NULL, L"模块数量过多", L"GetTargetModuleBase", 0); - return NULL; - } - DWORD processCount = cbNeeded / sizeof(HMODULE); - - char moduleName[32]; - for (DWORD i = 0; i < processCount; i++) { - GetModuleBaseNameA(process, moduleHandleList[i], moduleName, 32); - if (!strncmp(dll.c_str(), moduleName, dll.size())) { - return moduleHandleList[i]; + DWORD count = needed / sizeof(HMODULE); + char module_name[MAX_PATH]; + for (DWORD i = 0; i < count; i++) { + GetModuleBaseNameA(process, modules[i], module_name, sizeof(module_name)); + if (!strncmp(dll.c_str(), module_name, dll.size())) { + return modules[i]; } } return NULL; } -HANDLE InjectDll(DWORD pid, LPCWSTR dllPath, HMODULE *injectedBase) +HANDLE inject_dll(DWORD pid, const string &dll_path, HMODULE *injected_base) { - HANDLE hThread; - SIZE_T cszDLL = (wcslen(dllPath) + 1) * sizeof(WCHAR); + SIZE_T path_size = dll_path.size() + 1; // 1. 打开目标进程 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); - if (hProcess == NULL) { - MessageBox(NULL, L"打开进程失败", L"InjectDll", 0); + if (!hProcess) { + util::MsgBox(NULL, "打开进程失败", "inject_dll", 0); return NULL; } // 2. 在目标进程的内存里开辟空间 - LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, cszDLL, MEM_COMMIT, PAGE_READWRITE); - if (pRemoteAddress == NULL) { - MessageBox(NULL, L"DLL 路径写入失败", L"InjectDll", 0); + LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, path_size, MEM_COMMIT, PAGE_READWRITE); + if (!pRemoteAddress) { + handle_injection_error(hProcess, NULL, "DLL 路径写入失败"); return NULL; } // 3. 把 dll 的路径写入到目标进程的内存空间中 - WriteProcessMemory(hProcess, pRemoteAddress, dllPath, cszDLL, NULL); + WriteProcessMemory(hProcess, pRemoteAddress, dll_path.c_str(), path_size, NULL); - // 3. 创建一个远程线程,让目标进程调用 LoadLibrary - HMODULE k32 = GetModuleHandle(L"kernel32.dll"); - if (k32 == NULL) { - MessageBox(NULL, L"获取 kernel32 失败", L"InjectDll", 0); + // 4. 创建一个远程线程,让目标进程调用 LoadLibrary + HMODULE k32 = GetModuleHandleA("kernel32.dll"); + if (!k32) { + handle_injection_error(hProcess, pRemoteAddress, "获取 kernel32 失败"); return NULL; } - FARPROC libAddr = GetProcAddress(k32, "LoadLibraryW"); + FARPROC libAddr = GetProcAddress(k32, "LoadLibraryA"); if (!libAddr) { - MessageBox(NULL, L"获取 LoadLibrary 失败", L"InjectDll", 0); + handle_injection_error(hProcess, pRemoteAddress, "获取 LoadLibrary 失败"); return NULL; } - hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, pRemoteAddress, 0, NULL); - if (hThread == NULL) { - VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE); - CloseHandle(hProcess); - MessageBox(NULL, L"CreateRemoteThread 失败", L"InjectDll", 0); + HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, pRemoteAddress, 0, NULL); + if (!hThread) { + handle_injection_error(hProcess, pRemoteAddress, "CreateRemoteThread 失败"); return NULL; } - WaitForSingleObject(hThread, -1); + WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); - *injectedBase = GetTargetModuleBase(hProcess, filesystem::path(Wstring2String(dllPath)).filename().string()); + *injected_base = get_target_module_base(hProcess, filesystem::path(dll_path).filename().string()); VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE); - // CloseHandle(hProcess); // Close when exit - return hProcess; } -bool EjectDll(HANDLE process, HMODULE dllBase) +bool eject_dll(HANDLE process, HMODULE dll_base) { - HANDLE hThread = NULL; - - // 使目标进程调用 FreeLibrary,卸载 DLL - HMODULE k32 = GetModuleHandle(L"kernel32.dll"); - if (k32 == NULL) { - MessageBox(NULL, L"获取 kernel32 失败", L"InjectDll", 0); - return NULL; + HMODULE k32 = GetModuleHandleA("kernel32.dll"); + if (!k32) { + util::MsgBox(NULL, "获取 kernel32 失败", "eject_dll", 0); + return false; } FARPROC libAddr = GetProcAddress(k32, "FreeLibraryAndExitThread"); if (!libAddr) { - MessageBox(NULL, L"获取 FreeLibrary 失败", L"InjectDll", 0); - return NULL; + util::MsgBox(NULL, "获取 FreeLibrary 失败", "eject_dll", 0); + return false; } - hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, (LPVOID)dllBase, 0, NULL); - if (hThread == NULL) { - MessageBox(NULL, L"FreeLibrary 调用失败!", L"EjectDll", 0); + + HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, (LPVOID)dll_base, 0, NULL); + if (!hThread) { + util::MsgBox(NULL, "FreeLibrary 调用失败!", "eject_dll", 0); return false; } @@ -116,38 +114,34 @@ bool EjectDll(HANDLE process, HMODULE dllBase) return true; } -static UINT64 GetFuncOffset(LPCWSTR dllPath, LPCSTR funcName) +static uint64_t get_func_offset(const string &dll_path, const string &func_name) { - HMODULE dll = LoadLibrary(dllPath); - if (dll == NULL) { - MessageBox(NULL, L"获取 DLL 失败", L"GetFuncOffset", 0); + HMODULE dll = LoadLibraryA(dll_path.c_str()); + if (!dll) { + util::MsgBox(NULL, "获取 DLL 失败", "get_func_offset", 0); return 0; } - LPVOID absAddr = GetProcAddress(dll, funcName); - UINT64 offset = (UINT64)absAddr - (UINT64)dll; + LPVOID absAddr = GetProcAddress(dll, func_name.c_str()); + uint64_t offset = reinterpret_cast(absAddr) - reinterpret_cast(dll); FreeLibrary(dll); return offset; } -bool CallDllFunc(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPDWORD ret) +bool call_dll_func(HANDLE process, const string &dll_path, HMODULE dll_base, const string &func_name, DWORD *ret) { - UINT64 offset = GetFuncOffset(dllPath, funcName); - if (offset == 0) { - return false; + uint64_t offset = get_func_offset(dll_path, func_name); + if (offset == 0 || offset > (UINT64_MAX - reinterpret_cast(dll_base))) { + return false; // 避免溢出 } - UINT64 pFunc = (UINT64)dllBase + GetFuncOffset(dllPath, funcName); - if (pFunc <= (UINT64)dllBase) { - return false; - } - + uint64_t pFunc = reinterpret_cast(dll_base) + offset; HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, NULL, 0, NULL); - if (hThread == NULL) { + if (!hThread) { return false; } WaitForSingleObject(hThread, INFINITE); - if (ret != NULL) { + if (ret) { GetExitCodeThread(hThread, ret); } @@ -155,35 +149,32 @@ bool CallDllFunc(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcNa return true; } -bool CallDllFuncEx(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPVOID parameter, size_t sz, - LPDWORD ret) +bool call_dll_func_ex(HANDLE process, const string &dll_path, HMODULE dll_base, const string &func_name, + LPVOID parameter, size_t size, DWORD *ret) { - UINT64 offset = GetFuncOffset(dllPath, funcName); - if (offset == 0) { - return false; + uint64_t offset = get_func_offset(dll_path, func_name); + if (offset == 0 || offset > (UINT64_MAX - reinterpret_cast(dll_base))) { + return false; // 避免溢出 } - UINT64 pFunc = (UINT64)dllBase + GetFuncOffset(dllPath, funcName); - if (pFunc <= (UINT64)dllBase) { + uint64_t pFunc = reinterpret_cast(dll_base) + offset; + LPVOID pRemoteAddress = VirtualAllocEx(process, NULL, size, MEM_COMMIT, PAGE_READWRITE); + if (!pRemoteAddress) { + util::MsgBox(NULL, "申请内存失败", "call_dll_func_ex", 0); return false; } - LPVOID pRemoteAddress = VirtualAllocEx(process, NULL, sz, MEM_COMMIT, PAGE_READWRITE); - if (pRemoteAddress == NULL) { - MessageBox(NULL, L"申请内存失败", L"CallDllFuncEx", 0); - return NULL; - } - - WriteProcessMemory(process, pRemoteAddress, parameter, sz, NULL); + WriteProcessMemory(process, pRemoteAddress, parameter, size, NULL); HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, pRemoteAddress, 0, NULL); - if (hThread == NULL) { - VirtualFree(pRemoteAddress, 0, MEM_RELEASE); - MessageBox(NULL, L"远程调用失败", L"CallDllFuncEx", 0); + if (!hThread) { + VirtualFreeEx(process, pRemoteAddress, 0, MEM_RELEASE); + util::MsgBox(NULL, "远程调用失败", "call_dll_func_ex", 0); return false; } + WaitForSingleObject(hThread, INFINITE); - VirtualFree(pRemoteAddress, 0, MEM_RELEASE); - if (ret != NULL) { + VirtualFreeEx(process, pRemoteAddress, 0, MEM_RELEASE); + if (ret) { GetExitCodeThread(hThread, ret); } diff --git a/WeChatFerry/sdk/injector.h b/WeChatFerry/sdk/injector.h index 07b4a28..83988a6 100644 --- a/WeChatFerry/sdk/injector.h +++ b/WeChatFerry/sdk/injector.h @@ -1,9 +1,11 @@ #pragma once +#include + #include "framework.h" -HANDLE InjectDll(DWORD pid, LPCWSTR dllPath, HMODULE *injectedBase); -bool EjectDll(HANDLE process, HMODULE dllBase); -bool CallDllFunc(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, DWORD *ret); -bool CallDllFuncEx(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPVOID parameter, size_t sz, - DWORD *ret); +HANDLE inject_dll(DWORD pid, const std::string &dll_path, HMODULE *injected_base); +bool eject_dll(HANDLE process, HMODULE dll_base); +bool call_dll_func(HANDLE process, const std::string &dll_path, HMODULE dll_base, const std::string &func, DWORD *ret); +bool call_dll_func_ex(HANDLE process, const std::string &dll_path, HMODULE dll_base, const std::string &func, + LPVOID parameter, size_t size, DWORD *ret); diff --git a/WeChatFerry/sdk/sdk.cpp b/WeChatFerry/sdk/sdk.cpp index 8b1c790..8363414 100644 --- a/WeChatFerry/sdk/sdk.cpp +++ b/WeChatFerry/sdk/sdk.cpp @@ -1,4 +1,5 @@ -#include "framework.h" +#include "sdk.h" + #include #include #include @@ -6,55 +7,67 @@ #include #include #include + +#include "framework.h" #include #include "injector.h" -#include "sdk.h" #include "util.h" -static BOOL injected = false; +extern "C" IMAGE_DOS_HEADER __ImageBase; + +static bool injected = false; static HANDLE wcProcess = NULL; static HMODULE spyBase = NULL; -static std::wstring spyDllPath; +static std::string spyDllPath; -constexpr char DISCLAIMER_FILE[] = ".license_accepted.flag"; -constexpr char DISCLAIMER_TEXT_FILE[] = "DISCLAIMER.md"; +constexpr char WCFSDKDLL[] = "sdk.dll"; +constexpr char WCFSPYDLL[] = "spy.dll"; +constexpr char WCFSPYDLL_DEBUG[] = "spy_debug.dll"; -static std::optional ReadDisclaimerText(const char *filePath) +constexpr std::string_view DISCLAIMER_FLAG = ".license_accepted.flag"; +constexpr std::string_view DISCLAIMER_TEXT_FILE = "DISCLAIMER.md"; + +namespace fs = std::filesystem; + +static fs::path get_module_directory() { - std::ifstream file(filePath, std::ios::binary); - if (!file.is_open()) { - return std::nullopt; // 文件打开失败 - } - - std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - return String2Wstring(content); + char buffer[MAX_PATH] = { 0 }; + HMODULE hModule = reinterpret_cast(&__ImageBase); + GetModuleFileNameA(hModule, buffer, MAX_PATH); + fs::path modulePath(buffer); + return modulePath.parent_path(); } -static bool ShowDisclaimer() +static bool show_disclaimer() { - if (std::filesystem::exists(DISCLAIMER_FILE)) { + fs::path sdk_path = get_module_directory(); + if (fs::exists(sdk_path / DISCLAIMER_FLAG)) { return true; } - std::optional disclaimerTextOpt = ReadDisclaimerText(DISCLAIMER_TEXT_FILE); - if (!disclaimerTextOpt.has_value() || disclaimerTextOpt->empty()) { - MessageBox(NULL, L"免责声明文件为空或读取失败。", L"错误", MB_ICONERROR); + fs::path path = sdk_path / DISCLAIMER_TEXT_FILE; + std::ifstream file(path, std::ios::binary); + if (!file.is_open()) { + util::MsgBox(NULL, "免责声明文件读取失败。", "错误", MB_ICONERROR); return false; } - std::wstring disclaimerText = *disclaimerTextOpt; - - int result = MessageBox(NULL, disclaimerText.c_str(), L"免责声明", MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2); + auto disclaimerText = std::string((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + if (disclaimerText.empty()) { + util::MsgBox(NULL, "免责声明文件为空", "错误", MB_ICONERROR); + return false; + } + int result = util::MsgBox(NULL, disclaimerText.c_str(), "免责声明", MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2); if (result == IDCANCEL) { - MessageBox(NULL, L"您拒绝了免责声明,程序将退出。", L"提示", MB_ICONINFORMATION); + util::MsgBox(NULL, "您拒绝了免责声明,程序将退出。", "提示", MB_ICONINFORMATION); return false; } - std::ofstream flagFile(DISCLAIMER_FILE, std::ios::out | std::ios::trunc); + std::ofstream flagFile(sdk_path / DISCLAIMER_FLAG, std::ios::out | std::ios::trunc); if (!flagFile) { - MessageBox(NULL, L"无法创建协议标志文件。", L"错误", MB_ICONERROR); + util::MsgBox(NULL, "无法创建协议标志文件。", "错误", MB_ICONERROR); return false; } flagFile << "User accepted the license agreement."; @@ -62,82 +75,79 @@ static bool ShowDisclaimer() return true; } -static std::wstring GetDllPath(bool debug) +static std::string get_dll_path(bool debug) { - WCHAR buffer[MAX_PATH] = { 0 }; - GetModuleFileName(GetModuleHandle(WCFSDKDLL), buffer, MAX_PATH); - - std::filesystem::path path(buffer); - path.remove_filename(); // 移除文件名,保留目录路径 + char buffer[MAX_PATH] = { 0 }; + GetModuleFileNameA(GetModuleHandleA(WCFSDKDLL), buffer, MAX_PATH); + fs::path path(buffer); + path.remove_filename(); // 只保留目录路径 path /= debug ? WCFSPYDLL_DEBUG : WCFSPYDLL; - if (!std::filesystem::exists(path)) { - MessageBox(NULL, path.c_str(), L"文件不存在", MB_ICONERROR); - return L""; + if (!fs::exists(path)) { + util::MsgBox(NULL, path.string().c_str(), "文件不存在", MB_ICONERROR); + return ""; } - return path.wstring(); + return path.string(); } int WxInitSDK(bool debug, int port) { - if (!ShowDisclaimer()) { + if (!show_disclaimer()) { exit(-1); // 用户拒绝协议,退出程序 } int status = 0; DWORD wcPid = 0; - spyDllPath = GetDllPath(debug); + spyDllPath = get_dll_path(debug); if (spyDllPath.empty()) { return ERROR_FILE_NOT_FOUND; // DLL 文件路径不存在 } - status = OpenWeChat(&wcPid); + status = util::open_wechat(wcPid); if (status != 0) { - MessageBox(NULL, L"打开微信失败", L"WxInitSDK", 0); + util::MsgBox(NULL, "打开微信失败", "WxInitSDK", 0); return status; } - if (!IsProcessX64(wcPid)) { - MessageBox(NULL, L"只支持 64 位微信", L"WxInitSDK", 0); - return -1; - } - std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待微信打开 - wcProcess = InjectDll(wcPid, spyDllPath.c_str(), &spyBase); + wcProcess = inject_dll(wcPid, spyDllPath, &spyBase); if (wcProcess == NULL) { - MessageBox(NULL, L"注入失败", L"WxInitSDK", 0); + util::MsgBox(NULL, "注入失败", "WxInitSDK", 0); return -1; } - - PortPath_t pp = { 0 }; - pp.port = port; - sprintf_s(pp.path, MAX_PATH, "%s", std::filesystem::current_path().string().c_str()); - - if (!CallDllFuncEx(wcProcess, spyDllPath.c_str(), spyBase, "InitSpy", (LPVOID)&pp, sizeof(PortPath_t), NULL)) { - MessageBox(NULL, L"初始化失败", L"WxInitSDK", 0); - return -1; - } - injected = true; - return 0; + + util::PortPath pp = { 0 }; + pp.port = port; + snprintf(pp.path, MAX_PATH, "%s", fs::current_path().string().c_str()); + + status = -3; // TODO: 统一错误码 + bool success = call_dll_func_ex(wcProcess, spyDllPath, spyBase, "InitSpy", (LPVOID)&pp, sizeof(util::PortPath), + (DWORD *)&status); + if (!success || status != 0) { + WxDestroySDK(); + } + + return status; } int WxDestroySDK() { if (!injected) { + return 1; // 未注入 + } + + if (!call_dll_func(wcProcess, spyDllPath, spyBase, "CleanupSpy", NULL)) { return -1; } - if (!CallDllFunc(wcProcess, spyDllPath.c_str(), spyBase, "CleanupSpy", NULL)) { + if (!eject_dll(wcProcess, spyBase)) { return -2; } - - if (!EjectDll(wcProcess, spyBase)) { - return -3; // TODO: Unify error codes - } + injected = false; return 0; } diff --git a/WeChatFerry/spy/Spy.vcxproj b/WeChatFerry/spy/Spy.vcxproj index 6a66d2d..0854bf5 100644 --- a/WeChatFerry/spy/Spy.vcxproj +++ b/WeChatFerry/spy/Spy.vcxproj @@ -247,19 +247,20 @@ xcopy /y $(SolutionDir)DISCLAIMER.md $(SolutionDir)..\clients\python\wcferry - - - + + + - - + + + - + - + @@ -268,16 +269,16 @@ xcopy /y $(SolutionDir)DISCLAIMER.md $(SolutionDir)..\clients\python\wcferry - - + + - - - + + + - + - + diff --git a/WeChatFerry/spy/Spy.vcxproj.filters b/WeChatFerry/spy/Spy.vcxproj.filters index 3bf11bb..a2a537b 100644 --- a/WeChatFerry/spy/Spy.vcxproj.filters +++ b/WeChatFerry/spy/Spy.vcxproj.filters @@ -24,16 +24,16 @@ 头文件 - + 头文件 - + 头文件 - + 头文件 - + 头文件 @@ -63,16 +63,16 @@ nnrpc - + 头文件 - + 头文件 头文件 - + 头文件 @@ -87,6 +87,9 @@ 头文件 + + 头文件 + @@ -95,16 +98,16 @@ 源文件 - + 源文件 - + 源文件 - + 源文件 - + 源文件 @@ -125,13 +128,13 @@ nnrpc - + 源文件 - + 源文件 - + 源文件 diff --git a/WeChatFerry/spy/account_manager.cpp b/WeChatFerry/spy/account_manager.cpp new file mode 100644 index 0000000..b7c0306 --- /dev/null +++ b/WeChatFerry/spy/account_manager.cpp @@ -0,0 +1,110 @@ +#include "account_manager.h" + +#include + +#include "log.hpp" +#include "offsets.h" +#include "rpc_helper.h" +#include "spy.h" +#include "util.h" + +namespace account +{ + +namespace fs = std::filesystem; +namespace OsAcc = Offsets::Account; + +using get_account_service_t = QWORD (*)(); +using get_data_path_t = QWORD (*)(QWORD); + +// 缓存避免重复查询 +static std::optional cachedWxid; +static std::optional cachedHomePath; + +// 清除缓存 +static void clear_cached_wxid() { cachedWxid.reset(); } +static void clear_cached_home_path() { cachedHomePath.reset(); } + +static uint64_t get_account_service() +{ + static auto GetService = Spy::getFunction(OsAcc::SERVICE); + return GetService ? GetService() : 0; +} + +static std::string get_string_value(uint64_t base_addr, uint64_t offset) +{ + uint64_t type = util::get_qword(base_addr + offset + 0x18); + return (type == 0xF) ? util::get_p_string(base_addr + offset) : util::get_pp_string(base_addr + offset); +} + +bool is_logged_in() +{ + clear_cached_wxid(); + clear_cached_home_path(); + uint64_t service_addr = get_account_service(); + return service_addr && util::get_qword(service_addr + OsAcc::LOGIN) != 0; +} + +fs::path get_home_path() +{ + if (cachedHomePath) { + return *cachedHomePath; + } + WxString home; + auto GetDataPath = Spy::getFunction(OsAcc::PATH); + int64_t service_addr = get_account_service(); + GetDataPath((QWORD)&home); + if (home.wptr) { + cachedHomePath = util::w2s(std::wstring(home.wptr, home.size)); + } + return *cachedHomePath; +} + +std::string get_self_wxid() +{ + if (cachedWxid) { + return *cachedWxid; + } + uint64_t service_addr = get_account_service(); + if (!service_addr) return ""; + + cachedWxid = get_string_value(service_addr, OsAcc::WXID); + return *cachedWxid; +} + +UserInfo_t get_user_info() +{ + UserInfo_t ui; + uint64_t service_addr = get_account_service(); + if (!service_addr) return ui; + + ui.wxid = get_self_wxid(); + ui.home = get_home_path().generic_string(); + ui.name = get_string_value(service_addr, OsAcc::NAME); + ui.mobile = get_string_value(service_addr, OsAcc::MOBILE); + return ui; +} + +bool rpc_is_logged_in(uint8_t *out, size_t *len) +{ + return fill_response(out, len, [](Response &rsp) { rsp.msg.status = is_logged_in(); }); +} + +bool rpc_get_self_wxid(uint8_t *out, size_t *len) +{ + return fill_response( + out, len, [](Response &rsp) { rsp.msg.str = (char *)get_self_wxid().c_str(); }); +} + +bool rpc_get_user_info(uint8_t *out, size_t *len) +{ + UserInfo_t ui = get_user_info(); + return fill_response(out, len, ui, [](Response &rsp, UserInfo_t &ui) { + rsp.msg.ui.wxid = (char *)ui.wxid.c_str(); + rsp.msg.ui.name = (char *)ui.name.c_str(); + rsp.msg.ui.mobile = (char *)ui.mobile.c_str(); + rsp.msg.ui.home = (char *)ui.home.c_str(); + }); +} + +} // namespace account diff --git a/WeChatFerry/spy/account_manager.h b/WeChatFerry/spy/account_manager.h new file mode 100644 index 0000000..4eede78 --- /dev/null +++ b/WeChatFerry/spy/account_manager.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +#include "pb_types.h" + +namespace account +{ + +// 登录状态 +bool is_logged_in(); + +// 获取 WeChat 数据存储路径 +std::filesystem::path get_home_path(); + +// 获取自身 wxid +std::string get_self_wxid(); + +// 获取用户信息 +UserInfo_t get_user_info(); + +// RPC 方法 +bool rpc_is_logged_in(uint8_t *out, size_t *len); +bool rpc_get_self_wxid(uint8_t *out, size_t *len); +bool rpc_get_user_info(uint8_t *out, size_t *len); + +} // namespace account diff --git a/WeChatFerry/spy/chatroom_manager.cpp b/WeChatFerry/spy/chatroom_manager.cpp new file mode 100644 index 0000000..912bda2 --- /dev/null +++ b/WeChatFerry/spy/chatroom_manager.cpp @@ -0,0 +1,87 @@ +#pragma execution_character_set("utf-8") + +#include "chatroom_manager.h" +#include "log.hpp" +#include "offsets.h" +#include "pb_util.h" +#include "rpc_helper.h" +#include "spy.h" +#include "util.h" + +namespace chatroom +{ +namespace OsRoom = Offsets::Chatroom; + +using get_mgr_t = QWORD (*)(); +using add_member_t = QWORD (*)(QWORD, QWORD, WxString *, QWORD); +using delete_member_t = QWORD (*)(QWORD, QWORD, WxString *); +using invite_members_t = QWORD (*)(const wchar_t *, QWORD, WxString *, QWORD); + +template +bool rpc_chatroom_common(const MemberMgmt &m, uint8_t *out, size_t *len, Func func) +{ + int status = -1; + if (m.wxids && m.roomid) { + status = func(m.roomid, m.wxids); + } else { + LOG_ERROR("wxid 和 roomid 不能为空"); + } + return fill_response(out, len, [&](Response &rsp) { rsp.msg.status = status; }); +} + +int add_chatroom_member(const string &roomid, const string &wxids) +{ + auto get_chatroom_mgr = Spy::getFunction(OsRoom::MGR); + auto add_members = Spy::getFunction(OsRoom::ADD); + + WxString *wx_roomid = util::CreateWxString(roomid); + + QWORD tmp[2] = { 0 }; + auto wx_members = util::parse_wxids(wxids).wxWxids; + QWORD p_members = reinterpret_cast(&wx_members); + + return static_cast(add_members(get_chatroom_mgr(), p_members, wx_roomid, reinterpret_cast(tmp))); +} + +int del_chatroom_member(const string &roomid, const string &wxids) +{ + auto get_chatroom_mgr = Spy::getFunction(OsRoom::MGR); + auto del_members = Spy::getFunction(OsRoom::DEL); + + WxString *wx_roomid = util::CreateWxString(roomid); + auto wx_members = util::parse_wxids(wxids).wxWxids; + QWORD p_members = reinterpret_cast(&wx_members); + + return static_cast(del_members(get_chatroom_mgr(), p_members, wx_roomid)); +} + +int invite_chatroom_member(const string &roomid, const string &wxids) +{ + auto invite_members = Spy::getFunction(OsRoom::INV); + + wstring ws_roomid = util::s2w(roomid); + WxString *wx_roomid = util::CreateWxString(roomid); + + QWORD tmp[2] = { 0 }; + auto wx_members = util::parse_wxids(wxids).wxWxids; + QWORD p_members = reinterpret_cast(&wx_members); + + return static_cast(invite_members(ws_roomid.c_str(), p_members, wx_roomid, reinterpret_cast(tmp))); +} + +bool rpc_add_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len) +{ + return rpc_chatroom_common(m, out, len, add_chatroom_member); +} + +bool rpc_delete_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len) +{ + return rpc_chatroom_common(m, out, len, del_chatroom_member); +} + +bool rpc_invite_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len) +{ + return rpc_chatroom_common(m, out, len, invite_chatroom_member); +} + +} // namespace chatroom diff --git a/WeChatFerry/spy/chatroom_manager.h b/WeChatFerry/spy/chatroom_manager.h new file mode 100644 index 0000000..3be999d --- /dev/null +++ b/WeChatFerry/spy/chatroom_manager.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include "wcf.pb.h" + +namespace chatroom +{ + +// 添加成员到群聊 +int add_chatroom_member(const std::string &roomid, const std::string &wxids); + +// 从群聊中移除成员 +int del_chatroom_member(const std::string &roomid, const std::string &wxids); + +// 邀请成员加入群聊 +int invite_chatroom_member(const std::string &roomid, const std::string &wxids); + +// RPC 方法 +bool rpc_add_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len); +bool rpc_delete_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len); +bool rpc_invite_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len); + +} // namespace chatroom diff --git a/WeChatFerry/spy/chatroom_mgmt.cpp b/WeChatFerry/spy/chatroom_mgmt.cpp index 0950d45..ce36eac 100644 --- a/WeChatFerry/spy/chatroom_mgmt.cpp +++ b/WeChatFerry/spy/chatroom_mgmt.cpp @@ -3,16 +3,16 @@ #include #include "chatroom_mgmt.h" -#include "log.hpp" +#include "log.h" #include "util.h" using namespace std; extern QWORD g_WeChatWinDllAddr; -#define OS_GET_CHATROOM_MGR 0x1B83BD0 -#define OS_ADD_MEMBERS 0x2155100 -#define OS_DELETE_MEMBERS 0x2155740 -#define OS_INVITE_MEMBERS 0x2154AE0 +#define OS_GET_CHATROOM_MGR 0x1B894E0 +#define OS_ADD_MEMBERS 0x215A820 +#define OS_DELETE_MEMBERS 0x215AE60 +#define OS_INVITE_MEMBERS 0x215A200 typedef QWORD (*GetChatRoomMgr_t)(); typedef QWORD (*AddMemberToChatRoom_t)(QWORD, QWORD, QWORD, QWORD); @@ -111,3 +111,4 @@ int InviteChatroomMember(string roomid, string wxids) status = (int)InviteMembers((QWORD)wsRoomid.c_str(), pMembers, (QWORD)pWxRoomid, (QWORD)temp); return status; } + diff --git a/WeChatFerry/spy/chatroom_mgmt.h b/WeChatFerry/spy/chatroom_mgmt.h deleted file mode 100644 index da1eabf..0000000 --- a/WeChatFerry/spy/chatroom_mgmt.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -int AddChatroomMember(std::string roomid, std::string wxids); -int DelChatroomMember(std::string roomid, std::string wxids); -int InviteChatroomMember(std::string roomid, std::string wxids); diff --git a/WeChatFerry/spy/contact_manager.cpp b/WeChatFerry/spy/contact_manager.cpp new file mode 100644 index 0000000..64728ea --- /dev/null +++ b/WeChatFerry/spy/contact_manager.cpp @@ -0,0 +1,131 @@ +#pragma execution_character_set("utf-8") + +#include "contact_manager.h" + +#include "log.hpp" +#include "offsets.h" +#include "pb_util.h" +#include "rpc_helper.h" +#include "spy.h" +#include "util.h" + +using namespace std; + +namespace contact +{ +namespace OsCon = Offsets::Contact; + +using get_contact_mgr_t = QWORD (*)(); +using get_contact_list_t = QWORD (*)(QWORD, QWORD); + +#define FEAT_LEN 5 +static const uint8_t FEAT_COUNTRY[FEAT_LEN] = { 0xA4, 0xD9, 0x02, 0x4A, 0x18 }; +static const uint8_t FEAT_PROVINCE[FEAT_LEN] = { 0xE2, 0xEA, 0xA8, 0xD1, 0x18 }; +static const uint8_t FEAT_CITY[FEAT_LEN] = { 0x1D, 0x02, 0x5B, 0xBF, 0x18 }; + +static QWORD find_mem(QWORD start, QWORD end, const void *target, size_t len) +{ + uint8_t *p = reinterpret_cast(start); + while (reinterpret_cast(p) < end) { + if (memcmp(p, target, len) == 0) { + return reinterpret_cast(p); + } + p++; + } + return 0; +} + +static string get_cnt_string(QWORD start, QWORD end, const uint8_t *feat, size_t len) +{ + QWORD pfeat = find_mem(start, end, feat, len); + if (pfeat == 0) { + return ""; + } + + DWORD lfeat = util::get_dword(pfeat + len); + if (lfeat <= 2) { + return ""; + } + + return util::w2s(util::get_p_wstring(pfeat + FEAT_LEN + 4, lfeat)); +} + +vector get_contacts() +{ + vector contacts; + auto func_get_contact_mgr = Spy::getFunction(OsCon::MGR); + auto func_get_contact_list = Spy::getFunction(OsCon::LIST); + + QWORD mgr = func_get_contact_mgr(); + QWORD addr[3] = { 0 }; + if (func_get_contact_list(mgr, reinterpret_cast(addr)) != 1) { + LOG_ERROR("get_contacts failed"); + return contacts; + } + + QWORD pstart = addr[0]; + QWORD pend = addr[2]; + while (pstart < pend) { + RpcContact_t cnt; + QWORD pbin = util::get_qword(pstart + OsCon::BIN); + QWORD lenbin = util::get_dword(pstart + OsCon::BIN_LEN); + + cnt.wxid = util::get_str_by_wstr_addr(pstart + OsCon::WXID); + cnt.code = util::get_str_by_wstr_addr(pstart + OsCon::CODE); + cnt.remark = util::get_str_by_wstr_addr(pstart + OsCon::REMARK); + cnt.name = util::get_str_by_wstr_addr(pstart + OsCon::NAME); + + cnt.country = get_cnt_string(pbin, pbin + lenbin, FEAT_COUNTRY, FEAT_LEN); + cnt.province = get_cnt_string(pbin, pbin + lenbin, FEAT_PROVINCE, FEAT_LEN); + cnt.city = get_cnt_string(pbin, pbin + lenbin, FEAT_CITY, FEAT_LEN); + + cnt.gender = (pbin == 0) ? 0 : static_cast(*(uint8_t *)(pbin + OsCon::GENDER)); + + contacts.push_back(cnt); + pstart += OsCon::STEP; + } + + return contacts; +} + +int accept_new_friend(const std::string &v3, const std::string &v4, int scene) +{ + LOG_ERROR("技术太菜,实现不了。"); + return -1; // 成功返回 1 +} + +RpcContact_t get_contact_by_wxid(const string &wxid) +{ + RpcContact_t contact; + LOG_ERROR("技术太菜,实现不了。"); + return contact; +} + +bool rpc_get_contacts(uint8_t *out, size_t *len) +{ + vector contacts = get_contacts(); + return fill_response(out, len, [&](Response &rsp) { + rsp.msg.contacts.contacts.funcs.encode = encode_contacts; + rsp.msg.contacts.contacts.arg = &contacts; + }); +} + +bool rpc_get_contact_info(const string &wxid, uint8_t *out, size_t *len) +{ + vector contacts = { get_contact_by_wxid(wxid) }; + return fill_response(out, len, [&](Response &rsp) { + rsp.msg.contacts.contacts.funcs.encode = encode_contacts; + rsp.msg.contacts.contacts.arg = &contacts; + }); +} + +bool rpc_accept_friend(const Verification &v, uint8_t *out, size_t *len) +{ + const string v3 = v.v3 ? v.v3 : ""; + const string v4 = v.v4 ? v.v4 : ""; + int scene = v.scene; + return fill_response( + out, len, [&](Response &rsp) { rsp.msg.status = accept_new_friend(v3, v4, scene); }); +} + +} // namespace contact diff --git a/WeChatFerry/spy/contact_manager.h b/WeChatFerry/spy/contact_manager.h new file mode 100644 index 0000000..440091e --- /dev/null +++ b/WeChatFerry/spy/contact_manager.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "wcf.pb.h" + +#include "pb_types.h" + +namespace contact +{ + +// 获取所有联系人 +std::vector get_contacts(); + +// 根据 wxid 获取联系人信息 +RpcContact_t get_contact_by_wxid(const std::string &wxid); + +// 接受好友请求 +int accept_new_friend(const std::string &v3, const std::string &v4, int scene); + +// 发送好友请求 +// int add_friend_by_wxid(const std::string &wxid, const std::string &msg); + +// RPC 方法 +bool rpc_get_contacts(uint8_t *out, size_t *len); +bool rpc_get_contact_info(const std::string &wxid, uint8_t *out, size_t *len); +bool rpc_accept_friend(const Verification &v, uint8_t *out, size_t *len); + +} // namespace contact diff --git a/WeChatFerry/spy/contact_mgmt.cpp b/WeChatFerry/spy/contact_mgmt.cpp deleted file mode 100644 index 84f916a..0000000 --- a/WeChatFerry/spy/contact_mgmt.cpp +++ /dev/null @@ -1,192 +0,0 @@ -#pragma execution_character_set("utf-8") - -#include "contact_mgmt.h" -#include "log.hpp" -#include "util.h" - -using namespace std; -extern QWORD g_WeChatWinDllAddr; - -#define OS_GET_CONTACT_MGR 0x1B417A0 -#define OS_GET_CONTACT_LIST 0x219ED10 -#define OS_CONTACT_BIN 0x200 -#define OS_CONTACT_BIN_LEN 0x208 -#define OS_CONTACT_WXID 0x10 -#define OS_CONTACT_CODE 0x30 -#define OS_CONTACT_REMARK 0x80 -#define OS_CONTACT_NAME 0xA0 -#define OS_CONTACT_GENDER 0x0E -#define OS_CONTACT_STEP 0x6A8 - -typedef QWORD (*GetContactMgr_t)(); -typedef QWORD (*GetContactList_t)(QWORD, QWORD); - -#define FEAT_LEN 5 -static const uint8_t FEAT_COUNTRY[FEAT_LEN] = { 0xA4, 0xD9, 0x02, 0x4A, 0x18 }; -static const uint8_t FEAT_PROVINCE[FEAT_LEN] = { 0xE2, 0xEA, 0xA8, 0xD1, 0x18 }; -static const uint8_t FEAT_CITY[FEAT_LEN] = { 0x1D, 0x02, 0x5B, 0xBF, 0x18 }; - -static QWORD FindMem(QWORD start, QWORD end, const void *target, size_t len) -{ - uint8_t *p = (uint8_t *)start; - while ((QWORD)p < end) { - if (memcmp((void *)p, target, len) == 0) { - return (QWORD)p; - } - p++; - } - - return 0; -} - -static string GetCntString(QWORD start, QWORD end, const uint8_t *feat, size_t len) -{ - QWORD pfeat = FindMem(start, end, feat, len); - if (pfeat == 0) { - return ""; - } - - DWORD lfeat = GET_DWORD(pfeat + len); - if (lfeat <= 2) { - return ""; - } - - return Wstring2String(wstring(GET_WSTRING_FROM_P(pfeat + FEAT_LEN + 4), lfeat)); -} - -vector GetContacts() -{ - vector contacts; - GetContactMgr_t funcGetContactMgr = (GetContactMgr_t)(g_WeChatWinDllAddr + OS_GET_CONTACT_MGR); - GetContactList_t funcGetContactList = (GetContactList_t)(g_WeChatWinDllAddr + OS_GET_CONTACT_LIST); - - QWORD mgr = funcGetContactMgr(); - QWORD addr[3] = { 0 }; - if (funcGetContactList(mgr, (QWORD)addr) != 1) { - LOG_ERROR("GetContacts failed"); - return contacts; - } - - QWORD pstart = (QWORD)addr[0]; - QWORD pend = (QWORD)addr[2]; - while (pstart < pend) { - RpcContact_t cnt; - QWORD pbin = GET_QWORD(pstart + OS_CONTACT_BIN); - QWORD lenbin = GET_DWORD(pstart + OS_CONTACT_BIN_LEN); - - cnt.wxid = GetStringByWstrAddr(pstart + OS_CONTACT_WXID); - cnt.code = GetStringByWstrAddr(pstart + OS_CONTACT_CODE); - cnt.remark = GetStringByWstrAddr(pstart + OS_CONTACT_REMARK); - cnt.name = GetStringByWstrAddr(pstart + OS_CONTACT_NAME); - - cnt.country = GetCntString(pbin, pbin + lenbin, FEAT_COUNTRY, FEAT_LEN); - cnt.province = GetCntString(pbin, pbin + lenbin, FEAT_PROVINCE, FEAT_LEN); - cnt.city = GetCntString(pbin, pbin + lenbin, FEAT_CITY, FEAT_LEN); - - if (pbin == 0) { - cnt.gender = 0; - } else { - cnt.gender = (DWORD) * (uint8_t *)(pbin + OS_CONTACT_GENDER); - } - - contacts.push_back(cnt); - pstart += OS_CONTACT_STEP; - } - - return contacts; -} - -#if 0 -int AcceptNewFriend(string v3, string v4, int scene) -{ - int success = 0; - - DWORD acceptNewFriendCall1 = g_WeChatWinDllAddr + g_WxCalls.anf.call1; - DWORD acceptNewFriendCall2 = g_WeChatWinDllAddr + g_WxCalls.anf.call2; - DWORD acceptNewFriendCall3 = g_WeChatWinDllAddr + g_WxCalls.anf.call3; - DWORD acceptNewFriendCall4 = g_WeChatWinDllAddr + g_WxCalls.anf.call4; - - char buffer[0x40] = { 0 }; - char nullbuffer[0x3CC] = { 0 }; - - LOG_DEBUG("\nv3: {}\nv4: {}\nscene: {}", v3, v4, scene); - - wstring wsV3 = String2Wstring(v3); - wstring wsV4 = String2Wstring(v4); - WxString wxV3(wsV3); - WxString wxV4(wsV4); - - __asm { - pushad; - pushfd; - lea ecx, buffer; - call acceptNewFriendCall1; - mov esi, 0x0; - mov edi, scene; - push esi; - push edi; - sub esp, 0x14; - mov ecx, esp; - lea eax, wxV4; - push eax; - call acceptNewFriendCall2; - sub esp, 0x8; - push 0x0; - lea eax, nullbuffer; - push eax; - lea eax, wxV3; - push eax; - lea ecx, buffer; - call acceptNewFriendCall3; - mov success, eax; - lea ecx, buffer; - call acceptNewFriendCall4; - popfd; - popad; - } - - return success; // 成功返回 1 -} - -/*没啥用,非好友获取不到*/ -RpcContact_t GetContactByWxid(string wxid) -{ - RpcContact_t contact; - char buff[0x440] = { 0 }; - wstring wsWxid = String2Wstring(wxid); - WxString pri(wsWxid); - DWORD contact_mgr_addr = g_WeChatWinDllAddr + 0x75A4A0; - DWORD get_contact_addr = g_WeChatWinDllAddr + 0xC04E00; - DWORD free_contact_addr = g_WeChatWinDllAddr + 0xEA7880; - __asm { - PUSHAD - PUSHFD - CALL contact_mgr_addr - LEA ECX,buff - PUSH ECX - LEA ECX,pri - PUSH ECX - MOV ECX,EAX - CALL get_contact_addr - POPFD - POPAD - } - - contact.wxid = wxid; - contact.code = GetStringByWstrAddr((DWORD)buff + g_WxCalls.contact.wxCode); - contact.remark = GetStringByWstrAddr((DWORD)buff + g_WxCalls.contact.wxRemark); - contact.name = GetStringByWstrAddr((DWORD)buff + g_WxCalls.contact.wxName); - contact.gender = GET_DWORD((DWORD)buff + 0x148); - - __asm { - PUSHAD - PUSHFD - LEA ECX,buff - CALL free_contact_addr - POPFD - POPAD - } - - return contact; -} -#endif diff --git a/WeChatFerry/spy/contact_mgmt.h b/WeChatFerry/spy/contact_mgmt.h deleted file mode 100644 index 460bf89..0000000 --- a/WeChatFerry/spy/contact_mgmt.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "string" -#include - -#include "pb_types.h" - -vector GetContacts(); -int AcceptNewFriend(std::string v3, std::string v4, int scene); -int AddFriendByWxid(std::string wxid, std::string msg); -RpcContact_t GetContactByWxid(std::string wxid); diff --git a/WeChatFerry/spy/database_executor.cpp b/WeChatFerry/spy/database_executor.cpp new file mode 100644 index 0000000..42e0d9a --- /dev/null +++ b/WeChatFerry/spy/database_executor.cpp @@ -0,0 +1,277 @@ +#include "database_executor.h" + +#include +#include + +#include "log.hpp" +#include "offsets.h" +#include "pb_util.h" +#include "rpc_helper.h" +#include "spy.h" +#include "sqlite3.h" +#include "util.h" + +namespace db +{ +namespace OsDb = Offsets::Db; + +using db_map_t = std::map; +static db_map_t db_map; + +static void get_db_handle(QWORD base, QWORD offset) +{ + auto *wsp = reinterpret_cast(*(QWORD *)(base + offset + OsDb::NAME)); + std::string dbname = util::w2s(std::wstring(wsp)); + db_map[dbname] = util::get_qword(base + offset); +} + +static void get_msg_db_handle(QWORD msg_mgr_addr) +{ + QWORD db_index = util::get_qword(msg_mgr_addr + 0x68); + QWORD p_start = util::get_qword(msg_mgr_addr + 0x50); + for (uint32_t i = 0; i < db_index; i++) { + QWORD db_addr = util::get_qword(p_start + i * 0x08); + if (db_addr) { + // MSGi.db + std::string dbname = util::w2s(util::get_pp_wstring(db_addr)); + db_map[dbname] = util::get_qword(db_addr + 0x78); + + // MediaMsgi.db + QWORD mmdb_addr = util::get_qword(db_addr + 0x20); + std::string mmdbname = util::w2s(util::get_pp_wstring(mmdb_addr + 0x78)); + db_map[mmdbname] = util::get_qword(mmdb_addr + 0x50); + } + } +} + +db_map_t get_db_handles() +{ + db_map.clear(); + QWORD db_instance_addr = util::get_qword(Spy::WeChatDll.load() + OsDb::INSTANCE); + + get_db_handle(db_instance_addr, OsDb::MICROMSG); // MicroMsg.db + get_db_handle(db_instance_addr, OsDb::CHAT_MSG); // ChatMsg.db + get_db_handle(db_instance_addr, OsDb::MISC); // Misc.db + get_db_handle(db_instance_addr, OsDb::EMOTION); // Emotion.db + get_db_handle(db_instance_addr, OsDb::MEDIA); // Media.db + get_db_handle(db_instance_addr, OsDb::FUNCTION_MSG); // Function.db + + get_msg_db_handle(util::get_qword(Spy::WeChatDll.load() + OsDb::MSG_I)); // MSGi.db & MediaMsgi.db + return db_map; +} + +DbNames_t get_db_names() +{ + if (db_map.empty()) { + db_map = get_db_handles(); + } + + DbNames_t names; + for (const auto &[k, _] : db_map) { + names.push_back(k); + } + return names; +} + +static int cb_get_tables(void *ret, int argc, char **argv, char **azColName) +{ + auto *tables = static_cast(ret); + DbTable_t tbl; + for (int i = 0; i < argc; i++) { + if (strcmp(azColName[i], "name") == 0) { + tbl.name = argv[i] ? argv[i] : ""; + } else if (strcmp(azColName[i], "sql") == 0) { + std::string sql(argv[i]); + sql.erase(std::remove(sql.begin(), sql.end(), '\t'), sql.end()); + tbl.sql = sql; + } + } + tables->push_back(tbl); + return 0; +} + +DbTables_t get_db_tables(const std::string &db) +{ + DbTables_t tables; + if (db_map.empty()) { + db_map = get_db_handles(); + } + + auto it = db_map.find(db); + if (it == db_map.end()) { + return tables; + } + + constexpr const char *sql = "SELECT name FROM sqlite_master WHERE type='table';"; + auto p_sqlite3_exec = Spy::getFunction(OsDb::EXEC); + p_sqlite3_exec(it->second, sql, (Sqlite3_callback)cb_get_tables, (void *)&tables, nullptr); + + return tables; +} + +DbRows_t exec_db_query(const std::string &db, const std::string &sql) +{ + DbRows_t rows; + + auto func_prepare = Spy::getFunction(OsDb::PREPARE); + auto func_step = Spy::getFunction(OsDb::STEP); + auto func_column_count = Spy::getFunction(OsDb::COLUMN_COUNT); + auto func_column_name = Spy::getFunction(OsDb::COLUMN_NAME); + auto func_column_type = Spy::getFunction(OsDb::COLUMN_TYPE); + auto func_column_blob = Spy::getFunction(OsDb::COLUMN_BLOB); + auto func_column_bytes = Spy::getFunction(OsDb::COLUMN_BYTES); + auto func_finalize = Spy::getFunction(OsDb::FINALIZE); + + if (db_map.empty()) { + db_map = get_db_handles(); + } + + auto it = db_map.find(db); + if (it == db_map.end() || it->second == 0) { + LOG_WARN("Empty handle for database '{}', retrying...", db); + db_map = get_db_handles(); + it = db_map.find(db); + if (it == db_map.end() || it->second == 0) { + LOG_ERROR("Failed to get handle for database '{}'", db); + return rows; + } + } + + QWORD *stmt; + int rc = func_prepare(it->second, sql.c_str(), -1, &stmt, nullptr); + if (rc != SQLITE_OK) { + LOG_ERROR("SQL prepare failed for '{}': error code {}", db, rc); + return rows; + } + + while (func_step(stmt) == SQLITE_ROW) { + DbRow_t row; + int col_count = func_column_count(stmt); + for (int i = 0; i < col_count; i++) { + DbField_t field; + field.type = func_column_type(stmt, i); + field.column = func_column_name(stmt, i); + + int length = func_column_bytes(stmt, i); + const void *blob = func_column_blob(stmt, i); + if (length > 0 && field.type != SQLITE_NULL) { + field.content.resize(length); + std::memcpy(field.content.data(), blob, length); + } + row.push_back(field); + } + rows.push_back(row); + } + + func_finalize(stmt); + return rows; +} + +int get_local_id_and_dbidx(uint64_t id, uint64_t *local_id, uint32_t *db_idx) +{ + if (!local_id || !db_idx) { + LOG_ERROR("Invalid pointer arguments!"); + return -1; + } + + QWORD msg_mgr_addr = util::get_qword(Spy::WeChatDll.load() + OsDb::MSG_I); + int db_index = static_cast(util::get_qword(msg_mgr_addr + 0x68)); // 总不能 int 还不够吧? + QWORD p_start = util::get_qword(msg_mgr_addr + 0x50); + + *db_idx = 0; + for (int i = db_index - 1; i >= 0; i--) { // 从后往前遍历 + QWORD db_addr = util::get_qword(p_start + i * 0x08); + if (!db_addr) { + continue; + } + + std::string dbname = util::w2s(util::get_pp_wstring(db_addr)); + db_map[dbname] = util::get_qword(db_addr + 0x78); + + std::string sql = "SELECT localId FROM MSG WHERE MsgSvrID=" + std::to_string(id) + ";"; + DbRows_t rows = exec_db_query(dbname, sql); + + if (rows.empty() || rows.front().empty()) { + continue; + } + + const DbField_t &field = rows.front().front(); + if (field.column != "localId" || field.type != SQLITE_INTEGER) { + continue; + } + + std::string id_str(field.content.begin(), field.content.end()); + try { + *local_id = std::stoull(id_str); + } catch (const std::exception &e) { + LOG_ERROR("Failed to parse localId: {}", e.what()); + continue; + } + + *db_idx = static_cast(util::get_qword(util::get_qword(db_addr + 0x28) + 0x1E8) >> 32); + return 0; + } + + return -1; +} + +std::vector get_audio_data(uint64_t id) +{ + QWORD msg_mgr_addr = util::get_qword(Spy::WeChatDll.load() + OsDb::MSG_I); + int db_index = static_cast(util::get_qword(msg_mgr_addr + 0x68)); + + std::string sql = "SELECT Buf FROM Media WHERE Reserved0=" + std::to_string(id) + ";"; + for (int i = db_index - 1; i >= 0; i--) { + std::string dbname = "MediaMSG" + std::to_string(i) + ".db"; + DbRows_t rows = exec_db_query(dbname, sql); + + if (rows.empty() || rows.front().empty()) { + continue; + } + + const DbField_t &field = rows.front().front(); + if (field.column != "Buf" || field.content.empty()) { + continue; + } + + // 首字节为 0x02,估计是混淆用的?去掉。 + if (field.content.front() == 0x02) { + return std::vector(field.content.begin() + 1, field.content.end()); + } + + return field.content; + } + + return {}; +} + +bool rpc_get_db_names(uint8_t *out, size_t *len) +{ + DbNames_t names = get_db_names(); + return fill_response(out, len, [&](Response &rsp) { + rsp.msg.dbs.names.funcs.encode = encode_dbnames; + rsp.msg.dbs.names.arg = &names; + }); +} + +bool rpc_get_db_tables(const std::string &db, uint8_t *out, size_t *len) +{ + DbTables_t tables = get_db_tables(db); + return fill_response(out, len, [&](Response &rsp) { + rsp.msg.tables.tables.funcs.encode = encode_tables; + rsp.msg.tables.tables.arg = &tables; + }); +} + +bool rpc_exec_db_query(const DbQuery query, uint8_t *out, size_t *len) +{ + const std::string db(query.db); + const std::string sql(query.sql); + DbRows_t rows = exec_db_query(db, sql); + return fill_response(out, len, [&](Response &rsp) { + rsp.msg.rows.rows.funcs.encode = encode_rows; + rsp.msg.rows.rows.arg = &rows; + }); +} + +} // namespace db diff --git a/WeChatFerry/spy/database_executor.h b/WeChatFerry/spy/database_executor.h new file mode 100644 index 0000000..0d50373 --- /dev/null +++ b/WeChatFerry/spy/database_executor.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +#include "wcf.pb.h" + +#include "pb_types.h" + +namespace db +{ + +// 获取数据库名称列表 +DbNames_t get_db_names(); + +// 获取指定数据库的表列表 +DbTables_t get_db_tables(const std::string &db); + +// 执行 SQL 查询 +DbRows_t exec_db_query(const std::string &db, const std::string &sql); + +// 获取本地消息 ID 和数据库索引 +int get_local_id_and_dbidx(uint64_t id, uint64_t *local_id, uint32_t *db_idx); + +// 获取音频数据 +std::vector get_audio_data(uint64_t msg_id); + +// RPC 方法 +bool rpc_get_db_names(uint8_t *out, size_t *len); +bool rpc_get_db_tables(const std::string &db, uint8_t *out, size_t *len); +bool rpc_exec_db_query(const DbQuery query, uint8_t *out, size_t *len); + +} // namespace db diff --git a/WeChatFerry/spy/dllmain.cpp b/WeChatFerry/spy/dllmain.cpp index 580ec4c..66475e4 100644 --- a/WeChatFerry/spy/dllmain.cpp +++ b/WeChatFerry/spy/dllmain.cpp @@ -1,9 +1,5 @@ // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "framework.h" -#include -#include - -#include "spy.h" BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { diff --git a/WeChatFerry/spy/exec_sql.cpp b/WeChatFerry/spy/exec_sql.cpp deleted file mode 100644 index 834c856..0000000 --- a/WeChatFerry/spy/exec_sql.cpp +++ /dev/null @@ -1,232 +0,0 @@ -#include - -#include "exec_sql.h" -#include "log.hpp" -#include "sqlite3.h" -#include "util.h" - -#define OFFSET_DB_INSTANCE 0x5902000 -#define OFFSET_DB_MICROMSG 0xB8 -#define OFFSET_DB_CHAT_MSG 0x2C8 -#define OFFSET_DB_MISC 0x5F0 -#define OFFSET_DB_EMOTION 0x15F0 -#define OFFSET_DB_MEDIA 0xF48 -#define OFFSET_DB_BIZCHAT_MSG 0x1A70 -#define OFFSET_DB_FUNCTION_MSG 0x1B98 -#define OFFSET_DB_NAME 0x28 -#define OFFSET_DB_MSG_MGR 0x595F900 - -extern UINT64 g_WeChatWinDllAddr; - -typedef map dbMap_t; -static dbMap_t dbMap; - -static void GetDbHandle(QWORD base, QWORD offset) -{ - wchar_t *wsp = (wchar_t *)(*(QWORD *)(base + offset + OFFSET_DB_NAME)); - string dbname = Wstring2String(wstring(wsp)); - dbMap[dbname] = GET_QWORD(base + offset); -} - -static void GetMsgDbHandle(QWORD msgMgrAddr) -{ - QWORD dbIndex = GET_QWORD(msgMgrAddr + 0x68); - QWORD pStart = GET_QWORD(msgMgrAddr + 0x50); - for (uint32_t i = 0; i < dbIndex; i++) { - QWORD dbAddr = GET_QWORD(pStart + i * 0x08); - if (dbAddr) { - // MSGi.db - string dbname = Wstring2String(GET_WSTRING(dbAddr)); - dbMap[dbname] = GET_QWORD(dbAddr + 0x78); - - // MediaMsgi.db - QWORD mmdbAddr = GET_QWORD(dbAddr + 0x20); - string mmdbname = Wstring2String(GET_WSTRING(mmdbAddr + 0x78)); - dbMap[mmdbname] = GET_QWORD(mmdbAddr + 0x50); - } - } -} - -dbMap_t GetDbHandles() -{ - dbMap.clear(); - - QWORD dbInstanceAddr = GET_QWORD(g_WeChatWinDllAddr + OFFSET_DB_INSTANCE); - - GetDbHandle(dbInstanceAddr, OFFSET_DB_MICROMSG); // MicroMsg.db - GetDbHandle(dbInstanceAddr, OFFSET_DB_CHAT_MSG); // ChatMsg.db - GetDbHandle(dbInstanceAddr, OFFSET_DB_MISC); // Misc.db - GetDbHandle(dbInstanceAddr, OFFSET_DB_EMOTION); // Emotion.db - GetDbHandle(dbInstanceAddr, OFFSET_DB_MEDIA); // Media.db - GetDbHandle(dbInstanceAddr, OFFSET_DB_FUNCTION_MSG); // Function.db - - GetMsgDbHandle(GET_QWORD(g_WeChatWinDllAddr + OFFSET_DB_MSG_MGR)); // MSGi.db & MediaMsgi.db - - return dbMap; -} - -DbNames_t GetDbNames() -{ - DbNames_t names; - if (dbMap.empty()) { - dbMap = GetDbHandles(); - } - - for (auto &[k, v] : dbMap) { - names.push_back(k); - } - - return names; -} - -static int cbGetTables(void *ret, int argc, char **argv, char **azColName) -{ - DbTables_t *tbls = (DbTables_t *)ret; - DbTable_t tbl; - for (int i = 0; i < argc; i++) { - if (strcmp(azColName[i], "name") == 0) { - tbl.name = argv[i] ? argv[i] : ""; - } else if (strcmp(azColName[i], "sql") == 0) { - string sql(argv[i]); - sql.erase(std::remove(sql.begin(), sql.end(), '\t'), sql.end()); - tbl.sql = sql.c_str(); - } - } - tbls->push_back(tbl); - return 0; -} - -DbTables_t GetDbTables(const string db) -{ - DbTables_t tables; - if (dbMap.empty()) { - dbMap = GetDbHandles(); - } - - auto it = dbMap.find(db); - if (it == dbMap.end()) { - return tables; // DB not found - } - - const char *sql = "select name, sql from sqlite_master where type=\"table\";"; - Sqlite3_exec p_Sqlite3_exec = (Sqlite3_exec)(g_WeChatWinDllAddr + SQLITE3_EXEC_OFFSET); - - p_Sqlite3_exec(it->second, sql, (Sqlite3_callback)cbGetTables, (void *)&tables, 0); - - return tables; -} - -DbRows_t ExecDbQuery(const string db, const string sql) -{ - DbRows_t rows; - Sqlite3_prepare func_prepare = (Sqlite3_prepare)(g_WeChatWinDllAddr + SQLITE3_PREPARE_OFFSET); - Sqlite3_step func_step = (Sqlite3_step)(g_WeChatWinDllAddr + SQLITE3_STEP_OFFSET); - Sqlite3_column_count func_column_count = (Sqlite3_column_count)(g_WeChatWinDllAddr + SQLITE3_COLUMN_COUNT_OFFSET); - Sqlite3_column_name func_column_name = (Sqlite3_column_name)(g_WeChatWinDllAddr + SQLITE3_COLUMN_NAME_OFFSET); - Sqlite3_column_type func_column_type = (Sqlite3_column_type)(g_WeChatWinDllAddr + SQLITE3_COLUMN_TYPE_OFFSET); - Sqlite3_column_blob func_column_blob = (Sqlite3_column_blob)(g_WeChatWinDllAddr + SQLITE3_COLUMN_BLOB_OFFSET); - Sqlite3_column_bytes func_column_bytes = (Sqlite3_column_bytes)(g_WeChatWinDllAddr + SQLITE3_COLUMN_BYTES_OFFSET); - Sqlite3_finalize func_finalize = (Sqlite3_finalize)(g_WeChatWinDllAddr + SQLITE3_FINALIZE_OFFSET); - - if (dbMap.empty()) { - dbMap = GetDbHandles(); - } - - QWORD *stmt; - QWORD handle = dbMap[db]; - if (handle == 0) { - LOG_WARN("Empty handle, retrying..."); - dbMap = GetDbHandles(); - } - - int rc = func_prepare(dbMap[db], sql.c_str(), -1, &stmt, 0); - if (rc != SQLITE_OK) { - return rows; - } - - while (func_step(stmt) == SQLITE_ROW) { - DbRow_t row; - int col_count = func_column_count(stmt); - for (int i = 0; i < col_count; i++) { - DbField_t field; - field.type = func_column_type(stmt, i); - field.column = func_column_name(stmt, i); - - int length = func_column_bytes(stmt, i); - const void *blob = func_column_blob(stmt, i); - if (length && (field.type != 5)) { - field.content.reserve(length); - copy((uint8_t *)blob, (uint8_t *)blob + length, back_inserter(field.content)); - } - row.push_back(field); - } - rows.push_back(row); - } - return rows; -} - -int GetLocalIdandDbidx(uint64_t id, uint64_t *localId, uint32_t *dbIdx) -{ - QWORD msgMgrAddr = GET_QWORD(g_WeChatWinDllAddr + OFFSET_DB_MSG_MGR); - int dbIndex = (int)GET_QWORD(msgMgrAddr + 0x68); // 总不能 int 还不够吧? - QWORD pStart = GET_QWORD(msgMgrAddr + 0x50); - - *dbIdx = 0; - for (int i = dbIndex - 1; i >= 0; i--) { // 从后往前遍历 - QWORD dbAddr = GET_QWORD(pStart + i * 0x08); - if (dbAddr) { - string dbname = Wstring2String(GET_WSTRING(dbAddr)); - dbMap[dbname] = GET_QWORD(dbAddr + 0x78); - string sql = "SELECT localId FROM MSG WHERE MsgSvrID=" + to_string(id) + ";"; - DbRows_t rows = ExecDbQuery(dbname, sql); - if (rows.empty()) { - continue; - } - DbRow_t row = rows.front(); - if (row.empty()) { - continue; - } - DbField_t field = row.front(); - if ((field.column.compare("localId") != 0) && (field.type != 1)) { - continue; - } - - *localId = strtoull((const char *)(field.content.data()), NULL, 10); - *dbIdx = (uint32_t)(GET_QWORD(GET_QWORD(dbAddr + 0x28) + 0x1E8) >> 32); - - return 0; - } - } - - return -1; -} - -vector GetAudioData(uint64_t id) -{ - QWORD msgMgrAddr = GET_QWORD(g_WeChatWinDllAddr + OFFSET_DB_MSG_MGR); - int dbIndex = (int)GET_QWORD(msgMgrAddr + 0x68); - - string sql = "SELECT Buf FROM Media WHERE Reserved0=" + to_string(id) + ";"; - for (int i = dbIndex - 1; i >= 0; i--) { - string dbname = "MediaMSG" + to_string(i) + ".db"; - DbRows_t rows = ExecDbQuery(dbname, sql); - if (rows.empty()) { - continue; - } - DbRow_t row = rows.front(); - if (row.empty()) { - continue; - } - DbField_t field = row.front(); - if (field.column.compare("Buf") != 0) { - continue; - } - - // 首字节为 0x02,估计是混淆用的?去掉。 - vector rv(field.content.begin() + 1, field.content.end()); - - return rv; - } - - return vector(); -} diff --git a/WeChatFerry/spy/exec_sql.h b/WeChatFerry/spy/exec_sql.h deleted file mode 100644 index ea7fc8f..0000000 --- a/WeChatFerry/spy/exec_sql.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -#include "pb_types.h" - -DbNames_t GetDbNames(); -DbTables_t GetDbTables(const string db); -DbRows_t ExecDbQuery(const string db, const string sql); -int GetLocalIdandDbidx(uint64_t id, uint64_t *localId, uint32_t *dbIdx); -vector GetAudioData(uint64_t msgid); diff --git a/WeChatFerry/spy/funcs.cpp b/WeChatFerry/spy/funcs.cpp deleted file mode 100644 index c12f9b5..0000000 --- a/WeChatFerry/spy/funcs.cpp +++ /dev/null @@ -1,410 +0,0 @@ -#pragma warning(disable : 4244) - -#include "framework.h" -#include -#include - -#include "codec.h" -#include "exec_sql.h" -#include "funcs.h" -#include "log.hpp" -#include "spy_types.h" -#include "util.h" - -using namespace std; -namespace fs = std::filesystem; - -extern bool gIsListeningPyq; -extern QWORD g_WeChatWinDllAddr; - -#define HEADER_PNG1 0x89 -#define HEADER_PNG2 0x50 -#define HEADER_JPG1 0xFF -#define HEADER_JPG2 0xD8 -#define HEADER_GIF1 0x47 -#define HEADER_GIF2 0x49 - -#define OS_LOGIN_STATUS 0x595C9E8 -#define OS_GET_SNS_DATA_MGR 0x21E2200 -#define OS_GET_SNS_FIRST_PAGE 0x2E212D0 -#define OS_GET_SNS_TIMELINE_MGR 0x2DB3390 -#define OS_GET_SNS_NEXT_PAGE 0x2EC8970 -#define OS_NEW_CHAT_MSG 0x1B5E140 -#define OS_FREE_CHAT_MSG 0x1B55850 -#define OS_GET_CHAT_MGR 0x1B876C0 -#define OS_GET_MGR_BY_PREFIX_LOCAL_ID 0x213FB00 -#define OS_GET_PRE_DOWNLOAD_MGR 0x1C0EE70 -#define OS_PUSH_ATTACH_TASK 0x1CDF4E0 -#define OS_LOGIN_QR_CODE 0x59620D8 - -typedef QWORD (*GetSNSDataMgr_t)(); -typedef QWORD (*GetSnsTimeLineMgr_t)(); -typedef QWORD (*GetSNSFirstPage_t)(QWORD, QWORD, QWORD); -typedef QWORD (*GetSNSNextPageScene_t)(QWORD, QWORD); -typedef QWORD (*GetChatMgr_t)(); -typedef QWORD (*NewChatMsg_t)(QWORD); -typedef QWORD (*FreeChatMsg_t)(QWORD); -typedef QWORD (*GetPreDownLoadMgr_t)(); -typedef QWORD (*GetMgrByPrefixLocalId_t)(QWORD, QWORD); -typedef QWORD (*PushAttachTask_t)(QWORD, QWORD, QWORD, QWORD); -typedef QWORD (*GetOCRManager_t)(); -typedef QWORD (*DoOCRTask_t)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); - -int IsLogin(void) { return (int)GET_QWORD(g_WeChatWinDllAddr + OS_LOGIN_STATUS); } - -static string get_key(uint8_t header1, uint8_t header2, uint8_t *key) -{ - // PNG? - *key = HEADER_PNG1 ^ header1; - if ((HEADER_PNG2 ^ *key) == header2) { - return ".png"; - } - - // JPG? - *key = HEADER_JPG1 ^ header1; - if ((HEADER_JPG2 ^ *key) == header2) { - return ".jpg"; - } - - // GIF? - *key = HEADER_GIF1 ^ header1; - if ((HEADER_GIF2 ^ *key) == header2) { - return ".gif"; - } - - return ""; // 错误 -} - -string DecryptImage(string src, string dir) -{ - if (!fs::exists(src)) { - LOG_ERROR("File not exists: {}", src); - return ""; - } - - ifstream in(src.c_str(), ios::binary); - if (!in.is_open()) { - LOG_ERROR("Failed to read file {}", src); - return ""; - } - - filebuf *pfb = in.rdbuf(); - size_t size = pfb->pubseekoff(0, ios::end, ios::in); - pfb->pubseekpos(0, ios::in); - - vector buff; - buff.reserve(size); - char *pBuf = buff.data(); - pfb->sgetn(pBuf, size); - in.close(); - - uint8_t key = 0x00; - string ext = get_key(pBuf[0], pBuf[1], &key); - if (ext.empty()) { - LOG_ERROR("Failed to get key."); - return ""; - } - - for (size_t i = 0; i < size; i++) { - pBuf[i] ^= key; - } - - string dst = ""; - - try { - if (dir.empty()) { - dst = fs::path(src).replace_extension(ext).string(); - } else { - dst = (dir.back() == '\\' || dir.back() == '/') ? dir : (dir + "/"); - - // 判断dir文件夹是否存在,若不存在则创建(否则将无法创建出文件) - if (!fs::exists(dst)) {//判断该文件夹是否存在 - bool success = fs::create_directories(dst); //Windows创建文件夹 - if (!success) { //创建失败 - LOG_ERROR("Failed to mkdir:{}", dst); - return ""; - } - } - - dst += fs::path(src).stem().string() + ext; - } - - replace(dst.begin(), dst.end(), '\\', '/'); - } catch (const std::exception &e) { - LOG_ERROR(GB2312ToUtf8(e.what())); - } catch (...) { - LOG_ERROR("Unknow exception."); - return ""; - } - - ofstream out(dst.c_str(), ios::binary); - if (!out.is_open()) { - LOG_ERROR("Failed to write file {}", dst); - return ""; - } - - out.write(pBuf, size); - out.close(); - - return dst; -} - -static int GetFirstPage() -{ - int status = -1; - - GetSNSDataMgr_t GetSNSDataMgr = (GetSNSDataMgr_t)(g_WeChatWinDllAddr + OS_GET_SNS_DATA_MGR); - GetSNSFirstPage_t GetSNSFirstPage = (GetSNSFirstPage_t)(g_WeChatWinDllAddr + OS_GET_SNS_FIRST_PAGE); - - QWORD buff[16] = { 0 }; - QWORD mgr = GetSNSDataMgr(); - status = (int)GetSNSFirstPage(mgr, (QWORD)buff, 1); - - return status; -} - -static int GetNextPage(QWORD id) -{ - int status = -1; - - GetSnsTimeLineMgr_t GetSnsTimeLineMgr = (GetSnsTimeLineMgr_t)(g_WeChatWinDllAddr + OS_GET_SNS_TIMELINE_MGR); - GetSNSNextPageScene_t GetSNSNextPageScene = (GetSNSNextPageScene_t)(g_WeChatWinDllAddr + OS_GET_SNS_NEXT_PAGE); - - QWORD mgr = GetSnsTimeLineMgr(); - status = (int)GetSNSNextPageScene(mgr, id); - - return status; -} - -int RefreshPyq(QWORD id) -{ - if (!gIsListeningPyq) { - LOG_ERROR("没有启动朋友圈消息接收,参考:enable_receiving_msg"); - return -1; - } - - if (id == 0) { - return GetFirstPage(); - } - - return GetNextPage(id); -} - -/******************************************************************************* - * 都说我不写注释,写一下吧 - * 其实也没啥好写的,就是下载资源 - * 主要介绍一下几个参数: - * id:好理解,消息 id - * thumb:图片或者视频的缩略图路径;如果是视频,后缀为 mp4 后就是存在路径了 - * extra:图片、文件的路径 - *******************************************************************************/ -int DownloadAttach(QWORD id, string thumb, string extra) -{ - int status = -1; - QWORD localId; - uint32_t dbIdx; - - if (fs::exists(extra)) { // 第一道,不重复下载。TODO: 通过文件大小来判断 - return 0; - } - - if (GetLocalIdandDbidx(id, &localId, &dbIdx) != 0) { - LOG_ERROR("Failed to get localId, Please check id: {}", to_string(id)); - return status; - } - - NewChatMsg_t NewChatMsg = (NewChatMsg_t)(g_WeChatWinDllAddr + OS_NEW_CHAT_MSG); - FreeChatMsg_t FreeChatMsg = (FreeChatMsg_t)(g_WeChatWinDllAddr + OS_FREE_CHAT_MSG); - GetChatMgr_t GetChatMgr = (GetChatMgr_t)(g_WeChatWinDllAddr + OS_GET_CHAT_MGR); - GetPreDownLoadMgr_t GetPreDownLoadMgr = (GetPreDownLoadMgr_t)(g_WeChatWinDllAddr + OS_GET_PRE_DOWNLOAD_MGR); - PushAttachTask_t PushAttachTask = (PushAttachTask_t)(g_WeChatWinDllAddr + OS_PUSH_ATTACH_TASK); - GetMgrByPrefixLocalId_t GetMgrByPrefixLocalId - = (GetMgrByPrefixLocalId_t)(g_WeChatWinDllAddr + OS_GET_MGR_BY_PREFIX_LOCAL_ID); - - LARGE_INTEGER l; - l.HighPart = dbIdx; - l.LowPart = (DWORD)localId; - - char *buff = (char *)HeapAlloc(GetProcessHeap(), 0, 0x460); - if (buff == nullptr) { - LOG_ERROR("Failed to allocate memory."); - return status; - } - - QWORD pChatMsg = NewChatMsg((QWORD)buff); - GetChatMgr(); - GetMgrByPrefixLocalId(l.QuadPart, pChatMsg); - - QWORD type = GET_QWORD(buff + 0x38); - - string save_path = ""; - string thumb_path = ""; - - switch (type) { - case 0x03: { // Image: extra - save_path = extra; - break; - } - case 0x3E: - case 0x2B: { // Video: thumb - thumb_path = thumb; - save_path = fs::path(thumb).replace_extension("mp4").string(); - break; - } - case 0x31: { // File: extra - save_path = extra; - break; - } - default: - break; - } - - if (fs::exists(save_path)) { // 不重复下载。TODO: 通过文件大小来判断 - return 0; - } - - LOG_DEBUG("path: {}", save_path); - // 创建父目录,由于路径来源于微信,不做检查 - fs::create_directory(fs::path(save_path).parent_path().string()); - - int temp = 1; - WxString *pSavePath = NewWxStringFromStr(save_path); - WxString *pThumbPath = NewWxStringFromStr(thumb_path); - - memcpy(&buff[0x280], pThumbPath, sizeof(WxString)); - memcpy(&buff[0x2A0], pSavePath, sizeof(WxString)); - memcpy(&buff[0x40C], &temp, sizeof(temp)); - - QWORD mgr = GetPreDownLoadMgr(); - status = (int)PushAttachTask(mgr, pChatMsg, 0, 1); - FreeChatMsg(pChatMsg); - - return status; -} - -string GetAudio(QWORD id, string dir) -{ - string mp3path = (dir.back() == '\\' || dir.back() == '/') ? dir : (dir + "/"); - mp3path += to_string(id) + ".mp3"; - replace(mp3path.begin(), mp3path.end(), '\\', '/'); - if (fs::exists(mp3path)) { // 不重复下载 - return mp3path; - } - - vector silk = GetAudioData(id); - if (silk.size() == 0) { - LOG_ERROR("Empty audio data."); - return ""; - } - - Silk2Mp3(silk, mp3path, 24000); - - return mp3path; -} - -string GetPCMAudio(uint64_t id, string dir, int32_t sr) -{ - string pcmpath = (dir.back() == '\\' || dir.back() == '/') ? dir : (dir + "/"); - pcmpath += to_string(id) + ".pcm"; - replace(pcmpath.begin(), pcmpath.end(), '\\', '/'); - if (fs::exists(pcmpath)) { // 不重复下载 - return pcmpath; - } - vector pcm; - vector silk = GetAudioData(id); - if (silk.size() == 0) { - LOG_ERROR("Empty audio data."); - return ""; - } - - SilkDecode(silk, pcm, sr); - errno_t err; - FILE* fPCM; - err = fopen_s(&fPCM, pcmpath.c_str(), "wb"); - if (err != 0) { - printf("Error: could not open input file %s\n", pcmpath.c_str()); - exit(0); - } - - fwrite(pcm.data(), sizeof(uint8_t), pcm.size(), fPCM); - fclose(fPCM); - - return pcmpath; -} - - -OcrResult_t GetOcrResult(string path) -{ - OcrResult_t ret = { -1, "" }; -#if 0 // 参数没调好,会抛异常,看看有没有好心人来修复 - if (!fs::exists(path)) { - LOG_ERROR("Can not find: {}", path); - return ret; - } - - GetOCRManager_t GetOCRManager = (GetOCRManager_t)(g_WeChatWinDllAddr + 0x1D6C3C0); - DoOCRTask_t DoOCRTask = (DoOCRTask_t)(g_WeChatWinDllAddr + 0x2D10BC0); - - QWORD unk1 = 0, unk2 = 0, unused = 0; - QWORD *pUnk1 = &unk1; - QWORD *pUnk2 = &unk2; - // 路径分隔符有要求,必须为 `\` - wstring wsPath = String2Wstring(fs::path(path).make_preferred().string()); - WxString wxPath(wsPath); - vector *pv = (vector *)HeapAlloc(GetProcessHeap(), 0, 0x20); - RawVector_t *pRv = (RawVector_t *)pv; - pRv->finish = pRv->start; - char buff[0x98] = { 0 }; - memcpy(buff, &pRv->start, sizeof(QWORD)); - - QWORD mgr = GetOCRManager(); - ret.status = (int)DoOCRTask(mgr, (QWORD)&wxPath, unused, (QWORD)buff, (QWORD)&pUnk1, (QWORD)&pUnk2); - - QWORD count = GET_QWORD(buff + 0x8); - if (count > 0) { - QWORD header = GET_QWORD(buff); - for (QWORD i = 0; i < count; i++) { - QWORD content = GET_QWORD(header); - ret.result += Wstring2String(GET_WSTRING(content + 0x28)); - ret.result += "\n"; - header = content; - } - } -#endif - return ret; -} - -int RevokeMsg(QWORD id) -{ - int status = -1; -#if 0 // 这个挺鸡肋的,因为自己发的消息没法直接获得 msgid,就这样吧 - QWORD localId; - uint32_t dbIdx; - if (GetLocalIdandDbidx(id, &localId, &dbIdx) != 0) { - LOG_ERROR("Failed to get localId, Please check id: {}", to_string(id)); - return status; - } -#endif - return status; -} - -string GetLoginUrl() -{ - LPVOID targetAddress = reinterpret_cast(g_WeChatWinDllAddr) + OS_LOGIN_QR_CODE; - - char *dataPtr = *reinterpret_cast(targetAddress); // 读取指针内容 - if (!dataPtr) { - LOG_ERROR("Failed to get login url"); - return "error"; - } - - // 读取字符串内容 - std::string data(dataPtr, 22); - return "http://weixin.qq.com/x/" + data; -} - -int ReceiveTransfer(string wxid, string transferid, string transactionid) -{ - // 别想了,这个不实现了 - return -1; -} diff --git a/WeChatFerry/spy/funcs.h b/WeChatFerry/spy/funcs.h deleted file mode 100644 index cc0f89d..0000000 --- a/WeChatFerry/spy/funcs.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "stdint.h" -#include - -int IsLogin(void); -std::string GetAudio(uint64_t id, std::string dir); -std::string GetPCMAudio(uint64_t id, std::string dir, int32_t sr); -std::string DecryptImage(std::string src, std::string dst); -int RefreshPyq(uint64_t id); -int DownloadAttach(uint64_t id, std::string thumb, std::string extra); -int RevokeMsg(uint64_t id); -OcrResult_t GetOcrResult(std::string path); -string GetLoginUrl(); -int ReceiveTransfer(std::string wxid, std::string transferid, std::string transactionid); diff --git a/WeChatFerry/spy/message_handler.cpp b/WeChatFerry/spy/message_handler.cpp new file mode 100644 index 0000000..9537bf1 --- /dev/null +++ b/WeChatFerry/spy/message_handler.cpp @@ -0,0 +1,281 @@ +#include "message_handler.h" + +#include +#include +#include +#include + +#include "framework.h" + +#include "account_manager.h" +#include "log.hpp" +#include "offsets.h" +#include "pb_util.h" +#include "rpc_helper.h" +#include "spy.h" +#include "util.h" + +namespace message +{ + +namespace fs = std::filesystem; + +namespace OsLog = Offsets::Message::Log; +namespace OsRecv = Offsets::Message::Receive; + +QWORD Handler::DispatchMsg(QWORD arg1, QWORD arg2) +{ + auto &handler = getInstance(); + WxMsg_t wxMsg = {}; + try { + wxMsg.id = util::get_qword(arg2 + OsRecv::ID); + wxMsg.type = util::get_dword(arg2 + OsRecv::TYPE); + wxMsg.is_self = util::get_dword(arg2 + OsRecv::SELF); + wxMsg.ts = util::get_dword(arg2 + OsRecv::TIMESTAMP); + wxMsg.content = util::get_str_by_wstr_addr(arg2 + OsRecv::CONTENT); + wxMsg.sign = util::get_str_by_wstr_addr(arg2 + OsRecv::SIGN); + wxMsg.xml = util::get_str_by_wstr_addr(arg2 + OsRecv::XML); + wxMsg.roomid = util::get_str_by_wstr_addr(arg2 + OsRecv::ROOMID); + + if (wxMsg.roomid.find("@chatroom") != std::string::npos) { // 群 ID 的格式为 xxxxxxxxxxx@chatroom + wxMsg.is_group = true; + wxMsg.sender = wxMsg.is_self ? account::get_self_wxid() : util::get_str_by_wstr_addr(arg2 + OsRecv::WXID); + } else { + wxMsg.is_group = false; + wxMsg.sender = wxMsg.is_self ? account::get_self_wxid() : wxMsg.roomid; + } + + fs::path thumb = util::get_str_by_wstr_addr(arg2 + OsRecv::THUMB); + if (!thumb.empty()) { + wxMsg.thumb = (account::get_home_path() / thumb).generic_string(); + } + + fs::path extra = util::get_str_by_wstr_addr(arg2 + OsRecv::EXTRA); + if (!extra.empty()) { + wxMsg.extra = (account::get_home_path() / extra).generic_string(); + } + LOG_DEBUG("{}", wxMsg.content); + } catch (const std::exception &e) { + LOG_ERROR(util::gb2312_to_utf8(e.what())); + } + + { + std::unique_lock lock(handler.mutex_); + handler.msgQueue_.push(wxMsg); // 推送到队列 + } + + handler.cv_.notify_all(); + return handler.realRecvMsg(arg1, arg2); +} + +QWORD Handler::PrintWxLog(QWORD a1, QWORD a2, QWORD a3, QWORD a4, QWORD a5, QWORD a6, QWORD a7, QWORD a8, QWORD a9, + QWORD a10, QWORD a11, QWORD a12) +{ + auto &handler = getInstance(); + + QWORD p = handler.realWxLog(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); + if (p == 0 || p == 1) { + return p; + } + + LOG_INFO("【WX】\n{}", util::gb2312_to_utf8((char *)p)); + return p; +} + +QWORD Handler::DispatchPyq(QWORD arg1, QWORD arg2, QWORD arg3) +{ + auto &handler = getInstance(); + QWORD startAddr = *(QWORD *)(arg2 + OsRecv::PYQ_START); + QWORD endAddr = *(QWORD *)(arg2 + OsRecv::PYQ_END); + + if (startAddr == 0) { + return 0; + } + + while (startAddr < endAddr) { + WxMsg_t wxMsg; + + wxMsg.type = 0x00; + wxMsg.is_self = false; + wxMsg.is_group = false; + wxMsg.id = util::get_qword(startAddr); + wxMsg.ts = util::get_dword(startAddr + OsRecv::PYQ_TS); + wxMsg.xml = util::get_str_by_wstr_addr(startAddr + OsRecv::PYQ_XML); + wxMsg.sender = util::get_str_by_wstr_addr(startAddr + OsRecv::PYQ_SENDER); + wxMsg.content = util::get_str_by_wstr_addr(startAddr + OsRecv::PYQ_CONTENT); + + { + std::unique_lock lock(handler.mutex_); + handler.msgQueue_.push(wxMsg); + } + + handler.cv_.notify_all(); + startAddr += 0x1618; + } + + return handler.realRecvPyq(arg1, arg2, arg3); +} + +Handler &Handler::getInstance() +{ + static Handler instance; + return instance; +} + +Handler::Handler() +{ + isLogging = false; + isListeningMsg = false; + isListeningPyq = false; +} + +Handler::~Handler() +{ + DisableLog(); + UnListenMsg(); + UnListenPyq(); +} + +MsgTypes_t Handler::GetMsgTypes() +{ + return { { 0x00, "朋友圈消息" }, + { 0x01, "文字" }, + { 0x03, "图片" }, + { 0x22, "语音" }, + { 0x25, "好友确认" }, + { 0x28, "POSSIBLEFRIEND_MSG" }, + { 0x2A, "名片" }, + { 0x2B, "视频" }, + { 0x2F, "石头剪刀布 | 表情图片" }, + { 0x30, "位置" }, + { 0x31, "共享实时位置、文件、转账、链接" }, + { 0x32, "VOIPMSG" }, + { 0x33, "微信初始化" }, + { 0x34, "VOIPNOTIFY" }, + { 0x35, "VOIPINVITE" }, + { 0x3E, "小视频" }, + { 0x42, "微信红包" }, + { 0x270F, "SYSNOTICE" }, + { 0x2710, "红包、系统消息" }, + { 0x2712, "撤回消息" }, + { 0x100031, "搜狗表情" }, + { 0x1000031, "链接" }, + { 0x1A000031, "微信红包" }, + { 0x20010031, "红包封面" }, + { 0x2D000031, "视频号视频" }, + { 0x2E000031, "视频号名片" }, + { 0x31000031, "引用消息" }, + { 0x37000031, "拍一拍" }, + { 0x3A000031, "视频号直播" }, + { 0x3A100031, "商品链接" }, + { 0x3A200031, "视频号直播" }, + { 0x3E000031, "音乐链接" }, + { 0x41000031, "文件" } }; +} + +std::optional Handler::popMessage() +{ + std::lock_guard lock(mutex_); + if (msgQueue_.empty()) { + return std::nullopt; + } + WxMsg_t msg = std::move(msgQueue_.front()); + msgQueue_.pop(); + return msg; +} + +int Handler::EnableLog() +{ + if (isLogging) return 1; + + pLogLevel = reinterpret_cast(Spy::WeChatDll.load() + OsLog::LEVEL); + funcWxLog = Spy::getFunction(OsLog::CALL); + + if (InitializeHook() != MH_OK) return -1; + if (MH_CreateHook(funcWxLog, &PrintWxLog, reinterpret_cast(&realWxLog)) != MH_OK) return -2; + if (MH_EnableHook(funcWxLog) != MH_OK) return -3; + + *pLogLevel = 0; + isLogging = true; + return 0; +} + +int Handler::DisableLog() +{ + if (!isLogging) return 1; + if (MH_DisableHook(funcWxLog) != MH_OK) return -1; + if (UninitializeHook() != MH_OK) return -2; + *pLogLevel = 6; + isLogging = false; + return 0; +} + +int Handler::ListenMsg() +{ + if (isListeningMsg) return 1; + + funcRecvMsg = Spy::getFunction(OsRecv::CALL); + if (InitializeHook() != MH_OK) return -1; + if (MH_CreateHook(funcRecvMsg, &DispatchMsg, reinterpret_cast(&realRecvMsg)) != MH_OK) return -1; + if (MH_EnableHook(funcRecvMsg) != MH_OK) return -1; + + isListeningMsg = true; + return 0; +} + +int Handler::UnListenMsg() +{ + if (!isListeningMsg) return 1; + if (MH_DisableHook(funcRecvMsg) != MH_OK) return -1; + if (UninitializeHook() != MH_OK) return -1; + isListeningMsg = false; + return 0; +} + +int Handler::ListenPyq() +{ + if (isListeningPyq) return 1; + + funcRecvPyq = Spy::getFunction(OsRecv::PYQ_CALL); + if (InitializeHook() != MH_OK) return -1; + if (MH_CreateHook(funcRecvPyq, &DispatchPyq, reinterpret_cast(&realRecvPyq)) != MH_OK) return -1; + if (MH_EnableHook(funcRecvPyq) != MH_OK) return -1; + + isListeningPyq = true; + return 0; +} + +int Handler::UnListenPyq() +{ + if (!isListeningPyq) return 1; + if (MH_DisableHook(funcRecvPyq) != MH_OK) return -1; + if (UninitializeHook() != MH_OK) return -1; + isListeningPyq = false; + return 0; +} + +MH_STATUS Handler::InitializeHook() +{ + if (isMH_Initialized) return MH_OK; + MH_STATUS status = MH_Initialize(); + if (status == MH_OK) isMH_Initialized = true; + return status; +} + +MH_STATUS Handler::UninitializeHook() +{ + if (!isMH_Initialized || isLogging || isListeningMsg || isListeningPyq) return MH_OK; + MH_STATUS status = MH_Uninitialize(); + if (status == MH_OK) isMH_Initialized = false; + return status; +} + +bool Handler::rpc_get_msg_types(uint8_t *out, size_t *len) +{ + MsgTypes_t types = GetMsgTypes(); + return fill_response(out, len, [&](Response &rsp) { + rsp.msg.types.types.funcs.encode = encode_types; + rsp.msg.types.types.arg = &types; + }); +} +} diff --git a/WeChatFerry/spy/message_handler.h b/WeChatFerry/spy/message_handler.h new file mode 100644 index 0000000..53e3d1a --- /dev/null +++ b/WeChatFerry/spy/message_handler.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "MinHook.h" + +#include "pb_types.h" +#include "spy_types.h" + +namespace message +{ + +class Handler +{ +public: + static Handler &getInstance(); + + // 0: 成功, -1: 失败, 1: 已经开启 + int EnableLog(); + int DisableLog(); + int ListenPyq(); + int UnListenPyq(); + int ListenMsg(); + int UnListenMsg(); + + MsgTypes_t GetMsgTypes(); + + bool isLoggingEnabled() const { return isLogging.load(); } + bool isMessageListening() const { return isListeningMsg.load(); } + bool isPyqListening() const { return isListeningPyq.load(); } + + std::optional popMessage(); + std::condition_variable &getConditionVariable() { return cv_; }; + std::mutex &getMutex() { return mutex_; }; + + bool rpc_get_msg_types(uint8_t *out, size_t *len); + +private: + Handler(); + ~Handler(); + + mutable std::mutex mutex_; + std::condition_variable cv_; + std::queue msgQueue_; + + std::atomic isLogging { false }; + std::atomic isListeningMsg { false }; + std::atomic isListeningPyq { false }; + + using funcRecvMsg_t = QWORD (*)(QWORD, QWORD); + using funcWxLog_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); + using funcRecvPyq_t = QWORD (*)(QWORD, QWORD, QWORD); + + uint32_t *pLogLevel; + funcWxLog_t funcWxLog, realWxLog; + funcRecvMsg_t funcRecvMsg, realRecvMsg; + funcRecvPyq_t funcRecvPyq, realRecvPyq; + + bool isMH_Initialized { false }; + + MH_STATUS InitializeHook(); + MH_STATUS UninitializeHook(); + + static QWORD DispatchMsg(QWORD arg1, QWORD arg2); + static QWORD PrintWxLog(QWORD a1, QWORD a2, QWORD a3, QWORD a4, QWORD a5, QWORD a6, QWORD a7, QWORD a8, QWORD a9, + QWORD a10, QWORD a11, QWORD a12); + static QWORD DispatchPyq(QWORD arg1, QWORD arg2, QWORD arg3); +}; + +} // namespace message diff --git a/WeChatFerry/spy/message_sender.cpp b/WeChatFerry/spy/message_sender.cpp new file mode 100644 index 0000000..2cbfac3 --- /dev/null +++ b/WeChatFerry/spy/message_sender.cpp @@ -0,0 +1,366 @@ +#include "message_sender.h" + +#include +#include + +#include "account_manager.h" +#include "database_executor.h" +#include "log.hpp" +#include "offsets.h" +#include "rpc_helper.h" +#include "spy.h" +#include "spy_types.h" +#include "util.h" + +namespace message +{ + +namespace OsSend = Offsets::Message::Send; + +Sender &Sender::get_instance() +{ + static Sender instance; + return instance; +} + +Sender::Sender() +{ + func_new_chat_msg = Spy::getFunction(OsSend::INSTANCE); + func_free_chat_msg = Spy::getFunction(OsSend::FREE); + func_send_msg_mgr = Spy::getFunction(OsSend::MGR); + func_send_text = Spy::getFunction(OsSend::TEXT); + func_send_image = Spy::getFunction(OsSend::IMAGE); + func_get_app_mgr = Spy::getFunction(OsSend::APP_MGR); + func_send_file = Spy::getFunction(OsSend::FILE); + func_new_mmreader = Spy::getFunction(OsSend::NEW_MM_READER); + func_free_mmreader = Spy::getFunction(OsSend::FREE_MM_READER); + func_send_rich_text = Spy::getFunction(OsSend::RICH_TEXT); + func_send_pat = Spy::getFunction(OsSend::PAT); + func_forward = Spy::getFunction(OsSend::FORWARD); + func_get_emotion_mgr = Spy::getFunction(OsSend::EMOTION_MGR); + func_send_emotion = Spy::getFunction(OsSend::EMOTION); + func_send_xml = Spy::getFunction(OsSend::XML); + func_xml_buf_sign = Spy::getFunction(OsSend::XML_BUF_SIGN); +} + +void Sender::send_text(const std::string &wxid, const std::string &msg, const std::string &at_wxids) +{ + util::WxStringHolder holderMsg(msg); + util::WxStringHolder holderWxid(wxid); + + auto wxAtWxids = util::parse_wxids(at_wxids).wxWxids; + QWORD pWxAtWxids = wxAtWxids.empty() ? 0 : reinterpret_cast(&wxAtWxids); + + char buffer[1104] = { 0 }; + func_send_msg_mgr(); + func_send_text(reinterpret_cast(&buffer), &holderWxid.wx, &holderMsg.wx, pWxAtWxids, 1, 1, 0, 0); + func_free_chat_msg(reinterpret_cast(&buffer)); +} + +void Sender::send_image(const std::string &wxid, const std::string &path) +{ + util::WxStringHolder holderWxid(wxid); + util::WxStringHolder holderPath(path); + + char msg[1192] = { 0 }; + char msgTmp[1192] = { 0 }; + QWORD *flag[10] = { 0 }; + + QWORD tmp1 = 1, tmp2 = 0, tmp3 = 0; + QWORD pMsgTmp = func_new_chat_msg((QWORD)(&msgTmp)); + flag[0] = reinterpret_cast(tmp1); + flag[1] = reinterpret_cast(pMsgTmp); + flag[8] = &tmp2; + flag[9] = &tmp3; + + QWORD pMsg = func_new_chat_msg((QWORD)(&msg)); + QWORD sendMgr = func_send_msg_mgr(); + func_send_image(sendMgr, pMsg, &holderWxid.wx, &holderPath.wx, reinterpret_cast(&flag)); + + func_free_chat_msg(pMsg); + func_free_chat_msg(pMsgTmp); +} + +void Sender::send_file(const std::string &wxid, const std::string &path) +{ + WxString *wxWxid = util::CreateWxString(wxid); + WxString *wxPath = util::CreateWxString(path); + if (!wxWxid || !wxPath) { + util::FreeWxString(wxWxid); + util::FreeWxString(wxPath); + return; + } + + char *chat_msg = reinterpret_cast(util::AllocFromHeap(0x460)); + if (!chat_msg) { + util::FreeWxString(wxWxid); + util::FreeWxString(wxPath); + return; + } + + QWORD *tmp1 = util::AllocBuffer(4); + QWORD *tmp2 = util::AllocBuffer(4); + QWORD *tmp3 = util::AllocBuffer(4); + if (!tmp1 || !tmp2 || !tmp3) { + func_free_chat_msg(reinterpret_cast(chat_msg)); + util::FreeBuffer(chat_msg); + util::FreeBuffer(tmp1); + util::FreeBuffer(tmp2); + util::FreeBuffer(tmp3); + util::FreeWxString(wxWxid); + util::FreeWxString(wxPath); + return; + } + + QWORD app_mgr = func_get_app_mgr(); + func_send_file(app_mgr, chat_msg, wxWxid, wxPath, 1, tmp1, 0, tmp2, 0, tmp3, 0, 0xC); + func_free_chat_msg(reinterpret_cast(chat_msg)); + + util::FreeBuffer(chat_msg); + util::FreeBuffer(tmp1); + util::FreeBuffer(tmp2); + util::FreeBuffer(tmp3); + util::FreeWxString(wxWxid); + util::FreeWxString(wxPath); +} + +void Sender::send_xml(const std::string &receiver, const std::string &xml, const std::string &path, uint64_t type) +{ +#if 0 + std::unique_ptr buff(new char[0x500]()); + std::unique_ptr buff2(new char[0x500]()); + char nullBuf[0x1C] = { 0 }; + + func_new_chat_msg(reinterpret_cast(buff.get())); + func_new_chat_msg(reinterpret_cast(buff2.get())); + + QWORD sbuf[4] = { 0, 0, 0, 0 }; + QWORD sign = func_xml_buf_sign(reinterpret_cast(buff2.get()), reinterpret_cast(sbuf), 0x1); + + auto wxReceiver = new_wx_string(receiver); + auto wxXml = new_wx_string(xml); + auto wxPath = new_wx_string(path); + auto wxSender = new_wx_string(account::get_self_wxid()); + + func_send_xml(reinterpret_cast(buff.get()), reinterpret_cast(wxSender.get()), + reinterpret_cast(wxReceiver.get()), reinterpret_cast(wxXml.get()), + reinterpret_cast(wxPath.get()), reinterpret_cast(nullBuf), type, 0x4, sign, + reinterpret_cast(buff2.get())); + + func_free_chat_msg(reinterpret_cast(buff.get())); + func_free_chat_msg(reinterpret_cast(buff2.get())); +#endif +} + +void Sender::send_emotion(const std::string &wxid, const std::string &path) +{ + WxString *wxWxid = util::CreateWxString(wxid); + WxString *wxPath = util::CreateWxString(path); + QWORD *buff = util::AllocBuffer(8); + if (!wxWxid || !wxPath || !buff) { + util::FreeWxString(wxWxid); + util::FreeWxString(wxPath); + util::FreeBuffer(buff); + return; + } + + QWORD mgr = func_get_emotion_mgr(); + func_send_emotion(mgr, wxPath, buff, wxWxid, 2, buff, 0, buff); + util::FreeBuffer(buff); + util::FreeWxString(wxWxid); + util::FreeWxString(wxPath); +} + +int Sender::send_rich_text(const RichText &rt) +{ + QWORD status = -1; + + char *buff = util::AllocBuffer(0x3F0); + WxString *pReceiver = util::CreateWxString(rt.receiver); + WxString *pTitle = util::CreateWxString(rt.title); + WxString *pUrl = util::CreateWxString(rt.url); + WxString *pThumburl = util::CreateWxString(rt.thumburl); + WxString *pDigest = util::CreateWxString(rt.digest); + WxString *pAccount = util::CreateWxString(rt.account); + WxString *pName = util::CreateWxString(rt.name); + if (!buff || !pReceiver || !pTitle || !pUrl || !pThumburl || !pDigest || !pAccount || !pName) { + util::FreeWxString(pReceiver); + util::FreeWxString(pTitle); + util::FreeWxString(pUrl); + util::FreeWxString(pThumburl); + util::FreeWxString(pDigest); + util::FreeWxString(pAccount); + util::FreeWxString(pName); + util::FreeBuffer(buff); + LOG_ERROR("Out of Memory..."); + return static_cast(status); + } + + func_new_mmreader(reinterpret_cast(buff)); + memcpy(buff + 0x8, pTitle, sizeof(WxString)); + memcpy(buff + 0x48, pUrl, sizeof(WxString)); + memcpy(buff + 0xB0, pThumburl, sizeof(WxString)); + memcpy(buff + 0xF0, pDigest, sizeof(WxString)); + memcpy(buff + 0x2C0, pAccount, sizeof(WxString)); + memcpy(buff + 0x2E0, pName, sizeof(WxString)); + + status = func_send_rich_text(func_get_app_mgr(), pReceiver, buff); + func_free_mmreader(reinterpret_cast(buff)); + + // TODO: 验证是否有内存泄露 + // util::FreeWxString(pReceiver); + // util::FreeWxString(pTitle); + // util::FreeWxString(pUrl); + // util::FreeWxString(pThumburl); + // util::FreeWxString(pDigest); + // util::FreeWxString(pAccount); + // util::FreeWxString(pName); + util::FreeBuffer(buff); + + return static_cast(status); +} + +int Sender::send_pat(const std::string &roomid, const std::string &wxid) +{ + QWORD status = -1; + + util::WxStringHolder holderRoom(roomid); + util::WxStringHolder holderWxid(wxid); + + status = func_send_pat(&holderRoom.wx, &holderWxid.wx); + + return static_cast(status); +} + +int Sender::forward(QWORD msgid, const std::string &receiver) +{ + uint32_t dbIdx = 0; + QWORD localId = 0; + + if (db::get_local_id_and_dbidx(msgid, &localId, &dbIdx) != 0) { + LOG_ERROR("Failed to get localId, Please check id: {}", msgid); + return -1; + } + + LARGE_INTEGER l; + l.HighPart = dbIdx; + l.LowPart = static_cast(localId); + + WxString *pReceiver = util::CreateWxString(receiver); + + return static_cast(func_forward(pReceiver, l.QuadPart, 0x4, 0x0)); +} + +// RPC 方法 +bool Sender::rpc_send_text(const TextMsg &text, uint8_t *out, size_t *len) +{ + return fill_response(out, len, [&](Response &rsp) { + if (text.msg == nullptr || text.receiver == nullptr || strlen(text.msg) == 0 || strlen(text.receiver) == 0) { + LOG_ERROR("Empty message or receiver."); + rsp.msg.status = -1; + } else { + send_text(text.receiver, text.msg, text.aters ? text.aters : ""); + rsp.msg.status = 0; + } + }); +} + +bool Sender::rpc_send_image(const PathMsg &file, uint8_t *out, size_t *len) +{ + std::string path(file.path); + std::string receiver(file.receiver); + return fill_response(out, len, [&](Response &rsp) { + if (path.empty() || receiver.empty()) { + LOG_ERROR("Empty path or receiver."); + rsp.msg.status = -1; + } else { + send_image(receiver, path); + rsp.msg.status = 0; + } + }); +} + +bool Sender::rpc_send_file(const PathMsg &file, uint8_t *out, size_t *len) +{ + std::string path(file.path); + std::string receiver(file.receiver); + return fill_response(out, len, [&](Response &rsp) { + if (path.empty() || receiver.empty()) { + LOG_ERROR("Empty path or receiver."); + rsp.msg.status = -1; + } else { + send_file(receiver, path); + rsp.msg.status = 0; + } + }); +} + +bool Sender::rpc_send_emotion(const PathMsg &file, uint8_t *out, size_t *len) +{ + std::string path(file.path); + std::string receiver(file.receiver); + return fill_response(out, len, [&](Response &rsp) { + if (path.empty() || receiver.empty()) { + LOG_ERROR("Empty path or receiver."); + rsp.msg.status = -1; + } else { + send_emotion(receiver, path); + rsp.msg.status = 0; + } + }); +} + +bool Sender::rpc_send_xml(const XmlMsg &xml, uint8_t *out, size_t *len) +{ + return fill_response(out, len, [&](Response &rsp) { + if (xml.content == nullptr || xml.receiver == nullptr) { + LOG_ERROR("Empty content or receiver."); + rsp.msg.status = -1; + } else { + // send_xml(xml.receiver, xml.content, xml.path, xml.type); + rsp.msg.status = -1; + } + }); +} + +bool Sender::rpc_send_rich_text(const RichText &rt, uint8_t *out, size_t *len) +{ + return fill_response(out, len, [&](Response &rsp) { + if (rt.receiver == nullptr) { + LOG_ERROR("Empty receiver."); + rsp.msg.status = -1; + } else { + rsp.msg.status = send_rich_text(rt); + } + }); +} + +bool Sender::rpc_send_pat(const PatMsg &pat, uint8_t *out, size_t *len) +{ + std::string wxid(pat.wxid); + std::string roomid(pat.roomid); + return fill_response(out, len, [&](Response &rsp) { + if (roomid.empty() || wxid.empty()) { + LOG_ERROR("Empty roomid or wxid."); + rsp.msg.status = -1; + } else { + rsp.msg.status = send_pat(roomid, wxid); + } + }); +} + +bool Sender::rpc_forward(const ForwardMsg &fm, uint8_t *out, size_t *len) +{ + uint64_t msgid = fm.id; + std::string receiver(fm.receiver); + return fill_response(out, len, [&](Response &rsp) { + if (receiver.empty()) { + LOG_ERROR("Empty receiver."); + rsp.msg.status = -1; + } else { + rsp.msg.status = forward(msgid, receiver); + } + }); +} + +} diff --git a/WeChatFerry/spy/message_sender.h b/WeChatFerry/spy/message_sender.h new file mode 100644 index 0000000..7811395 --- /dev/null +++ b/WeChatFerry/spy/message_sender.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include + +#include "wcf.pb.h" + +#include "spy_types.h" + +namespace message +{ + +class Sender +{ +public: + static Sender &get_instance(); + + void send_text(const std::string &wxid, const std::string &msg, const std::string &at_wxids = ""); + void send_image(const std::string &wxid, const std::string &path); + void send_file(const std::string &wxid, const std::string &path); + void send_xml(const std::string &receiver, const std::string &xml, const std::string &path, uint64_t type); + void send_emotion(const std::string &wxid, const std::string &path); + int send_rich_text(const RichText &rt); + int send_pat(const std::string &roomid, const std::string &wxid); + int forward(uint64_t msgid, const std::string &receiver); + + // RPC 方法 + bool rpc_send_text(const TextMsg &text, uint8_t *out, size_t *len); + bool rpc_send_image(const PathMsg &file, uint8_t *out, size_t *len); + bool rpc_send_file(const PathMsg &file, uint8_t *out, size_t *len); + bool rpc_send_emotion(const PathMsg &file, uint8_t *out, size_t *len); + bool rpc_send_xml(const XmlMsg &rt, uint8_t *out, size_t *len); + bool rpc_send_rich_text(const RichText &rt, uint8_t *out, size_t *len); + bool rpc_send_pat(const PatMsg &pat, uint8_t *out, size_t *len); + bool rpc_forward(const ForwardMsg &fm, uint8_t *out, size_t *len); + +private: + Sender(); + ~Sender() = default; + + Sender(const Sender &) = delete; + Sender &operator=(const Sender &) = delete; + + using New_t = QWORD (*)(QWORD); + using Free_t = QWORD (*)(QWORD); + using SendMsgMgr_t = QWORD (*)(); + using GetAppMgr_t = QWORD (*)(); + using SendText_t = QWORD (*)(QWORD, WxString *, WxString *, QWORD, QWORD, QWORD, QWORD, QWORD); + using SendImage_t = QWORD (*)(QWORD, QWORD, WxString *, WxString *, QWORD); + using SendFile_t = QWORD (*)(QWORD, char *, WxString *, WxString *, QWORD, QWORD *, QWORD, QWORD *, QWORD, QWORD *, + QWORD, QWORD); + using SendRichText_t = QWORD (*)(QWORD, WxString *, char *); + using SendPat_t = QWORD (*)(WxString *, WxString *); + using Forward_t = QWORD (*)(WxString *, QWORD, QWORD, QWORD); + using GetEmotionMgr_t = QWORD (*)(); + using SendEmotion_t = QWORD (*)(QWORD, WxString *, QWORD *, WxString *, QWORD, QWORD *, QWORD, QWORD *); + using XmlBufSign_t = QWORD (*)(QWORD, QWORD, QWORD); + using SendXml_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); + + New_t func_new_chat_msg; + Free_t func_free_chat_msg; + SendMsgMgr_t func_send_msg_mgr; + GetAppMgr_t func_get_app_mgr; + SendText_t func_send_text; + SendImage_t func_send_image; + SendFile_t func_send_file; + New_t func_new_mmreader; + Free_t func_free_mmreader; + SendRichText_t func_send_rich_text; + SendPat_t func_send_pat; + Forward_t func_forward; + GetEmotionMgr_t func_get_emotion_mgr; + SendEmotion_t func_send_emotion; + XmlBufSign_t func_xml_buf_sign; + SendXml_t func_send_xml; + + std::unique_ptr new_wx_string(const char *str); + std::unique_ptr new_wx_string(const std::string &str); +}; +} diff --git a/WeChatFerry/spy/misc_manager.cpp b/WeChatFerry/spy/misc_manager.cpp new file mode 100644 index 0000000..fa968c4 --- /dev/null +++ b/WeChatFerry/spy/misc_manager.cpp @@ -0,0 +1,421 @@ +#pragma warning(disable : 4244) +#include "misc_manager.h" + +#include +#include + +#include "framework.h" + +#include "codec.h" +#include "database_executor.h" +#include "log.hpp" +#include "message_handler.h" +#include "offsets.h" +#include "rpc_helper.h" +#include "spy.h" +#include "spy_types.h" +#include "util.h" + +namespace misc +{ +using namespace std; +namespace fs = std::filesystem; +namespace OsMisc = Offsets::Misc; +namespace OsSns = Offsets::Misc::Sns; + +using get_sns_data_mgr_t = QWORD (*)(); +using get_sns_timeline_mgr_t = QWORD (*)(); +using get_sns_first_page_t = QWORD (*)(QWORD, QWORD, QWORD); +using get_sns_next_page_scene_t = QWORD (*)(QWORD, QWORD); +using get_chat_mgr_t = QWORD (*)(); +using new_chat_msg_t = QWORD (*)(char *); +using free_chat_msg_t = QWORD (*)(QWORD); +using get_pre_download_mgr_t = QWORD (*)(); +using get_mgr_by_prefix_localid_t = QWORD (*)(QWORD, QWORD); +using push_attach_task_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD); +using get_ocr_manager_t = QWORD (*)(); +using do_ocr_task_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); +using get_qr_code_mgr_t = QWORD (*)(); + +struct ImagePattern { + uint8_t header1_candidate; + uint8_t header2_expected; + const char *extension; +}; + +static constexpr ImagePattern patterns[] = { + { 0x89, 0x50, ".png" }, + { 0xFF, 0xD8, ".jpg" }, + { 0x47, 0x49, ".gif" }, +}; + +static std::string detect_image_extension(uint8_t header1, uint8_t header2, uint8_t *key) +{ + + for (const auto &pat : patterns) { + *key = pat.header1_candidate ^ header1; + if ((pat.header2_expected ^ *key) == header2) { + return pat.extension; + } + } + LOG_ERROR("未知类型:{:02x} {:02x}", header1, header2); + return ""; +} + +std::string decrypt_image(const fs::path &src, const fs::path &dst_dir) +{ + if (!fs::exists(src)) { + LOG_ERROR("文件不存在: {}", src.string()); + return ""; + } + + std::ifstream in(src, std::ios::binary); + if (!in) { + LOG_ERROR("无法打开文件: {}", src.string()); + return ""; + } + + std::vector buffer(std::istreambuf_iterator(in), {}); + if (buffer.size() < 2) return ""; + + uint8_t key = 0x00; + auto ext = detect_image_extension(buffer[0], buffer[1], &key); + if (ext.empty()) { + LOG_ERROR("无法检测文件类型."); + return ""; + } + + std::for_each(buffer.begin(), buffer.end(), [key](char &c) { c ^= key; }); + + fs::path dst_path = dst_dir / (src.stem().string() + ext); + if (!fs::exists(dst_dir)) fs::create_directories(dst_dir); + + std::ofstream out(dst_path, std::ios::binary); + if (!out) { + LOG_ERROR("写入文件失败: {}", dst_path.generic_string()); + return ""; + } + + out.write(buffer.data(), buffer.size()); + return dst_path.generic_string(); +} + +static int get_first_page() +{ + int status = -1; + + auto GetSNSDataMgr = Spy::getFunction(OsSns::DATA_MGR); + auto GetSNSFirstPage = Spy::getFunction(OsSns::FIRST); + + QWORD buff[16] = { 0 }; + QWORD mgr = GetSNSDataMgr(); + status = (int)GetSNSFirstPage(mgr, (QWORD)&buff, 1); + + return status; +} + +static int get_next_page(QWORD id) +{ + int status = -1; + + auto GetSnsTimeLineMgr = Spy::getFunction(OsSns::TIMELINE); + auto GetSNSNextPageScene = Spy::getFunction(OsSns::NEXT); + + QWORD mgr = GetSnsTimeLineMgr(); + status = (int)GetSNSNextPageScene(mgr, id); + + return status; +} + +int refresh_pyq(uint64_t id) +{ + auto &msgHandler = message::Handler::getInstance(); + if (!msgHandler.isPyqListening()) { + LOG_ERROR("没有启动朋友圈消息接收,参考:enable_receiving_msg"); + return -1; + } + + if (id == 0) { + return get_first_page(); + } + + return get_next_page(id); +} + +/******************************************************************************* + * 都说我不写注释,写一下吧 + * 其实也没啥好写的,就是下载资源 + * 主要介绍一下几个参数: + * id:好理解,消息 id + * thumb:图片或者视频的缩略图路径;如果是视频,后缀为 mp4 后就是存在路径了 + * extra:图片、文件的路径 + *******************************************************************************/ +int download_attachment(uint64_t id, const fs::path &thumb, const fs::path &extra) +{ + int status = -1; + QWORD localId; + uint32_t dbIdx; + + if (fs::exists(extra)) { // 第一道,不重复下载。TODO: 通过文件大小来判断 + LOG_WARN("文件已存在:{}", extra.generic_string()); + return 0; + } + + if (db::get_local_id_and_dbidx(id, &localId, &dbIdx) != 0) { + LOG_ERROR("获取 localId 失败, 请检查消息 id: {} 是否正确", to_string(id)); + return status; + } + + auto NewChatMsg = Spy::getFunction(OsMisc::INSATNCE); + auto FreeChatMsg = Spy::getFunction(OsMisc::FREE); + auto GetChatMgr = Spy::getFunction(OsMisc::CHAT_MGR); + auto GetPreDownLoadMgr = Spy::getFunction(OsMisc::PRE_DOWNLOAD_MGR); + auto PushAttachTask = Spy::getFunction(OsMisc::PUSH_ATTACH_TASK); + auto GetMgrByPrefixLocalId = Spy::getFunction(OsMisc::PRE_LOCAL_ID_MGR); + + LARGE_INTEGER l; + l.HighPart = dbIdx; + l.LowPart = (DWORD)localId; + + char *buff = util::AllocBuffer(0x460); + if (buff == nullptr) { + LOG_ERROR("申请内存失败."); + return status; + } + + QWORD pChatMsg = NewChatMsg(buff); + GetChatMgr(); + GetMgrByPrefixLocalId(l.QuadPart, pChatMsg); + QWORD type = util::get_qword(reinterpret_cast(buff) + 0x38); + + fs::path save_path, thumb_path; + switch (type) { + case 0x03: { // Image: extra + save_path = extra; + break; + } + case 0x3E: + case 0x2B: { // Video: thumb + thumb_path = thumb; + save_path = fs::path(thumb).replace_extension("mp4"); + break; + } + case 0x31: { // File: extra + save_path = extra; + break; + } + default: + LOG_ERROR("不支持的文件类型: {}", type); + return -2; + } + + if (fs::exists(save_path)) { // 不重复下载。TODO: 通过文件大小来判断 + return 0; + } + + LOG_DEBUG("保存路径: {}", save_path.generic_string()); + // 创建父目录,由于路径来源于微信,不做检查 + fs::create_directory(save_path.parent_path()); + + int temp = 1; + auto wx_save_path = util::CreateWxString(save_path.make_preferred().string()); + auto wx_thumb_path = util::CreateWxString(thumb_path.make_preferred().string()); + + memcpy(&buff[0x280], wx_thumb_path, sizeof(WxString)); + memcpy(&buff[0x2A0], wx_save_path, sizeof(WxString)); + memcpy(&buff[0x40C], &temp, sizeof(temp)); + + QWORD mgr = GetPreDownLoadMgr(); + status = (int)PushAttachTask(mgr, pChatMsg, 0, 1); + FreeChatMsg(pChatMsg); + util::FreeBuffer(buff); + + return status; +} + +std::string get_audio(uint64_t id, const fs::path &dir) +{ + if (!fs::exists(dir)) fs::create_directories(dir); + + fs::path mp3path = dir / (std::to_string(id) + ".mp3"); + if (fs::exists(mp3path)) return mp3path.generic_string(); + + auto silk = db::get_audio_data(id); + if (silk.empty()) { + LOG_ERROR("没有获取到语音数据."); + return ""; + } + + Silk2Mp3(silk, mp3path.generic_string(), 24000); + return mp3path.generic_string(); +} + +std::string get_pcm_audio(uint64_t id, const fs::path &dir, int32_t sr) +{ + if (!fs::exists(dir)) fs::create_directories(dir); + + fs::path pcmpath = dir / (std::to_string(id) + ".pcm"); + if (fs::exists(pcmpath)) return pcmpath.generic_string(); + + auto silk = db::get_audio_data(id); + if (silk.empty()) { + LOG_ERROR("没有获取到语音数据."); + return ""; + } + + std::vector pcm; + SilkDecode(silk, pcm, sr); + + std::ofstream out(pcmpath, std::ios::binary); + if (!out) { + LOG_ERROR("创建文件失败: {}", pcmpath.generic_string()); + return ""; + } + + out.write(reinterpret_cast(pcm.data()), pcm.size()); + return pcmpath.generic_string(); +} + +OcrResult_t get_ocr_result(const fs::path &path) +{ + OcrResult_t ret = { -1, "" }; +#if 0 // 参数没调好,会抛异常,看看有没有好心人来修复 + if (!fs::exists(path)) { + LOG_ERROR("Can not find: {}", path); + return ret; + } + + get_ocr_manager_t GetOCRManager = (get_ocr_manager_t)(g_WeChatWinDllAddr + 0x1D6C3C0); + do_ocr_task_t DoOCRTask = (do_ocr_task_t)(g_WeChatWinDllAddr + 0x2D10BC0); + + QWORD unk1 = 0, unk2 = 0, unused = 0; + QWORD *pUnk1 = &unk1; + QWORD *pUnk2 = &unk2; + // 路径分隔符有要求,必须为 `\` + wstring wsPath = util::s2w(fs::path(path).make_preferred().string()); + WxString wxPath(wsPath); + vector *pv = (vector *)HeapAlloc(GetProcessHeap(), 0, 0x20); + RawVector_t *pRv = (RawVector_t *)pv; + pRv->finish = pRv->start; + char buff[0x98] = { 0 }; + memcpy(buff, &pRv->start, sizeof(QWORD)); + + QWORD mgr = GetOCRManager(); + ret.status = (int)DoOCRTask(mgr, (QWORD)&wxPath, unused, (QWORD)buff, (QWORD)&pUnk1, (QWORD)&pUnk2); + + QWORD count = util::get_qword(buff + 0x8); + if (count > 0) { + QWORD header = util::get_qword(buff); + for (QWORD i = 0; i < count; i++) { + QWORD content = util::get_qword(header); + ret.result += util::w2s(get_pp_wstring(content + 0x28)); + ret.result += "\n"; + header = content; + } + } +#endif + return ret; +} + +int revoke_message(uint64_t id) +{ + int status = -1; +#if 0 // 这个挺鸡肋的,因为自己发的消息没法直接获得 msgid,就这样吧 + QWORD localId; + uint32_t dbIdx; + if (GetLocalIdandDbidx(id, &localId, &dbIdx) != 0) { + LOG_ERROR("Failed to get localId, Please check id: {}", to_string(id)); + return status; + } +#endif + return status; +} + +std::string get_login_url() +{ + std::string uri; + auto get_qr_code_mgr = Spy::getFunction(OsMisc::QR_CODE); + + uint64_t addr = get_qr_code_mgr() + 0x68; + uint64_t len = *(uint64_t *)(addr + 0x10); + if (len == 0) { + LOG_ERROR("获取二维码失败."); + return uri; + } + + if (*(uint64_t *)(addr + 0x18) == 0xF) { + uri = std::string((char *)addr, len); + } else { + uri = std::string(*(char **)(addr), len); + } + + return "http://weixin.qq.com/x/" + uri; +} + +int receive_transfer(const std::string &wxid, const std::string &transferid, const std::string &transactionid) +{ + LOG_ERROR("技术太菜,实现不了。"); + return -1; +} + +bool rpc_get_audio(const AudioMsg &am, uint8_t *out, size_t *len) +{ + return fill_response( + out, len, [&](Response &rsp) { rsp.msg.str = (char *)get_audio(am.id, am.dir).c_str(); }); +} + +bool rpc_get_pcm_audio(uint64_t id, const fs::path &dir, int32_t sr, uint8_t *out, size_t *len) { return false; } + +bool rpc_decrypt_image(const DecPath &dec, uint8_t *out, size_t *len) +{ + return fill_response( + out, len, [&](Response &rsp) { rsp.msg.str = (char *)decrypt_image(dec.src, dec.dst).c_str(); }); +} + +bool rpc_get_login_url(uint8_t *out, size_t *len) +{ + return fill_response( + out, len, [&](Response &rsp) { rsp.msg.str = (char *)get_login_url().c_str(); }); +} + +bool rpc_refresh_pyq(uint64_t id, uint8_t *out, size_t *len) +{ + return fill_response(out, len, + [&](Response &rsp) { rsp.msg.status = refresh_pyq(id); }); +} + +bool rpc_download_attachment(const AttachMsg &att, uint8_t *out, size_t *len) +{ + int status = -1; + if (att.thumb || att.extra) { + std::string thumb = att.thumb ? att.thumb : ""; + std::string extra = att.extra ? att.extra : ""; + status = download_attachment(att.id, thumb, extra); + } else { + LOG_ERROR("文件地址不能全为空"); + } + + return fill_response(out, len, [&](Response &rsp) { rsp.msg.status = status; }); +} + +bool rpc_revoke_message(uint64_t id, uint8_t *out, size_t *len) +{ + return fill_response(out, len, + [&](Response &rsp) { rsp.msg.status = revoke_message(id); }); +} + +bool rpc_get_ocr_result(const fs::path &path, uint8_t *out, size_t *len) +{ + auto ret = get_ocr_result(path); + return fill_response(out, len, [&](Response &rsp) { + rsp.msg.ocr.status = ret.status; + rsp.msg.ocr.result = (char *)ret.result.c_str(); + }); +} + +bool rpc_receive_transfer(const Transfer &tf, uint8_t *out, size_t *len) +{ + return fill_response( + out, len, [&](Response &rsp) { rsp.msg.status = receive_transfer(tf.wxid, tf.tfid, tf.taid); }); +} +} // namespace misc diff --git a/WeChatFerry/spy/misc_manager.h b/WeChatFerry/spy/misc_manager.h new file mode 100644 index 0000000..0792c3f --- /dev/null +++ b/WeChatFerry/spy/misc_manager.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "wcf.pb.h" + +#include "pb_types.h" + +namespace misc +{ + +std::string get_audio(uint64_t id, const std::filesystem::path &dir); +std::string get_pcm_audio(uint64_t id, const std::filesystem::path &dir, int32_t sr); +std::string decrypt_image(const std::filesystem::path &src, const std::filesystem::path &dst); +std::string get_login_url(); + +int refresh_pyq(uint64_t id); +int download_attachment(uint64_t id, const std::filesystem::path &thumb, const std::filesystem::path &extra); +int revoke_message(uint64_t id); + +OcrResult_t get_ocr_result(const std::filesystem::path &path); +int receive_transfer(const std::string &wxid, const std::string &transferid, const std::string &transactionid); + +// RPC +// clang-format off +bool rpc_get_audio(const AudioMsg &am, uint8_t *out, size_t *len); +bool rpc_get_pcm_audio(uint64_t id, const std::filesystem::path &dir, int32_t sr, uint8_t *out, size_t *len); +bool rpc_decrypt_image(const DecPath &dec, uint8_t *out, size_t *len); +bool rpc_get_login_url(uint8_t *out, size_t *len); +bool rpc_refresh_pyq(uint64_t id, uint8_t *out, size_t *len); +bool rpc_download_attachment(const AttachMsg &att, uint8_t *out, size_t *len); +bool rpc_revoke_message(uint64_t id, uint8_t *out, size_t *len); +bool rpc_get_ocr_result(const std::filesystem::path &path, uint8_t *out, size_t *len); +bool rpc_receive_transfer(const Transfer &tf, uint8_t *out, size_t *len); +// clang-format on +} // namespace misc diff --git a/WeChatFerry/spy/offsets.h b/WeChatFerry/spy/offsets.h new file mode 100644 index 0000000..15d3222 --- /dev/null +++ b/WeChatFerry/spy/offsets.h @@ -0,0 +1,149 @@ +#pragma once + +#include + +namespace Offsets +{ + +namespace Account +{ + constexpr uint64_t SERVICE = 0x1B58B50; // 账户服务 + constexpr uint64_t PATH = 0x25E9090; // 数据路径 + constexpr uint64_t WXID = 0x80; // WXID + constexpr uint64_t NAME = 0x1E8; // 昵称 + constexpr uint64_t MOBILE = 0x128; // 手机号 + constexpr uint64_t LOGIN = 0x7F8; // 登录状态 +} + +namespace Chatroom +{ + constexpr uint64_t MGR = 0x1B86F60; + constexpr uint64_t DEL = 0x2158830; + constexpr uint64_t ADD = 0x21581F0; + constexpr uint64_t INV = 0x2157BD0; +} + +namespace Contact +{ + constexpr uint64_t MGR = 0x1B44B20; + constexpr uint64_t LIST = 0x21A1E00; + constexpr uint64_t BIN = 0x200; + constexpr uint64_t BIN_LEN = 0x208; + constexpr uint64_t WXID = 0x10; + constexpr uint64_t CODE = 0x30; + constexpr uint64_t REMARK = 0x80; + constexpr uint64_t NAME = 0xA0; + constexpr uint64_t GENDER = 0x0E; + constexpr uint64_t STEP = 0x6A8; +} + +namespace Db +{ + constexpr uint64_t INSTANCE = 0x59226C8; // 数据库实例地址 + constexpr uint64_t MSG_I = 0x5980420; // MSGi.db & MediaMsgi.db + constexpr uint64_t MICROMSG = 0xB8; + constexpr uint64_t CHAT_MSG = 0x2C8; + constexpr uint64_t MISC = 0x5F0; + constexpr uint64_t EMOTION = 0x15F0; + constexpr uint64_t MEDIA = 0xF48; + constexpr uint64_t BIZCHAT_MSG = 0x1AC0; + constexpr uint64_t FUNCTION_MSG = 0x1B98; + constexpr uint64_t NAME = 0x28; + + // SQLITE3 + constexpr uint64_t EXEC = 0x3A76430; + // constexpr uint64_t BACKUP_INIT = EXEC - 0x1D113E0; + constexpr uint64_t PREPARE = EXEC + 0x7CB0; + // constexpr uint64_t OPEN = EXEC - 0x1CA2430; + // constexpr uint64_t BACKUP_STEP = EXEC - 0x1D110A0; + // constexpr uint64_t BACKUP_REMAINING = EXEC - 0x1D10880; + // constexpr uint64_t BACKUP_PAGECOUNT = EXEC - 0x1D10890; + // constexpr uint64_t BACKUP_FINISH = EXEC - 0x1D10940; + // constexpr uint64_t SLEEP = EXEC - 0x1CA1BB0; + // constexpr uint64_t ERRCODE = EXEC - 0x1CA3770; + // constexpr uint64_t CLOSE = EXEC - 0x1CA4FD0; + constexpr uint64_t STEP = EXEC - 0x3C000; + constexpr uint64_t COLUMN_COUNT = EXEC - 0x3B7E0; + constexpr uint64_t COLUMN_NAME = EXEC - 0x3ADE0; + constexpr uint64_t COLUMN_TYPE = EXEC - 0x3AF90; + constexpr uint64_t COLUMN_BLOB = EXEC - 0x3B7B0; + constexpr uint64_t COLUMN_BYTES = EXEC - 0x3B6C0; + constexpr uint64_t FINALIZE = EXEC - 0x3CF50; +} + +namespace Message +{ + namespace Log + { + constexpr uint64_t LEVEL = 0x56E4244; // 日志级别 + constexpr uint64_t CALL = 0x261B890; // 日志函数 + } + + namespace Receive + { + constexpr uint64_t CALL = 0x2141E80; // 接收消息 Call + constexpr uint64_t ID = 0x30; // 消息 ID + constexpr uint64_t TYPE = 0x38; // 消息类型 + constexpr uint64_t SELF = 0x3C; // 消息是否来自自己 + constexpr uint64_t TIMESTAMP = 0x44; // 消息时间戳 + constexpr uint64_t ROOMID = 0x48; // 群聊 ID(或者发送者 wxid) + constexpr uint64_t CONTENT = 0x88; // 消息内容 + constexpr uint64_t WXID = 0x240; // 发送者 wxid + constexpr uint64_t SIGN = 0x260; // 消息签名 + constexpr uint64_t THUMB = 0x280; // 缩略图路径 + constexpr uint64_t EXTRA = 0x2A0; // 原图路径 + constexpr uint64_t XML = 0x308; // 消息 XML + + constexpr uint64_t PYQ_CALL = 0x2E56080; // 接收朋友圈 Call + constexpr uint64_t PYQ_START = 0x30; // 开始地址 + constexpr uint64_t PYQ_END = 0x38; // 结束地址 + constexpr uint64_t PYQ_SENDER = 0x18; // 发布者 + constexpr uint64_t PYQ_TS = 0x38; // 时间戳 + constexpr uint64_t PYQ_CONTENT = 0x48; // 文本内容 + constexpr uint64_t PYQ_XML = 0x9B8; // 其他内容 + } + + namespace Send + { + constexpr uint64_t MGR = 0x1B57350; + constexpr uint64_t INSTANCE = 0x1B614C0; + constexpr uint64_t FREE = 0x1B58BD0; + constexpr uint64_t TEXT = 0x22C9CA0; + constexpr uint64_t IMAGE = 0x22BF430; + constexpr uint64_t APP_MGR = 0x1B5C2F0; + constexpr uint64_t FILE = 0x20D30E0; + constexpr uint64_t XML = 0x0; + constexpr uint64_t XML_BUF_SIGN = 0x0; + constexpr uint64_t EMOTION_MGR = 0x1BD2310; + constexpr uint64_t EMOTION = 0x21B8100; + + constexpr uint64_t NEW_MM_READER = 0x1B60A10; + constexpr uint64_t FREE_MM_READER = 0x1B5FDE0; + constexpr uint64_t RICH_TEXT = 0x20DD0C0; + + constexpr uint64_t PAT = 0x2CC1E90; + + constexpr uint64_t FORWARD = 0x22C9220; + } +} + +namespace Misc +{ + constexpr uint64_t QR_CODE = 0x2025A80; + + constexpr uint64_t INSATNCE = Message::Send::INSTANCE; + constexpr uint64_t FREE = Message::Send::FREE; + constexpr uint64_t CHAT_MGR = 0x1B8AA50; + constexpr uint64_t PRE_LOCAL_ID_MGR = 0x2142BF0; + constexpr uint64_t PRE_DOWNLOAD_MGR = 0x1C12260; + constexpr uint64_t PUSH_ATTACH_TASK = 0x1CE3050; + + namespace Sns + { + constexpr uint64_t DATA_MGR = 0x21E52F0; + constexpr uint64_t TIMELINE = 0x2DC6180; + constexpr uint64_t FIRST = 0x2E346C0; + constexpr uint64_t NEXT = 0x2E5A270; + } +} +} diff --git a/WeChatFerry/spy/receive_msg.cpp b/WeChatFerry/spy/receive_msg.cpp deleted file mode 100644 index e283ce0..0000000 --- a/WeChatFerry/spy/receive_msg.cpp +++ /dev/null @@ -1,378 +0,0 @@ -#pragma execution_character_set("utf-8") - -#include "MinHook.h" -#include "framework.h" -#include -#include -#include - -#include "log.hpp" -#include "receive_msg.h" -#include "user_info.h" -#include "util.h" - -// Defined in rpc_server.cpp -extern bool gIsLogging, gIsListening, gIsListeningPyq; -extern mutex gMutex; -extern condition_variable gCV; -extern queue gMsgQueue; - -// Defined in spy.cpp -extern QWORD g_WeChatWinDllAddr; - -#define OS_RECV_MSG_ID 0x30 -#define OS_RECV_MSG_TYPE 0x38 -#define OS_RECV_MSG_SELF 0x3C -#define OS_RECV_MSG_TS 0x44 -#define OS_RECV_MSG_ROOMID 0x48 -#define OS_RECV_MSG_CONTENT 0x88 -#define OS_RECV_MSG_WXID 0x240 -#define OS_RECV_MSG_SIGN 0x260 -#define OS_RECV_MSG_THUMB 0x280 -#define OS_RECV_MSG_EXTRA 0x2A0 -#define OS_RECV_MSG_XML 0x308 -#define OS_RECV_MSG_CALL 0x213ED90 -#define OS_PYQ_MSG_START 0x30 -#define OS_PYQ_MSG_END 0x38 -#define OS_PYQ_MSG_TS 0x38 -#define OS_PYQ_MSG_XML 0x9B8 -#define OS_PYQ_MSG_SENDER 0x18 -#define OS_PYQ_MSG_CONTENT 0x48 -#define OS_PYQ_MSG_CALL 0x2E42C90 -#define OS_WXLOG 0x2613D20 - -typedef QWORD (*RecvMsg_t)(QWORD, QWORD); -typedef QWORD (*WxLog_t)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); -typedef QWORD (*RecvPyq_t)(QWORD, QWORD, QWORD); - -static RecvMsg_t funcRecvMsg = nullptr; -static RecvMsg_t realRecvMsg = nullptr; -static WxLog_t funcWxLog = nullptr; -static WxLog_t realWxLog = nullptr; -static RecvPyq_t funcRecvPyq = nullptr; -static RecvPyq_t realRecvPyq = nullptr; -static bool isMH_Initialized = false; - -MsgTypes_t GetMsgTypes() -{ - const MsgTypes_t m = { - { 0x00, "朋友圈消息" }, - { 0x01, "文字" }, - { 0x03, "图片" }, - { 0x22, "语音" }, - { 0x25, "好友确认" }, - { 0x28, "POSSIBLEFRIEND_MSG" }, - { 0x2A, "名片" }, - { 0x2B, "视频" }, - { 0x2F, "石头剪刀布 | 表情图片" }, - { 0x30, "位置" }, - { 0x31, "共享实时位置、文件、转账、链接" }, - { 0x32, "VOIPMSG" }, - { 0x33, "微信初始化" }, - { 0x34, "VOIPNOTIFY" }, - { 0x35, "VOIPINVITE" }, - { 0x3E, "小视频" }, - { 0x42, "微信红包" }, - { 0x270F, "SYSNOTICE" }, - { 0x2710, "红包、系统消息" }, - { 0x2712, "撤回消息" }, - { 0x100031, "搜狗表情" }, - { 0x1000031, "链接" }, - { 0x1A000031, "微信红包" }, - { 0x20010031, "红包封面" }, - { 0x2D000031, "视频号视频" }, - { 0x2E000031, "视频号名片" }, - { 0x31000031, "引用消息" }, - { 0x37000031, "拍一拍" }, - { 0x3A000031, "视频号直播" }, - { 0x3A100031, "商品链接" }, - { 0x3A200031, "视频号直播" }, - { 0x3E000031, "音乐链接" }, - { 0x41000031, "文件" }, - }; - - return m; -} - -static QWORD DispatchMsg(QWORD arg1, QWORD arg2) -{ - WxMsg_t wxMsg = { 0 }; - try { - wxMsg.id = GET_QWORD(arg2 + OS_RECV_MSG_ID); - wxMsg.type = GET_DWORD(arg2 + OS_RECV_MSG_TYPE); - wxMsg.is_self = GET_DWORD(arg2 + OS_RECV_MSG_SELF); - wxMsg.ts = GET_DWORD(arg2 + OS_RECV_MSG_TS); - wxMsg.content = GetStringByWstrAddr(arg2 + OS_RECV_MSG_CONTENT); - wxMsg.sign = GetStringByWstrAddr(arg2 + OS_RECV_MSG_SIGN); - wxMsg.xml = GetStringByWstrAddr(arg2 + OS_RECV_MSG_XML); - - string roomid = GetStringByWstrAddr(arg2 + OS_RECV_MSG_ROOMID); - wxMsg.roomid = roomid; - if (roomid.find("@chatroom") != string::npos) { // 群 ID 的格式为 xxxxxxxxxxx@chatroom - wxMsg.is_group = true; - if (wxMsg.is_self) { - wxMsg.sender = GetSelfWxid(); - } else { - wxMsg.sender = GetStringByWstrAddr(arg2 + OS_RECV_MSG_WXID); - } - } else { - wxMsg.is_group = false; - if (wxMsg.is_self) { - wxMsg.sender = GetSelfWxid(); - } else { - wxMsg.sender = roomid; - } - } - - wxMsg.thumb = GetStringByWstrAddr(arg2 + OS_RECV_MSG_THUMB); - if (!wxMsg.thumb.empty()) { - wxMsg.thumb = GetHomePath() + wxMsg.thumb; - replace(wxMsg.thumb.begin(), wxMsg.thumb.end(), '\\', '/'); - } - - wxMsg.extra = GetStringByWstrAddr(arg2 + OS_RECV_MSG_EXTRA); - if (!wxMsg.extra.empty()) { - wxMsg.extra = GetHomePath() + wxMsg.extra; - replace(wxMsg.extra.begin(), wxMsg.extra.end(), '\\', '/'); - } - } catch (const std::exception &e) { - LOG_ERROR(GB2312ToUtf8(e.what())); - } catch (...) { - LOG_ERROR("Unknow exception."); - } - - { - unique_lock lock(gMutex); - gMsgQueue.push(wxMsg); // 推送到队列 - } - - gCV.notify_all(); // 通知各方消息就绪 - return realRecvMsg(arg1, arg2); -} - -static QWORD PrintWxLog(QWORD a1, QWORD a2, QWORD a3, QWORD a4, QWORD a5, QWORD a6, QWORD a7, QWORD a8, QWORD a9, - QWORD a10, QWORD a11, QWORD a12) -{ - QWORD p = realWxLog(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); - if (p == 0 || p == 1) { - return p; - } - - LOG_INFO("【WX】\n{}", GB2312ToUtf8((char *)p)); - - return p; -} - -static void DispatchPyq(QWORD arg1, QWORD arg2, QWORD arg3) -{ - QWORD startAddr = *(QWORD *)(arg2 + OS_PYQ_MSG_START); - QWORD endAddr = *(QWORD *)(arg2 + OS_PYQ_MSG_END); - - if (startAddr == 0) { - return; - } - - while (startAddr < endAddr) { - WxMsg_t wxMsg; - - wxMsg.type = 0x00; // 朋友圈消息 - wxMsg.is_self = false; - wxMsg.is_group = false; - wxMsg.id = GET_QWORD(startAddr); - wxMsg.ts = GET_DWORD(startAddr + OS_PYQ_MSG_TS); - wxMsg.xml = GetStringByWstrAddr(startAddr + OS_PYQ_MSG_XML); - wxMsg.sender = GetStringByWstrAddr(startAddr + OS_PYQ_MSG_SENDER); - wxMsg.content = GetStringByWstrAddr(startAddr + OS_PYQ_MSG_CONTENT); - - { - unique_lock lock(gMutex); - gMsgQueue.push(wxMsg); // 推送到队列 - } - - gCV.notify_all(); // 通知各方消息就绪 - - startAddr += 0x1618; - } -} - -static MH_STATUS InitializeHook() -{ - if (isMH_Initialized) { - return MH_OK; - } - MH_STATUS status = MH_Initialize(); - if (status == MH_OK) { - isMH_Initialized = true; - } - return status; -} - -static MH_STATUS UninitializeHook() -{ - if (!isMH_Initialized) { - return MH_OK; - } - if (gIsLogging || gIsListening || gIsListeningPyq) { - return MH_OK; - } - MH_STATUS status = MH_Uninitialize(); - if (status == MH_OK) { - isMH_Initialized = false; - } - return status; -} - -void EnableLog() -{ - MH_STATUS status = MH_UNKNOWN; - if (gIsLogging) { - LOG_WARN("gIsLogging"); - return; - } - WxLog_t funcWxLog = (WxLog_t)(g_WeChatWinDllAddr + OS_WXLOG); - - status = InitializeHook(); - if (status != MH_OK) { - LOG_ERROR("MH_Initialize failed: {}", to_string(status)); - return; - } - - status = MH_CreateHook(funcWxLog, &PrintWxLog, reinterpret_cast(&realWxLog)); - if (status != MH_OK) { - LOG_ERROR("MH_CreateHook failed: {}", to_string(status)); - return; - } - - status = MH_EnableHook(funcWxLog); - if (status != MH_OK) { - LOG_ERROR("MH_EnableHook failed: {}", to_string(status)); - return; - } - gIsLogging = true; -} - -void DisableLog() -{ - MH_STATUS status = MH_UNKNOWN; - if (!gIsLogging) { - return; - } - - status = MH_DisableHook(funcWxLog); - if (status != MH_OK) { - LOG_ERROR("MH_DisableHook failed: {}", to_string(status)); - return; - } - - gIsLogging = false; - - status = UninitializeHook(); - if (status != MH_OK) { - LOG_ERROR("MH_Uninitialize failed: {}", to_string(status)); - return; - } -} - -void ListenMessage() -{ - MH_STATUS status = MH_UNKNOWN; - if (gIsListening) { - LOG_WARN("gIsListening"); - return; - } - funcRecvMsg = (RecvMsg_t)(g_WeChatWinDllAddr + OS_RECV_MSG_CALL); - - status = InitializeHook(); - if (status != MH_OK) { - LOG_ERROR("MH_Initialize failed: {}", to_string(status)); - return; - } - - status = MH_CreateHook(funcRecvMsg, &DispatchMsg, reinterpret_cast(&realRecvMsg)); - if (status != MH_OK) { - LOG_ERROR("MH_CreateHook failed: {}", to_string(status)); - return; - } - - status = MH_EnableHook(funcRecvMsg); - if (status != MH_OK) { - LOG_ERROR("MH_EnableHook failed: {}", to_string(status)); - return; - } - - gIsListening = true; -} - -void UnListenMessage() -{ - MH_STATUS status = MH_UNKNOWN; - if (!gIsListening) { - return; - } - - status = MH_DisableHook(funcRecvMsg); - if (status != MH_OK) { - LOG_ERROR("MH_DisableHook failed: {}", to_string(status)); - return; - } - - gIsListening = false; - - status = UninitializeHook(); - if (status != MH_OK) { - LOG_ERROR("MH_Uninitialize failed: {}", to_string(status)); - return; - } -} - -void ListenPyq() -{ - MH_STATUS status = MH_UNKNOWN; - if (gIsListeningPyq) { - LOG_WARN("gIsListeningPyq"); - return; - } - funcRecvPyq = (RecvPyq_t)(g_WeChatWinDllAddr + OS_PYQ_MSG_CALL); - - status = InitializeHook(); - if (status != MH_OK) { - LOG_ERROR("MH_Initialize failed: {}", to_string(status)); - return; - } - - status = MH_CreateHook(funcRecvPyq, &DispatchPyq, reinterpret_cast(&realRecvPyq)); - if (status != MH_OK) { - LOG_ERROR("MH_CreateHook failed: {}", to_string(status)); - return; - } - - status = MH_EnableHook(funcRecvPyq); - if (status != MH_OK) { - LOG_ERROR("MH_EnableHook failed: {}", to_string(status)); - return; - } - - gIsListeningPyq = true; -} - -void UnListenPyq() -{ - MH_STATUS status = MH_UNKNOWN; - if (!gIsListeningPyq) { - return; - } - - status = MH_DisableHook(funcRecvPyq); - if (status != MH_OK) { - LOG_ERROR("MH_DisableHook failed: {}", to_string(status)); - return; - } - - gIsListeningPyq = false; - - status = UninitializeHook(); - if (status != MH_OK) { - LOG_ERROR("MH_Uninitialize failed: {}", to_string(status)); - return; - } -} diff --git a/WeChatFerry/spy/receive_msg.h b/WeChatFerry/spy/receive_msg.h deleted file mode 100644 index 73d8759..0000000 --- a/WeChatFerry/spy/receive_msg.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "pb_types.h" - -void EnableLog(); -void DisableLog(); -void ListenPyq(); -void UnListenPyq(); -void ListenMessage(); -void UnListenMessage(); -MsgTypes_t GetMsgTypes(); diff --git a/WeChatFerry/spy/rpc_helper.h b/WeChatFerry/spy/rpc_helper.h new file mode 100644 index 0000000..feeb081 --- /dev/null +++ b/WeChatFerry/spy/rpc_helper.h @@ -0,0 +1,92 @@ +#pragma once + +#include + +#include + +#include "wcf.pb.h" + +#include "log.hpp" +#include "pb_encode.h" +#include "pb_types.h" + +static const std::unordered_map rpc_tag_map + = { { Functions_FUNC_IS_LOGIN, Response_status_tag }, + { Functions_FUNC_GET_SELF_WXID, Response_str_tag }, + { Functions_FUNC_GET_USER_INFO, Response_ui_tag }, + { Functions_FUNC_GET_MSG_TYPES, Response_types_tag }, + { Functions_FUNC_GET_CONTACTS, Response_contacts_tag }, + { Functions_FUNC_GET_DB_NAMES, Response_dbs_tag }, + { Functions_FUNC_GET_DB_TABLES, Response_tables_tag }, + { Functions_FUNC_GET_AUDIO_MSG, Response_str_tag }, + { Functions_FUNC_SEND_TXT, Response_status_tag }, + { Functions_FUNC_SEND_IMG, Response_status_tag }, + { Functions_FUNC_SEND_FILE, Response_status_tag }, + { Functions_FUNC_SEND_XML, Response_status_tag }, + { Functions_FUNC_SEND_RICH_TXT, Response_status_tag }, + { Functions_FUNC_SEND_PAT_MSG, Response_status_tag }, + { Functions_FUNC_FORWARD_MSG, Response_status_tag }, + { Functions_FUNC_SEND_EMOTION, Response_status_tag }, + { Functions_FUNC_ENABLE_RECV_TXT, Response_status_tag }, + { Functions_FUNC_DISABLE_RECV_TXT, Response_status_tag }, + { Functions_FUNC_EXEC_DB_QUERY, Response_rows_tag }, + { Functions_FUNC_REFRESH_PYQ, Response_status_tag }, + { Functions_FUNC_DOWNLOAD_ATTACH, Response_status_tag }, + { Functions_FUNC_GET_CONTACT_INFO, Response_contacts_tag }, + { Functions_FUNC_ACCEPT_FRIEND, Response_status_tag }, + { Functions_FUNC_RECV_TRANSFER, Response_status_tag }, + { Functions_FUNC_REVOKE_MSG, Response_status_tag }, + { Functions_FUNC_REFRESH_QRCODE, Response_str_tag }, + { Functions_FUNC_DECRYPT_IMAGE, Response_str_tag }, + { Functions_FUNC_EXEC_OCR, Response_ocr_tag }, + { Functions_FUNC_ADD_ROOM_MEMBERS, Response_status_tag }, + { Functions_FUNC_DEL_ROOM_MEMBERS, Response_status_tag }, + { Functions_FUNC_INV_ROOM_MEMBERS, Response_status_tag } }; + +template bool fill_response(uint8_t *out, size_t *len, AssignFunc assign) +{ + Response rsp = Response_init_default; + rsp.func = FuncType; + + auto it = rpc_tag_map.find(FuncType); + if (it == rpc_tag_map.end()) { + LOG_ERROR("Unknown function type: {}", magic_enum::enum_name(rsp.func)); + return false; + } + rsp.which_msg = it->second; + + assign(rsp); + + pb_ostream_t stream = pb_ostream_from_buffer(out, *len); + if (!pb_encode(&stream, Response_fields, &rsp)) { + LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); + return false; + } + *len = stream.bytes_written; + return true; +} + +template +bool fill_response(uint8_t *out, size_t *len, DataType &&data, AssignFunc &&assign) +{ + Response rsp = Response_init_default; + rsp.func = FuncType; + + auto it = rpc_tag_map.find(FuncType); + if (it == rpc_tag_map.end()) { + LOG_ERROR("Unknown function type: {}", magic_enum::enum_name(rsp.func)); + return false; + } + rsp.which_msg = it->second; + + assign(rsp, data); + + pb_ostream_t stream = pb_ostream_from_buffer(out, *len); + if (!pb_encode(&stream, Response_fields, &rsp)) { + LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); + return false; + } + *len = stream.bytes_written; + + return true; +} diff --git a/WeChatFerry/spy/rpc_server.cpp b/WeChatFerry/spy/rpc_server.cpp index 1d09e30..094420b 100644 --- a/WeChatFerry/spy/rpc_server.cpp +++ b/WeChatFerry/spy/rpc_server.cpp @@ -1,5 +1,7 @@ #pragma warning(disable : 4251) +#include "rpc_server.h" + #include #include #include @@ -12,833 +14,265 @@ #include #include -#include #include #include -#include "wcf.pb.h" - -#include "chatroom_mgmt.h" -#include "contact_mgmt.h" -#include "exec_sql.h" -#include "funcs.h" +#include "account_manager.h" +#include "chatroom_manager.h" +#include "contact_manager.h" +#include "database_executor.h" #include "log.hpp" +#include "message_handler.h" +#include "message_sender.h" +#include "misc_manager.h" #include "pb_types.h" #include "pb_util.h" -#include "receive_msg.h" -#include "rpc_server.h" -#include "send_msg.h" +#include "rpc_helper.h" #include "spy.h" #include "spy_types.h" -#include "user_info.h" #include "util.h" -#define URL_SIZE 20 -#define BASE_URL "tcp://0.0.0.0" -#define G_BUF_SIZE (16 * 1024 * 1024) - namespace fs = std::filesystem; -bool gIsLogging = false; -bool gIsListening = false; -bool gIsListeningPyq = false; -mutex gMutex; -condition_variable gCV; -queue gMsgQueue; +constexpr size_t DEFAULT_BUF_SIZE = 16 * 1024 * 1024; -static int lport = 0; -static DWORD lThreadId = 0; -static bool lIsRunning = false; -static nng_socket cmdSock, msgSock; // TODO: 断开检测 -static uint8_t gBuffer[G_BUF_SIZE] = { 0 }; +std::unique_ptr RpcServer::instance_ = nullptr; -bool func_is_login(uint8_t *out, size_t *len) +RpcServer &RpcServer::getInstance() { - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_IS_LOGIN; - rsp.which_msg = Response_status_tag; - rsp.msg.status = IsLogin(); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; + if (!instance_) { + instance_.reset(new RpcServer()); } - *len = stream.bytes_written; - - return true; + return *instance_; } -bool func_get_self_wxid(uint8_t *out, size_t *len) +void RpcServer::destroyInstance() { - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_SELF_WXID; - rsp.which_msg = Response_str_tag; - - string wxid = GetSelfWxid(); - rsp.msg.str = (char *)wxid.c_str(); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; + if (instance_) { + instance_->stop(); + instance_.reset(); } - *len = stream.bytes_written; - - return true; } -bool func_get_user_info(uint8_t *out, size_t *len) +RpcServer::RpcServer(int port) + : port_(port), handler_(message::Handler::getInstance()), sender_(message::Sender::get_instance()) { - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_USER_INFO; - rsp.which_msg = Response_ui_tag; - - UserInfo_t ui = GetUserInfo(); - rsp.msg.ui.wxid = (char *)ui.wxid.c_str(); - rsp.msg.ui.name = (char *)ui.name.c_str(); - rsp.msg.ui.mobile = (char *)ui.mobile.c_str(); - rsp.msg.ui.home = (char *)ui.home.c_str(); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; + LOG_DEBUG("RpcServer 构造: 端口 {}", port_); } -bool func_get_msg_types(uint8_t *out, size_t *len) +RpcServer::~RpcServer() { - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_MSG_TYPES; - rsp.which_msg = Response_types_tag; - - MsgTypes_t types = GetMsgTypes(); - rsp.msg.types.types.funcs.encode = encode_types; - rsp.msg.types.types.arg = &types; - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; + stop(); + LOG_DEBUG("RpcServer 被析构,释放所有资源"); } -bool func_get_contacts(uint8_t *out, size_t *len) +std::string RpcServer::build_url(int port) { - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_CONTACTS; - rsp.which_msg = Response_contacts_tag; - - vector contacts = GetContacts(); - rsp.msg.contacts.contacts.funcs.encode = encode_contacts; - rsp.msg.contacts.contacts.arg = &contacts; - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; + return std::string(RpcServer::RPC_SERVER_ADDRESS) + ":" + std::to_string(port); } -bool func_get_db_names(uint8_t *out, size_t *len) +int RpcServer::start(int port) { - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_DB_NAMES; - rsp.which_msg = Response_dbs_tag; - - DbNames_t dbnames = GetDbNames(); - rsp.msg.dbs.names.funcs.encode = encode_dbnames; - rsp.msg.dbs.names.arg = &dbnames; - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; + if (isRunning_.load()) { + LOG_WARN("RPC 服务已在运行"); + return 1; } - *len = stream.bytes_written; - return true; + port_ = port; + isRunning_ = true; + + try { + cmdThread_ = std::thread(&RpcServer::run_rpc_server, this); + } catch (const std::exception &e) { + LOG_ERROR("启动 RPC 服务器失败: {}", e.what()); + isRunning_ = false; + return -2; + } +#if ENABLE_WX_LOG + handler_.EnableLog(); +#endif + LOG_INFO("RPC 服务器成功启动,监听端口: {}", port_); + return 0; } -bool func_get_db_tables(char *db, uint8_t *out, size_t *len) +int RpcServer::stop() { - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_DB_TABLES; - rsp.which_msg = Response_tables_tag; - - DbTables_t tables = GetDbTables(db); - rsp.msg.tables.tables.funcs.encode = encode_tables; - rsp.msg.tables.tables.arg = &tables; - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; + if (!isRunning_.load()) { + LOG_WARN("RPC 服务未运行"); + return 1; } - *len = stream.bytes_written; + isRunning_ = false; - return true; + handler_.UnListenPyq(); + handler_.UnListenMsg(); +#if ENABLE_WX_LOG + handler_.DisableLog(); +#endif + nng_fini(); + if (cmdThread_.joinable()) { + LOG_DEBUG("等待命令线程关闭"); + cmdThread_.join(); + } + LOG_DEBUG("命令线程已经关闭"); + + if (msgThread_.joinable()) { + LOG_DEBUG("等待消息线程关闭"); + msgThread_.join(); + } + LOG_DEBUG("消息线程已经关闭"); + LOG_INFO("RPC 服务已停止"); + return 0; } -bool func_get_audio_msg(uint64_t id, char *dir, uint8_t *out, size_t *len) +void RpcServer::on_message_callback() { - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_AUDIO_MSG; - rsp.which_msg = Response_str_tag; - string path = ""; + try { + int rv; + nng_socket msgSock = NNG_SOCKET_INITIALIZER; + Response rsp = Response_init_default; + rsp.func = Functions_FUNC_ENABLE_RECV_TXT; + rsp.which_msg = Response_wxmsg_tag; + std::vector msgBuffer(DEFAULT_BUF_SIZE); - if (dir == NULL) { - LOG_ERROR("Empty dir."); - } else { - path = GetAudio(id, dir); - } + pb_ostream_t stream = pb_ostream_from_buffer(msgBuffer.data(), msgBuffer.size()); - rsp.msg.str = (char *)path.c_str(); + std::string url = build_url(port_ + 1); + if ((rv = nng_pair1_open(&msgSock)) != 0) { + LOG_ERROR("nng_pair0_open error {}", nng_strerror(rv)); + return; + } - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; + if ((rv = nng_listen(msgSock, url.c_str(), NULL, 0)) != 0) { + LOG_ERROR("nng_listen error {}", nng_strerror(rv)); + return; + } - return true; -} + if ((rv = nng_setopt_ms(msgSock, NNG_OPT_SENDTIMEO, 5000)) != 0) { + LOG_ERROR("nng_setopt_ms: {}", nng_strerror(rv)); + return; + } -bool func_send_txt(TextMsg txt, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_TXT; - rsp.which_msg = Response_status_tag; + while (handler_.isMessageListening()) { + std::optional msgOpt; + { + std::unique_lock lock(handler_.getMutex()); + bool hasMessage + = handler_.getConditionVariable().wait_for(lock, std::chrono::milliseconds(1000), [&]() { + lock.unlock(); + msgOpt = handler_.popMessage(); + lock.lock(); + return msgOpt.has_value(); + }); - if ((txt.msg == NULL) || (txt.receiver == NULL)) { - LOG_ERROR("Empty message or receiver."); - rsp.msg.status = -1; // Empty message or empty receiver - } else { - string msg(txt.msg); - string receiver(txt.receiver); - string aters(txt.aters ? txt.aters : ""); - - SendTextMessage(receiver, msg, aters); - rsp.msg.status = 0; - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_send_img(char *path, char *receiver, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_IMG; - rsp.which_msg = Response_status_tag; - - if ((path == NULL) || (receiver == NULL)) { - LOG_ERROR("Empty path or receiver."); - rsp.msg.status = -1; - } else if (!fs::exists(String2Wstring(path))) { - LOG_ERROR("Path does not exists: {}", path); - rsp.msg.status = -2; - } else { - SendImageMessage(receiver, path); - rsp.msg.status = 0; - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_send_file(char *path, char *receiver, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_FILE; - rsp.which_msg = Response_status_tag; - - if ((path == NULL) || (receiver == NULL)) { - LOG_ERROR("Empty path or receiver."); - rsp.msg.status = -1; - } else if (!fs::exists(String2Wstring(path))) { - LOG_ERROR("Path does not exists: {}", path); - rsp.msg.status = -2; - } else { - SendFileMessage(receiver, path); - rsp.msg.status = 0; - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_send_emotion(char *path, char *receiver, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_EMOTION; - rsp.which_msg = Response_status_tag; - - if ((path == NULL) || (receiver == NULL)) { - LOG_ERROR("Empty path or receiver."); - rsp.msg.status = -1; - } else { - SendEmotionMessage(receiver, path); - rsp.msg.status = 0; - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_send_xml(XmlMsg xml, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_XML; - rsp.which_msg = Response_status_tag; - - if ((xml.content == NULL) || (xml.receiver == NULL)) { - LOG_ERROR("Empty content or receiver."); - rsp.msg.status = -1; - } else { - string receiver(xml.receiver); - string content(xml.content); - string path(xml.path ? xml.path : ""); - uint64_t type = (uint64_t)xml.type; - SendXmlMessage(receiver, content, path, type); - rsp.msg.status = 0; - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_send_rich_txt(RichText rt, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_RICH_TXT; - rsp.which_msg = Response_status_tag; - - if (rt.receiver == NULL) { - LOG_ERROR("Empty receiver."); - rsp.msg.status = -1; - } else { - RichText_t rtt; - rtt.account = string(rt.account ? rt.account : ""); - rtt.digest = string(rt.digest ? rt.digest : ""); - rtt.name = string(rt.name ? rt.name : ""); - rtt.receiver = string(rt.receiver ? rt.receiver : ""); - rtt.thumburl = string(rt.thumburl ? rt.thumburl : ""); - rtt.title = string(rt.title ? rt.title : ""); - rtt.url = string(rt.url ? rt.url : ""); - - rsp.msg.status = SendRichTextMessage(rtt); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_send_pat_msg(char *roomid, char *wxid, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_PAT_MSG; - rsp.which_msg = Response_status_tag; - - if ((roomid == NULL) || (wxid == NULL)) { - LOG_ERROR("Empty roomid or wxid."); - rsp.msg.status = -1; - } else { - rsp.msg.status = SendPatMessage(roomid, wxid); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_forward_msg(uint64_t id, char *receiver, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_FORWARD_MSG; - rsp.which_msg = Response_status_tag; - - if (receiver == NULL) { - LOG_ERROR("Empty roomid or wxid."); - rsp.msg.status = -1; - } else { - rsp.msg.status = ForwardMessage(id, receiver); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -static void PushMessage() -{ - static uint8_t buffer[G_BUF_SIZE] = { 0 }; - - int rv; - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_ENABLE_RECV_TXT; - rsp.which_msg = Response_wxmsg_tag; - - pb_ostream_t stream = pb_ostream_from_buffer(buffer, G_BUF_SIZE); - - char url[URL_SIZE + 1] = { 0 }; - sprintf_s(url, URL_SIZE, "%s:%d", BASE_URL, lport + 1); - if ((rv = nng_pair1_open(&msgSock)) != 0) { - LOG_ERROR("nng_pair0_open error {}", nng_strerror(rv)); - return; - } - - if ((rv = nng_listen(msgSock, url, NULL, 0)) != 0) { - LOG_ERROR("nng_listen error {}", nng_strerror(rv)); - return; - } - - LOG_INFO("MSG Server listening on {}", url); - if ((rv = nng_setopt_ms(msgSock, NNG_OPT_SENDTIMEO, 2000)) != 0) { - LOG_ERROR("nng_setopt_ms: {}", nng_strerror(rv)); - return; - } - - while (gIsListening) { - unique_lock lock(gMutex); - if (gCV.wait_for(lock, chrono::milliseconds(1000), []() { return !gMsgQueue.empty(); })) { - while (!gMsgQueue.empty()) { - auto wxmsg = gMsgQueue.front(); - rsp.msg.wxmsg.id = wxmsg.id; - rsp.msg.wxmsg.is_self = wxmsg.is_self; - rsp.msg.wxmsg.is_group = wxmsg.is_group; - rsp.msg.wxmsg.type = wxmsg.type; - rsp.msg.wxmsg.ts = wxmsg.ts; - rsp.msg.wxmsg.roomid = (char *)wxmsg.roomid.c_str(); - rsp.msg.wxmsg.content = (char *)wxmsg.content.c_str(); - rsp.msg.wxmsg.sender = (char *)wxmsg.sender.c_str(); - rsp.msg.wxmsg.sign = (char *)wxmsg.sign.c_str(); - rsp.msg.wxmsg.thumb = (char *)wxmsg.thumb.c_str(); - rsp.msg.wxmsg.extra = (char *)wxmsg.extra.c_str(); - rsp.msg.wxmsg.xml = (char *)wxmsg.xml.c_str(); - gMsgQueue.pop(); - LOG_DEBUG("Push msg: {}", wxmsg.content); - pb_ostream_t stream = pb_ostream_from_buffer(buffer, G_BUF_SIZE); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); + if (!hasMessage) { continue; } + } - rv = nng_send(msgSock, buffer, stream.bytes_written, 0); - if (rv != 0) { - LOG_ERROR("msgSock-nng_send: {}", nng_strerror(rv)); - } - LOG_DEBUG("Send data length {}", stream.bytes_written); + if (!msgOpt.has_value()) { + LOG_WARN("popMessage returned empty after wait_for success."); + continue; + } + + WxMsg_t wxmsg = std::move(msgOpt.value()); + rsp.msg.wxmsg.id = wxmsg.id; + rsp.msg.wxmsg.is_self = wxmsg.is_self; + rsp.msg.wxmsg.is_group = wxmsg.is_group; + rsp.msg.wxmsg.type = wxmsg.type; + rsp.msg.wxmsg.ts = wxmsg.ts; + rsp.msg.wxmsg.roomid = (char *)wxmsg.roomid.c_str(); + rsp.msg.wxmsg.content = (char *)wxmsg.content.c_str(); + rsp.msg.wxmsg.sender = (char *)wxmsg.sender.c_str(); + rsp.msg.wxmsg.sign = (char *)wxmsg.sign.c_str(); + rsp.msg.wxmsg.thumb = (char *)wxmsg.thumb.c_str(); + rsp.msg.wxmsg.extra = (char *)wxmsg.extra.c_str(); + rsp.msg.wxmsg.xml = (char *)wxmsg.xml.c_str(); + + LOG_DEBUG("Push msg: {}", wxmsg.content); + pb_ostream_t stream = pb_ostream_from_buffer(msgBuffer.data(), msgBuffer.size()); + if (!pb_encode(&stream, Response_fields, &rsp)) { + LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); + continue; + } + + rv = nng_send(msgSock, msgBuffer.data(), stream.bytes_written, 0); + if (rv != 0) { + LOG_ERROR("msgSock-nng_send: {}", nng_strerror(rv)); + } + LOG_DEBUG("Send data length {}", stream.bytes_written); + } + nng_close(msgSock); + LOG_DEBUG("Leave MSG Server."); + } catch (const std::exception &e) { + LOG_ERROR("Fatal exception in on_message_callback: {}", e.what()); + } catch (...) { + LOG_ERROR("Unknown fatal exception in on_message_callback."); + } +} + +bool RpcServer::start_message_listener(bool pyq, uint8_t *out, size_t *len) +{ + return fill_response(out, len, [&](Response &rsp) { + rsp.msg.status = handler_.ListenMsg(); + if (rsp.msg.status == 0) { + if (pyq) { + handler_.ListenPyq(); + } + msgThread_ = std::thread(&RpcServer::on_message_callback, this); + } + }); +} + +bool RpcServer::stop_message_listener(uint8_t *out, size_t *len) +{ + return fill_response(out, len, [&](Response &rsp) { + rsp.msg.status = handler_.UnListenMsg(); + if (rsp.msg.status == 0) { + handler_.UnListenPyq(); + if (msgThread_.joinable()) { + msgThread_.join(); } } - } - nng_close(msgSock); + }); } -bool func_enable_recv_txt(bool pyq, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_ENABLE_RECV_TXT; - rsp.which_msg = Response_status_tag; - rsp.msg.status = 0; +const std::unordered_map RpcServer::rpcFunctionMap = { + // clang-format off + { Functions_FUNC_IS_LOGIN, [](const Request &r, uint8_t *out, size_t *len) { return account::rpc_is_logged_in(out, len); } }, + { Functions_FUNC_GET_SELF_WXID, [](const Request &r, uint8_t *out, size_t *len) { return account::rpc_get_self_wxid(out, len); } }, + { Functions_FUNC_GET_USER_INFO, [](const Request &r, uint8_t *out, size_t *len) { return account::rpc_get_user_info(out, len); } }, + { Functions_FUNC_GET_MSG_TYPES, [](const Request &r, uint8_t *out, size_t *len) { return RpcServer::getInstance().handler_.rpc_get_msg_types(out, len); } }, + { Functions_FUNC_ENABLE_RECV_TXT, [](const Request &r, uint8_t *out, size_t *len) { return RpcServer::getInstance().start_message_listener(r.msg.flag, out, len); } }, + { Functions_FUNC_DISABLE_RECV_TXT, [](const Request &r, uint8_t *out, size_t *len) { return RpcServer::getInstance().stop_message_listener(out, len); } }, + { Functions_FUNC_GET_CONTACTS, [](const Request &r, uint8_t *out, size_t *len) { return contact::rpc_get_contacts(out, len); } }, + { Functions_FUNC_GET_DB_NAMES, [](const Request &r, uint8_t *out, size_t *len) { return db::rpc_get_db_names(out, len); } }, + { Functions_FUNC_GET_DB_TABLES, [](const Request &r, uint8_t *out, size_t *len) { return db::rpc_get_db_tables(r.msg.str, out, len); } }, + { Functions_FUNC_GET_AUDIO_MSG, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_get_audio(r.msg.am, out, len); } }, + { Functions_FUNC_SEND_TXT, [](const Request &r, uint8_t *out, size_t *len) { return RpcServer::getInstance().sender_.rpc_send_text(r.msg.txt, out, len); } }, + { Functions_FUNC_SEND_IMG, [](const Request &r, uint8_t *out, size_t *len) { return RpcServer::getInstance().sender_.rpc_send_image(r.msg.file, out, len); } }, + { Functions_FUNC_SEND_FILE, [](const Request &r, uint8_t *out, size_t *len) { return RpcServer::getInstance().sender_.rpc_send_file(r.msg.file, out, len); } }, + { Functions_FUNC_SEND_XML, [](const Request &r, uint8_t *out, size_t *len) { return RpcServer::getInstance().sender_.rpc_send_xml(r.msg.xml, out, len); } }, + { Functions_FUNC_SEND_EMOTION, [](const Request &r, uint8_t *out, size_t *len) { return RpcServer::getInstance().sender_.rpc_send_emotion(r.msg.file, out, len); } }, + { Functions_FUNC_SEND_RICH_TXT, [](const Request &r, uint8_t *out, size_t *len) { return RpcServer::getInstance().sender_.rpc_send_rich_text(r.msg.rt, out, len); } }, + { Functions_FUNC_SEND_PAT_MSG, [](const Request &r, uint8_t *out, size_t *len) { return RpcServer::getInstance().sender_.rpc_send_pat(r.msg.pm, out, len); } }, + { Functions_FUNC_FORWARD_MSG, [](const Request &r, uint8_t *out, size_t *len) { return RpcServer::getInstance().sender_.rpc_forward(r.msg.fm, out, len); } }, + { Functions_FUNC_EXEC_DB_QUERY, [](const Request &r, uint8_t *out, size_t *len) { return db::rpc_exec_db_query(r.msg.query, out, len); } }, + { Functions_FUNC_ACCEPT_FRIEND, [](const Request &r, uint8_t *out, size_t *len) { return contact::rpc_accept_friend(r.msg.v, out, len); } }, + { Functions_FUNC_RECV_TRANSFER, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_receive_transfer(r.msg.tf, out, len); } }, + { Functions_FUNC_REFRESH_PYQ, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_refresh_pyq(r.msg.ui64, out, len); } }, + { Functions_FUNC_DOWNLOAD_ATTACH, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_download_attachment(r.msg.att, out, len); } }, + { Functions_FUNC_GET_CONTACT_INFO, [](const Request &r, uint8_t *out, size_t *len) { return contact::rpc_get_contact_info(r.msg.str, out, len); } }, + { Functions_FUNC_REVOKE_MSG, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_revoke_message(r.msg.ui64, out, len); } }, + { Functions_FUNC_REFRESH_QRCODE, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_get_login_url(out, len); } }, + { Functions_FUNC_DECRYPT_IMAGE, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_decrypt_image(r.msg.dec, out, len); } }, + { Functions_FUNC_EXEC_OCR, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_get_ocr_result(r.msg.str, out, len); } }, + { Functions_FUNC_ADD_ROOM_MEMBERS, [](const Request &r, uint8_t *out, size_t *len) { return chatroom::rpc_add_chatroom_member(r.msg.m, out, len); } }, + { Functions_FUNC_DEL_ROOM_MEMBERS, [](const Request &r, uint8_t *out, size_t *len) { return chatroom::rpc_delete_chatroom_member(r.msg.m, out, len); } }, + { Functions_FUNC_INV_ROOM_MEMBERS, [](const Request &r, uint8_t *out, size_t *len) { return chatroom::rpc_invite_chatroom_member(r.msg.m, out, len); } }, + // clang-format on +}; - if (!gIsListening) { - ListenMessage(); - if (pyq) { - ListenPyq(); - } - HANDLE msgThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PushMessage, NULL, NULL, NULL); - if (msgThread == NULL) { - rsp.msg.status = GetLastError(); - LOG_ERROR("func_enable_recv_txt failed: {}", rsp.msg.status); - } else { - CloseHandle(msgThread); - } - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_disable_recv_txt(uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_DISABLE_RECV_TXT; - rsp.which_msg = Response_status_tag; - rsp.msg.status = 0; - - UnListenPyq(); - UnListenMessage(); // 可能需要1秒之后才能退出,见 PushMessage - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_exec_db_query(char *db, char *sql, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_EXEC_DB_QUERY; - rsp.which_msg = Response_rows_tag; - DbRows_t rows; - - if ((db == NULL) || (sql == NULL)) { - LOG_ERROR("Empty db or sql."); - } else { - rows = ExecDbQuery(db, sql); - } - - rsp.msg.rows.rows.arg = &rows; - rsp.msg.rows.rows.funcs.encode = encode_rows; - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_refresh_pyq(uint64_t id, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_REFRESH_PYQ; - rsp.which_msg = Response_status_tag; - - rsp.msg.status = RefreshPyq(id); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_download_attach(AttachMsg att, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_DOWNLOAD_ATTACH; - rsp.which_msg = Response_status_tag; - - uint64_t id = att.id; - string thumb = string(att.thumb ? att.thumb : ""); - string extra = string(att.extra ? att.extra : ""); - - rsp.msg.status = DownloadAttach(id, thumb, extra); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_revoke_msg(uint64_t id, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_REVOKE_MSG; - rsp.which_msg = Response_status_tag; - - rsp.msg.status = RevokeMsg(id); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_refresh_qrcode(uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_REFRESH_QRCODE; - rsp.which_msg = Response_str_tag; - - rsp.msg.str = (char *)GetLoginUrl().c_str(); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_receive_transfer(char *wxid, char *tfid, char *taid, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_RECV_TRANSFER; - rsp.which_msg = Response_status_tag; - - if ((wxid == NULL) || (tfid == NULL) || (taid == NULL)) { - rsp.msg.status = -1; - LOG_ERROR("Empty wxid, tfid or taid."); - } else { - rsp.msg.status = ReceiveTransfer(wxid, tfid, taid); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -#if 0 -bool func_accept_friend(char *v3, char *v4, int32_t scene, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_ACCEPT_FRIEND; - rsp.which_msg = Response_status_tag; - - if ((v3 == NULL) || (v4 == NULL)) { - rsp.msg.status = -1; - LOG_ERROR("Empty V3 or V4."); - } else { - rsp.msg.status = AcceptNewFriend(v3, v4, scene); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_get_contact_info(string wxid, uint8_t *out, size_t *len) -{ - /*借用 Functions_FUNC_GET_CONTACTS */ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_CONTACT_INFO; - rsp.which_msg = Response_contacts_tag; - - vector contacts; - contacts.push_back(GetContactByWxid(wxid)); - - rsp.msg.contacts.contacts.funcs.encode = encode_contacts; - rsp.msg.contacts.contacts.arg = &contacts; - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} -#endif - -bool func_decrypt_image(DecPath dec, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_DECRYPT_IMAGE; - rsp.which_msg = Response_str_tag; - string path = ""; - - if ((dec.src == NULL) || (dec.dst == NULL)) { - LOG_ERROR("Empty src or dst."); - } else { - path = DecryptImage(dec.src, dec.dst); - } - - rsp.msg.str = (char *)path.c_str(); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_exec_ocr(char *path, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_EXEC_OCR; - rsp.which_msg = Response_ocr_tag; - OcrResult_t ret = { -1, "" }; - - if (path == NULL) { - LOG_ERROR("Empty path."); - } else { - ret = GetOcrResult(path); - } - - rsp.msg.ocr.status = ret.status; - rsp.msg.ocr.result = (char *)ret.result.c_str(); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - return true; -} - -bool func_add_room_members(char *roomid, char *wxids, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_ADD_ROOM_MEMBERS; - rsp.which_msg = Response_status_tag; - rsp.msg.status = 0; - - if ((roomid == NULL) || (wxids == NULL)) { - LOG_ERROR("Empty roomid or wxids."); - rsp.msg.status = -1; - } else { - rsp.msg.status = AddChatroomMember(roomid, wxids); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_del_room_members(char *roomid, char *wxids, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_DEL_ROOM_MEMBERS; - rsp.which_msg = Response_status_tag; - rsp.msg.status = 0; - - if ((roomid == NULL) || (wxids == NULL)) { - LOG_ERROR("Empty roomid or wxids."); - rsp.msg.status = -1; - } else { - rsp.msg.status = DelChatroomMember(roomid, wxids); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_invite_room_members(char *roomid, char *wxids, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_INV_ROOM_MEMBERS; - rsp.which_msg = Response_status_tag; - rsp.msg.status = 0; - - if ((roomid == NULL) || (wxids == NULL)) { - LOG_ERROR("Empty roomid or wxids."); - rsp.msg.status = -1; - } else { - rsp.msg.status = InviteChatroomMember(roomid, wxids); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -static bool dispatcher(uint8_t *in, size_t in_len, uint8_t *out, size_t *out_len) +bool RpcServer::dispatcher(uint8_t *in, size_t in_len, uint8_t *out, size_t *out_len) { bool ret = false; Request req = Request_init_default; @@ -851,234 +285,78 @@ static bool dispatcher(uint8_t *in, size_t in_len, uint8_t *out, size_t *out_len LOG_DEBUG("{:#04x}[{}] length: {}", (uint8_t)req.func, magic_enum::enum_name(req.func), in_len); - switch (req.func) { - case Functions_FUNC_IS_LOGIN: { - ret = func_is_login(out, out_len); - break; - } - case Functions_FUNC_GET_SELF_WXID: { - ret = func_get_self_wxid(out, out_len); - break; - } - case Functions_FUNC_GET_USER_INFO: { - ret = func_get_user_info(out, out_len); - break; - } - case Functions_FUNC_GET_MSG_TYPES: { - ret = func_get_msg_types(out, out_len); - break; - } - case Functions_FUNC_GET_CONTACTS: { - ret = func_get_contacts(out, out_len); - break; - } - case Functions_FUNC_GET_DB_NAMES: { - ret = func_get_db_names(out, out_len); - break; - } - case Functions_FUNC_GET_DB_TABLES: { - ret = func_get_db_tables(req.msg.str, out, out_len); - break; - } - case Functions_FUNC_GET_AUDIO_MSG: { - ret = func_get_audio_msg(req.msg.am.id, req.msg.am.dir, out, out_len); - break; - } - case Functions_FUNC_SEND_TXT: { - ret = func_send_txt(req.msg.txt, out, out_len); - break; - } - case Functions_FUNC_SEND_IMG: { - ret = func_send_img(req.msg.file.path, req.msg.file.receiver, out, out_len); - break; - } - case Functions_FUNC_SEND_FILE: { - ret = func_send_file(req.msg.file.path, req.msg.file.receiver, out, out_len); - break; - } - case Functions_FUNC_SEND_RICH_TXT: { - ret = func_send_rich_txt(req.msg.rt, out, out_len); - break; - } - case Functions_FUNC_SEND_PAT_MSG: { - ret = func_send_pat_msg(req.msg.pm.roomid, req.msg.pm.wxid, out, out_len); - break; - } - case Functions_FUNC_FORWARD_MSG: { - ret = func_forward_msg(req.msg.fm.id, req.msg.fm.receiver, out, out_len); - break; - } - case Functions_FUNC_SEND_EMOTION: { - ret = func_send_emotion(req.msg.file.path, req.msg.file.receiver, out, out_len); - break; - } -#if 0 - case Functions_FUNC_SEND_XML: { - ret = func_send_xml(req.msg.xml, out, out_len); - break; - } -#endif - case Functions_FUNC_ENABLE_RECV_TXT: { - ret = func_enable_recv_txt(req.msg.flag, out, out_len); - break; - } - case Functions_FUNC_DISABLE_RECV_TXT: { - ret = func_disable_recv_txt(out, out_len); - break; - } - case Functions_FUNC_EXEC_DB_QUERY: { - ret = func_exec_db_query(req.msg.query.db, req.msg.query.sql, out, out_len); - break; - } - case Functions_FUNC_REFRESH_PYQ: { - ret = func_refresh_pyq(req.msg.ui64, out, out_len); - break; - } - case Functions_FUNC_DOWNLOAD_ATTACH: { - ret = func_download_attach(req.msg.att, out, out_len); - break; - } - case Functions_FUNC_RECV_TRANSFER: { - ret = func_receive_transfer(req.msg.tf.wxid, req.msg.tf.tfid, req.msg.tf.taid, out, out_len); - break; - } - case Functions_FUNC_REVOKE_MSG: { - ret = func_revoke_msg(req.msg.ui64, out, out_len); - break; - } - case Functions_FUNC_REFRESH_QRCODE: { - ret = func_refresh_qrcode(out, out_len); - break; - } -#if 0 - case Functions_FUNC_ACCEPT_FRIEND: { - ret = func_accept_friend(req.msg.v.v3, req.msg.v.v4, req.msg.v.scene, out, out_len); - break; - } - case Functions_FUNC_GET_CONTACT_INFO: { - ret = func_get_contact_info(req.msg.str, out, out_len); - break; - } -#endif - case Functions_FUNC_DECRYPT_IMAGE: { - ret = func_decrypt_image(req.msg.dec, out, out_len); - break; - } - case Functions_FUNC_EXEC_OCR: { - ret = func_exec_ocr(req.msg.str, out, out_len); - break; - } - case Functions_FUNC_ADD_ROOM_MEMBERS: { - ret = func_add_room_members(req.msg.m.roomid, req.msg.m.wxids, out, out_len); - break; - } - case Functions_FUNC_DEL_ROOM_MEMBERS: { - ret = func_del_room_members(req.msg.m.roomid, req.msg.m.wxids, out, out_len); - break; - } - case Functions_FUNC_INV_ROOM_MEMBERS: { - ret = func_invite_room_members(req.msg.m.roomid, req.msg.m.wxids, out, out_len); - break; - } - default: { - LOG_ERROR("[UNKNOW FUNCTION]"); - break; - } + auto it = RpcServer::rpcFunctionMap.find(req.func); + if (it != RpcServer::rpcFunctionMap.end()) { + ret = it->second(req, out, out_len); + } else { + LOG_ERROR("[未知方法]"); } pb_release(Request_fields, &req); return ret; } -static int RunServer() +void RpcServer::run_rpc_server() { - int rv = 0; - char url[URL_SIZE + 1] = { 0 }; - sprintf_s(url, URL_SIZE, "%s:%d", BASE_URL, lport); + int rv = 0; + nng_socket cmdSock = NNG_SOCKET_INITIALIZER; + std::string url = build_url(port_); + if ((rv = nng_pair1_open(&cmdSock)) != 0) { - LOG_ERROR("nng_pair0_open error {}", nng_strerror(rv)); - return rv; + LOG_ERROR("nng_pair1_open error: {}", nng_strerror(rv)); + return; } - if ((rv = nng_listen(cmdSock, (char *)url, NULL, 0)) != 0) { - LOG_ERROR("nng_listen error {}", nng_strerror(rv)); - return rv; + if ((rv = nng_listen(cmdSock, url.c_str(), nullptr, 0)) != 0) { + LOG_ERROR("nng_listen error: {}", nng_strerror(rv)); + nng_close(cmdSock); + nng_fini(); + return; } - LOG_INFO("CMD Server listening on {}", (char *)url); if ((rv = nng_setopt_ms(cmdSock, NNG_OPT_SENDTIMEO, 1000)) != 0) { LOG_ERROR("nng_setopt_ms error: {}", nng_strerror(rv)); - return rv; + nng_close(cmdSock); + nng_fini(); + return; } - lIsRunning = true; - while (lIsRunning) { - uint8_t *in = NULL; - size_t in_len, out_len = G_BUF_SIZE; - if ((rv = nng_recv(cmdSock, &in, &in_len, NNG_FLAG_ALLOC)) != 0) { + LOG_INFO("CMD Server listening on {}", url); + std::vector cmdBuffer(DEFAULT_BUF_SIZE); + + while (isRunning_.load()) { + uint8_t *in = nullptr; + size_t in_len, out_len = cmdBuffer.size(); + + rv = nng_recv(cmdSock, &in, &in_len, NNG_FLAG_ALLOC); + if (rv != 0) { LOG_ERROR("cmdSock-nng_recv error: {}", nng_strerror(rv)); break; } - try { - // LOG_BUFFER(in, in_len); - if (dispatcher(in, in_len, gBuffer, &out_len)) { - LOG_DEBUG("Send data length {}", out_len); - // LOG_BUFFER(gBuffer, out_len); - rv = nng_send(cmdSock, gBuffer, out_len, 0); - if (rv != 0) { - LOG_ERROR("cmdSock-nng_send: {}", nng_strerror(rv)); - } - } else { // Error - LOG_ERROR("Dispatcher failed..."); - rv = nng_send(cmdSock, gBuffer, 0, 0); + try { + if (dispatcher(in, in_len, cmdBuffer.data(), &out_len)) { + LOG_DEBUG("Send data length {}", out_len); + + rv = nng_send(cmdSock, cmdBuffer.data(), out_len, 0); + if (rv != 0) { + LOG_ERROR("cmdSock-nng_send: {}", nng_strerror(rv)); + } + } else { // 处理失败情况 + LOG_ERROR("Dispatcher failed..."); + rv = nng_send(cmdSock, cmdBuffer.data(), 0, 0); if (rv != 0) { LOG_ERROR("cmdSock-nng_send: {}", nng_strerror(rv)); } - // break; } } catch (const std::exception &e) { - LOG_ERROR(GB2312ToUtf8(e.what())); + LOG_ERROR(util::gb2312_to_utf8(e.what())); } catch (...) { - LOG_ERROR("Unknow exception."); + LOG_ERROR("Unknown exception."); } + nng_free(in, in_len); } - RpcStopServer(); - LOG_DEBUG("Leave RunServer"); - return rv; -} - -int RpcStartServer(int port) -{ - if (lIsRunning) { - return 0; - } - - lport = port; - - HANDLE rpcThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RunServer, NULL, NULL, &lThreadId); - if (rpcThread != 0) { - CloseHandle(rpcThread); - } -#if ENABLE_WX_LOG - EnableLog(); -#endif - return 0; -} - -int RpcStopServer() -{ - if (lIsRunning) { - nng_close(cmdSock); - nng_close(msgSock); - // UnListenMessage(); - lIsRunning = false; - Sleep(1000); - LOG_INFO("Server stoped."); - } -#if ENABLE_WX_LOG - DisableLog(); -#endif - return 0; + nng_close(cmdSock); + LOG_DEBUG("Leave RunRpcServer"); } diff --git a/WeChatFerry/spy/rpc_server.h b/WeChatFerry/spy/rpc_server.h index 3bc31e1..76dbc1b 100644 --- a/WeChatFerry/spy/rpc_server.h +++ b/WeChatFerry/spy/rpc_server.h @@ -1,10 +1,58 @@ #pragma once -#ifdef SPY_EXPORTS -#define SPY_API __declspec(dllexport) -#else -#define SPY_API __declspec(dllimport) -#endif +#include +#include +#include +#include -int RpcStartServer(int port); -int RpcStopServer(); +#include + +#include "wcf.pb.h" + +#include "message_handler.h" +#include "message_sender.h" + +class RpcServer +{ +public: + static RpcServer &getInstance(); + static void destroyInstance(); + + int start(int port = RPC_DEFAULT_PORT); + int stop(); + +private: + RpcServer(int port = RPC_DEFAULT_PORT); + ~RpcServer(); + RpcServer(const RpcServer &) = delete; + RpcServer &operator=(const RpcServer &) = delete; + + void run_rpc_server(); + void on_message_callback(); + bool start_message_listener(bool pyq, uint8_t *out, size_t *len); + bool stop_message_listener(uint8_t *out, size_t *len); + bool dispatcher(uint8_t *in, size_t in_len, uint8_t *out, size_t *out_len); + + static std::string build_url(int port); + + using FunctionHandler = std::function; + + // 服务器默认端口号和绑定地址 + static constexpr int RPC_DEFAULT_PORT = 10086; + static constexpr const char *RPC_SERVER_ADDRESS = "tcp://0.0.0.0"; + + int port_ = RPC_DEFAULT_PORT; + std::atomic isRunning_ { false }; + std::thread cmdThread_; + std::thread msgThread_; + + message::Handler &handler_; + message::Sender &sender_; + + struct Deleter { + void operator()(RpcServer *server) const { delete server; } + }; + + static std::unique_ptr instance_; + static const std::unordered_map rpcFunctionMap; +}; diff --git a/WeChatFerry/spy/send_msg.cpp b/WeChatFerry/spy/send_msg.cpp deleted file mode 100644 index dca37f7..0000000 --- a/WeChatFerry/spy/send_msg.cpp +++ /dev/null @@ -1,273 +0,0 @@ - -#include "framework.h" -#include -#include - -#include "exec_sql.h" -#include "log.hpp" -#include "send_msg.h" -#include "spy_types.h" -#include "util.h" - -extern HANDLE g_hEvent; -extern QWORD g_WeChatWinDllAddr; -extern string GetSelfWxid(); // Defined in spy.cpp - -#define SRTM_SIZE 0x3F0 - -#define OS_NEW 0x1B5E140 -#define OS_FREE 0x1B55850 -#define OS_SEND_MSG_MGR 0x1B53FD0 -#define OS_SEND_TEXT 0x22C6B60 -#define OS_SEND_IMAGE 0x22BC2F0 -#define OS_GET_APP_MSG_MGR 0x1B58F70 -#define OS_SEND_FILE 0x20D0230 -#define OS_RTM_NEW 0x1B5D690 -#define OS_RTM_FREE 0x1B5CA60 -#define OS_SEND_RICH_TEXT 0x20DA210 -#define OS_SEND_PAT_MSG 0x2CAEC00 -#define OS_FORWARD_MSG 0x22C60E0 -#define OS_GET_EMOTION_MGR 0x1BCEF10 -#define OS_SEND_EMOTION 0x21B52D5 -#define OS_XML_BUFSIGN 0x24F0D70 -#define OS_SEND_XML 0x20CF360 - -typedef QWORD (*New_t)(QWORD); -typedef QWORD (*Free_t)(QWORD); -typedef QWORD (*SendMsgMgr_t)(); -typedef QWORD (*GetAppMsgMgr_t)(); -typedef QWORD (*SendTextMsg_t)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); -typedef QWORD (*SendImageMsg_t)(QWORD, QWORD, QWORD, QWORD, QWORD); -typedef QWORD (*SendFileMsg_t)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD *, QWORD, QWORD *, QWORD, QWORD *, QWORD, - QWORD); -typedef QWORD (*SendRichTextMsg_t)(QWORD, QWORD, QWORD); -typedef QWORD (*SendPatMsg_t)(QWORD, QWORD); -typedef QWORD (*ForwardMsg_t)(QWORD, QWORD, QWORD, QWORD); -typedef QWORD (*GetEmotionMgr_t)(); -typedef QWORD (*SendEmotion_t)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); - -typedef QWORD (*XmlBufSign_t)(QWORD, QWORD, QWORD); -typedef QWORD (*SendXmlMsg_t)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); - -void SendTextMessage(string wxid, string msg, string atWxids) -{ - QWORD success = 0; - wstring wsWxid = String2Wstring(wxid); - wstring wsMsg = String2Wstring(msg); - WxString wxMsg(wsMsg); - WxString wxWxid(wsWxid); - - vector vAtWxids; - vector vWxAtWxids; - if (!atWxids.empty()) { - wstringstream wss(String2Wstring(atWxids)); - while (wss.good()) { - wstring wstr; - getline(wss, wstr, L','); - vAtWxids.push_back(wstr); - WxString wxAtWxid(vAtWxids.back()); - vWxAtWxids.push_back(wxAtWxid); - } - } else { - WxString wxEmpty = WxString(); - vWxAtWxids.push_back(wxEmpty); - } - - QWORD wxAters = (QWORD) & ((RawVector_t *)&vWxAtWxids)->start; - - char buffer[0x460] = { 0 }; - SendMsgMgr_t funcSendMsgMgr = (SendMsgMgr_t)(g_WeChatWinDllAddr + OS_SEND_MSG_MGR); - SendTextMsg_t funcSendTextMsg = (SendTextMsg_t)(g_WeChatWinDllAddr + OS_SEND_TEXT); - Free_t funcFree = (Free_t)(g_WeChatWinDllAddr + OS_FREE); - funcSendMsgMgr(); - success = funcSendTextMsg((QWORD)(&buffer), (QWORD)(&wxWxid), (QWORD)(&wxMsg), wxAters, 1, 1, 0, 0); - funcFree((QWORD)(&buffer)); -} - -void SendImageMessage(string wxid, string path) -{ - wstring wsWxid = String2Wstring(wxid); - wstring wsPath = String2Wstring(path); - - WxString wxWxid(wsWxid); - WxString wxPath(wsPath); - - New_t funcNew = (New_t)(g_WeChatWinDllAddr + OS_NEW); - Free_t funcFree = (Free_t)(g_WeChatWinDllAddr + OS_FREE); - SendMsgMgr_t funcSendMsgMgr = (SendMsgMgr_t)(g_WeChatWinDllAddr + OS_SEND_MSG_MGR); - SendImageMsg_t funcSendImage = (SendImageMsg_t)(g_WeChatWinDllAddr + OS_SEND_IMAGE); - - char msg[0x460] = { 0 }; - char msgTmp[0x460] = { 0 }; - QWORD *flag[10] = { 0 }; - - QWORD tmp1 = 0, tmp2 = 0; - QWORD pMsgTmp = funcNew((QWORD)(&msgTmp)); - flag[8] = &tmp1; - flag[9] = &tmp2; - flag[1] = (QWORD *)(pMsgTmp); - - QWORD pMsg = funcNew((QWORD)(&msg)); - QWORD sendMgr = funcSendMsgMgr(); - funcSendImage(sendMgr, pMsg, (QWORD)(&wxWxid), (QWORD)(&wxPath), (QWORD)(&flag)); - funcFree(pMsg); - funcFree(pMsgTmp); -} - -void SendFileMessage(string wxid, string path) -{ - wstring wsWxid = String2Wstring(wxid); - wstring wsPath = String2Wstring(path); - - WxString wxWxid(wsWxid); - WxString wxPath(wsPath); - - New_t funcNew = (New_t)(g_WeChatWinDllAddr + OS_NEW); - Free_t funcFree = (Free_t)(g_WeChatWinDllAddr + OS_FREE); - GetAppMsgMgr_t funcGetAppMsgMgr = (GetAppMsgMgr_t)(g_WeChatWinDllAddr + OS_GET_APP_MSG_MGR); - SendFileMsg_t funcSendFile = (SendFileMsg_t)(g_WeChatWinDllAddr + OS_SEND_FILE); - - char msg[0x460] = { 0 }; - QWORD tmp1[4] = { 0 }; - QWORD tmp2[4] = { 0 }; - QWORD tmp3[4] = { 0 }; - - QWORD pMsg = funcNew((QWORD)(&msg)); - QWORD appMgr = funcGetAppMsgMgr(); - funcSendFile(appMgr, pMsg, (QWORD)(&wxWxid), (QWORD)(&wxPath), 1, tmp1, 0, tmp2, 0, tmp3, 0, 0); - funcFree(pMsg); -} - -int SendRichTextMessage(RichText_t &rt) -{ // TODO: Fix memory leak - QWORD status = -1; - - New_t funcNew = (New_t)(g_WeChatWinDllAddr + OS_RTM_NEW); - Free_t funcFree = (Free_t)(g_WeChatWinDllAddr + OS_RTM_FREE); - GetAppMsgMgr_t funcGetAppMsgMgr = (GetAppMsgMgr_t)(g_WeChatWinDllAddr + OS_GET_APP_MSG_MGR); - SendRichTextMsg_t funcForwordPublicMsg = (SendRichTextMsg_t)(g_WeChatWinDllAddr + OS_SEND_RICH_TEXT); - - char *buff = (char *)HeapAlloc(GetProcessHeap(), 0, SRTM_SIZE); - if (buff == NULL) { - LOG_ERROR("Out of Memory..."); - return -1; - } - - memset(buff, 0, SRTM_SIZE); - funcNew((QWORD)buff); - WxString *pReceiver = NewWxStringFromStr(rt.receiver); - WxString *pTitle = NewWxStringFromStr(rt.title); - WxString *pUrl = NewWxStringFromStr(rt.url); - WxString *pThumburl = NewWxStringFromStr(rt.thumburl); - WxString *pDigest = NewWxStringFromStr(rt.digest); - WxString *pAccount = NewWxStringFromStr(rt.account); - WxString *pName = NewWxStringFromStr(rt.name); - - memcpy(buff + 0x8, pTitle, sizeof(WxString)); - memcpy(buff + 0x48, pUrl, sizeof(WxString)); - memcpy(buff + 0xB0, pThumburl, sizeof(WxString)); - memcpy(buff + 0xF0, pDigest, sizeof(WxString)); - memcpy(buff + 0x2C0, pAccount, sizeof(WxString)); - memcpy(buff + 0x2E0, pName, sizeof(WxString)); - - QWORD mgr = funcGetAppMsgMgr(); - status = funcForwordPublicMsg(mgr, (QWORD)(pReceiver), (QWORD)(buff)); - funcFree((QWORD)buff); - - return (int)status; -} - -int SendPatMessage(string roomid, string wxid) -{ - QWORD status = -1; - - wstring wsRoomid = String2Wstring(roomid); - wstring wsWxid = String2Wstring(wxid); - WxString wxRoomid(wsRoomid); - WxString wxWxid(wsWxid); - - SendPatMsg_t funcSendPatMsg = (SendPatMsg_t)(g_WeChatWinDllAddr + OS_SEND_PAT_MSG); - - status = funcSendPatMsg((QWORD)(&wxRoomid), (QWORD)(&wxWxid)); - return (int)status; -} - -int ForwardMessage(QWORD msgid, string receiver) -{ - int status = -1; - uint32_t dbIdx = 0; - QWORD localId = 0; - - ForwardMsg_t funcForwardMsg = (ForwardMsg_t)(g_WeChatWinDllAddr + OS_FORWARD_MSG); - if (GetLocalIdandDbidx(msgid, &localId, &dbIdx) != 0) { - LOG_ERROR("Failed to get localId, Please check id: {}", to_string(msgid)); - return status; - } - - WxString *pReceiver = NewWxStringFromStr(receiver); - - LARGE_INTEGER l; - l.HighPart = dbIdx; - l.LowPart = (DWORD)localId; - - status = (int)funcForwardMsg((QWORD)pReceiver, l.QuadPart, 0x4, 0x0); - - return status; -} - -void SendEmotionMessage(string wxid, string path) -{ - GetEmotionMgr_t GetEmotionMgr = (GetEmotionMgr_t)(g_WeChatWinDllAddr + OS_GET_EMOTION_MGR); - SendEmotion_t SendEmotion = (SendEmotion_t)(g_WeChatWinDllAddr + OS_SEND_EMOTION); - - WxString *pWxPath = NewWxStringFromStr(path); - WxString *pWxWxid = NewWxStringFromStr(wxid); - - QWORD *buff = (QWORD *)HeapAlloc(GetProcessHeap(), 0, 0x20); - if (buff == NULL) { - LOG_ERROR("Out of Memory..."); - return; - } - - memset(buff, 0, 0x20); - QWORD mgr = GetEmotionMgr(); - SendEmotion(mgr, (QWORD)pWxPath, (QWORD)buff, (QWORD)pWxWxid, 2, (QWORD)buff, 0, (QWORD)buff); -} - -void SendXmlMessage(string receiver, string xml, string path, QWORD type) -{ - if (g_WeChatWinDllAddr == 0) { - return; - } - - New_t funcNew = (New_t)(g_WeChatWinDllAddr + OS_NEW); - Free_t funcFree = (Free_t)(g_WeChatWinDllAddr + OS_FREE); - - XmlBufSign_t xmlBufSign = (XmlBufSign_t)(g_WeChatWinDllAddr + OS_XML_BUFSIGN); - SendXmlMsg_t sendXmlMsg = (SendXmlMsg_t)(g_WeChatWinDllAddr + OS_SEND_XML); - - char buff[0x500] = { 0 }; - char buff2[0x500] = { 0 }; - char nullBuf[0x1C] = { 0 }; - - QWORD pBuf = (QWORD)(&buff); - QWORD pBuf2 = (QWORD)(&buff2); - - funcNew(pBuf); - funcNew(pBuf2); - - QWORD sbuf[4] = { 0, 0, 0, 0 }; - - QWORD sign = xmlBufSign(pBuf2, (QWORD)(&sbuf), 0x1); - - WxString *pReceiver = NewWxStringFromStr(receiver); - WxString *pXml = NewWxStringFromStr(xml); - WxString *pPath = NewWxStringFromStr(path); - WxString *pSender = NewWxStringFromStr(GetSelfWxid()); - - sendXmlMsg(pBuf, (QWORD)pSender, (QWORD)pReceiver, (QWORD)pXml, (QWORD)pPath, (QWORD)(&nullBuf), type, 0x4, sign, - pBuf2); - - funcFree((QWORD)&buff); - funcFree((QWORD)&buff2); -} diff --git a/WeChatFerry/spy/send_msg.h b/WeChatFerry/spy/send_msg.h deleted file mode 100644 index 0218a5d..0000000 --- a/WeChatFerry/spy/send_msg.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -using namespace std; - -typedef struct { - string name; - string account; - string title; - string digest; - string url; - string thumburl; - string receiver; -} RichText_t; - -void SendTextMessage(string wxid, string msg, string atWxids); -void SendImageMessage(string wxid, string path); -void SendFileMessage(string wxid, string path); -void SendXmlMessage(string receiver, string xml, string path, uint64_t type); -void SendEmotionMessage(string wxid, string path); -int SendRichTextMessage(RichText_t &rt); -int SendPatMessage(string roomid, string wxid); -int ForwardMessage(uint64_t msgid, string receiver); diff --git a/WeChatFerry/spy/spy.aps b/WeChatFerry/spy/spy.aps index 9a2d146..1996570 100644 Binary files a/WeChatFerry/spy/spy.aps and b/WeChatFerry/spy/spy.aps differ diff --git a/WeChatFerry/spy/spy.cpp b/WeChatFerry/spy/spy.cpp index e179aa0..dc6a700 100644 --- a/WeChatFerry/spy/spy.cpp +++ b/WeChatFerry/spy/spy.cpp @@ -1,49 +1,48 @@ -#include +#include "spy.h" + +#include #include "log.hpp" #include "rpc_server.h" #include "spy.h" #include "util.h" -UINT64 g_WeChatWinDllAddr = 0; - -static bool IsWxVersionMatched(const wchar_t *version) +namespace Spy { - if (wcscmp(version, SUPPORT_VERSION) != 0) { - return false; - } - return true; -} - -void InitSpy(LPVOID args) +int Init(void *args) { - - wchar_t version[16] = { 0 }; - PortPath_t *pp = (PortPath_t *)args; + auto *pp = static_cast(args); Log::InitLogger(pp->path); - g_WeChatWinDllAddr = (UINT64)GetModuleHandle(L"WeChatWin.dll"); // 获取wechatWin模块地址 - if (g_WeChatWinDllAddr == 0) { - LOG_ERROR("获取 wechatWin.dll 模块地址失败"); - return; // TODO: 退出进程,避免后面操作失败 + if (auto dll_addr = GetModuleHandle(L"WeChatWin.dll")) { + WeChatDll.store(reinterpret_cast(dll_addr)); + } else { + LOG_ERROR("获取 WeChatWin.dll 模块地址失败"); + return -1; } - if (!GetWeChatVersion(version)) { // 获取微信版本 - LOG_ERROR("获取微信版本失败"); - return; - } - LOG_INFO("WeChat version: {}", Wstring2String(version).c_str()); - if (!IsWxVersionMatched(version)) { - LOG_ERROR("不支持当前版本"); - MessageBox(NULL, L"不支持当前版本", L"错误", 0); - return; + std::string version = util::get_wechat_version(); + std::string msg = fmt::format("WCF 支持版本: {},当前版本: {}", SUPPORT_VERSION, version); + if (version != SUPPORT_VERSION) { + LOG_ERROR(msg); + util::MsgBox(NULL, msg.c_str(), "微信版本错误", MB_ICONERROR); + return -2; } - RpcStartServer(pp->port); + LOG_INFO(msg); + RpcServer::getInstance().start(pp->port); + + return 0; } -void CleanupSpy() +void Cleanup() { LOG_DEBUG("CleanupSpy"); - RpcStopServer(); + RpcServer::destroyInstance(); +} +} + +extern "C" { +__declspec(dllexport) int InitSpy(void *args) { return Spy::Init(args); } +__declspec(dllexport) void CleanupSpy() { Spy::Cleanup(); } } diff --git a/WeChatFerry/spy/spy.h b/WeChatFerry/spy/spy.h index 525109a..ae8f402 100644 --- a/WeChatFerry/spy/spy.h +++ b/WeChatFerry/spy/spy.h @@ -1,8 +1,20 @@ #pragma once -#include "framework.h" +#include +#include +#include -#define SUPPORT_VERSION L"3.9.11.25" +namespace Spy +{ +constexpr std::string_view SUPPORT_VERSION = "3.9.12.17"; +inline std::atomic WeChatDll { 0 }; -void InitSpy(int port); -void CleanupSpy(); +template inline T getFunction(std::uintptr_t offset) { return reinterpret_cast(WeChatDll + offset); } +template inline T getFunction(std::uintptr_t base, std::uintptr_t offset) +{ + return reinterpret_cast(base + offset); +} + +int Init(void *args); +void Cleanup(); +} diff --git a/WeChatFerry/spy/spy.rc b/WeChatFerry/spy/spy.rc index daf9908..42d3264 100644 --- a/WeChatFerry/spy/spy.rc +++ b/WeChatFerry/spy/spy.rc @@ -13,7 +13,7 @@ #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// -// ����(���壬�й�) resources +// 中文(简体,中国) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED @@ -51,8 +51,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 39,3,5,0 - PRODUCTVERSION 3,9,11,25 + FILEVERSION 39,4,1,0 + PRODUCTVERSION 3,9,12,17 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +69,12 @@ BEGIN BEGIN VALUE "CompanyName", "WeChatFerry" VALUE "FileDescription", "WeChatFerry" - VALUE "FileVersion", "39.3.5.0" + VALUE "FileVersion", "39.4.1.0" VALUE "InternalName", "spy.dll" VALUE "LegalCopyright", "Copyright (C) 2023" VALUE "OriginalFilename", "spy.dll" VALUE "ProductName", "WeChatFerry" - VALUE "ProductVersion", "3.9.11.25" + VALUE "ProductVersion", "3.9.12.17" END END BLOCK "VarFileInfo" @@ -83,7 +83,7 @@ BEGIN END END -#endif // ����(���壬�й�) resources +#endif // 中文(简体,中国) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/WeChatFerry/spy/spy_types.h b/WeChatFerry/spy/spy_types.h index 7fd1008..4e0f764 100644 --- a/WeChatFerry/spy/spy_types.h +++ b/WeChatFerry/spy/spy_types.h @@ -5,36 +5,51 @@ typedef uint64_t QWORD; -struct WxString { +class WxString +{ +public: const wchar_t *wptr; DWORD size; - DWORD capacity; + DWORD length; const char *ptr; DWORD clen; - WxString() + + WxString() : wptr(nullptr), size(0), length(0), ptr(nullptr), clen(0) { } + + explicit WxString(const std::wstring &ws) + : wptr(ws.c_str()), size(static_cast(ws.size())), length(static_cast(ws.length())), ptr(nullptr), + clen(0) { - wptr = NULL; - size = 0; - capacity = 0; - ptr = NULL; - clen = 0; } - WxString(std::wstring &ws) + WxString(const WxString &) = delete; + WxString &operator=(const WxString &) = delete; + + WxString(WxString &&other) noexcept + : wptr(other.wptr), size(other.size), length(other.length), ptr(other.ptr), clen(other.clen) { - wptr = ws.c_str(); - size = (DWORD)ws.size(); - capacity = (DWORD)ws.capacity(); - ptr = NULL; - clen = 0; + other.wptr = nullptr; + other.size = 0; + other.length = 0; + other.ptr = nullptr; + other.clen = 0; + } + + WxString &operator=(WxString &&other) noexcept + { + if (this != &other) { + wptr = other.wptr; + size = other.size; + length = other.length; + ptr = other.ptr; + clen = other.clen; + + other.wptr = nullptr; + other.size = 0; + other.length = 0; + other.ptr = nullptr; + other.clen = 0; + } + return *this; } }; - -typedef struct RawVector { -#ifdef _DEBUG - QWORD head; -#endif - QWORD start; - QWORD finish; - QWORD end; -} RawVector_t; diff --git a/WeChatFerry/spy/sqlite3.h b/WeChatFerry/spy/sqlite3.h index b50ec2a..a680be7 100644 --- a/WeChatFerry/spy/sqlite3.h +++ b/WeChatFerry/spy/sqlite3.h @@ -138,25 +138,6 @@ #define SQLITE_NULL 5 #define SQLITE_TEXT 3 -#define SQLITE3_EXEC_OFFSET 0x3A5EDA0 -#define SQLITE3_BACKUP_INIT_OFFSET 0x3A18EA0 -#define SQLITE3_PREPARE_OFFSET 0x3A66A20 -#define SQLITE3_OPEN_OFFSET 0x3A9E210 -#define SQLITE3_BACKUP_STEP_OFFSET 0x3A193F0 -#define SQLITE3_BACKUP_REMAINING_OFFSET 0x1B26EB0 -#define SQLITE3_BACKUP_PAGECOUNT_OFFSET 0x1B26EE0 -#define SQLITE3_BACKUP_FINISH_OFFSET 0x3A19AF0 -#define SQLITE3_SLEEP_OFFSET 0x3A9EE70 -#define SQLITE3_ERRCODE_OFFSET 0x3A9CB10 -#define SQLITE3_CLOSE_OFFSET 0x3A9AC70 -#define SQLITE3_STEP_OFFSET 0x3A22DA0 -#define SQLITE3_COLUMN_COUNT_OFFSET 0x3A235C0 -#define SQLITE3_COLUMN_NAME_OFFSET 0x3A23FC0 -#define SQLITE3_COLUMN_TYPE_OFFSET 0x3A23E10 -#define SQLITE3_COLUMN_BLOB_OFFSET 0x3A235F0 -#define SQLITE3_COLUMN_BYTES_OFFSET 0x3A236E0 -#define SQLITE3_FINALIZE_OFFSET 0x3A21E50 - typedef int (*Sqlite3_callback)(void *, int, char **, char **); typedef int(__cdecl *Sqlite3_exec)(QWORD, /* An open database */ diff --git a/WeChatFerry/spy/user_info.cpp b/WeChatFerry/spy/user_info.cpp deleted file mode 100644 index a273592..0000000 --- a/WeChatFerry/spy/user_info.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "user_info.h" -#include "log.hpp" -#include "util.h" - -extern UINT64 g_WeChatWinDllAddr; - -#define OS_USER_HOME 0x5932770 -#define OS_USER_WXID 0x595C270 -#define OS_USER_NAME 0x595C3D8 -#define OS_USER_MOBILE 0x595C318 - -static char home[MAX_PATH] = { 0 }; - -string GetHomePath() -{ - if (home[0] == 0) { - string path = Wstring2String(GET_WSTRING(g_WeChatWinDllAddr + OS_USER_HOME)) + "\\WeChat Files\\"; - strncpy_s(home, path.c_str(), path.size()); - } - - return string(home); -} - -string GetSelfWxid() -{ - UINT64 wxidType = 0; - try { - wxidType = GET_UINT64(g_WeChatWinDllAddr + OS_USER_WXID + 0x18); - if (wxidType == 0xF) { - return GET_STRING_FROM_P(g_WeChatWinDllAddr + OS_USER_WXID); - } else { - return GET_STRING(g_WeChatWinDllAddr + OS_USER_WXID); - } - } catch (...) { - LOG_ERROR("wxid type: {:#x}", wxidType); - LOG_BUFFER((uint8_t *)(g_WeChatWinDllAddr + OS_USER_WXID), 20); - return "empty_wxid"; - } -} - -UserInfo_t GetUserInfo() -{ - UserInfo_t ui; - - ui.wxid = GetSelfWxid(); - - UINT64 nameType = GET_UINT64(g_WeChatWinDllAddr + OS_USER_NAME + 0x18); - if (nameType == 0xF) { - ui.name = GET_STRING_FROM_P(g_WeChatWinDllAddr + OS_USER_NAME); - } else { // 0x1F - ui.name = GET_STRING(g_WeChatWinDllAddr + OS_USER_NAME); - } - - ui.mobile = GET_STRING_FROM_P(g_WeChatWinDllAddr + OS_USER_MOBILE); - ui.home = GetHomePath(); - - return ui; -} diff --git a/WeChatFerry/spy/user_info.h b/WeChatFerry/spy/user_info.h deleted file mode 100644 index 8d70dc7..0000000 --- a/WeChatFerry/spy/user_info.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -#include "pb_types.h" - -using namespace std; - -string GetHomePath(); -string GetSelfWxid(); - -UserInfo_t GetUserInfo(); diff --git a/clients/python/MANIFEST.in b/clients/python/MANIFEST.in index e228aeb..bc25aee 100644 --- a/clients/python/MANIFEST.in +++ b/clients/python/MANIFEST.in @@ -1,2 +1,2 @@ include wcferry/*.dll -include wcferry/*.exe +include wcferry/*.md diff --git a/clients/python/README.MD b/clients/python/README.MD index 1f4c4b6..1b9ec75 100644 --- a/clients/python/README.MD +++ b/clients/python/README.MD @@ -44,8 +44,8 @@ python -m grpc_tools.protoc --python_out=. --proto_path=../../../WeChatFerry/rpc ## 版本更新 -### v39.3.3.1 -* 修复 `send_xml` +### v39.4.1.0 +* 修复乱码问题
点击查看更多 @@ -70,7 +70,6 @@ python -m grpc_tools.protoc --python_out=. --proto_path=../../../WeChatFerry/rpc * 发送图片消息 * 发送文件消息 * 发送卡片消息 -* 发送 XML 消息 * 发送 GIF 消息 * 拍一拍群友 * 转发消息 diff --git a/clients/python/wcferry/DISCLAIMER.md b/clients/python/wcferry/DISCLAIMER.md new file mode 100644 index 0000000..520a4ac --- /dev/null +++ b/clients/python/wcferry/DISCLAIMER.md @@ -0,0 +1,18 @@ +# 免责声明 + +1. **本工具为开源项目,仅提供基础功能,供用户进行合法的学习、研究和非商业用途**。禁止将本工具用于任何违法或侵权行为。 + +2. **二次开发者的责任**: + - 任何基于本工具进行的二次开发、修改或衍生产品,其行为及后果由二次开发者独立承担,与本工具贡献者无关。 + - **禁止使用贡献者的姓名、项目名称或相关信息作为二次开发产品的背书或推广手段**。 + - 建议二次开发者在其衍生产品中添加自己的免责声明,明确责任归属。 + +3. **用户责任**: + - 使用本工具或其衍生产品的所有后果由用户自行承担。原贡献者不对因直接或间接使用本工具而导致的任何损失、责任或争议负责。 + +4. **法律约束**: + - 用户和二次开发者须遵守《中华人民共和国网络安全法》、《中华人民共和国著作权法》等相关法律法规。 + - 本工具涉及的所有第三方商标或产品名称,其权利归权利人所有,作者与第三方无任何直接或间接关联。 + +5. **作者保留权利**: + - 本工具作者保留随时修改、更新、删除或终止本工具的权利,无需事先通知或承担任何义务。 diff --git a/clients/python/wcferry/client.py b/clients/python/wcferry/client.py index 6583014..99f5999 100644 --- a/clients/python/wcferry/client.py +++ b/clients/python/wcferry/client.py @@ -1,7 +1,7 @@ #! /usr/bin/env python3 # -*- coding: utf-8 -*- -__version__ = "39.3.3.3" +__version__ = "39.4.1.0" import atexit import base64 @@ -10,6 +10,7 @@ import logging import mimetypes import os import re +import subprocess import sys from queue import Queue from threading import Thread @@ -70,6 +71,7 @@ class Wcf(): self._dl_path = f"{self._wcf_root}/.dl" os.makedirs(self._dl_path, exist_ok=True) self.LOG = logging.getLogger("WCF") + self._set_console_utf8() self.LOG.info(f"wcferry version: {__version__}") self.port = port self.host = host @@ -115,6 +117,13 @@ class Wcf(): def __del__(self) -> None: self.cleanup() + def _set_console_utf8(self): + try: + subprocess.run("chcp 65001", shell=True, check=True, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except subprocess.CalledProcessError as e: + self.LOG.error(f"修改控制台代码页失败: {e}") + def cleanup(self) -> None: """关闭连接,回收资源""" if not self._is_running: @@ -141,7 +150,9 @@ class Wcf(): data = req.SerializeToString() self.cmd_socket.send(data) rsp = wcf_pb2.Response() - rsp.ParseFromString(self.cmd_socket.recv_msg().bytes) + bs = self.cmd_socket.recv_msg().bytes + self.LOG.debug(bs.hex()) + rsp.ParseFromString(bs) return rsp def is_receiving_msg(self) -> bool: @@ -150,7 +161,6 @@ class Wcf(): def get_qrcode(self) -> str: """获取登录二维码,已经登录则返回空字符串""" - raise Exception("Not implemented, yet") req = wcf_pb2.Request() req.func = wcf_pb2.FUNC_REFRESH_QRCODE # FUNC_REFRESH_QRCODE rsp = self._send_request(req) @@ -398,6 +408,7 @@ class Wcf(): Returns: int: 0 为成功,其他失败 """ + raise Exception("Not implemented, yet") req = wcf_pb2.Request() req.func = wcf_pb2.FUNC_SEND_XML # FUNC_SEND_XML req.xml.receiver = receiver @@ -763,6 +774,7 @@ class Wcf(): Returns: int: 1 为成功,其他失败 """ + raise Exception("Not implemented, yet") req = wcf_pb2.Request() req.func = wcf_pb2.FUNC_REVOKE_MSG # FUNC_REVOKE_MSG req.ui64 = id diff --git a/clients/python/wcferry/wcf_pb2.py b/clients/python/wcferry/wcf_pb2.py index da7267e..acb1981 100644 --- a/clients/python/wcferry/wcf_pb2.py +++ b/clients/python/wcferry/wcf_pb2.py @@ -1,13 +1,22 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: wcf.proto +# Protobuf Python Version: 5.29.0 """Generated protocol buffer code.""" -from google.protobuf.internal import enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 0, + '', + 'wcf.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -17,370 +26,86 @@ _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\twcf.proto\x12\x03wcf\"\xff\x03\n\x07Request\x12\x1c\n\x04\x66unc\x18\x01 \x01(\x0e\x32\x0e.wcf.Functions\x12\x1b\n\x05\x65mpty\x18\x02 \x01(\x0b\x32\n.wcf.EmptyH\x00\x12\r\n\x03str\x18\x03 \x01(\tH\x00\x12\x1b\n\x03txt\x18\x04 \x01(\x0b\x32\x0c.wcf.TextMsgH\x00\x12\x1c\n\x04\x66ile\x18\x05 \x01(\x0b\x32\x0c.wcf.PathMsgH\x00\x12\x1d\n\x05query\x18\x06 \x01(\x0b\x32\x0c.wcf.DbQueryH\x00\x12\x1e\n\x01v\x18\x07 \x01(\x0b\x32\x11.wcf.VerificationH\x00\x12\x1c\n\x01m\x18\x08 \x01(\x0b\x32\x0f.wcf.MemberMgmtH\x00\x12\x1a\n\x03xml\x18\t \x01(\x0b\x32\x0b.wcf.XmlMsgH\x00\x12\x1b\n\x03\x64\x65\x63\x18\n \x01(\x0b\x32\x0c.wcf.DecPathH\x00\x12\x1b\n\x02tf\x18\x0b \x01(\x0b\x32\r.wcf.TransferH\x00\x12\x12\n\x04ui64\x18\x0c \x01(\x04\x42\x02\x30\x01H\x00\x12\x0e\n\x04\x66lag\x18\r \x01(\x08H\x00\x12\x1d\n\x03\x61tt\x18\x0e \x01(\x0b\x32\x0e.wcf.AttachMsgH\x00\x12\x1b\n\x02\x61m\x18\x0f \x01(\x0b\x32\r.wcf.AudioMsgH\x00\x12\x1b\n\x02rt\x18\x10 \x01(\x0b\x32\r.wcf.RichTextH\x00\x12\x19\n\x02pm\x18\x11 \x01(\x0b\x32\x0b.wcf.PatMsgH\x00\x12\x1d\n\x02\x66m\x18\x12 \x01(\x0b\x32\x0f.wcf.ForwardMsgH\x00\x42\x05\n\x03msg\"\xc7\x02\n\x08Response\x12\x1c\n\x04\x66unc\x18\x01 \x01(\x0e\x32\x0e.wcf.Functions\x12\x10\n\x06status\x18\x02 \x01(\x05H\x00\x12\r\n\x03str\x18\x03 \x01(\tH\x00\x12\x1b\n\x05wxmsg\x18\x04 \x01(\x0b\x32\n.wcf.WxMsgH\x00\x12\x1e\n\x05types\x18\x05 \x01(\x0b\x32\r.wcf.MsgTypesH\x00\x12$\n\x08\x63ontacts\x18\x06 \x01(\x0b\x32\x10.wcf.RpcContactsH\x00\x12\x1b\n\x03\x64\x62s\x18\x07 \x01(\x0b\x32\x0c.wcf.DbNamesH\x00\x12\x1f\n\x06tables\x18\x08 \x01(\x0b\x32\r.wcf.DbTablesH\x00\x12\x1b\n\x04rows\x18\t \x01(\x0b\x32\x0b.wcf.DbRowsH\x00\x12\x1b\n\x02ui\x18\n \x01(\x0b\x32\r.wcf.UserInfoH\x00\x12\x1a\n\x03ocr\x18\x0b \x01(\x0b\x32\x0b.wcf.OcrMsgH\x00\x42\x05\n\x03msg\"\x07\n\x05\x45mpty\"\xbe\x01\n\x05WxMsg\x12\x0f\n\x07is_self\x18\x01 \x01(\x08\x12\x10\n\x08is_group\x18\x02 \x01(\x08\x12\x0e\n\x02id\x18\x03 \x01(\x04\x42\x02\x30\x01\x12\x0c\n\x04type\x18\x04 \x01(\r\x12\n\n\x02ts\x18\x05 \x01(\r\x12\x0e\n\x06roomid\x18\x06 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x07 \x01(\t\x12\x0e\n\x06sender\x18\x08 \x01(\t\x12\x0c\n\x04sign\x18\t \x01(\t\x12\r\n\x05thumb\x18\n \x01(\t\x12\r\n\x05\x65xtra\x18\x0b \x01(\t\x12\x0b\n\x03xml\x18\x0c \x01(\t\"7\n\x07TextMsg\x12\x0b\n\x03msg\x18\x01 \x01(\t\x12\x10\n\x08receiver\x18\x02 \x01(\t\x12\r\n\x05\x61ters\x18\x03 \x01(\t\")\n\x07PathMsg\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x10\n\x08receiver\x18\x02 \x01(\t\"G\n\x06XmlMsg\x12\x10\n\x08receiver\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\x12\x0c\n\x04path\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\x04\"a\n\x08MsgTypes\x12\'\n\x05types\x18\x01 \x03(\x0b\x32\x18.wcf.MsgTypes.TypesEntry\x1a,\n\nTypesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x87\x01\n\nRpcContact\x12\x0c\n\x04wxid\x18\x01 \x01(\t\x12\x0c\n\x04\x63ode\x18\x02 \x01(\t\x12\x0e\n\x06remark\x18\x03 \x01(\t\x12\x0c\n\x04name\x18\x04 \x01(\t\x12\x0f\n\x07\x63ountry\x18\x05 \x01(\t\x12\x10\n\x08province\x18\x06 \x01(\t\x12\x0c\n\x04\x63ity\x18\x07 \x01(\t\x12\x0e\n\x06gender\x18\x08 \x01(\x05\"0\n\x0bRpcContacts\x12!\n\x08\x63ontacts\x18\x01 \x03(\x0b\x32\x0f.wcf.RpcContact\"\x18\n\x07\x44\x62Names\x12\r\n\x05names\x18\x01 \x03(\t\"$\n\x07\x44\x62Table\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0b\n\x03sql\x18\x02 \x01(\t\"(\n\x08\x44\x62Tables\x12\x1c\n\x06tables\x18\x01 \x03(\x0b\x32\x0c.wcf.DbTable\"\"\n\x07\x44\x62Query\x12\n\n\x02\x64\x62\x18\x01 \x01(\t\x12\x0b\n\x03sql\x18\x02 \x01(\t\"8\n\x07\x44\x62\x46ield\x12\x0c\n\x04type\x18\x01 \x01(\x05\x12\x0e\n\x06\x63olumn\x18\x02 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\x0c\"%\n\x05\x44\x62Row\x12\x1c\n\x06\x66ields\x18\x01 \x03(\x0b\x32\x0c.wcf.DbField\"\"\n\x06\x44\x62Rows\x12\x18\n\x04rows\x18\x01 \x03(\x0b\x32\n.wcf.DbRow\"5\n\x0cVerification\x12\n\n\x02v3\x18\x01 \x01(\t\x12\n\n\x02v4\x18\x02 \x01(\t\x12\r\n\x05scene\x18\x03 \x01(\x05\"+\n\nMemberMgmt\x12\x0e\n\x06roomid\x18\x01 \x01(\t\x12\r\n\x05wxids\x18\x02 \x01(\t\"D\n\x08UserInfo\x12\x0c\n\x04wxid\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06mobile\x18\x03 \x01(\t\x12\x0c\n\x04home\x18\x04 \x01(\t\"#\n\x07\x44\x65\x63Path\x12\x0b\n\x03src\x18\x01 \x01(\t\x12\x0b\n\x03\x64st\x18\x02 \x01(\t\"4\n\x08Transfer\x12\x0c\n\x04wxid\x18\x01 \x01(\t\x12\x0c\n\x04tfid\x18\x02 \x01(\t\x12\x0c\n\x04taid\x18\x03 \x01(\t\"9\n\tAttachMsg\x12\x0e\n\x02id\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\r\n\x05thumb\x18\x02 \x01(\t\x12\r\n\x05\x65xtra\x18\x03 \x01(\t\"\'\n\x08\x41udioMsg\x12\x0e\n\x02id\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x0b\n\x03\x64ir\x18\x02 \x01(\t\"y\n\x08RichText\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x63\x63ount\x18\x02 \x01(\t\x12\r\n\x05title\x18\x03 \x01(\t\x12\x0e\n\x06\x64igest\x18\x04 \x01(\t\x12\x0b\n\x03url\x18\x05 \x01(\t\x12\x10\n\x08thumburl\x18\x06 \x01(\t\x12\x10\n\x08receiver\x18\x07 \x01(\t\"&\n\x06PatMsg\x12\x0e\n\x06roomid\x18\x01 \x01(\t\x12\x0c\n\x04wxid\x18\x02 \x01(\t\"(\n\x06OcrMsg\x12\x0e\n\x06status\x18\x01 \x01(\x05\x12\x0e\n\x06result\x18\x02 \x01(\t\".\n\nForwardMsg\x12\x0e\n\x02id\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x10\n\x08receiver\x18\x02 \x01(\t\"\xb7\x02\n\x08RoomData\x12)\n\x07members\x18\x01 \x03(\x0b\x32\x18.wcf.RoomData.RoomMember\x12\x14\n\x07\x66ield_2\x18\x02 \x01(\x05H\x00\x88\x01\x01\x12\x0f\n\x07\x66ield_3\x18\x03 \x01(\x05\x12\x14\n\x07\x66ield_4\x18\x04 \x01(\x05H\x01\x88\x01\x01\x12\x10\n\x08\x63\x61pacity\x18\x05 \x01(\x05\x12\x14\n\x07\x66ield_6\x18\x06 \x01(\tH\x02\x88\x01\x01\x12\x0f\n\x07\x66ield_7\x18\x07 \x01(\x05\x12\x0f\n\x07\x66ield_8\x18\x08 \x01(\x05\x12\x0e\n\x06\x61\x64mins\x18\t \x03(\t\x1a\x45\n\nRoomMember\x12\x0c\n\x04wxid\x18\x01 \x01(\t\x12\x11\n\x04name\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\r\n\x05state\x18\x03 \x01(\x05\x42\x07\n\x05_nameB\n\n\x08_field_2B\n\n\x08_field_4B\n\n\x08_field_6*\xf2\x05\n\tFunctions\x12\x11\n\rFUNC_RESERVED\x10\x00\x12\x11\n\rFUNC_IS_LOGIN\x10\x01\x12\x16\n\x12\x46UNC_GET_SELF_WXID\x10\x10\x12\x16\n\x12\x46UNC_GET_MSG_TYPES\x10\x11\x12\x15\n\x11\x46UNC_GET_CONTACTS\x10\x12\x12\x15\n\x11\x46UNC_GET_DB_NAMES\x10\x13\x12\x16\n\x12\x46UNC_GET_DB_TABLES\x10\x14\x12\x16\n\x12\x46UNC_GET_USER_INFO\x10\x15\x12\x16\n\x12\x46UNC_GET_AUDIO_MSG\x10\x16\x12\x11\n\rFUNC_SEND_TXT\x10 \x12\x11\n\rFUNC_SEND_IMG\x10!\x12\x12\n\x0e\x46UNC_SEND_FILE\x10\"\x12\x11\n\rFUNC_SEND_XML\x10#\x12\x15\n\x11\x46UNC_SEND_EMOTION\x10$\x12\x16\n\x12\x46UNC_SEND_RICH_TXT\x10%\x12\x15\n\x11\x46UNC_SEND_PAT_MSG\x10&\x12\x14\n\x10\x46UNC_FORWARD_MSG\x10\'\x12\x18\n\x14\x46UNC_ENABLE_RECV_TXT\x10\x30\x12\x19\n\x15\x46UNC_DISABLE_RECV_TXT\x10@\x12\x16\n\x12\x46UNC_EXEC_DB_QUERY\x10P\x12\x16\n\x12\x46UNC_ACCEPT_FRIEND\x10Q\x12\x16\n\x12\x46UNC_RECV_TRANSFER\x10R\x12\x14\n\x10\x46UNC_REFRESH_PYQ\x10S\x12\x18\n\x14\x46UNC_DOWNLOAD_ATTACH\x10T\x12\x19\n\x15\x46UNC_GET_CONTACT_INFO\x10U\x12\x13\n\x0f\x46UNC_REVOKE_MSG\x10V\x12\x17\n\x13\x46UNC_REFRESH_QRCODE\x10W\x12\x16\n\x12\x46UNC_DECRYPT_IMAGE\x10`\x12\x11\n\rFUNC_EXEC_OCR\x10\x61\x12\x19\n\x15\x46UNC_ADD_ROOM_MEMBERS\x10p\x12\x19\n\x15\x46UNC_DEL_ROOM_MEMBERS\x10q\x12\x19\n\x15\x46UNC_INV_ROOM_MEMBERS\x10rB\r\n\x0b\x63om.iamteerb\x06proto3') -_FUNCTIONS = DESCRIPTOR.enum_types_by_name['Functions'] -Functions = enum_type_wrapper.EnumTypeWrapper(_FUNCTIONS) -FUNC_RESERVED = 0 -FUNC_IS_LOGIN = 1 -FUNC_GET_SELF_WXID = 16 -FUNC_GET_MSG_TYPES = 17 -FUNC_GET_CONTACTS = 18 -FUNC_GET_DB_NAMES = 19 -FUNC_GET_DB_TABLES = 20 -FUNC_GET_USER_INFO = 21 -FUNC_GET_AUDIO_MSG = 22 -FUNC_SEND_TXT = 32 -FUNC_SEND_IMG = 33 -FUNC_SEND_FILE = 34 -FUNC_SEND_XML = 35 -FUNC_SEND_EMOTION = 36 -FUNC_SEND_RICH_TXT = 37 -FUNC_SEND_PAT_MSG = 38 -FUNC_FORWARD_MSG = 39 -FUNC_ENABLE_RECV_TXT = 48 -FUNC_DISABLE_RECV_TXT = 64 -FUNC_EXEC_DB_QUERY = 80 -FUNC_ACCEPT_FRIEND = 81 -FUNC_RECV_TRANSFER = 82 -FUNC_REFRESH_PYQ = 83 -FUNC_DOWNLOAD_ATTACH = 84 -FUNC_GET_CONTACT_INFO = 85 -FUNC_REVOKE_MSG = 86 -FUNC_REFRESH_QRCODE = 87 -FUNC_DECRYPT_IMAGE = 96 -FUNC_EXEC_OCR = 97 -FUNC_ADD_ROOM_MEMBERS = 112 -FUNC_DEL_ROOM_MEMBERS = 113 -FUNC_INV_ROOM_MEMBERS = 114 - - -_REQUEST = DESCRIPTOR.message_types_by_name['Request'] -_RESPONSE = DESCRIPTOR.message_types_by_name['Response'] -_EMPTY = DESCRIPTOR.message_types_by_name['Empty'] -_WXMSG = DESCRIPTOR.message_types_by_name['WxMsg'] -_TEXTMSG = DESCRIPTOR.message_types_by_name['TextMsg'] -_PATHMSG = DESCRIPTOR.message_types_by_name['PathMsg'] -_XMLMSG = DESCRIPTOR.message_types_by_name['XmlMsg'] -_MSGTYPES = DESCRIPTOR.message_types_by_name['MsgTypes'] -_MSGTYPES_TYPESENTRY = _MSGTYPES.nested_types_by_name['TypesEntry'] -_RPCCONTACT = DESCRIPTOR.message_types_by_name['RpcContact'] -_RPCCONTACTS = DESCRIPTOR.message_types_by_name['RpcContacts'] -_DBNAMES = DESCRIPTOR.message_types_by_name['DbNames'] -_DBTABLE = DESCRIPTOR.message_types_by_name['DbTable'] -_DBTABLES = DESCRIPTOR.message_types_by_name['DbTables'] -_DBQUERY = DESCRIPTOR.message_types_by_name['DbQuery'] -_DBFIELD = DESCRIPTOR.message_types_by_name['DbField'] -_DBROW = DESCRIPTOR.message_types_by_name['DbRow'] -_DBROWS = DESCRIPTOR.message_types_by_name['DbRows'] -_VERIFICATION = DESCRIPTOR.message_types_by_name['Verification'] -_MEMBERMGMT = DESCRIPTOR.message_types_by_name['MemberMgmt'] -_USERINFO = DESCRIPTOR.message_types_by_name['UserInfo'] -_DECPATH = DESCRIPTOR.message_types_by_name['DecPath'] -_TRANSFER = DESCRIPTOR.message_types_by_name['Transfer'] -_ATTACHMSG = DESCRIPTOR.message_types_by_name['AttachMsg'] -_AUDIOMSG = DESCRIPTOR.message_types_by_name['AudioMsg'] -_RICHTEXT = DESCRIPTOR.message_types_by_name['RichText'] -_PATMSG = DESCRIPTOR.message_types_by_name['PatMsg'] -_OCRMSG = DESCRIPTOR.message_types_by_name['OcrMsg'] -_FORWARDMSG = DESCRIPTOR.message_types_by_name['ForwardMsg'] -_ROOMDATA = DESCRIPTOR.message_types_by_name['RoomData'] -_ROOMDATA_ROOMMEMBER = _ROOMDATA.nested_types_by_name['RoomMember'] -Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), { - 'DESCRIPTOR' : _REQUEST, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.Request) - }) -_sym_db.RegisterMessage(Request) - -Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), { - 'DESCRIPTOR' : _RESPONSE, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.Response) - }) -_sym_db.RegisterMessage(Response) - -Empty = _reflection.GeneratedProtocolMessageType('Empty', (_message.Message,), { - 'DESCRIPTOR' : _EMPTY, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.Empty) - }) -_sym_db.RegisterMessage(Empty) - -WxMsg = _reflection.GeneratedProtocolMessageType('WxMsg', (_message.Message,), { - 'DESCRIPTOR' : _WXMSG, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.WxMsg) - }) -_sym_db.RegisterMessage(WxMsg) - -TextMsg = _reflection.GeneratedProtocolMessageType('TextMsg', (_message.Message,), { - 'DESCRIPTOR' : _TEXTMSG, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.TextMsg) - }) -_sym_db.RegisterMessage(TextMsg) - -PathMsg = _reflection.GeneratedProtocolMessageType('PathMsg', (_message.Message,), { - 'DESCRIPTOR' : _PATHMSG, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.PathMsg) - }) -_sym_db.RegisterMessage(PathMsg) - -XmlMsg = _reflection.GeneratedProtocolMessageType('XmlMsg', (_message.Message,), { - 'DESCRIPTOR' : _XMLMSG, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.XmlMsg) - }) -_sym_db.RegisterMessage(XmlMsg) - -MsgTypes = _reflection.GeneratedProtocolMessageType('MsgTypes', (_message.Message,), { - - 'TypesEntry' : _reflection.GeneratedProtocolMessageType('TypesEntry', (_message.Message,), { - 'DESCRIPTOR' : _MSGTYPES_TYPESENTRY, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.MsgTypes.TypesEntry) - }) - , - 'DESCRIPTOR' : _MSGTYPES, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.MsgTypes) - }) -_sym_db.RegisterMessage(MsgTypes) -_sym_db.RegisterMessage(MsgTypes.TypesEntry) - -RpcContact = _reflection.GeneratedProtocolMessageType('RpcContact', (_message.Message,), { - 'DESCRIPTOR' : _RPCCONTACT, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.RpcContact) - }) -_sym_db.RegisterMessage(RpcContact) - -RpcContacts = _reflection.GeneratedProtocolMessageType('RpcContacts', (_message.Message,), { - 'DESCRIPTOR' : _RPCCONTACTS, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.RpcContacts) - }) -_sym_db.RegisterMessage(RpcContacts) - -DbNames = _reflection.GeneratedProtocolMessageType('DbNames', (_message.Message,), { - 'DESCRIPTOR' : _DBNAMES, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.DbNames) - }) -_sym_db.RegisterMessage(DbNames) - -DbTable = _reflection.GeneratedProtocolMessageType('DbTable', (_message.Message,), { - 'DESCRIPTOR' : _DBTABLE, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.DbTable) - }) -_sym_db.RegisterMessage(DbTable) - -DbTables = _reflection.GeneratedProtocolMessageType('DbTables', (_message.Message,), { - 'DESCRIPTOR' : _DBTABLES, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.DbTables) - }) -_sym_db.RegisterMessage(DbTables) - -DbQuery = _reflection.GeneratedProtocolMessageType('DbQuery', (_message.Message,), { - 'DESCRIPTOR' : _DBQUERY, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.DbQuery) - }) -_sym_db.RegisterMessage(DbQuery) - -DbField = _reflection.GeneratedProtocolMessageType('DbField', (_message.Message,), { - 'DESCRIPTOR' : _DBFIELD, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.DbField) - }) -_sym_db.RegisterMessage(DbField) - -DbRow = _reflection.GeneratedProtocolMessageType('DbRow', (_message.Message,), { - 'DESCRIPTOR' : _DBROW, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.DbRow) - }) -_sym_db.RegisterMessage(DbRow) - -DbRows = _reflection.GeneratedProtocolMessageType('DbRows', (_message.Message,), { - 'DESCRIPTOR' : _DBROWS, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.DbRows) - }) -_sym_db.RegisterMessage(DbRows) - -Verification = _reflection.GeneratedProtocolMessageType('Verification', (_message.Message,), { - 'DESCRIPTOR' : _VERIFICATION, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.Verification) - }) -_sym_db.RegisterMessage(Verification) - -MemberMgmt = _reflection.GeneratedProtocolMessageType('MemberMgmt', (_message.Message,), { - 'DESCRIPTOR' : _MEMBERMGMT, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.MemberMgmt) - }) -_sym_db.RegisterMessage(MemberMgmt) - -UserInfo = _reflection.GeneratedProtocolMessageType('UserInfo', (_message.Message,), { - 'DESCRIPTOR' : _USERINFO, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.UserInfo) - }) -_sym_db.RegisterMessage(UserInfo) - -DecPath = _reflection.GeneratedProtocolMessageType('DecPath', (_message.Message,), { - 'DESCRIPTOR' : _DECPATH, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.DecPath) - }) -_sym_db.RegisterMessage(DecPath) - -Transfer = _reflection.GeneratedProtocolMessageType('Transfer', (_message.Message,), { - 'DESCRIPTOR' : _TRANSFER, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.Transfer) - }) -_sym_db.RegisterMessage(Transfer) - -AttachMsg = _reflection.GeneratedProtocolMessageType('AttachMsg', (_message.Message,), { - 'DESCRIPTOR' : _ATTACHMSG, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.AttachMsg) - }) -_sym_db.RegisterMessage(AttachMsg) - -AudioMsg = _reflection.GeneratedProtocolMessageType('AudioMsg', (_message.Message,), { - 'DESCRIPTOR' : _AUDIOMSG, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.AudioMsg) - }) -_sym_db.RegisterMessage(AudioMsg) - -RichText = _reflection.GeneratedProtocolMessageType('RichText', (_message.Message,), { - 'DESCRIPTOR' : _RICHTEXT, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.RichText) - }) -_sym_db.RegisterMessage(RichText) - -PatMsg = _reflection.GeneratedProtocolMessageType('PatMsg', (_message.Message,), { - 'DESCRIPTOR' : _PATMSG, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.PatMsg) - }) -_sym_db.RegisterMessage(PatMsg) - -OcrMsg = _reflection.GeneratedProtocolMessageType('OcrMsg', (_message.Message,), { - 'DESCRIPTOR' : _OCRMSG, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.OcrMsg) - }) -_sym_db.RegisterMessage(OcrMsg) - -ForwardMsg = _reflection.GeneratedProtocolMessageType('ForwardMsg', (_message.Message,), { - 'DESCRIPTOR' : _FORWARDMSG, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.ForwardMsg) - }) -_sym_db.RegisterMessage(ForwardMsg) - -RoomData = _reflection.GeneratedProtocolMessageType('RoomData', (_message.Message,), { - - 'RoomMember' : _reflection.GeneratedProtocolMessageType('RoomMember', (_message.Message,), { - 'DESCRIPTOR' : _ROOMDATA_ROOMMEMBER, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.RoomData.RoomMember) - }) - , - 'DESCRIPTOR' : _ROOMDATA, - '__module__' : 'wcf_pb2' - # @@protoc_insertion_point(class_scope:wcf.RoomData) - }) -_sym_db.RegisterMessage(RoomData) -_sym_db.RegisterMessage(RoomData.RoomMember) - -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\n\013com.iamteer' - _REQUEST.fields_by_name['ui64']._options = None - _REQUEST.fields_by_name['ui64']._serialized_options = b'0\001' - _WXMSG.fields_by_name['id']._options = None - _WXMSG.fields_by_name['id']._serialized_options = b'0\001' - _MSGTYPES_TYPESENTRY._options = None - _MSGTYPES_TYPESENTRY._serialized_options = b'8\001' - _ATTACHMSG.fields_by_name['id']._options = None - _ATTACHMSG.fields_by_name['id']._serialized_options = b'0\001' - _AUDIOMSG.fields_by_name['id']._options = None - _AUDIOMSG.fields_by_name['id']._serialized_options = b'0\001' - _FORWARDMSG.fields_by_name['id']._options = None - _FORWARDMSG.fields_by_name['id']._serialized_options = b'0\001' - _FUNCTIONS._serialized_start=2728 - _FUNCTIONS._serialized_end=3482 - _REQUEST._serialized_start=19 - _REQUEST._serialized_end=530 - _RESPONSE._serialized_start=533 - _RESPONSE._serialized_end=860 - _EMPTY._serialized_start=862 - _EMPTY._serialized_end=869 - _WXMSG._serialized_start=872 - _WXMSG._serialized_end=1062 - _TEXTMSG._serialized_start=1064 - _TEXTMSG._serialized_end=1119 - _PATHMSG._serialized_start=1121 - _PATHMSG._serialized_end=1162 - _XMLMSG._serialized_start=1164 - _XMLMSG._serialized_end=1235 - _MSGTYPES._serialized_start=1237 - _MSGTYPES._serialized_end=1334 - _MSGTYPES_TYPESENTRY._serialized_start=1290 - _MSGTYPES_TYPESENTRY._serialized_end=1334 - _RPCCONTACT._serialized_start=1337 - _RPCCONTACT._serialized_end=1472 - _RPCCONTACTS._serialized_start=1474 - _RPCCONTACTS._serialized_end=1522 - _DBNAMES._serialized_start=1524 - _DBNAMES._serialized_end=1548 - _DBTABLE._serialized_start=1550 - _DBTABLE._serialized_end=1586 - _DBTABLES._serialized_start=1588 - _DBTABLES._serialized_end=1628 - _DBQUERY._serialized_start=1630 - _DBQUERY._serialized_end=1664 - _DBFIELD._serialized_start=1666 - _DBFIELD._serialized_end=1722 - _DBROW._serialized_start=1724 - _DBROW._serialized_end=1761 - _DBROWS._serialized_start=1763 - _DBROWS._serialized_end=1797 - _VERIFICATION._serialized_start=1799 - _VERIFICATION._serialized_end=1852 - _MEMBERMGMT._serialized_start=1854 - _MEMBERMGMT._serialized_end=1897 - _USERINFO._serialized_start=1899 - _USERINFO._serialized_end=1967 - _DECPATH._serialized_start=1969 - _DECPATH._serialized_end=2004 - _TRANSFER._serialized_start=2006 - _TRANSFER._serialized_end=2058 - _ATTACHMSG._serialized_start=2060 - _ATTACHMSG._serialized_end=2117 - _AUDIOMSG._serialized_start=2119 - _AUDIOMSG._serialized_end=2158 - _RICHTEXT._serialized_start=2160 - _RICHTEXT._serialized_end=2281 - _PATMSG._serialized_start=2283 - _PATMSG._serialized_end=2321 - _OCRMSG._serialized_start=2323 - _OCRMSG._serialized_end=2363 - _FORWARDMSG._serialized_start=2365 - _FORWARDMSG._serialized_end=2411 - _ROOMDATA._serialized_start=2414 - _ROOMDATA._serialized_end=2725 - _ROOMDATA_ROOMMEMBER._serialized_start=2620 - _ROOMDATA_ROOMMEMBER._serialized_end=2689 +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'wcf_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\013com.iamteer' + _globals['_REQUEST'].fields_by_name['ui64']._loaded_options = None + _globals['_REQUEST'].fields_by_name['ui64']._serialized_options = b'0\001' + _globals['_WXMSG'].fields_by_name['id']._loaded_options = None + _globals['_WXMSG'].fields_by_name['id']._serialized_options = b'0\001' + _globals['_MSGTYPES_TYPESENTRY']._loaded_options = None + _globals['_MSGTYPES_TYPESENTRY']._serialized_options = b'8\001' + _globals['_ATTACHMSG'].fields_by_name['id']._loaded_options = None + _globals['_ATTACHMSG'].fields_by_name['id']._serialized_options = b'0\001' + _globals['_AUDIOMSG'].fields_by_name['id']._loaded_options = None + _globals['_AUDIOMSG'].fields_by_name['id']._serialized_options = b'0\001' + _globals['_FORWARDMSG'].fields_by_name['id']._loaded_options = None + _globals['_FORWARDMSG'].fields_by_name['id']._serialized_options = b'0\001' + _globals['_FUNCTIONS']._serialized_start=2728 + _globals['_FUNCTIONS']._serialized_end=3482 + _globals['_REQUEST']._serialized_start=19 + _globals['_REQUEST']._serialized_end=530 + _globals['_RESPONSE']._serialized_start=533 + _globals['_RESPONSE']._serialized_end=860 + _globals['_EMPTY']._serialized_start=862 + _globals['_EMPTY']._serialized_end=869 + _globals['_WXMSG']._serialized_start=872 + _globals['_WXMSG']._serialized_end=1062 + _globals['_TEXTMSG']._serialized_start=1064 + _globals['_TEXTMSG']._serialized_end=1119 + _globals['_PATHMSG']._serialized_start=1121 + _globals['_PATHMSG']._serialized_end=1162 + _globals['_XMLMSG']._serialized_start=1164 + _globals['_XMLMSG']._serialized_end=1235 + _globals['_MSGTYPES']._serialized_start=1237 + _globals['_MSGTYPES']._serialized_end=1334 + _globals['_MSGTYPES_TYPESENTRY']._serialized_start=1290 + _globals['_MSGTYPES_TYPESENTRY']._serialized_end=1334 + _globals['_RPCCONTACT']._serialized_start=1337 + _globals['_RPCCONTACT']._serialized_end=1472 + _globals['_RPCCONTACTS']._serialized_start=1474 + _globals['_RPCCONTACTS']._serialized_end=1522 + _globals['_DBNAMES']._serialized_start=1524 + _globals['_DBNAMES']._serialized_end=1548 + _globals['_DBTABLE']._serialized_start=1550 + _globals['_DBTABLE']._serialized_end=1586 + _globals['_DBTABLES']._serialized_start=1588 + _globals['_DBTABLES']._serialized_end=1628 + _globals['_DBQUERY']._serialized_start=1630 + _globals['_DBQUERY']._serialized_end=1664 + _globals['_DBFIELD']._serialized_start=1666 + _globals['_DBFIELD']._serialized_end=1722 + _globals['_DBROW']._serialized_start=1724 + _globals['_DBROW']._serialized_end=1761 + _globals['_DBROWS']._serialized_start=1763 + _globals['_DBROWS']._serialized_end=1797 + _globals['_VERIFICATION']._serialized_start=1799 + _globals['_VERIFICATION']._serialized_end=1852 + _globals['_MEMBERMGMT']._serialized_start=1854 + _globals['_MEMBERMGMT']._serialized_end=1897 + _globals['_USERINFO']._serialized_start=1899 + _globals['_USERINFO']._serialized_end=1967 + _globals['_DECPATH']._serialized_start=1969 + _globals['_DECPATH']._serialized_end=2004 + _globals['_TRANSFER']._serialized_start=2006 + _globals['_TRANSFER']._serialized_end=2058 + _globals['_ATTACHMSG']._serialized_start=2060 + _globals['_ATTACHMSG']._serialized_end=2117 + _globals['_AUDIOMSG']._serialized_start=2119 + _globals['_AUDIOMSG']._serialized_end=2158 + _globals['_RICHTEXT']._serialized_start=2160 + _globals['_RICHTEXT']._serialized_end=2281 + _globals['_PATMSG']._serialized_start=2283 + _globals['_PATMSG']._serialized_end=2321 + _globals['_OCRMSG']._serialized_start=2323 + _globals['_OCRMSG']._serialized_end=2363 + _globals['_FORWARDMSG']._serialized_start=2365 + _globals['_FORWARDMSG']._serialized_end=2411 + _globals['_ROOMDATA']._serialized_start=2414 + _globals['_ROOMDATA']._serialized_end=2725 + _globals['_ROOMDATA_ROOMMEMBER']._serialized_start=2620 + _globals['_ROOMDATA_ROOMMEMBER']._serialized_end=2689 # @@protoc_insertion_point(module_scope)