2025-02-04 15:35:09 +08:00
|
|
|
|
#include "util.h"
|
|
|
|
|
|
2022-10-15 20:25:42 +08:00
|
|
|
|
#include <codecvt>
|
|
|
|
|
#include <locale>
|
2025-02-04 17:27:35 +08:00
|
|
|
|
#include <optional>
|
2022-10-15 20:25:42 +08:00
|
|
|
|
#include <strsafe.h>
|
|
|
|
|
#include <wchar.h>
|
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
#include "framework.h"
|
2025-02-04 17:27:35 +08:00
|
|
|
|
#include <Shlwapi.h>
|
|
|
|
|
#include <tlhelp32.h>
|
2025-02-04 15:35:09 +08:00
|
|
|
|
|
2025-01-27 12:11:41 +08:00
|
|
|
|
#include "log.hpp"
|
2022-10-15 20:25:42 +08:00
|
|
|
|
|
|
|
|
|
#pragma comment(lib, "shlwapi")
|
|
|
|
|
#pragma comment(lib, "Version.lib")
|
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
namespace util
|
|
|
|
|
{
|
2022-10-15 20:25:42 +08:00
|
|
|
|
|
2025-02-04 21:00:29 +08:00
|
|
|
|
constexpr char WECHATEXE[] = "WeChat.exe";
|
|
|
|
|
constexpr char WECHATWINDLL[] = "WeChatWin.dll";
|
2025-02-04 20:40:46 +08:00
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
std::wstring s2w(const std::string &s)
|
2022-10-15 20:25:42 +08:00
|
|
|
|
{
|
2025-02-04 15:35:09 +08:00
|
|
|
|
if (s.empty()) return std::wstring();
|
|
|
|
|
int size_needed = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), nullptr, 0);
|
|
|
|
|
std::wstring ws(size_needed, 0);
|
|
|
|
|
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), &ws[0], size_needed);
|
2022-10-15 20:25:42 +08:00
|
|
|
|
return ws;
|
|
|
|
|
}
|
2022-10-16 22:39:16 +08:00
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
std::string w2s(const std::wstring &ws)
|
2022-10-15 20:25:42 +08:00
|
|
|
|
{
|
2025-02-04 15:35:09 +08:00
|
|
|
|
if (ws.empty()) return std::string();
|
|
|
|
|
int size_needed
|
|
|
|
|
= WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), static_cast<int>(ws.size()), nullptr, 0, nullptr, nullptr);
|
|
|
|
|
std::string s(size_needed, 0);
|
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), static_cast<int>(ws.size()), &s[0], size_needed, nullptr, nullptr);
|
2022-10-15 20:25:42 +08:00
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
std::string gb2312_to_utf8(const char *gb2312)
|
2023-11-22 22:35:00 +08:00
|
|
|
|
{
|
2025-02-04 15:35:09 +08:00
|
|
|
|
if (!gb2312) return "";
|
2023-11-22 22:35:00 +08:00
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
int size_needed = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, nullptr, 0);
|
|
|
|
|
std::wstring ws(size_needed, 0);
|
2023-11-22 22:35:00 +08:00
|
|
|
|
MultiByteToWideChar(CP_ACP, 0, gb2312, -1, &ws[0], size_needed);
|
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
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);
|
2023-11-22 22:35:00 +08:00
|
|
|
|
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 17:34:48 +08:00
|
|
|
|
static DWORD get_wechat_pid()
|
2022-10-15 20:25:42 +08:00
|
|
|
|
{
|
2025-02-04 15:35:09 +08:00
|
|
|
|
DWORD pid = 0;
|
|
|
|
|
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
|
|
|
if (hSnapshot == INVALID_HANDLE_VALUE) return 0;
|
2022-10-15 20:25:42 +08:00
|
|
|
|
|
|
|
|
|
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
|
|
|
|
|
while (Process32Next(hSnapshot, &pe32)) {
|
2025-02-04 21:00:29 +08:00
|
|
|
|
if (pe32.szExeFile == s2w(WECHATEXE)) {
|
2022-10-15 20:25:42 +08:00
|
|
|
|
pid = pe32.th32ProcessID;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CloseHandle(hSnapshot);
|
|
|
|
|
return pid;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 17:34:48 +08:00
|
|
|
|
static std::optional<std::string> get_wechat_path()
|
2022-10-15 20:25:42 +08:00
|
|
|
|
{
|
2025-02-04 17:27:35 +08:00
|
|
|
|
HKEY hKey;
|
|
|
|
|
if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Tencent\\WeChat", 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
|
|
|
|
|
LOG_ERROR("无法打开注册表项");
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
char path[MAX_PATH] = { 0 };
|
2025-02-04 17:27:35 +08:00
|
|
|
|
DWORD type = REG_SZ;
|
|
|
|
|
DWORD size = sizeof(path);
|
|
|
|
|
if (RegQueryValueExA(hKey, "InstallPath", nullptr, &type, reinterpret_cast<LPBYTE>(path), &size) != ERROR_SUCCESS) {
|
|
|
|
|
RegCloseKey(hKey);
|
|
|
|
|
LOG_ERROR("无法读取注册表中的 InstallPath");
|
2025-02-04 15:35:09 +08:00
|
|
|
|
return std::nullopt;
|
2022-10-15 20:25:42 +08:00
|
|
|
|
}
|
2025-02-04 17:27:35 +08:00
|
|
|
|
RegCloseKey(hKey);
|
2022-10-15 20:25:42 +08:00
|
|
|
|
|
2025-02-04 17:27:35 +08:00
|
|
|
|
PathAppendA(path, WECHATEXE);
|
|
|
|
|
return std::string(path);
|
|
|
|
|
}
|
2022-10-15 20:25:42 +08:00
|
|
|
|
|
2025-02-04 17:34:48 +08:00
|
|
|
|
static std::optional<std::string> get_wechat_win_dll_path()
|
2025-02-04 17:27:35 +08:00
|
|
|
|
{
|
|
|
|
|
auto wechat_path = get_wechat_path();
|
|
|
|
|
if (!wechat_path) {
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
2022-10-15 20:25:42 +08:00
|
|
|
|
|
2025-02-04 17:27:35 +08:00
|
|
|
|
std::string dll_path = *wechat_path;
|
|
|
|
|
PathRemoveFileSpecA(dll_path.data());
|
|
|
|
|
PathAppendA(dll_path.data(), WECHATWINDLL);
|
|
|
|
|
|
|
|
|
|
if (PathFileExistsA(dll_path.c_str())) {
|
|
|
|
|
return dll_path;
|
2025-02-04 15:35:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 17:27:35 +08:00
|
|
|
|
// 微信从(大约)3.7开始,增加了一层版本目录: [3.7.0.29]
|
|
|
|
|
PathRemoveFileSpecA(dll_path.data());
|
|
|
|
|
WIN32_FIND_DATAA find_data;
|
|
|
|
|
HANDLE hFind = FindFirstFileA((dll_path + "\\*.*").c_str(), &find_data);
|
|
|
|
|
if (hFind == INVALID_HANDLE_VALUE) {
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
FindClose(hFind);
|
|
|
|
|
|
|
|
|
|
std::string versioned_path = dll_path + "\\" + find_data.cFileName + WECHATWINDLL;
|
|
|
|
|
return PathFileExistsA(versioned_path.c_str()) ? std::optional<std::string>(versioned_path) : std::nullopt;
|
2022-10-15 20:25:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 17:34:48 +08:00
|
|
|
|
static std::optional<std::string> get_file_version(const std::string &file_path)
|
2023-06-08 16:23:21 +08:00
|
|
|
|
{
|
2025-02-04 17:27:35 +08:00
|
|
|
|
if (!PathFileExistsA(file_path.c_str())) {
|
2025-02-04 15:35:09 +08:00
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 17:27:35 +08:00
|
|
|
|
DWORD dummy = 0;
|
|
|
|
|
DWORD size = GetFileVersionInfoSizeA(file_path.c_str(), &dummy);
|
2025-02-04 15:35:09 +08:00
|
|
|
|
if (size == 0) {
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 17:27:35 +08:00
|
|
|
|
std::vector<BYTE> buffer(size);
|
|
|
|
|
if (!GetFileVersionInfoA(file_path.c_str(), 0, size, buffer.data())) {
|
2025-02-04 15:35:09 +08:00
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 17:27:35 +08:00
|
|
|
|
VS_FIXEDFILEINFO *ver_info = nullptr;
|
|
|
|
|
UINT ver_size = 0;
|
|
|
|
|
if (!VerQueryValueA(buffer.data(), "\\", reinterpret_cast<LPVOID *>(&ver_info), &ver_size)) {
|
2025-02-04 15:35:09 +08:00
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 17:27:35 +08:00
|
|
|
|
return fmt::format("{}.{}.{}.{}", HIWORD(ver_info->dwFileVersionMS), LOWORD(ver_info->dwFileVersionMS),
|
|
|
|
|
HIWORD(ver_info->dwFileVersionLS), LOWORD(ver_info->dwFileVersionLS));
|
2023-06-08 16:23:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
std::string get_wechat_version()
|
2023-06-08 16:23:21 +08:00
|
|
|
|
{
|
2025-02-04 17:27:35 +08:00
|
|
|
|
auto dll_path = get_wechat_win_dll_path();
|
|
|
|
|
if (!dll_path) {
|
|
|
|
|
LOG_ERROR("无法获取 WeChatWin.dll 路径");
|
|
|
|
|
return "";
|
|
|
|
|
}
|
2025-02-04 15:35:09 +08:00
|
|
|
|
|
2025-02-04 17:27:35 +08:00
|
|
|
|
auto version = get_file_version(*dll_path);
|
|
|
|
|
if (!version) {
|
|
|
|
|
LOG_ERROR("无法获取 WeChat 版本信息");
|
|
|
|
|
return "";
|
2025-02-04 15:35:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 17:27:35 +08:00
|
|
|
|
return *version;
|
2023-06-08 16:23:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 17:34:48 +08:00
|
|
|
|
int open_wechat(DWORD &pid)
|
|
|
|
|
{
|
|
|
|
|
pid = get_wechat_pid();
|
|
|
|
|
if (pid != 0) {
|
|
|
|
|
return ERROR_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto wechat_path = util::get_wechat_path();
|
|
|
|
|
if (!wechat_path) {
|
|
|
|
|
LOG_ERROR("获取 WeChat 安装路径失败");
|
|
|
|
|
return ERROR_FILE_NOT_FOUND;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
return ERROR_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
uint32_t get_memory_int_by_address(HANDLE hProcess, uint64_t addr)
|
2022-10-15 20:25:42 +08:00
|
|
|
|
{
|
2025-02-04 15:35:09 +08:00
|
|
|
|
uint32_t value = 0;
|
|
|
|
|
if (!addr || !hProcess) return value;
|
2022-10-15 20:25:42 +08:00
|
|
|
|
|
2025-02-04 17:27:35 +08:00
|
|
|
|
ReadProcessMemory(hProcess, reinterpret_cast<LPCVOID>(addr), &value, sizeof(value), nullptr);
|
2022-10-15 20:25:42 +08:00
|
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
std::wstring get_unicode_info_by_address(HANDLE hProcess, uint64_t address)
|
2022-10-15 20:25:42 +08:00
|
|
|
|
{
|
2025-02-04 15:35:09 +08:00
|
|
|
|
if (!hProcess || !address) return L"";
|
2022-10-15 20:25:42 +08:00
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
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"";
|
2022-10-15 20:25:42 +08:00
|
|
|
|
|
|
|
|
|
wchar_t cValue[500] = { 0 };
|
2025-02-04 15:35:09 +08:00
|
|
|
|
if (ReadProcessMemory(hProcess, reinterpret_cast<LPCVOID>(str_address), cValue, (str_len + 1) * sizeof(wchar_t),
|
|
|
|
|
nullptr)) {
|
|
|
|
|
return std::wstring(cValue);
|
2022-10-15 20:25:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
return L"";
|
2022-10-15 20:25:42 +08:00
|
|
|
|
}
|
2023-05-25 00:18:10 +08:00
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
void dbg_msg(const char *format, ...)
|
2023-05-25 00:18:10 +08:00
|
|
|
|
{
|
2025-02-04 15:35:09 +08:00
|
|
|
|
if (!format) return;
|
|
|
|
|
|
|
|
|
|
va_list args;
|
|
|
|
|
va_start(args, format);
|
|
|
|
|
|
|
|
|
|
va_list args_copy;
|
|
|
|
|
va_copy(args_copy, args);
|
|
|
|
|
int len = vsnprintf(nullptr, 0, format, args_copy);
|
|
|
|
|
va_end(args_copy);
|
|
|
|
|
|
|
|
|
|
std::vector<char> buffer(len + 1);
|
|
|
|
|
vsnprintf(buffer.data(), buffer.size(), format, args);
|
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
|
|
OutputDebugStringA(buffer.data());
|
2023-05-25 00:18:10 +08:00
|
|
|
|
}
|
2024-06-24 21:37:47 +08:00
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
std::unique_ptr<WxString> new_wx_string(const char *str)
|
|
|
|
|
{
|
|
|
|
|
return new_wx_string(str ? std::string(str) : std::string());
|
|
|
|
|
}
|
2024-06-24 21:37:47 +08:00
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
std::unique_ptr<WxString> new_wx_string(const std::string &str) { return std::make_unique<WxString>(s2w(str)); }
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<WxString> new_wx_string(const wchar_t *wstr)
|
2024-06-24 21:37:47 +08:00
|
|
|
|
{
|
2025-02-04 15:35:09 +08:00
|
|
|
|
return new_wx_string(wstr ? std::wstring(wstr) : std::wstring());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<WxString> new_wx_string(const std::wstring &wstr) { return std::make_unique<WxString>(wstr); }
|
2024-06-24 21:37:47 +08:00
|
|
|
|
|
2025-02-04 15:35:09 +08:00
|
|
|
|
std::vector<WxString> parse_wxids(const std::string &wxids)
|
|
|
|
|
{
|
|
|
|
|
std::vector<WxString> wx_members;
|
|
|
|
|
std::wstringstream wss(s2w(wxids));
|
|
|
|
|
std::wstring wstr;
|
|
|
|
|
while (getline(wss, wstr, L',')) {
|
|
|
|
|
wx_members.emplace_back(wstr);
|
|
|
|
|
}
|
|
|
|
|
return wx_members;
|
2024-06-24 21:37:47 +08:00
|
|
|
|
}
|
2025-02-04 15:35:09 +08:00
|
|
|
|
|
|
|
|
|
} // namespace util
|