From e62a37b3542c2f7abc1a396b3aae067b0233a1c8 Mon Sep 17 00:00:00 2001 From: Gy Hu Date: Mon, 26 Dec 2022 16:44:14 +0800 Subject: [PATCH 01/59] =?UTF-8?q?=E6=94=AF=E6=8C=813.8.1.26=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ README.md | 29 ++++++++++++++++++++--------- src/chat_room.cc | 26 +++++++++++++------------- src/contact.cc | 16 ++++++---------- src/forward.cc | 8 ++++---- src/get_db_handle.cc | 10 +++++----- src/hook_img.cc | 6 +++--- src/hook_recv_msg.cc | 4 ++-- src/new_sqlite3.h | 36 ++++++++++++++++++------------------ src/self_info.cc | 26 +++++++++++++------------- src/send_file.cc | 8 ++++---- src/send_image.cc | 8 ++++---- src/send_text.cc | 6 +++--- 13 files changed, 97 insertions(+), 88 deletions(-) diff --git a/.gitignore b/.gitignore index 39cc024..807a2c0 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ #*.exe *.out *.app +/out +CMakePresets.json \ No newline at end of file diff --git a/README.md b/README.md index 89f57ed..bd3a2a9 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@ # wxhelper -wechat hook . +wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26版本。 #### 免责声明: 本仓库发布的内容,仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关! #### 项目说明: -本项目是个人学习学习逆向的项目,主要参考https://github.com/ttttupup/ComWeChatRobot,在此基础上实现了wechat 3.8.0.41的版本的部分内容。 +本项目是个人学习学习逆向的项目,主要参考https://github.com/ttttupup/ComWeChatRobot,在此基础上实现了微信的的其它版本的部分内容。 #### 使用说明: -支持的版本3.8.0.41,目前是最新版本。 +支持的版本3.8.0.41,3.8.1.26。 src:主要的dll代码 tool:简单的注入工具,一个是控制台,一个是图形界面。 python: 简单的服务器,用以接收消息内容。 release:编译好的dll。 +0.首先安装对应的微信版本,主分支是3.8.0.41版本,3.8.1.26分支对应3.8.1.26版本。 1.通过cmake构建成功后,将wxhelper.dll注入到微信,本地启动tcp server,监听19088端口。 2.通过http协议与dll通信,方便客户端操作。 3.接口的url为http://127.0.0.1:19088,注入成功后,直接进行调用即可。 @@ -21,11 +22,19 @@ release:编译好的dll。 #### 编译环境 -Visual Studio 2022(x86) -Visual Studio code -cmake +Visual Studio 2022(x86) + +Visual Studio code + +cmake + vcpkg + +#### 更新说明 +2022-12-26 : 增加3.8.1.26版本支持。 + + ### 接口文档: #### 0.检查微信登录** @@ -683,6 +692,8 @@ vcpkg #### 感谢 -https://github.com/ljc545w/ComWeChatRobot -https://github.com/NationalSecurityAgency/ghidra -https://github.com/x64dbg/x64dbg \ No newline at end of file +https://github.com/ljc545w/ComWeChatRobot + +https://github.com/NationalSecurityAgency/ghidra + +https://github.com/x64dbg/x64dbg diff --git a/src/chat_room.cc b/src/chat_room.cc index b07e2f8..52662e9 100644 --- a/src/chat_room.cc +++ b/src/chat_room.cc @@ -4,17 +4,17 @@ #include "common.h" #include "wechat_data.h" -#define WX_CHAT_ROOM_MGR_OFFSET 0x686e40 -#define WX_GET_CHAT_ROOM_DETAIL_INFO_OFFSET 0xa70920 -#define WX_NEW_CHAT_ROOM_INFO_OFFSET 0xd03ec0 -#define WX_FREE_CHAT_ROOM_INFO_OFFSET 0x7226e0 -#define WX_DEL_CHAT_ROOM_MEMBER_OFFSET 0xa668f0 -#define WX_INIT_CHAT_MSG_OFFSET 0xdbcc40 -#define WX_FREE_CHAT_MSG_OFFSET 0x651c40 -#define WX_ADD_MEMBER_TO_CHAT_ROOM_OFFSET 0xa66400 -#define WX_GET_MEMBER_FROM_CHAT_ROOM_OFFSET 0xa71650 -#define WX_INIT_CHAT_ROOM_OFFSET 0xd01c30 -#define WX_FREE_CHAT_ROOM_OFFSET 0xa79310 +#define WX_CHAT_ROOM_MGR_OFFSET 0x67ee70 +#define WX_GET_CHAT_ROOM_DETAIL_INFO_OFFSET 0xa73a80 +#define WX_NEW_CHAT_ROOM_INFO_OFFSET 0xd07010 +#define WX_FREE_CHAT_ROOM_INFO_OFFSET 0xd072f0 +#define WX_DEL_CHAT_ROOM_MEMBER_OFFSET 0xa69a50 +#define WX_INIT_CHAT_MSG_OFFSET 0xdbf380 +#define WX_FREE_CHAT_MSG_OFFSET 0x649ac0 +#define WX_ADD_MEMBER_TO_CHAT_ROOM_OFFSET 0xa69560 +#define WX_GET_MEMBER_FROM_CHAT_ROOM_OFFSET 0xa749b0 +#define WX_INIT_CHAT_ROOM_OFFSET 0xd04d80 +#define WX_FREE_CHAT_ROOM_OFFSET 0xa7c620 int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info) { int success = 0; @@ -24,7 +24,7 @@ int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info) { DWORD get_chat_room_detail_addr = base + WX_GET_CHAT_ROOM_DETAIL_INFO_OFFSET; DWORD create_chat_room_info_addr = base + WX_NEW_CHAT_ROOM_INFO_OFFSET; DWORD free_chat_room_info_addr = base + WX_FREE_CHAT_ROOM_INFO_OFFSET; - char chat_room_info[0xA4] = {0}; + char chat_room_info[0xDC] = {0}; __asm { PUSHAD LEA ECX,chat_room_info @@ -144,7 +144,7 @@ int GetMemberFromChatRoom(wchar_t* chat_room_id,ChatRoomInner & out){ int success = 0; WeChatString chat_room(chat_room_id); DWORD chat_room_ptr = (DWORD) &chat_room; - char buffer[0x1A0] = {0}; + char buffer[0x1D4] = {0}; DWORD base = GetWeChatWinBase(); DWORD get_member_addr = base + WX_GET_MEMBER_FROM_CHAT_ROOM_OFFSET; DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; diff --git a/src/contact.cc b/src/contact.cc index 608cae9..0ba1ca3 100644 --- a/src/contact.cc +++ b/src/contact.cc @@ -4,15 +4,11 @@ #include "common.h" #include "wechat_data.h" -#define WX_CONTACT_MGR_INSTANCE_OFFSET 0x655d60 -#define WX_CONTACT_GET_LIST_OFFSET 0xa97da0 -#define WX_CONTACT_DEL_OFFSET 0xa9bd10 -#define WX_INIT_CHAT_MSG_OFFSET 0xdbcc40 -#define WX_DB_QUERY_OFFSET 0xa9ba20 -#define WX_SYNC_MGR_OFFSET 0x993fa0 -#define WX_SYNC_MGR_OFFSET 0x993fa0 -#define WX_DO_DEL_CONTACT_OFFSET 0xb9a750 -#define WX_DEL_CONTACT_VTABLE_OFFSET 0x2886990 +#define WX_CONTACT_MGR_INSTANCE_OFFSET 0x64dc30 +#define WX_CONTACT_GET_LIST_OFFSET 0xa9b000 +#define WX_CONTACT_DEL_OFFSET 0xa9ef40 +#define WX_INIT_CHAT_MSG_OFFSET 0xdbf380 +#define WX_DB_QUERY_OFFSET 0xa9ec40 int GetAllContact(vector &vec) { DWORD base = GetWeChatWinBase(); DWORD get_instance = base + WX_CONTACT_MGR_INSTANCE_OFFSET; @@ -66,7 +62,7 @@ int GetAllContact(vector &vec) { temp.type = *(DWORD *)(start + 0x50); temp.verify_flag = *(DWORD *)(start + 0x54); vec.push_back(temp); - start += 0x3E8; + start += 0x438; } return success; } diff --git a/src/forward.cc b/src/forward.cc index 62a421d..3ec3b0d 100644 --- a/src/forward.cc +++ b/src/forward.cc @@ -4,8 +4,8 @@ #include "common.h" #include "get_db_handle.h" #include "wechat_data.h" -#define WX_FORWARD_MSG_OFFSET 0xb68c80 -#define WX_INIT_CHAT_MSG_OFFSET 0xdbcc40 +#define WX_FORWARD_MSG_OFFSET 0xb6a4e0 +#define WX_INIT_CHAT_MSG_OFFSET 0xdbf380 int ForwardMsg(wchar_t *wxid, unsigned long long msgid) { int success = 0; @@ -27,11 +27,11 @@ int ForwardMsg(wchar_t *wxid, unsigned long long msgid) { PUSH EAX SUB ESP,0x14 MOV ECX,ESP - LEA ESI, to_user; + LEA ESI,to_user PUSH ESI CALL init_chat_msg_addr CALL forward_msg_addr - MOVZX EAX,AL; + MOVZX EAX,AL MOV success,EAX ADD ESP,0x1c POPFD diff --git a/src/get_db_handle.cc b/src/get_db_handle.cc index 015a395..58a5cbe 100644 --- a/src/get_db_handle.cc +++ b/src/get_db_handle.cc @@ -5,7 +5,7 @@ #include "new_sqlite3.h" #include "pch.h" #include "wechat_data.h" -#define CONTACT_G_PINSTANCE 0x2bee928 +#define CONTACT_G_PINSTANCE 0x2c42e78 #define DB_MICRO_MSG_OFFSET 0x68 #define DB_CHAT_MSG_OFFSET 0x1C0 #define DB_MISC_OFFSET 0x3D8 @@ -15,10 +15,10 @@ #define DB_FUNCTION_MSG_OFFSET 0x11B0 #define DB_NAME_OFFSET 0x14 -#define PUBLIC_MSG_MGR_OFFSET 0x2c294c0 -#define MULTI_DB_MSG_MGR_OFFSET 0x2c2aff4 -#define FAVORITE_STORAGE_MGR_OFFSET 0x2c2aa14 -#define FTS_FAVORITE_MGR_OFFSET 0x2bef468 +#define PUBLIC_MSG_MGR_OFFSET 0x2c7ec88 +#define MULTI_DB_MSG_MGR_OFFSET 0x2c807d0 +#define FAVORITE_STORAGE_MGR_OFFSET 0x2c801f8 +#define FTS_FAVORITE_MGR_OFFSET 0x2c439b8 using namespace std; map dbmap; diff --git a/src/hook_img.cc b/src/hook_img.cc index 656f915..afb977e 100644 --- a/src/hook_img.cc +++ b/src/hook_img.cc @@ -6,9 +6,9 @@ // #define WX_HOOK_IMG_OFFSET 0xd7eaa5 // #define WX_HOOK_IMG_NEXT_OFFSET 0xda56e0 -#define WX_HOOK_IMG_OFFSET 0xc63ebc -#define WX_HOOK_IMG_NEXT_OFFSET 0xd7e9e0 -#define WX_SELF_ID_OFFSET 0x2BEE08C +#define WX_HOOK_IMG_OFFSET 0xc672cc +#define WX_HOOK_IMG_NEXT_OFFSET 0xd82370 +#define WX_SELF_ID_OFFSET 0x2C42A38 #define BUFSIZE 1024 #define JPEG0 0xFF diff --git a/src/hook_recv_msg.cc b/src/hook_recv_msg.cc index 7043a4b..5728370 100644 --- a/src/hook_recv_msg.cc +++ b/src/hook_recv_msg.cc @@ -10,8 +10,8 @@ using namespace nlohmann; using namespace std; -#define WX_RECV_MSG_HOOK_OFFSET 0xb94796 -#define WX_RECV_MSG_HOOK_NEXT_OFFSET 0x6fe2c0 +#define WX_RECV_MSG_HOOK_OFFSET 0xb97126 +#define WX_RECV_MSG_HOOK_NEXT_OFFSET 0x6fc850 // SyncMgr::addMsgListToDB // #define WX_RECV_MSG_HOOK_OFFSET 0xB9C919 diff --git a/src/new_sqlite3.h b/src/new_sqlite3.h index 4ea593c..a27713f 100644 --- a/src/new_sqlite3.h +++ b/src/new_sqlite3.h @@ -135,24 +135,24 @@ #define SQLITE_NULL 5 #define SQLITE_TEXT 3 -#define SQLITE3_EXEC_OFFSET 0x1b623b0 -#define SQLITE3_BACKUP_INIT_OFFSET 0x1b27d50 -#define SQLITE3_PREPARE_OFFSET 0x1b68d00 -#define SQLITE3_OPEN_OFFSET 0x1b96cf0 -#define SQLITE3_BACKUP_STEP_OFFSET 0x1b28150 -#define SQLITE3_BACKUP_REMAINING_OFFSET 0x1b28890 -#define SQLITE3_BACKUP_PAGECOUNT_OFFSET 0x1b288a0 -#define SQLITE3_BACKUP_FINISH_OFFSET 0x1b28790 -#define SQLITE3_SLEEP_OFFSET 0x1b97530 -#define SQLITE3_ERRCODE_OFFSET 0x1b95990 -#define SQLITE3_CLOSE_OFFSET 0x1b94110 -#define SQLITE3_STEP_OFFSET 0x1b30bc0 -#define SQLITE3_COLUMN_COUNT_OFFSET 0x1b310d0 -#define SQLITE3_COLUMN_NAME_OFFSET 0x1b319c0 -#define SQLITE3_COLUMN_TYPE_OFFSET 0x1b31860 -#define SQLITE3_COLUMN_BLOB_OFFSET 0x1b31110 -#define SQLITE3_COLUMN_BYTES_OFFSET 0x1b311f0 -#define SQLITE3_FINALIZE_OFFSET 0x1b2fb90 +#define SQLITE3_EXEC_OFFSET 0x1ba9de0 +#define SQLITE3_BACKUP_INIT_OFFSET 0x1b6f760 +#define SQLITE3_PREPARE_OFFSET 0x1bb0730 +#define SQLITE3_OPEN_OFFSET 0x1bde730 +#define SQLITE3_BACKUP_STEP_OFFSET 0x1b6fb60 +#define SQLITE3_BACKUP_REMAINING_OFFSET 0x1b702a0 +#define SQLITE3_BACKUP_PAGECOUNT_OFFSET 0x1b702b0 +#define SQLITE3_BACKUP_FINISH_OFFSET 0x1b701a0 +#define SQLITE3_SLEEP_OFFSET 0x1bdef70 +#define SQLITE3_ERRCODE_OFFSET 0x1bdd3d0 +#define SQLITE3_CLOSE_OFFSET 0x1bdbb20 +#define SQLITE3_STEP_OFFSET 0x1b785d0 +#define SQLITE3_COLUMN_COUNT_OFFSET 0x1b78ae0 +#define SQLITE3_COLUMN_NAME_OFFSET 0x1b793d0 +#define SQLITE3_COLUMN_TYPE_OFFSET 0x1b79270 +#define SQLITE3_COLUMN_BLOB_OFFSET 0x1b78b20 +#define SQLITE3_COLUMN_BYTES_OFFSET 0x1b78c00 +#define SQLITE3_FINALIZE_OFFSET 0x1b775a0 typedef int (*Sqlite3_callback)(void*, int, char**, char**); diff --git a/src/self_info.cc b/src/self_info.cc index 561da22..bb150ca 100644 --- a/src/self_info.cc +++ b/src/self_info.cc @@ -5,19 +5,19 @@ #include "wechat_data.h" -#define WX_SELF_NAME_OFFSET 0x2bee198 -#define WX_SELF_MOBILE_OFFSET 0x2BEE108 -#define WX_SELF_CITY_OFFSET 0x2BEE168 -#define WX_SELF_PROVINCE_OFFSET 0x2BEE150 -#define WX_SELF_COUNTRY_OFFSET 0x2BEE138 -#define WX_SELF_ACCOUNT_OFFSET 0x2BEE0F0 -#define WX_SELF_ID_OFFSET 0x2BEE08C -#define WX_SELF_SMALL_IMG_OFFSET 0x2BEE34C -#define WX_SELF_BIG_IMG_OFFSET 0x2BEE364 -#define WX_LOGIN_STATUS_OFFSET 0x2BEE4C0 -#define WX_APP_DATA_ROOT_PATH_OFFSET 0x2c2f478 -#define WX_APP_DATA_SAVE_PATH_OFFSET 0x2C10D04 -#define WX_CURRENT_DATA_PATH_OFFSET 0x2C0EC38 +#define WX_SELF_NAME_OFFSET 0x2C426E8 +#define WX_SELF_MOBILE_OFFSET 0x2C42658 +#define WX_SELF_CITY_OFFSET 0x2C426B8 +#define WX_SELF_PROVINCE_OFFSET 0x2C426A0 +#define WX_SELF_COUNTRY_OFFSET 0x2C42688 +#define WX_SELF_ACCOUNT_OFFSET 0x2C42640 +#define WX_SELF_ID_OFFSET 0x2C42A38 +#define WX_SELF_SMALL_IMG_OFFSET 0x2C4289C +#define WX_SELF_BIG_IMG_OFFSET 0x2C428B4 +#define WX_LOGIN_STATUS_OFFSET 0x2c42a10 +#define WX_APP_DATA_ROOT_PATH_OFFSET 0x2c84ae0 +#define WX_APP_DATA_SAVE_PATH_OFFSET 0x2c65728 +#define WX_CURRENT_DATA_PATH_OFFSET 0x2c636fc diff --git a/src/send_file.cc b/src/send_file.cc index a33727c..11f12e9 100644 --- a/src/send_file.cc +++ b/src/send_file.cc @@ -3,10 +3,10 @@ #include "common.h" #include "wechat_data.h" -#define WX_APP_MSG_MGR_OFFSET 0x665f60 -#define WX_SEND_FILE_OFFSET 0xa0ce20 -#define WX_INIT_CHAT_MSG_OFFSET 0xdbcc40 -#define WX_FREE_CHAT_MSG_OFFSET 0x651c40 +#define WX_APP_MSG_MGR_OFFSET 0x65df50 +#define WX_SEND_FILE_OFFSET 0xa10190 +#define WX_INIT_CHAT_MSG_OFFSET 0xdbf380 +#define WX_FREE_CHAT_MSG_OFFSET 0x649ac0 int SendFile(wchar_t *wxid, wchar_t *file_path){ int success = 0; diff --git a/src/send_image.cc b/src/send_image.cc index 29a358a..24f40f1 100644 --- a/src/send_image.cc +++ b/src/send_image.cc @@ -3,10 +3,10 @@ #include "common.h" #include "wechat_data.h" -#define WX_SEND_IMAGE_OFFSET 0xb68b90 -#define WX_SEND_MESSAGE_MGR_OFFSET 0x663320 -#define WX_INIT_CHAT_MSG_OFFSET 0xdbcc40 -#define WX_FREE_CHAT_MSG_OFFSET 0x651c40 +#define WX_SEND_IMAGE_OFFSET 0xb6a3f0 +#define WX_SEND_MESSAGE_MGR_OFFSET 0x65b2a0 +#define WX_INIT_CHAT_MSG_OFFSET 0xdbf380 +#define WX_FREE_CHAT_MSG_OFFSET 0x649ac0 int SendImage(wchar_t *wxid, wchar_t *image_path){ diff --git a/src/send_text.cc b/src/send_text.cc index 6ba20cc..995fdd8 100644 --- a/src/send_text.cc +++ b/src/send_text.cc @@ -5,11 +5,11 @@ #include "common.h" #include "wechat_data.h" -#define WX_SEND_TEXT_OFFSET 0xb690a0 +#define WX_SEND_TEXT_OFFSET 0xb6a930 -#define WX_SEND_MESSAGE_MGR_OFFSET 0x663320 +#define WX_SEND_MESSAGE_MGR_OFFSET 0x65b2a0 -#define WX_FREE_CHAT_MSG_OFFSET 0x651c40 +#define WX_FREE_CHAT_MSG_OFFSET 0x649ac0 /// @brief 发生文本消息 /// @param wxid wxid /// @param msg 文本消息 From ab3780c4134cbb52c8140c90cb34ec6652556706 Mon Sep 17 00:00:00 2001 From: Gy Hu Date: Mon, 26 Dec 2022 16:53:26 +0800 Subject: [PATCH 02/59] =?UTF-8?q?=E6=B3=A8=E5=85=A5dll?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++- tool/injector/injector.dll | Bin 0 -> 43008 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tool/injector/injector.dll diff --git a/.gitignore b/.gitignore index 807a2c0..7160b8f 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ *.out *.app /out -CMakePresets.json \ No newline at end of file +CMakePresets.json +.vscode \ No newline at end of file diff --git a/tool/injector/injector.dll b/tool/injector/injector.dll new file mode 100644 index 0000000000000000000000000000000000000000..eb61d1c6c5e65b162f7d37f3f8d856f2ce9ebee9 GIT binary patch literal 43008 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

Uj zZK|p<+a0#16;<_CJMvSIUo9!1%G^?I#$5xP?Oi@xY87sVIM70t!ThLIxa0t3+omI{ zp#kNlq1>`+o1-cyW;)ZR!|QqMM?>-gDo6|Dgk2h#Xr?ogs+l%N;o>Ew=H@1wgC$44Lkkbe&|a$p_f;C$L!o$;naz%a1ge`?RN=VX zz~d$d#qR+hkd?K`-<+ytc3l5xbkrjAFELeiv!$xmS`UI%G@(DnNFcS=B{+YtYBb@d z!tyGUZOJmmeks)^UH%DVnH!f`ZB300uyn>Ah`_+@8{B8Aa?sTy#@>&qvYD4aIjF~< z6F3QH@iIu>TtcLZ-<5jlY~04WGj_I}`-LTD zo00Acpt&{jQj^t={ueMW_GxS}t|nQpTY(PVG|yUN#&sDRiy0A!mNy0S*;8>9jgp<= z+LxaKtO`wF?AEw4v*VoEYvalT?F={$<^g~HmnPdR_!(#~z~_x}eq?Jj+i=UJ!PHo7 zo{6hrRMBN&ZS|K%V|yvJYy~vcKvyzm*vZ%_#%g0{2M){4Wpkj*BD$*fCdYS};JKP- zqbts1MX|_e=1z>w;c;?Qj6Fk`{)z~LvBLpR##vPAvM3uP_7kRM^(od`?pEXrGQDt{v9&M_}Y)?ON zh1otaKQCc)uE}mUH!Q|YZV+g+>nA#GjXHa^#f*E%QyZ*w4ZNw=F%=VUoyp!XZCTnx z+-h#L)?#*F8D1Na`cZ%uUcQ9iX!iG90yw8wi6&0T+K}~F*6yr>Ss!GL)?TE&M!Qz~ zxb`LOAuY>}&mNmSAv-lYH`|vzN_Ugaq1&R{sXL+T(^chIbDqmNk`tTzgWSSgW9}bv z_vg;id-R+12ldDF3-VlfJMs?ZeV%t&{9IpU?l4p-RRMqV5r? zQ&S64f0DX9^{&*7slQ8oDs@BJp0wj>C)4866Vvn3&FO9Fe@p*Ix=b@tlc2d+Gfp#J zqt;B;q-nA=dd*DDY|V|D3eC+LlV*uV(5%xuqQs;EtxN5c4Y3&?8^Ks z^BdW$*dQ$QnlII>$Ll{uWJ9KJ*|~xkIJ5seM9yL-37YKbve3O zIsG{!bJe*w=^ONJ{U7vQ`Xl=H^dIVb^nLoj>2J?-=WWXSIPYwpD!(Y-nm>@wQsF-4`) zmu37gqcEd5qb$Rb(VF4Pcrv3ssYNjSLJ2OAika)0O z-TS&rbJRKMIfk4ka^A{0ne$xk>$xX$|B*XVf2sZo{S>`MpQktIP5OHM3jGd!hyJMk zxc-K`d3n`&O?m$(?`L`U<^4KuTi)Awzs*0L4^quKF>1Wg9G)bCCnp}-hvrMxJ8dXexAmh=DCo-PScroJy zG-1uMWwm6rWqGn5hYs4a#-m@QYm2m{+MBf-w7-G0AIp9t`>pJEvyW$=%>E?%)9laD z$Kqgr7wRs74PK?YM)xDgI7c@_cfGDySEl=ku1aUtE!EwsTdr%>xpW@g&vp0f9@cHv zJ+6B~_j}#5x Date: Thu, 29 Dec 2022 14:58:41 +0800 Subject: [PATCH 03/59] =?UTF-8?q?=E6=96=B0=E5=A2=9Eocr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++--- src/api.cc | 10 +++++++ src/api.h | 1 + src/ocr.cc | 61 ++++++++++++++++++++++++++++++++++++++++ src/ocr.h | 5 ++++ 5 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 src/ocr.cc create mode 100644 src/ocr.h diff --git a/README.md b/README.md index bd3a2a9..93e326c 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,48 @@ Visual Studio code cmake vcpkg +#### 构建步骤 +以下是在vscode中操作,vs中的操作类似。 +1.安装vcpkg,cmake,vscode +2.安装相应的库,如果安装的版本不同,则根据vcpkg安装成功后提示的find_package修改CMakeLists.txt内容即可。或者自己编译。 +``` + vcpkg install mongoose + vcpkg install nlohmann-json +``` +3.vscode 配置CMakePresets.json,主要设置CMAKE_C_COMPILER 和CMAKE_CXX_COMPILER 为cl.exe.参考如下 +``` + { + "name": "x86-release", + "displayName": "x86-release", + "description": "Sets Ninja generator, build and install directory", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "architecture":{ + "value": "x86", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_C_COMPILER": "cl.exe", + "CMAKE_CXX_COMPILER": "cl.exe", + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "CMAKE_TOOLCHAIN_FILE": { + "value": "C:/soft/vcpkg/scripts/buildsystems/vcpkg.cmake", + "type": "FILEPATH" + } + }, + "environment": { + + } + + } +``` +4.vscode中右键configure all projects,在Terminal中点击Run Task,如没有先配置build任务,然后运行即可 #### 更新说明 2022-12-26 : 增加3.8.1.26版本支持。 - +2022-12-29 : 新增提取文字功能。 ### 接口文档: @@ -60,7 +97,6 @@ vcpkg |data|string|响应内容| ###### 接口示例 -地址:https://www.bincoding.cn/test 入参: ``` javascript ``` @@ -107,7 +143,6 @@ vcpkg |wxid|string|wxid| ###### 接口示例 -地址:https://www.bincoding.cn/test 入参: ``` javascript ``` @@ -143,7 +178,7 @@ vcpkg ###### 接口示例 -地址:https://www.bincoding.cn/test + 入参: ``` javascript { @@ -691,6 +726,44 @@ vcpkg ``` + +#### 49.提取文字** +###### 接口功能 +> 提取图片中的文字 + +###### 接口地址 +> [/api/?type=49](/api/?type=49) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|imagePath |ture |string| 图片路径 | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,0成功, -1失败,1 2 则是缓存或者正在进行中需再调用一次| +|result|string|成功提示| +|text|string|提取的相应文字| + + +###### 接口示例 +入参: +``` javascript +{ + "imagePath":"C:\\3a610d7bc1cf5a15d12225a64b8962.dat" +} + +``` +响应: +``` javascript +{"code":0,"result":"OK","text":"搜索商品新闻简报小程序上线啦!!!我的收藏地址管理促销单品多买多省热门榜单热门榜单首发新品精品推荐精品推荐首发新品FRONTI警微安奈儿 童装冬季款男¥39900¥59.90帕拉丁品牌 时尚双背包409 00¥49.90我是有底线的首页分类购物车我的"} +``` + + #### 感谢 https://github.com/ljc545w/ComWeChatRobot diff --git a/src/api.cc b/src/api.cc index 10f5d5b..82b0b51 100644 --- a/src/api.cc +++ b/src/api.cc @@ -18,6 +18,7 @@ #include "chat_room.h" #include "self_info.h" #include "hook_img.h" +#include "ocr.h" #pragma comment(lib, "ws2_32.lib") using namespace std; @@ -535,6 +536,15 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { int success = GetImgByName(WS2LW(image_path),WS2LW(save_path)); json ret_data = {{"code", success}, {"result", "OK"}}; ret = ret_data.dump(); + break; + } + case WECHAT_DO_OCR:{ + wstring image_path = get_http_req_param(hm, j_param, "imagePath", is_post); + string text(""); + int success = DoOCRTask(WS2LW(image_path),text); + json ret_data = {{"code", success}, {"result", "OK"},{"text",text}}; + ret = ret_data.dump(); + break; } default: break; diff --git a/src/api.h b/src/api.h index 4ecd6e3..e1fe71b 100644 --- a/src/api.h +++ b/src/api.h @@ -63,6 +63,7 @@ typedef enum WECHAT_HTTP_APISTag WECHAT_GET_CONTACT_ALL, WECHAT_GET_CHATROOM_INFO, WECHAT_GET_IMG_BY_NAME, + WECHAT_DO_OCR, } WECHAT_HTTP_APIS, *PWECHAT_HTTP_APIS; diff --git a/src/ocr.cc b/src/ocr.cc new file mode 100644 index 0000000..28d6b42 --- /dev/null +++ b/src/ocr.cc @@ -0,0 +1,61 @@ +#include "pch.h" +#include "ocr.h" + +#include "common.h" +#include "wechat_data.h" + +#define WX_INIT_OBJ_OFFSET 0x6cbab0 +#define WX_OCR_MANAGER_OFFSET 0x6cff00 +#define WX_DO_OCR_TASK_OFFSET 0x11e3210 + +int DoOCRTask(wchar_t *img_path, std::string &result) { + int success = -1; + WeChatString path(img_path); + WeChatString null_obj = {0}; + WeChatString ocr_result = {0}; + DWORD base = GetWeChatWinBase(); + DWORD ocr_manager_addr = base + WX_OCR_MANAGER_OFFSET; + DWORD do_ocr_task_addr = base + WX_DO_OCR_TASK_OFFSET; + DWORD init_addr = base + WX_INIT_OBJ_OFFSET; + __asm { + PUSHAD + PUSHFD + LEA ECX,ocr_result + CALL init_addr + CALL ocr_manager_addr + LEA ECX,null_obj + PUSH ECX + LEA ECX,ocr_result + PUSH ECX + PUSH ECX + LEA ECX,path + PUSH ECX + MOV ECX,EAX + CALL do_ocr_task_addr + MOV success,EAX + POPFD + POPAD + } + + if (success == 0) { + DWORD addr = (DWORD)&ocr_result; + DWORD ptr = *(DWORD *)addr; + DWORD num = *(DWORD *)(addr + 0x4); + if (num <= 0) { + return success; + } + + DWORD header = *(DWORD *)ptr; + for (unsigned int i = 0; i < num -1; i++) { + DWORD content = *(DWORD *)header; + result += unicode_to_utf8((wchar_t *)READ_WSTRING(content, 0x14).c_str()); + + header = content; + } +#ifdef _DEBUG + cout << "num:" << num << endl; + cout << "all:" << result << endl; +#endif + } + return success; +} \ No newline at end of file diff --git a/src/ocr.h b/src/ocr.h new file mode 100644 index 0000000..b6e73aa --- /dev/null +++ b/src/ocr.h @@ -0,0 +1,5 @@ +#ifndef OCR_H_ +#define OCR_H_ +#include +int DoOCRTask(wchar_t* img_path,std::string &result); +#endif \ No newline at end of file From 8b68a347a0a10b65f6beea36fdee13d9b47d7952 Mon Sep 17 00:00:00 2001 From: Gy Hu Date: Mon, 2 Jan 2023 11:45:04 +0800 Subject: [PATCH 04/59] =?UTF-8?q?=E9=80=80=E5=87=BA=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 37 +++++++++++++++++++++++++++++++++++++ src/api.cc | 3 +++ src/self_info.cc | 24 +++++++++++++++++++++++- src/self_info.h | 2 ++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 93e326c..a44dd7b 100644 --- a/README.md +++ b/README.md @@ -600,6 +600,43 @@ vcpkg +#### 44.退出登录** +###### 接口功能 +> 退出登录微信,相当于直接退出微信,跟手动退出比,少了重新打开登录的一步,dll注入后也会随微信关闭而关闭。调用后不能再继续操作dll。 + +###### 接口地址 +> [/api/?type=44](/api/?type=44) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,非0成功| +|result|string|成功提示| + + + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{"code":4344,"result":"OK"} +``` + + + #### 46.联系人列表** ###### 接口功能 > 联系人列表 diff --git a/src/api.cc b/src/api.cc index 82b0b51..2235262 100644 --- a/src/api.cc +++ b/src/api.cc @@ -461,6 +461,9 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_LOGOUT: { + int success = Logout(); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); break; } case WECHAT_GET_TRANSFER: { diff --git a/src/self_info.cc b/src/self_info.cc index bb150ca..f279e7f 100644 --- a/src/self_info.cc +++ b/src/self_info.cc @@ -19,7 +19,8 @@ #define WX_APP_DATA_SAVE_PATH_OFFSET 0x2c65728 #define WX_CURRENT_DATA_PATH_OFFSET 0x2c636fc - +#define WX_LOGOUT_OFFSET 0xccc320 +#define WX_ACCOUT_SERVICE_OFFSET 0x65bcc0 int GetSelfInfo(SelfInfoInner &out) { DWORD base = GetWeChatWinBase(); @@ -136,4 +137,25 @@ int GetSelfInfo(SelfInfoInner &out) { int CheckLogin(){ DWORD base = GetWeChatWinBase(); return *(DWORD*) (base + WX_LOGIN_STATUS_OFFSET); +} + + +int Logout(){ + int success = 0; + if(!CheckLogin()){ + return success; + } + DWORD base = GetWeChatWinBase(); + DWORD account_service_addr = base + WX_ACCOUT_SERVICE_OFFSET; + DWORD logout_addr = base + WX_LOGOUT_OFFSET; + __asm{ + PUSHAD + CALL account_service_addr + PUSH 0x0 + MOV ECX,EAX + CALL logout_addr + MOV success,EAX + POPAD + } + return success; } \ No newline at end of file diff --git a/src/self_info.h b/src/self_info.h index 50ddac3..ebec883 100644 --- a/src/self_info.h +++ b/src/self_info.h @@ -4,4 +4,6 @@ int GetSelfInfo(SelfInfoInner& out); int CheckLogin(); + +int Logout(); #endif \ No newline at end of file From a9b01eebe1fe9ee87ba6b8a2cb29001235a8f3fa Mon Sep 17 00:00:00 2001 From: Gy Hu Date: Mon, 2 Jan 2023 11:46:58 +0800 Subject: [PATCH 05/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a44dd7b..4acddbc 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,11 @@ vcpkg 4.vscode中右键configure all projects,在Terminal中点击Run Task,如没有先配置build任务,然后运行即可 #### 更新说明 -2022-12-26 : 增加3.8.1.26版本支持。 -2022-12-29 : 新增提取文字功能。 +2022-12-26 : 增加3.8.1.26版本支持。 +2022-12-29 : 新增提取文字功能。 + +2022-01-02 : 退出微信登录。 ### 接口文档: #### 0.检查微信登录** From 088a7d28cb0530ef58905b46c7601f0e6b08caaf Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 4 Jan 2023 09:55:45 +0800 Subject: [PATCH 06/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9cmake=E5=8F=98=E9=87=8F?= =?UTF-8?q?=EF=BC=8C=E6=9E=84=E5=BB=BA=E6=9B=B4=E9=80=9A=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 372c064..9112297 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ file(GLOB CPP_FILES ${PROJECT_SOURCE_DIR}/src/*.cc ${PROJECT_SOURCE_DIR}/src/* -include_directories(c:/soft/vcpkg/installed/x86-windows/include) +include_directories(${VCPKG_INSTALLED_DIR}/x86-windows/include) # add_subdirectory(3rd) From 4758fbf0057e5c83fc61309c06abb213b531aaa8 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Sun, 29 Jan 2023 14:15:12 +0800 Subject: [PATCH 07/59] 3.8.1.26 release dll --- release/wxhelper.dll | Bin 216576 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 release/wxhelper.dll diff --git a/release/wxhelper.dll b/release/wxhelper.dll deleted file mode 100644 index 4a07047f8dbb7954bad37ac67825570f589eac08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 216576 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%0DhL|0}WbCr6f_EXyA* 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∨Y94V}l+m~-Z)xfn2GEjh>J{KK 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 E4=*NzeE Date: Sun, 29 Jan 2023 14:23:15 +0800 Subject: [PATCH 08/59] 3.8.1.26 release dll --- release/3.8.1.26/wxhelper.dll | Bin 0 -> 218624 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 release/3.8.1.26/wxhelper.dll diff --git a/release/3.8.1.26/wxhelper.dll b/release/3.8.1.26/wxhelper.dll new file mode 100644 index 0000000000000000000000000000000000000000..351ce6a6514decf3558cadea3cd03823ab44a998 GIT binary patch literal 218624 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%0D0w%5y5zr0$=&Bh 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 I`v2hhZ)9=8)Bpeg literal 0 HcmV?d00001 From c508ae30f8e625bec1eaf5bc8e17f21fe87bd625 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Tue, 31 Jan 2023 16:10:58 +0800 Subject: [PATCH 09/59] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=BE=A4=E6=98=B5=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 42 +++++++++++++++++++++++++++++++++- release/3.8.1.26/wxhelper.dll | Bin 218624 -> 0 bytes src/api.cc | 6 +++++ src/chat_room.cc | 36 +++++++++++++++++++++++++++++ src/chat_room.h | 1 + 5 files changed, 84 insertions(+), 1 deletion(-) delete mode 100644 release/3.8.1.26/wxhelper.dll diff --git a/README.md b/README.md index 4acddbc..f2a2243 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ release:编译好的dll。 3.接口的url为http://127.0.0.1:19088,注入成功后,直接进行调用即可。 4.特别注意数据库查询接口需要先调用获取到句柄之后,才能进行查询。 5.相关功能只在win11环境下进行简单测试,其他环境无法保证。 +6.注意个别接口在3.8.0.41版本没有实现,具体参考源码。 #### 编译环境 @@ -73,7 +74,9 @@ vcpkg 2022-12-29 : 新增提取文字功能。 -2022-01-02 : 退出微信登录。 +2022-01-02 : 退出微信登录。 + +2022-01-31 : 新增修改群昵称(仅支持3.8.1.26)。 ### 接口文档: #### 0.检查微信登录** @@ -499,6 +502,43 @@ vcpkg ``` +#### 31.修改自身群昵称** +###### 接口功能 +> 增加群成员 + +###### 接口地址 +> [/api/?type=31](/api/?type=31) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |ture |string| 群id | +|wxid |ture |string| 自己的id,只能修改自己的群名片 | +|nickName |ture |string| 修改的昵称 | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"34932563384@chatroom", + "wxid":"wxid_272211111121112", + "nickName":"昵称test" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` diff --git a/release/3.8.1.26/wxhelper.dll b/release/3.8.1.26/wxhelper.dll deleted file mode 100644 index 351ce6a6514decf3558cadea3cd03823ab44a998..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 218624 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%0D0w%5y5zr0$=&Bh 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 I`v2hhZ)9=8)Bpeg diff --git a/src/api.cc b/src/api.cc index 2235262..ea21fcf 100644 --- a/src/api.cc +++ b/src/api.cc @@ -381,6 +381,12 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_CHATROOM_SET_SELF_NICKNAME: { + wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); + wstring wxid = get_http_req_param(hm, j_param, "wxid", is_post); + wstring nick = get_http_req_param(hm, j_param, "nickName", is_post); + int success = ModChatRoomMemberNickName(WS2LW(room_id),WS2LW(wxid),WS2LW(nick)); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); break; } case WECHAT_DATABASE_GET_HANDLES: { diff --git a/src/chat_room.cc b/src/chat_room.cc index 52662e9..162da51 100644 --- a/src/chat_room.cc +++ b/src/chat_room.cc @@ -15,6 +15,7 @@ #define WX_GET_MEMBER_FROM_CHAT_ROOM_OFFSET 0xa749b0 #define WX_INIT_CHAT_ROOM_OFFSET 0xd04d80 #define WX_FREE_CHAT_ROOM_OFFSET 0xa7c620 +#define WX_MOD_CHAT_ROOM_MEMBER_NICK_NAME_OFFSET 0xa6f8f0 int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info) { int success = 0; @@ -182,3 +183,38 @@ int GetMemberFromChatRoom(wchar_t* chat_room_id,ChatRoomInner & out){ } return success; } + +int ModChatRoomMemberNickName(wchar_t* chat_room_id,wchar_t* wxid,wchar_t * nick){ + int success = 0; + WeChatString chat_room(chat_room_id); + WeChatString self_wxid(wxid); + WeChatString new_nick(nick); + DWORD base = GetWeChatWinBase(); + DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; + DWORD mod_member_nick_name_addr = base + WX_MOD_CHAT_ROOM_MEMBER_NICK_NAME_OFFSET; + DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; + __asm{ + PUSHAD + CALL get_chat_room_mgr_addr + SUB ESP,0x14 + MOV ECX,ESP + LEA EDI,new_nick + PUSH EDI + CALL init_chat_msg_addr + SUB ESP,0x14 + LEA EAX,self_wxid + MOV ECX,ESP + PUSH EAX + CALL init_chat_msg_addr + SUB ESP,0x14 + LEA EAX,chat_room + MOV ECX,ESP + PUSH EAX + CALL init_chat_msg_addr + CALL mod_member_nick_name_addr + MOVZX EAX,AL + MOV success,EAX + POPAD + } + return success; +} \ No newline at end of file diff --git a/src/chat_room.h b/src/chat_room.h index 9ac930a..4987a2d 100644 --- a/src/chat_room.h +++ b/src/chat_room.h @@ -7,4 +7,5 @@ int DelMemberFromChatRoom(wchar_t* chat_room_id,wchar_t** wxids,int len); int AddMemberToChatRoom(wchar_t* chat_room_id, wchar_t** wxids,int len); int GetMemberFromChatRoom(wchar_t* chat_room_id,ChatRoomInner & out); +int ModChatRoomMemberNickName(wchar_t* chat_room_id,wchar_t* wxid,wchar_t * nick); #endif \ No newline at end of file From f82f85b2b601cc7e5f3f18906cd805878e1cf3f1 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Tue, 31 Jan 2023 16:50:18 +0800 Subject: [PATCH 10/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f2a2243..d1e8147 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,13 @@ tool:简单的注入工具,一个是控制台,一个是图形界面。 python: 简单的服务器,用以接收消息内容。 release:编译好的dll。 -0.首先安装对应的微信版本,主分支是3.8.0.41版本,3.8.1.26分支对应3.8.1.26版本。 -1.通过cmake构建成功后,将wxhelper.dll注入到微信,本地启动tcp server,监听19088端口。 -2.通过http协议与dll通信,方便客户端操作。 -3.接口的url为http://127.0.0.1:19088,注入成功后,直接进行调用即可。 -4.特别注意数据库查询接口需要先调用获取到句柄之后,才能进行查询。 -5.相关功能只在win11环境下进行简单测试,其他环境无法保证。 -6.注意个别接口在3.8.0.41版本没有实现,具体参考源码。 +0.首先安装对应的微信版本,主分支是3.8.0.41版本,3.8.1.26分支对应3.8.1.26版本。 +1.通过cmake构建成功后,将wxhelper.dll注入到微信,本地启动tcp server,监听19088端口。 +2.通过http协议与dll通信,方便客户端操作。 +3.接口的url为http://127.0.0.1:19088,注入成功后,直接进行调用即可。 +4.特别注意数据库查询接口需要先调用获取到句柄之后,才能进行查询。 +5.相关功能只在win11环境下进行简单测试,其他环境无法保证。 +6.注意个别接口在3.8.0.41版本没有实现,具体参考源码。 #### 编译环境 @@ -504,7 +504,7 @@ vcpkg #### 31.修改自身群昵称** ###### 接口功能 -> 增加群成员 +> 修改群名片 ###### 接口地址 > [/api/?type=31](/api/?type=31) From d86d4c0bf6f28b0c798aef34ec7fe1f428f17e85 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 1 Feb 2023 10:36:32 +0800 Subject: [PATCH 11/59] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8B=8D=E4=B8=80?= =?UTF-8?q?=E6=8B=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 +++++++++++++++++++++++++++++--- src/api.cc | 9 +++++++++ src/api.h | 1 + 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d1e8147..87c2cfa 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,9 @@ vcpkg 2022-01-02 : 退出微信登录。 -2022-01-31 : 新增修改群昵称(仅支持3.8.1.26)。 +2022-01-31 : 新增修改群昵称(仅支持3.8.1.26)。 + +2022-02-01 : 新增拍一拍(仅支持3.8.1.26)。 ### 接口文档: #### 0.检查微信登录** @@ -829,17 +831,41 @@ vcpkg |text|string|提取的相应文字| +#### 50.拍一拍** +###### 接口功能 +> 群里拍一拍用户 + +###### 接口地址 +> [/api/?type=50](/api/?type=50) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |ture |string| 微信群聊id | +|wxid |ture |string| 要拍的用户wxid,如果使用用户自定义的微信号,则不会显示群内昵称 | +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| +|text|string|提取的相应文字| + + ###### 接口示例 入参: ``` javascript { - "imagePath":"C:\\3a610d7bc1cf5a15d12225a64b8962.dat" + "chatRoomId":"123331@chatroom", + "wxid":"wxid_123456" } ``` 响应: ``` javascript -{"code":0,"result":"OK","text":"搜索商品新闻简报小程序上线啦!!!我的收藏地址管理促销单品多买多省热门榜单热门榜单首发新品精品推荐精品推荐首发新品FRONTI警微安奈儿 童装冬季款男¥39900¥59.90帕拉丁品牌 时尚双背包409 00¥49.90我是有底线的首页分类购物车我的"} +{"code":1,"result":"OK"} ``` diff --git a/src/api.cc b/src/api.cc index ea21fcf..9a08828 100644 --- a/src/api.cc +++ b/src/api.cc @@ -19,6 +19,7 @@ #include "self_info.h" #include "hook_img.h" #include "ocr.h" +#include "pat.h" #pragma comment(lib, "ws2_32.lib") using namespace std; @@ -555,6 +556,14 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { ret = ret_data.dump(); break; } + case WECHAT_SEND_PAT_MSG:{ + wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); + wstring wxid = get_http_req_param(hm, j_param, "wxid", is_post); + int success = SendPatMsg(WS2LW(room_id),WS2LW(wxid)); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } default: break; } diff --git a/src/api.h b/src/api.h index e1fe71b..1c2a361 100644 --- a/src/api.h +++ b/src/api.h @@ -64,6 +64,7 @@ typedef enum WECHAT_HTTP_APISTag WECHAT_GET_CHATROOM_INFO, WECHAT_GET_IMG_BY_NAME, WECHAT_DO_OCR, + WECHAT_SEND_PAT_MSG, } WECHAT_HTTP_APIS, *PWECHAT_HTTP_APIS; From 6b36287c4d868a0b2b311b0450879cf6fdc6861f Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 1 Feb 2023 10:39:41 +0800 Subject: [PATCH 12/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87c2cfa..4b256db 100644 --- a/README.md +++ b/README.md @@ -851,7 +851,7 @@ vcpkg |---|---|---| |code|int|返回状态,1成功, -1失败| |result|string|成功提示| -|text|string|提取的相应文字| + ###### 接口示例 From 2f1793b2b1673400c8abf7635990a33d7b4ffa99 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 1 Feb 2023 11:57:32 +0800 Subject: [PATCH 13/59] =?UTF-8?q?=E6=8B=8D=E4=B8=80=E6=8B=8D=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pat.cc | 34 ++++++++++++++++++++++++++++++++++ src/pat.h | 4 ++++ 2 files changed, 38 insertions(+) create mode 100644 src/pat.cc create mode 100644 src/pat.h diff --git a/src/pat.cc b/src/pat.cc new file mode 100644 index 0000000..551927c --- /dev/null +++ b/src/pat.cc @@ -0,0 +1,34 @@ +#include "pch.h" +#include "pat.h" + +#include "common.h" +#include "wechat_data.h" + +#define WX_PAT_MGR_OFFSET 0x7e91c0 +#define WX_SEND_PAT_MSG_OFFSET 0x1228510 +#define WX_RET_OFFSET 0x1AE4A45 + +int SendPatMsg(wchar_t* chat_room_id, wchar_t* wxid) { + int success = -1; + WeChatString chat_room(chat_room_id); + WeChatString self_wxid(wxid); + DWORD base = GetWeChatWinBase(); + DWORD get_pat_mgr_addr = base + WX_PAT_MGR_OFFSET; + DWORD send_pat_msg_addr = base + WX_SEND_PAT_MSG_OFFSET; + DWORD ret_addr = base + WX_RET_OFFSET; + __asm { + PUSHAD + CALL get_pat_mgr_addr + PUSH ret_addr + PUSH 0x0 + PUSH EAX + LEA ECX,chat_room + LEA EDX,self_wxid + CALL send_pat_msg_addr + ADD ESP,0xc + MOVZX EAX,AL + MOV success,EAX + POPAD + } + return success; +} \ No newline at end of file diff --git a/src/pat.h b/src/pat.h new file mode 100644 index 0000000..7893f9e --- /dev/null +++ b/src/pat.h @@ -0,0 +1,4 @@ +#ifndef PAT_H_ +#define PAT_H_ +int SendPatMsg(wchar_t* chat_room_id,wchar_t* wxid); +#endif \ No newline at end of file From 911f87211cac119bc04cd0d4ae149146e7cf3e5e Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 1 Feb 2023 17:01:53 +0800 Subject: [PATCH 14/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9hook=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=20=20close=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 64 ++++++++++++++++++++++++++++++++++++++++++++ src/api.cc | 6 ++++- src/hook_recv_msg.cc | 8 ++++-- src/hook_recv_msg.h | 2 +- 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4b256db..375e288 100644 --- a/README.md +++ b/README.md @@ -311,6 +311,38 @@ vcpkg ``` +#### 10.取消hook消息** +###### 接口功能 +> 取消hook消息 + +###### 接口地址 +> [/api/?type=10](/api/?type=10) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` #### 11.hook图片** ###### 接口功能 @@ -348,6 +380,38 @@ vcpkg ``` +#### 12.取消hook图片** +###### 接口功能 +> 取消hook图片 + +###### 接口地址 +> [/api/?type=12](/api/?type=12) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` #### 17.删除好友** ###### 接口功能 diff --git a/src/api.cc b/src/api.cc index 9a08828..61efb34 100644 --- a/src/api.cc +++ b/src/api.cc @@ -267,7 +267,11 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { } case WECHAT_MSG_START_HOOK: { int port = get_http_req_param_int(hm, j_param, "port", is_post); - int success = HookRecvMsg(port); + wstring ip = get_http_req_param(hm, j_param, "ip", is_post); + string client_ip = Wstring2String(ip); + char ip_cstr[16]; + strcpy_s(ip_cstr,client_ip.c_str()); + int success = HookRecvMsg(ip_cstr,port); json ret_data = {{"code", success}, {"result", "OK"}}; ret = ret_data.dump(); break; diff --git a/src/hook_recv_msg.cc b/src/hook_recv_msg.cc index 5728370..71fe1a5 100644 --- a/src/hook_recv_msg.cc +++ b/src/hook_recv_msg.cc @@ -20,6 +20,7 @@ using namespace std; #define CLIENT_IP "127.0.0.1" static int kServerPort = 0; static int kMessageHooked = FALSE; +static char kServerIp[16]= "127.0.0.1"; static DWORD kWeChatWinBase = GetWeChatWinBase(); static char kOriginReceMsgAsmCode[5] = {0}; @@ -59,10 +60,12 @@ BOOL SendBySocket(const char *buffer, size_t len) { memset(&client_addr, 0, sizeof(client_addr)); client_addr.sin_family = AF_INET; client_addr.sin_port = htons((u_short)kServerPort); - InetPtonA(AF_INET, CLIENT_IP, &client_addr.sin_addr.s_addr); + InetPtonA(AF_INET, kServerIp, &client_addr.sin_addr.s_addr); if (connect(client_socket, reinterpret_cast(&client_addr), sizeof(sockaddr)) < 0) { #ifdef _DEBUG + cout << "kServerIp:" << kServerIp < Date: Wed, 1 Feb 2023 17:05:12 +0800 Subject: [PATCH 15/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9hook=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 375e288..d90b36c 100644 --- a/README.md +++ b/README.md @@ -289,7 +289,7 @@ vcpkg |参数|必选|类型|说明| |---|---|---|---| |port |ture |string| 本地服务端端口,用来接收消息内容 | - +|ip |ture |string| 服务端ip地址,用来接收消息内容,可以是任意ip,即tcp客户端连接的服务端的ip (3.8.1.26版本)| ###### 返回字段 |返回字段|字段类型|说明 | @@ -303,6 +303,7 @@ vcpkg ``` javascript { "port": "19099" + "ip":"127.0.0.1" } ``` 响应: From b0e73002ff2e4bfe791b41398273b0b21b0c8d85 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Sat, 4 Feb 2023 12:04:28 +0800 Subject: [PATCH 16/59] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=BE=A4=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E7=BD=AE=E9=A1=B6=EF=BC=8C=E4=BF=AE=E6=94=B9=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20=20close=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 96 +++++++++++++++++++++++++++++++++++++++++-- src/api.cc | 16 ++++++++ src/api.h | 2 + src/chat_room.cc | 97 +++++++++++++++++++++++++++++++++++++++++++- src/chat_room.h | 3 ++ src/get_db_handle.cc | 19 +++++++++ src/get_db_handle.h | 2 + 7 files changed, 230 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d90b36c..b8c588b 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ release:编译好的dll。 4.特别注意数据库查询接口需要先调用获取到句柄之后,才能进行查询。 5.相关功能只在win11环境下进行简单测试,其他环境无法保证。 6.注意个别接口在3.8.0.41版本没有实现,具体参考源码。 +7.对应分支接口文档都是支持指定版本的,其他版本不支持,请特别注意版本。 #### 编译环境 @@ -67,18 +68,30 @@ vcpkg } ``` -4.vscode中右键configure all projects,在Terminal中点击Run Task,如没有先配置build任务,然后运行即可 +4.vscode中右键configure all projects,在Terminal中点击Run Task,如没有先配置build任务,然后运行即可 + +5.命令行注入工具,注入命令 +``` javascript + //-i 注入程序名 -p 注入dll路径 + // -u 卸载程序名 -d 卸载dll名称 + //注入 + ConsoleInject.exe -i demo.exe -p E:\testInject.dll + //卸载 + ConsoleInject.exe -u demo.exe -d testInject.dll +``` #### 更新说明 2022-12-26 : 增加3.8.1.26版本支持。 2022-12-29 : 新增提取文字功能。 -2022-01-02 : 退出微信登录。 +2023-01-02 : 退出微信登录。 -2022-01-31 : 新增修改群昵称(仅支持3.8.1.26)。 +2023-01-31 : 新增修改群昵称(仅支持3.8.1.26)。 -2022-02-01 : 新增拍一拍(仅支持3.8.1.26)。 +2023-02-01 : 新增拍一拍(仅支持3.8.1.26)。 + +2023-02-04 : 新增群消息置顶和取消置顶。 ### 接口文档: #### 0.检查微信登录** @@ -934,6 +947,81 @@ vcpkg ``` +#### 51.群内消息置顶** +###### 接口功能 +> 在群聊里置顶某条消息,可以置顶文字和图片消息,其他消息未测试,部分低版本移动端置顶消息点击后会直接取消,高版本会一直置顶,其他未测试。 + +###### 接口地址 +> [/api/?type=51](/api/?type=51) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |ture |string| 置顶消息的发送人wxid | +|msgid |ture |string| 消息id | +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,0成功, -1失败,-2 未查到该消息| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "wxid":"wxid_oy11111p4422", + "msgid":3334956046278903121 +} + +``` +响应: +``` javascript +{"code":0,"result":"OK"} +``` + + +#### 52.取消群内消息置顶** +###### 接口功能 +> 取消置顶的消息。部分低版本移动端会不显示移除消息,但是会正常移除。 + +###### 接口地址 +> [/api/?type=52](/api/?type=52) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |ture |string| 微信群聊id | +|msgid |ture |string| 消息id | +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,0成功, -1失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"2136311004@chatroom", + "msgid":3374951233278903120 +} + +``` +响应: +``` javascript +{"code":0,"result":"OK"} +``` + #### 感谢 https://github.com/ljc545w/ComWeChatRobot diff --git a/src/api.cc b/src/api.cc index 61efb34..7751bf7 100644 --- a/src/api.cc +++ b/src/api.cc @@ -568,6 +568,22 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { ret = ret_data.dump(); break; } + case WECHAT_SET_TOP_MSG:{ + wstring wxid = get_http_req_param(hm, j_param, "wxid", is_post); + ULONG64 msgid = get_http_param_ulong64(hm, j_param, "msgid", is_post); + int success = SetTopMsg(WS2LW(wxid),msgid); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_REMOVE_TOP_MSG:{ + wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); + ULONG64 msgid = get_http_param_ulong64(hm, j_param, "msgid", is_post); + int success = RemoveTopMsg(WS2LW(room_id),msgid); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } default: break; } diff --git a/src/api.h b/src/api.h index 1c2a361..2cff9e5 100644 --- a/src/api.h +++ b/src/api.h @@ -65,6 +65,8 @@ typedef enum WECHAT_HTTP_APISTag WECHAT_GET_IMG_BY_NAME, WECHAT_DO_OCR, WECHAT_SEND_PAT_MSG, + WECHAT_SET_TOP_MSG, + WECHAT_REMOVE_TOP_MSG, } WECHAT_HTTP_APIS, *PWECHAT_HTTP_APIS; diff --git a/src/chat_room.cc b/src/chat_room.cc index 162da51..c031975 100644 --- a/src/chat_room.cc +++ b/src/chat_room.cc @@ -2,8 +2,9 @@ #include "chat_room.h" #include "common.h" - +#include "get_db_handle.h" #include "wechat_data.h" +#include "base64.h" #define WX_CHAT_ROOM_MGR_OFFSET 0x67ee70 #define WX_GET_CHAT_ROOM_DETAIL_INFO_OFFSET 0xa73a80 #define WX_NEW_CHAT_ROOM_INFO_OFFSET 0xd07010 @@ -16,6 +17,9 @@ #define WX_INIT_CHAT_ROOM_OFFSET 0xd04d80 #define WX_FREE_CHAT_ROOM_OFFSET 0xa7c620 #define WX_MOD_CHAT_ROOM_MEMBER_NICK_NAME_OFFSET 0xa6f8f0 +#define WX_NEW_CHAT_MSG_OFFSET 0x64adc0 +#define WX_TOP_MSG_OFFSET 0xa76e60 +#define WX_REMOVE_TOP_MSG_OFFSET 0xa76c50 int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info) { int success = 0; @@ -216,5 +220,96 @@ int ModChatRoomMemberNickName(wchar_t* chat_room_id,wchar_t* wxid,wchar_t * nick MOV success,EAX POPAD } + return success; +} + + +int SetTopMsg(wchar_t* wxid,ULONG64 msg_id){ + int success = -1; + char chat_msg[0x2A8] ={0}; + DWORD base = GetWeChatWinBase(); + DWORD new_chat_msg_addr = base + WX_NEW_CHAT_MSG_OFFSET; + DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; + DWORD handle_top_msg_addr = base + WX_TOP_MSG_OFFSET; + vector local_msg = GetChatMsgByMsgId(msg_id); + if(local_msg.empty()){ + return -2; + } + string type = local_msg[3]; + string status = local_msg[10]; + string talker = local_msg[13]; + string content = local_msg[14]; + wstring w_talker = String2Wstring(talker); + wstring w_content = String2Wstring(content); + int msg_type =stoi(type); + int msg_status =stoi(status); + + #ifdef _DEBUG + wcout << "w_talker:" < GetChatMsgByMsgId(ULONG64 msgid){ + char sql[260] = {0}; + sprintf_s(sql, "select localId,TalkerId,MsgSvrID,Type,SubType,IsSender,CreateTime,Sequence,StatusEx,FlagEx,Status,MsgServerSeq,MsgSequence,StrTalker,StrContent,BytesExtra from MSG where MsgSvrID=%llu;", msgid); + wchar_t dbname[20] = {0}; + for (int i = 0;; i++) { + swprintf_s(dbname, L"MSG%d.db", i); + DWORD handle = GetDbHandleByDbName(dbname); + if (handle == 0) return {}; + vector> result; + int ret = Select(handle, (const char *)sql, result); + #ifdef _DEBUG + cout <<" size =" < +#include std::vector GetDbHandles(); DWORD GetDbHandleByDbName(wchar_t *dbname); unsigned int GetLocalIdByMsgId(ULONG64 msgid, int &dbIndex); +std::vector GetChatMsgByMsgId(ULONG64 msgid); #endif \ No newline at end of file From d818c826e5e27ed472bf49e0d93ed30f9315572a Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Sat, 4 Feb 2023 15:07:01 +0800 Subject: [PATCH 17/59] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=86=85=E9=83=A8=E5=87=BD=E6=95=B0=E9=87=8A=E6=94=BE=E5=86=85?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat_room.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/chat_room.cc b/src/chat_room.cc index c031975..643deb1 100644 --- a/src/chat_room.cc +++ b/src/chat_room.cc @@ -231,6 +231,7 @@ int SetTopMsg(wchar_t* wxid,ULONG64 msg_id){ DWORD new_chat_msg_addr = base + WX_NEW_CHAT_MSG_OFFSET; DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; DWORD handle_top_msg_addr = base + WX_TOP_MSG_OFFSET; + DWORD free_addr = base + WX_FREE_CHAT_ROOM_OFFSET; vector local_msg = GetChatMsgByMsgId(msg_id); if(local_msg.empty()){ return -2; @@ -276,6 +277,8 @@ int SetTopMsg(wchar_t* wxid,ULONG64 msg_id){ PUSH EAX CALL handle_top_msg_addr MOV success,EAX + LEA ECX,chat_msg + CALL free_addr POPAD } return success; @@ -285,8 +288,6 @@ int SetTopMsg(wchar_t* wxid,ULONG64 msg_id){ int RemoveTopMsg(wchar_t* chat_room_id,ULONG64 msg_id){ int success = -1; - DWORD left = (DWORD)(&msg_id); - DWORD right = (DWORD)(&msg_id+4); WeChatString chat_room(chat_room_id); DWORD base = GetWeChatWinBase(); DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; From 6dca99474b6706e629a00a312b281d5ccf9df009 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Sat, 4 Feb 2023 16:28:34 +0800 Subject: [PATCH 18/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=BD=AC=E5=8F=91?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=8E=A5=E5=8F=A3=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/README.md b/README.md index b8c588b..79f4833 100644 --- a/README.md +++ b/README.md @@ -721,6 +721,43 @@ vcpkg ``` +#### 40.转发消息** +###### 接口功能 +> 直接转发消息 + +###### 接口地址 +> [/api/?type=40](/api/?type=40) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |ture |string| 消息接收人wxid | +|msgid |ture |number| 消息id,hook消息接口中返回的消息id | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,非0成功| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "wxid": "filehelper", + "msgid":7215505498606506901 +} +``` +响应: +``` javascript +{"code":4344,"result":"OK"} +``` + #### 44.退出登录** ###### 接口功能 From 1216733b82ed03fe775d102378b21f8d7ecdaab8 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Sat, 4 Feb 2023 16:42:57 +0800 Subject: [PATCH 19/59] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=96=87=E6=A1=A3=20=20close=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 79f4833..c162aa1 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,35 @@ vcpkg 2023-02-01 : 新增拍一拍(仅支持3.8.1.26)。 2023-02-04 : 新增群消息置顶和取消置顶。 +#### 功能预览: +0.检查是否登录 +1.获取登录微信信息 +2.发送文本 +5.发送图片 +6.发送文件 +9.hook消息 +10.取消hook消息 +11.hook图片 +12.取消hook图片 +17.删除好友 +25.获取群成员 +27.删除群成员 +28.增加群成员 +31.修改群昵称 +32.获取数据库句柄 +34.查询数据库 +40.转发消息 +44.退出登录 +46.联系人列表 +47.获取群详情 +48.获取解密图片 +49.图片提取文字ocr +50.拍一拍 +51.群消息置顶消息 +52.群消息取消置顶 ### 接口文档: + #### 0.检查微信登录** ###### 接口功能 > 检查微信是否登录 From ebde430aecf6e3fb39d99b66234df3d658c6258d Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Mon, 6 Feb 2023 11:21:13 +0800 Subject: [PATCH 20/59] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E5=86=85=E5=AD=98=E6=B3=84=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat_room.cc | 59 ++++++++++++++++++++++++++++++++++++--------- src/common.cc | 2 +- src/common.h | 15 ++++++------ src/contact.cc | 2 +- src/contact.h | 2 +- src/db_operation.cc | 6 ++--- src/db_operation.h | 3 +-- src/hook_img.cc | 2 +- src/hook_img.h | 3 +-- src/ocr.cc | 2 +- src/self_info.cc | 1 + src/wechat_data.h | 48 ++++++++++++++++++++++++------------ 12 files changed, 98 insertions(+), 47 deletions(-) diff --git a/src/chat_room.cc b/src/chat_room.cc index 643deb1..ceed580 100644 --- a/src/chat_room.cc +++ b/src/chat_room.cc @@ -5,6 +5,8 @@ #include "get_db_handle.h" #include "wechat_data.h" #include "base64.h" +using namespace std; + #define WX_CHAT_ROOM_MGR_OFFSET 0x67ee70 #define WX_GET_CHAT_ROOM_DETAIL_INFO_OFFSET 0xa73a80 #define WX_NEW_CHAT_ROOM_INFO_OFFSET 0xd07010 @@ -45,21 +47,54 @@ int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info) { MOV success,EAX POPAD } - room_info.chat_room_id.ptr = *(wchar_t**)(chat_room_info + 0x4); - room_info.chat_room_id.length = *(DWORD*)(chat_room_info + 0x8); - room_info.chat_room_id.max_length = *(DWORD*)(chat_room_info + 0xC); + DWORD room_id_len = *(DWORD*)(chat_room_info + 0x8); + DWORD room_id_max_len = *(DWORD*)(chat_room_info + 0xC); + wchar_t * room_id = new wchar_t[room_id_len + 1]; + wmemcpy(room_id,*(wchar_t**)(chat_room_info + 0x4),room_id_len + 1); + room_info.chat_room_id.ptr = room_id; + room_info.chat_room_id.length = room_id_len; + room_info.chat_room_id.max_length = room_id_max_len; + - room_info.notice.ptr = *(wchar_t**)(chat_room_info + 0x18); - room_info.notice.length = *(DWORD*)(chat_room_info + 0x1C); - room_info.notice.max_length = *(DWORD*)(chat_room_info + 0x20); + DWORD notice_len = *(DWORD*)(chat_room_info + 0x1C); + DWORD notice_max_len = *(DWORD*)(chat_room_info + 0x20); + wchar_t* notice_ptr = *(wchar_t**)(chat_room_info + 0x18); + if(notice_len <= 0){ + room_info.notice.ptr = nullptr; + }else{ + wchar_t * notice = new wchar_t[notice_len + 1]; + wmemcpy(notice,notice_ptr,notice_len+1); + room_info.notice.ptr = notice; + } + room_info.notice.length = notice_len; + room_info.notice.max_length = notice_max_len; - room_info.admin.ptr = *(wchar_t**)(chat_room_info + 0x2C); - room_info.admin.length = *(DWORD*)(chat_room_info + 0x30); - room_info.admin.max_length = *(DWORD*)(chat_room_info + 0x34); + DWORD admin_len = *(DWORD*)(chat_room_info + 0x30); + DWORD admin_max_len = *(DWORD*)(chat_room_info + 0x34); + wchar_t* admin_ptr = *(wchar_t**)(chat_room_info + 0x2C); + if(admin_len <= 0){ + room_info.admin.ptr = nullptr; + }else{ + wchar_t * admin = new wchar_t[admin_len + 1]; + wmemcpy(admin,admin_ptr,admin_len+1); + room_info.admin.ptr = admin; + } + room_info.admin.length = admin_len; + room_info.admin.max_length = admin_max_len; + + DWORD xml_len = *(DWORD*)(chat_room_info + 0x54); + DWORD xml_max_len = *(DWORD*)(chat_room_info + 0x58); + wchar_t* xml_ptr = *(wchar_t**)(chat_room_info + 0x50); + if (xml_len <= 0){ + room_info.xml.ptr = nullptr; + }else{ + wchar_t * xml = new wchar_t[xml_len + 1]; + wmemcpy(xml,xml_ptr,xml_len+1); + room_info.xml.ptr = xml; + } + room_info.xml.length = xml_len; + room_info.xml.max_length = xml_max_len; - room_info.xml.ptr = *(wchar_t**)(chat_room_info + 0x50); - room_info.xml.length = *(DWORD*)(chat_room_info + 0x54); - room_info.xml.max_length = *(DWORD*)(chat_room_info + 0x58); __asm { PUSHAD LEA ECX,chat_room_info diff --git a/src/common.cc b/src/common.cc index 264188e..6d1288d 100644 --- a/src/common.cc +++ b/src/common.cc @@ -25,7 +25,7 @@ wstring utf8_to_unicode(const char *buffer) { /// @param wstr unicode /// @return string utf8 string unicode_to_utf8(wchar_t *wstr) { - int c_size = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, FALSE); + int c_size = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, FALSE); if (c_size > 0) { char *buffer = new char[c_size + 1]; WideCharToMultiByte(CP_UTF8, 0, wstr, -1, buffer, c_size, NULL, FALSE); diff --git a/src/common.h b/src/common.h index af9bb7e..f9eb55f 100644 --- a/src/common.h +++ b/src/common.h @@ -1,18 +1,17 @@ #ifndef COMMON_H_ #define COMMON_H_ #include -using namespace std; -#define READ_WSTRING(addr, offset) ((*(DWORD *)(addr + offset + 0x4) == 0) ? wstring(L"") : wstring((wchar_t *)(*(DWORD *)(addr + offset)), *(DWORD *)(addr + offset + 0x4))) +#define READ_WSTRING(addr, offset) ((*(DWORD *)(addr + offset + 0x4) == 0) ? std::wstring(L"") : std::wstring((wchar_t *)(*(DWORD *)(addr + offset)), *(DWORD *)(addr + offset + 0x4))) /// @brief utf8 转换成unicode /// @param buffer utf8 /// @return unicode -wstring utf8_to_unicode(const char *buffer); +std::wstring utf8_to_unicode(const char *buffer); /// @brief unicode转换utf8 /// @param wstr unicode /// @return utf8 -string unicode_to_utf8(wchar_t *wstr); +std::string unicode_to_utf8(wchar_t *wstr); /// @brief 获取WeChatWin.dll基址 /// @return 基址 @@ -35,7 +34,7 @@ void UnHookAnyAddress(DWORD hook_addr, char *origin); /// @brief get timeW /// @param timestamp timestamp /// @return str -wstring GetTimeW(long long timestamp); +std::wstring GetTimeW(long long timestamp); /// @brief unicode trans utf8 /// @param str unicode str /// @return utf8 str @@ -43,11 +42,11 @@ std::string UnicodeToUtf8(const wchar_t *str); /// @brief string convert wstring /// @param str /// @return -wstring String2Wstring(string str); +std::wstring String2Wstring(std::string str); /// @brief wstring convert string /// @param str /// @return -string Wstring2String(wstring wstr); +std::string Wstring2String(std::wstring wstr); /// @brief create dir /// @param path @@ -56,7 +55,7 @@ BOOL FindOrCreateDirectoryW(const wchar_t *path); template -vector split(T1 str, T2 letter) { +std::vector split(T1 str, T2 letter) { vector arr; size_t pos; while ((pos = str.find_first_of(letter)) != T1::npos) { diff --git a/src/contact.cc b/src/contact.cc index 0ba1ca3..0a14cd4 100644 --- a/src/contact.cc +++ b/src/contact.cc @@ -3,7 +3,7 @@ #include "common.h" #include "wechat_data.h" - +using namespace std; #define WX_CONTACT_MGR_INSTANCE_OFFSET 0x64dc30 #define WX_CONTACT_GET_LIST_OFFSET 0xa9b000 #define WX_CONTACT_DEL_OFFSET 0xa9ef40 diff --git a/src/contact.h b/src/contact.h index 8f643f4..9ec6c76 100644 --- a/src/contact.h +++ b/src/contact.h @@ -3,7 +3,7 @@ #include #include "wechat_data.h" -int GetAllContact(vector &vec); +int GetAllContact(std::vector &vec); diff --git a/src/db_operation.cc b/src/db_operation.cc index 3b43624..daecc1e 100644 --- a/src/db_operation.cc +++ b/src/db_operation.cc @@ -4,7 +4,7 @@ #include "base64.h" #include "common.h" #include "new_sqlite3.h" - +using namespace std; /// @brief free data void FreeResult(vector> &data) { @@ -15,11 +15,11 @@ void FreeResult(vector> &data) { for (unsigned j = 0; j < data[i].size(); j++) { SqlResult *sr = (SqlResult *)&data[i][j]; if (sr->column_name) { - delete sr->column_name; + delete[] sr->column_name; sr->column_name = NULL; } if (sr->content) { - delete sr->content; + delete[] sr->content; sr->content = NULL; } } diff --git a/src/db_operation.h b/src/db_operation.h index ad987e8..16ce011 100644 --- a/src/db_operation.h +++ b/src/db_operation.h @@ -2,7 +2,6 @@ #define DB_OPERATION_H_ #include #include -using namespace std; struct SqlResult { char *column_name; DWORD column_name_len; @@ -18,6 +17,6 @@ struct SqlResult { /// @return int ExecuteSQL(DWORD db, const char *sql, DWORD callback, void *data); -int Select(DWORD db_hanle, const char *sql,vector> &query_result); +int Select(DWORD db_hanle, const char *sql,std::vector> &query_result); #endif \ No newline at end of file diff --git a/src/hook_img.cc b/src/hook_img.cc index afb977e..7a052d4 100644 --- a/src/hook_img.cc +++ b/src/hook_img.cc @@ -2,7 +2,7 @@ #include "hook_img.h" #include "common.h" - +using namespace std; // #define WX_HOOK_IMG_OFFSET 0xd7eaa5 // #define WX_HOOK_IMG_NEXT_OFFSET 0xda56e0 diff --git a/src/hook_img.h b/src/hook_img.h index 68c458e..a0d6bb6 100644 --- a/src/hook_img.h +++ b/src/hook_img.h @@ -1,9 +1,8 @@ #ifndef HOOK_IMG_H_ #define HOOK_IMG_H_ #include "windows.h" -using namespace std; -int HookImg(wstring save_path); +int HookImg(std::wstring save_path); int UnHookImg(); int GetImgByName(wchar_t* file_path,wchar_t* save_dir); diff --git a/src/ocr.cc b/src/ocr.cc index 28d6b42..c22275e 100644 --- a/src/ocr.cc +++ b/src/ocr.cc @@ -7,7 +7,7 @@ #define WX_INIT_OBJ_OFFSET 0x6cbab0 #define WX_OCR_MANAGER_OFFSET 0x6cff00 #define WX_DO_OCR_TASK_OFFSET 0x11e3210 - +using namespace std; int DoOCRTask(wchar_t *img_path, std::string &result) { int success = -1; WeChatString path(img_path); diff --git a/src/self_info.cc b/src/self_info.cc index f279e7f..b527499 100644 --- a/src/self_info.cc +++ b/src/self_info.cc @@ -4,6 +4,7 @@ #include "common.h" #include "wechat_data.h" +using namespace std; #define WX_SELF_NAME_OFFSET 0x2C426E8 #define WX_SELF_MOBILE_OFFSET 0x2C42658 diff --git a/src/wechat_data.h b/src/wechat_data.h index d871ea5..6fc8d18 100644 --- a/src/wechat_data.h +++ b/src/wechat_data.h @@ -4,7 +4,6 @@ // #include #include -using namespace std; struct WeChatString { wchar_t *ptr; DWORD length; @@ -13,7 +12,7 @@ struct WeChatString { DWORD c_len = 0; WeChatString() { WeChatString(NULL); } - WeChatString(wstring &s) { + WeChatString(std::wstring &s) { ptr = (wchar_t *)(s.c_str()); length = s.length(); max_length = s.length() * 2; @@ -51,7 +50,7 @@ struct DatabaseInfo { DWORD handle = 0; wchar_t *db_name = NULL; DWORD db_name_len = 0; - vector tables; + std::vector tables; DWORD count = 0; DWORD extrainfo = 0; }; @@ -101,6 +100,25 @@ struct ChatRoomInfoInner { WeChatString notice; WeChatString admin; WeChatString xml; + + ~ChatRoomInfoInner(){ + if(chat_room_id.ptr){ + delete []chat_room_id.ptr; + chat_room_id.ptr = nullptr; + } + if(notice.ptr){ + delete []notice.ptr; + notice.ptr = nullptr; + } + if(admin.ptr){ + delete []admin.ptr; + admin.ptr = nullptr; + } + if(xml.ptr){ + delete []xml.ptr; + xml.ptr = nullptr; + } + } }; struct VectorInner { @@ -124,17 +142,17 @@ struct ChatRoomInner{ }; struct SelfInfoInner{ - string name; - string city; - string province; - string country; - string account; - string wxid; - string mobile; - string small_img; - string big_img; - string data_root_path; - string data_save_path; - string current_data_path; + std::string name; + std::string city; + std::string province; + std::string country; + std::string account; + std::string wxid; + std::string mobile; + std::string small_img; + std::string big_img; + std::string data_root_path; + std::string data_save_path; + std::string current_data_path; }; #endif From e7a460ad25ebdd96cdf6ab85005450578f02908d Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Mon, 6 Feb 2023 17:56:37 +0800 Subject: [PATCH 21/59] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E6=94=B6=E6=AC=BE=20=20close=20#12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 45 ++++++++++++++++++++++++++++++++++++++++++++- src/api.cc | 7 +++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c162aa1..841769f 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,10 @@ vcpkg 2023-02-01 : 新增拍一拍(仅支持3.8.1.26)。 -2023-02-04 : 新增群消息置顶和取消置顶。 +2023-02-04 : 新增群消息置顶和取消置顶。 + +2023-02-04 : 新增确认收款。 + #### 功能预览: 0.检查是否登录 1.获取登录微信信息 @@ -111,6 +114,7 @@ vcpkg 34.查询数据库 40.转发消息 44.退出登录 +45.确认收款 46.联系人列表 47.获取群详情 48.获取解密图片 @@ -822,6 +826,45 @@ vcpkg ``` +#### 45.确认收款** +###### 接口功能 +> 收到转账消息后,自动收款确认。type=49 即是转账消息。 + +###### 接口地址 +> [/api/?type=45](/api/?type=45) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid|string|转账人微信id,从hook的消息中获取| +|transcationId|string|从hook的消息中获取对应的字段内容。| +|transferId|string|从hook的消息中获取对应的字段内容。| + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功| +|result|string|成功提示| + + + + +###### 接口示例 +入参: +``` javascript +{ + "wxid":"wxid_agz5q76f11112", + "transcationId":"10000500012302060002831233124719620", + "transferId":"10000500012023020619112332136412" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` #### 46.联系人列表** ###### 接口功能 diff --git a/src/api.cc b/src/api.cc index 7751bf7..4882982 100644 --- a/src/api.cc +++ b/src/api.cc @@ -20,6 +20,7 @@ #include "hook_img.h" #include "ocr.h" #include "pat.h" +#include "confirm_receipt.h" #pragma comment(lib, "ws2_32.lib") using namespace std; @@ -478,6 +479,12 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_GET_TRANSFER: { + wstring wxid = get_http_req_param(hm, j_param, "wxid", is_post); + wstring transcationid = get_http_req_param(hm, j_param, "transcationId", is_post); + wstring transferid = get_http_req_param(hm, j_param, "transferId", is_post); + BOOL response = DoConfirmReceipt(WS2LW(wxid), WS2LW(transcationid), WS2LW(transferid)); + json ret_data = {{"msg", response}, {"result", "OK"}}; + ret = ret_data.dump(); break; } case WECHAT_GET_CONTACT_ALL: { From a0f83b5a9981ca1b37ae1e0187109d041ddb3d3b Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Tue, 7 Feb 2023 08:36:13 +0800 Subject: [PATCH 22/59] =?UTF-8?q?=E8=BD=AC=E8=B4=A6=E6=94=B6=E6=AC=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/confirm_receipt.cc | 49 ++++++++++++++++++++++++++++++++++++++++++ src/confirm_receipt.h | 6 ++++++ 2 files changed, 55 insertions(+) create mode 100644 src/confirm_receipt.cc create mode 100644 src/confirm_receipt.h diff --git a/src/confirm_receipt.cc b/src/confirm_receipt.cc new file mode 100644 index 0000000..16cfa13 --- /dev/null +++ b/src/confirm_receipt.cc @@ -0,0 +1,49 @@ +#include "pch.h" +#include "confirm_receipt.h" + +#include "common.h" +#include "wechat_data.h" + +#define WX_NEW_WCPAYINFO_OFFSET 0x69d2b0 +#define WX_FREE_WCPAYINFO_OFFSET 0x68d610 +#define WX_CONFIRM_RECEIPT_OFFSET 0x13d5e00 + +int DoConfirmReceipt(wchar_t *wxid, wchar_t *transcationid, + wchar_t *transferid) { + int success = -1; + WeChatString recv_id(wxid); + WeChatString transcation_id(transcationid); + WeChatString transfer_id(transferid); + char pay_info[0x134] = {0}; + DWORD base = GetWeChatWinBase(); + DWORD new_pay_info_addr = base + WX_NEW_WCPAYINFO_OFFSET; + DWORD free_pay_info_addr = base + WX_FREE_WCPAYINFO_OFFSET; + DWORD do_confirm_addr = base + WX_CONFIRM_RECEIPT_OFFSET; + __asm { + PUSHAD + LEA ECX,pay_info + CALL new_pay_info_addr + MOV dword ptr [pay_info + 0x4], 0x1 + MOV dword ptr [pay_info + 0x4C], 0x1 + POPAD + } + memcpy(&pay_info[0x1c], &transcation_id, sizeof(transcation_id)); + memcpy(&pay_info[0x38], &transfer_id, sizeof(transfer_id)); + + __asm { + PUSHAD + PUSH 0x1 + SUB ESP,0x8 + LEA EDX,recv_id + LEA ECX,pay_info + CALL do_confirm_addr + MOV success,EAX + ADD ESP,0xC + PUSH 0x0 + LEA ECX,pay_info + CALL free_pay_info_addr + POPAD + } + + return success; +} \ No newline at end of file diff --git a/src/confirm_receipt.h b/src/confirm_receipt.h new file mode 100644 index 0000000..0fdee8f --- /dev/null +++ b/src/confirm_receipt.h @@ -0,0 +1,6 @@ +#ifndef CONFIRM_RECEIPT_H +#define CONFIRM_RECEIPT_H + +int DoConfirmReceipt(wchar_t* wxid,wchar_t *transcationid, wchar_t *transferid); + +#endif \ No newline at end of file From 254a9eeb30c9bf31782434ae1b6e0fe167dc1008 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 8 Feb 2023 13:35:04 +0800 Subject: [PATCH 23/59] =?UTF-8?q?=E6=9C=8B=E5=8F=8B=E5=9C=88=E6=B6=88?= =?UTF-8?q?=E6=81=AFhook=E5=92=8C=E6=9C=8B=E5=8F=8B=E5=9C=88=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 107 ++++++++++++++++++++++++++++++++++++++++++- src/api.cc | 14 ++++++ src/api.h | 2 + src/hook_recv_msg.cc | 82 ++++++++++++++++++++++++++++++++- src/sns.cc | 68 +++++++++++++++++++++++++++ src/sns.h | 6 +++ 6 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 src/sns.cc create mode 100644 src/sns.h diff --git a/README.md b/README.md index 841769f..a98fbba 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,9 @@ vcpkg 2023-02-04 : 新增群消息置顶和取消置顶。 -2023-02-04 : 新增确认收款。 +2023-02-06 : 新增确认收款。 + +2023-02-08 : 新增朋友圈消息。 #### 功能预览: 0.检查是否登录 @@ -122,6 +124,8 @@ vcpkg 50.拍一拍 51.群消息置顶消息 52.群消息取消置顶 +53.朋友圈首页 +54.朋友圈下一页 ### 接口文档: @@ -1127,6 +1131,107 @@ vcpkg 响应: ``` javascript {"code":0,"result":"OK"} +``` + + + +#### 53.朋友圈首页消息** +###### 接口功能 +> 获取朋友圈最新消息,调用之后,会在tcpserver服务中收到朋友圈的消息。格式如下: +``` javascript +{ + 'data': [ + { + 'content': '朋友圈[玫瑰][玫瑰]', + 'createTime': 1675827480, + 'senderId': 'wxid_12333', + 'snsId': 14057859804711563695, + 'xml': '00' + }] +} + +``` + +###### 接口地址 +> [/api/?type=53](/api/?type=53) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript + + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 54.朋友圈下一页** +###### 接口功能 +> 朋友圈下一页,会在tcpserver服务中收到朋友圈的消息。格式如下: +``` javascript +{ + 'data': [ + { + 'content': '朋友圈[玫瑰][玫瑰]', + 'createTime': 1675827480, + 'senderId': 'wxid_12333', + 'snsId': 14057859804711563695, + 'xml': '00' + }] +} + +``` + +###### 接口地址 +> [/api/?type=54](/api/?type=54) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|snsId |ture |string| 朋友圈的snsId | + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript + + +``` +响应: +``` javascript +{"code":1,"result":"OK"} ``` #### 感谢 diff --git a/src/api.cc b/src/api.cc index 4882982..37d6732 100644 --- a/src/api.cc +++ b/src/api.cc @@ -21,6 +21,7 @@ #include "ocr.h" #include "pat.h" #include "confirm_receipt.h" +#include "sns.h" #pragma comment(lib, "ws2_32.lib") using namespace std; @@ -591,6 +592,19 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { ret = ret_data.dump(); break; } + case WECHAT_SNS_GET_FIRST_PAGE:{ + int success = GetFirstPage(); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_SNS_GET_NEXT_PAGE:{ + ULONG64 snsid = get_http_param_ulong64(hm, j_param, "snsId", is_post); + int success = GetNextPage(snsid); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } default: break; } diff --git a/src/api.h b/src/api.h index 2cff9e5..7c04269 100644 --- a/src/api.h +++ b/src/api.h @@ -67,6 +67,8 @@ typedef enum WECHAT_HTTP_APISTag WECHAT_SEND_PAT_MSG, WECHAT_SET_TOP_MSG, WECHAT_REMOVE_TOP_MSG, + WECHAT_SNS_GET_FIRST_PAGE, + WECHAT_SNS_GET_NEXT_PAGE, } WECHAT_HTTP_APIS, *PWECHAT_HTTP_APIS; diff --git a/src/hook_recv_msg.cc b/src/hook_recv_msg.cc index 71fe1a5..a4e1ff9 100644 --- a/src/hook_recv_msg.cc +++ b/src/hook_recv_msg.cc @@ -12,6 +12,8 @@ using namespace nlohmann; using namespace std; #define WX_RECV_MSG_HOOK_OFFSET 0xb97126 #define WX_RECV_MSG_HOOK_NEXT_OFFSET 0x6fc850 +#define WX_SNS_HOOK_OFFSET 0x12fb9a5 +#define WX_SNS_HOOK_NEXT_OFFSET 0x12fbc30 // SyncMgr::addMsgListToDB // #define WX_RECV_MSG_HOOK_OFFSET 0xB9C919 @@ -29,6 +31,13 @@ static DWORD kReceMsgJmpBackAddress = static DWORD kReceMsgNextAddress = kWeChatWinBase + WX_RECV_MSG_HOOK_NEXT_OFFSET; + +static char kOriginSnsMsgAsmCode[5] = {0}; +static DWORD kSnsMsgJmpBackAddress = + kWeChatWinBase + WX_SNS_HOOK_OFFSET + 0x5; +static DWORD kSnsMsgNextAddress = + kWeChatWinBase + WX_SNS_HOOK_NEXT_OFFSET; + struct InnerMessageStruct { char *buffer; int length; @@ -112,7 +121,7 @@ void SendSocketMessage(InnerMessageStruct *msg) { } /// @brief msg handle /// @param msg_addr msg address in memory -void OnRecvMsg(DWORD msg_addr) { +void __cdecl OnRecvMsg(DWORD msg_addr) { json j_msg; unsigned long long msgid = *(unsigned long long *)(msg_addr + 0x30); j_msg["msgId"] = msgid; @@ -175,6 +184,50 @@ void OnRecvMsg(DWORD msg_addr) { CloseHandle(thread); } } + + + + void __cdecl OnSnsTimeLineMsg(DWORD msg_addr) { + json j_sns; + DWORD begin_addr = *(DWORD *)(msg_addr + 0x20); + DWORD end_addr = *(DWORD *)(msg_addr + 0x24); + #ifdef _DEBUG + cout << "begin" <buffer = new char[jstr.size() + 1]; + memcpy(inner_msg->buffer, jstr.c_str(), jstr.size() + 1); + inner_msg->length = jstr.size(); + HANDLE thread = CreateThread( + NULL, 0, (LPTHREAD_START_ROUTINE)SendSocketMessage, inner_msg, NULL, 0); + if (thread) { + CloseHandle(thread); + } +} + /// @brief hook implement _declspec(naked) void handle_sync_msg() { __asm { @@ -190,6 +243,23 @@ _declspec(naked) void handle_sync_msg() { } } + +/// @brief hook sns msg implement +_declspec(naked) void handle_sns_msg() { + __asm { + PUSHAD + PUSHFD + PUSH [ESP + 0x24] + CALL OnSnsTimeLineMsg + ADD ESP, 0x4 + POPFD + POPAD + CALL kSnsMsgNextAddress + JMP kSnsMsgJmpBackAddress + } +} + + /// @brief hook any address address+0x5 /// @param port 端口 /// @return 成功返回1,已经hook返回2,失败返回-1 @@ -210,6 +280,14 @@ int HookRecvMsg(char* client_ip,int port) { kReceMsgJmpBackAddress = hook_recv_msg_addr + 0x5; HookAnyAddress(hook_recv_msg_addr, (LPVOID)handle_sync_msg, kOriginReceMsgAsmCode); + + DWORD hook_sns_msg_addr = kWeChatWinBase + WX_SNS_HOOK_OFFSET; + kSnsMsgNextAddress = kWeChatWinBase + WX_SNS_HOOK_NEXT_OFFSET; + kSnsMsgJmpBackAddress = hook_sns_msg_addr + 0x5; + HookAnyAddress(hook_sns_msg_addr, (LPVOID)handle_sns_msg, + kOriginSnsMsgAsmCode); + + kMessageHooked = TRUE; return 1; } @@ -218,7 +296,9 @@ int UnHookRecvMsg() { kServerPort = 0; if (!kMessageHooked) return 2; DWORD hook_recv_msg_addr = kWeChatWinBase + WX_RECV_MSG_HOOK_OFFSET; + DWORD hook_sns_addr = kWeChatWinBase + WX_SNS_HOOK_OFFSET; UnHookAnyAddress(hook_recv_msg_addr, kOriginReceMsgAsmCode); + UnHookAnyAddress(hook_sns_addr, kOriginSnsMsgAsmCode); kMessageHooked = FALSE; return 1; } \ No newline at end of file diff --git a/src/sns.cc b/src/sns.cc new file mode 100644 index 0000000..e46e0cf --- /dev/null +++ b/src/sns.cc @@ -0,0 +1,68 @@ +#include "pch.h" +#include "sns.h" + +#include "common.h" +#include "wechat_data.h" +using namespace std; +#define WX_SNS_DATA_MGR_OFFSET 0xac66a0 +#define WX_SNS_GET_FIRST_PAGE_OFFSET 0x12e46c0 +#define WX_SNS_TIME_LINE_MGR_OFFSET 0x128e6a0 +#define WX_SNS_TRY_GET_FIRST_PAGE_SCENE_OFFSET 0x12ff300 +#define WX_SNS_GET_NEXT_PAGE_OFFSET 0x12e4760 + +int GetFirstPage() { + int success = -1; + DWORD base = GetWeChatWinBase(); + DWORD sns_data_mgr_addr = base + WX_SNS_DATA_MGR_OFFSET; + DWORD get_first_page_addr = base + WX_SNS_GET_FIRST_PAGE_OFFSET; + + DWORD time_line_mgr_addr = base + WX_SNS_TIME_LINE_MGR_OFFSET; + DWORD get_first_page_scene_addr = base + WX_SNS_TRY_GET_FIRST_PAGE_SCENE_OFFSET; + char buff[0xB44] = {}; + __asm { + PUSHAD + CALL sns_data_mgr_addr + PUSH 0x1 + LEA ECX,buff + PUSH ECX + MOV ECX,EAX + CALL get_first_page_addr + MOV success,EAX + POPAD + } + +// __asm { +// PUSHAD +// CALL time_line_mgr_addr +// PUSH 0x1 +// MOV ECX,EAX +// CALL get_first_page_scene_addr +// MOV success, EAX +// POPAD +// } + return success; +} + + +int GetNextPage(ULONG64 sns_id) { + int success = -1; + DWORD base = GetWeChatWinBase(); + DWORD sns_data_mgr_addr = base + WX_SNS_DATA_MGR_OFFSET; + DWORD get_next_page_addr = base + WX_SNS_GET_NEXT_PAGE_OFFSET; + VectorInner temp = {}; + __asm{ + PUSHAD + CALL sns_data_mgr_addr + LEA ECX,temp + PUSH ECX + MOV EBX,dword ptr [sns_id + 0x4] + PUSH EBX + MOV EDI,dword ptr [sns_id ] + PUSH EDI + MOV ECX,EAX + CALL get_next_page_addr + MOV success,EAX + POPAD + } + return success; +} diff --git a/src/sns.h b/src/sns.h new file mode 100644 index 0000000..db782c0 --- /dev/null +++ b/src/sns.h @@ -0,0 +1,6 @@ +#ifndef SNS_H_ +#define SNS_H_ + +int GetFirstPage(); +int GetNextPage(ULONG64 sns_id); +#endif \ No newline at end of file From 915cb1602fbbeed7587467433db747b77fff9461 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 8 Feb 2023 13:43:57 +0800 Subject: [PATCH 24/59] =?UTF-8?q?=E6=96=87=E6=A1=A3=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a98fbba..cb43363 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26版本。 本仓库发布的内容,仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关! #### 项目说明: -本项目是个人学习学习逆向的项目,主要参考https://github.com/ttttupup/ComWeChatRobot,在此基础上实现了微信的的其它版本的部分内容。 +本项目是个人学习学习逆向的项目,主要参考 https://github.com/ljc545w/ComWeChatRobot ,在此基础上实现了微信的的其它版本的部分内容。 #### 使用说明: 支持的版本3.8.0.41,3.8.1.26。 @@ -1226,7 +1226,10 @@ vcpkg ###### 接口示例 入参: ``` javascript - +{ + "snsId":"14056334227327177401" + +} ``` 响应: From e563710f80c3a6ed89413f7dca1ca3af59938dea Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Thu, 9 Feb 2023 15:47:55 +0800 Subject: [PATCH 25/59] =?UTF-8?q?3.9.0.28=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 44 ++++++++++++++++++++++++-------------------- src/contact.cc | 4 ++-- src/hook_recv_msg.cc | 11 ++++------- src/pat.cc | 4 ++-- src/self_info.cc | 30 +++++++++++++++--------------- src/send_file.cc | 10 +++++----- src/send_image.cc | 10 +++++----- src/send_text.cc | 27 +++++++++++++++------------ src/sns.cc | 19 +++---------------- 9 files changed, 75 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index cb43363..fb1e9c6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # wxhelper -wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26版本。 +wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26,3.9.0.28版本。 #### 免责声明: 本仓库发布的内容,仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关! @@ -7,13 +7,13 @@ wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26版本。 本项目是个人学习学习逆向的项目,主要参考 https://github.com/ljc545w/ComWeChatRobot ,在此基础上实现了微信的的其它版本的部分内容。 #### 使用说明: -支持的版本3.8.0.41,3.8.1.26。 +支持的版本3.8.0.41,3.8.1.26,3.9.0.28。 src:主要的dll代码 tool:简单的注入工具,一个是控制台,一个是图形界面。 python: 简单的服务器,用以接收消息内容。 release:编译好的dll。 -0.首先安装对应的微信版本,主分支是3.8.0.41版本,3.8.1.26分支对应3.8.1.26版本。 +0.首先安装对应的微信版本,主分支是3.8.0.41版本,分支对应相应的微信版本号. 1.通过cmake构建成功后,将wxhelper.dll注入到微信,本地启动tcp server,监听19088端口。 2.通过http协议与dll通信,方便客户端操作。 3.接口的url为http://127.0.0.1:19088,注入成功后,直接进行调用即可。 @@ -21,6 +21,8 @@ release:编译好的dll。 5.相关功能只在win11环境下进行简单测试,其他环境无法保证。 6.注意个别接口在3.8.0.41版本没有实现,具体参考源码。 7.对应分支接口文档都是支持指定版本的,其他版本不支持,请特别注意版本。 +8.相应分支的文档对应相应版本,带有删除线的接口表示该版本的暂未实现,其他版本有实现。后续会继续实现。 + #### 编译环境 @@ -95,7 +97,9 @@ vcpkg 2023-02-06 : 新增确认收款。 -2023-02-08 : 新增朋友圈消息。 +2023-02-08 : 新增朋友圈消息。 + +2023-02-09 : 新增3.9.0.28版本基础功能。 #### 功能预览: 0.检查是否登录 @@ -105,25 +109,25 @@ vcpkg 6.发送文件 9.hook消息 10.取消hook消息 -11.hook图片 -12.取消hook图片 -17.删除好友 -25.获取群成员 -27.删除群成员 -28.增加群成员 -31.修改群昵称 -32.获取数据库句柄 -34.查询数据库 -40.转发消息 +~~11.hook图片~~ +~~12.取消hook图片~~ +~~17.删除好友~~ +~~25.获取群成员~~ +~~27.删除群成员~~ +~~28.增加群成员~~ +~~31.修改群昵称~~ +~~32.获取数据库句柄~~ +~~34.查询数据库~~ +~~40.转发消息~~ 44.退出登录 -45.确认收款 +~~45.确认收款~~ 46.联系人列表 -47.获取群详情 +~~47.获取群详情~~ 48.获取解密图片 -49.图片提取文字ocr -50.拍一拍 -51.群消息置顶消息 -52.群消息取消置顶 +~~49.图片提取文字ocr~~ +~~50.拍一拍~~ +~~51.群消息置顶消息~~ +~~52.群消息取消置顶~~ 53.朋友圈首页 54.朋友圈下一页 ### 接口文档: diff --git a/src/contact.cc b/src/contact.cc index 0a14cd4..f4695d8 100644 --- a/src/contact.cc +++ b/src/contact.cc @@ -4,8 +4,8 @@ #include "common.h" #include "wechat_data.h" using namespace std; -#define WX_CONTACT_MGR_INSTANCE_OFFSET 0x64dc30 -#define WX_CONTACT_GET_LIST_OFFSET 0xa9b000 +#define WX_CONTACT_MGR_INSTANCE_OFFSET 0x6f8990 +#define WX_CONTACT_GET_LIST_OFFSET 0xb97550 #define WX_CONTACT_DEL_OFFSET 0xa9ef40 #define WX_INIT_CHAT_MSG_OFFSET 0xdbf380 #define WX_DB_QUERY_OFFSET 0xa9ec40 diff --git a/src/hook_recv_msg.cc b/src/hook_recv_msg.cc index a4e1ff9..7c1ae43 100644 --- a/src/hook_recv_msg.cc +++ b/src/hook_recv_msg.cc @@ -10,10 +10,10 @@ using namespace nlohmann; using namespace std; -#define WX_RECV_MSG_HOOK_OFFSET 0xb97126 -#define WX_RECV_MSG_HOOK_NEXT_OFFSET 0x6fc850 -#define WX_SNS_HOOK_OFFSET 0x12fb9a5 -#define WX_SNS_HOOK_NEXT_OFFSET 0x12fbc30 +#define WX_RECV_MSG_HOOK_OFFSET 0xca0284 +#define WX_RECV_MSG_HOOK_NEXT_OFFSET 0x7d5030 +#define WX_SNS_HOOK_OFFSET 0x143ef09 +#define WX_SNS_HOOK_NEXT_OFFSET 0x143f1b0 // SyncMgr::addMsgListToDB // #define WX_RECV_MSG_HOOK_OFFSET 0xB9C919 @@ -147,9 +147,6 @@ void __cdecl OnRecvMsg(DWORD msg_addr) { if (content_len > 0) { j_msg["content"] = unicode_to_utf8((wchar_t *)READ_WSTRING(msg_addr, 0x70).c_str()); -#ifdef _DEBUG - printf("%s", j_msg["content"].get().c_str()); -#endif } int sign_len = *(DWORD *)(msg_addr + 0x18C); if (sign_len > 0) { diff --git a/src/pat.cc b/src/pat.cc index 551927c..2fa6ec8 100644 --- a/src/pat.cc +++ b/src/pat.cc @@ -4,9 +4,9 @@ #include "common.h" #include "wechat_data.h" -#define WX_PAT_MGR_OFFSET 0x7e91c0 +#define WX_PAT_MGR_OFFSET 0x8d0c00 #define WX_SEND_PAT_MSG_OFFSET 0x1228510 -#define WX_RET_OFFSET 0x1AE4A45 +#define WX_RET_OFFSET 0x1C94D34 int SendPatMsg(wchar_t* chat_room_id, wchar_t* wxid) { int success = -1; diff --git a/src/self_info.cc b/src/self_info.cc index b527499..e34c7eb 100644 --- a/src/self_info.cc +++ b/src/self_info.cc @@ -6,22 +6,22 @@ #include "wechat_data.h" using namespace std; -#define WX_SELF_NAME_OFFSET 0x2C426E8 -#define WX_SELF_MOBILE_OFFSET 0x2C42658 -#define WX_SELF_CITY_OFFSET 0x2C426B8 -#define WX_SELF_PROVINCE_OFFSET 0x2C426A0 -#define WX_SELF_COUNTRY_OFFSET 0x2C42688 -#define WX_SELF_ACCOUNT_OFFSET 0x2C42640 -#define WX_SELF_ID_OFFSET 0x2C42A38 -#define WX_SELF_SMALL_IMG_OFFSET 0x2C4289C -#define WX_SELF_BIG_IMG_OFFSET 0x2C428B4 -#define WX_LOGIN_STATUS_OFFSET 0x2c42a10 -#define WX_APP_DATA_ROOT_PATH_OFFSET 0x2c84ae0 -#define WX_APP_DATA_SAVE_PATH_OFFSET 0x2c65728 -#define WX_CURRENT_DATA_PATH_OFFSET 0x2c636fc +#define WX_SELF_NAME_OFFSET 0x2E2CE48 +#define WX_SELF_MOBILE_OFFSET 0x2E2CDB8 +#define WX_SELF_CITY_OFFSET 0x2E2CE18 +#define WX_SELF_PROVINCE_OFFSET 0x2E2CE00 +#define WX_SELF_COUNTRY_OFFSET 0x2E2CDE8 +#define WX_SELF_ACCOUNT_OFFSET 0x2e2d1d0 +#define WX_SELF_ID_OFFSET 0x2E2CD3C +#define WX_SELF_SMALL_IMG_OFFSET 0x2E2D014 +#define WX_SELF_BIG_IMG_OFFSET 0x2E2CFFC +#define WX_LOGIN_STATUS_OFFSET 0x2E2D1C0 +#define WX_APP_DATA_ROOT_PATH_OFFSET 0x2E73010 +#define WX_APP_DATA_SAVE_PATH_OFFSET 0x2E52DB0 +#define WX_CURRENT_DATA_PATH_OFFSET 0x2E4F290 -#define WX_LOGOUT_OFFSET 0xccc320 -#define WX_ACCOUT_SERVICE_OFFSET 0x65bcc0 +#define WX_LOGOUT_OFFSET 0xdd5c90 +#define WX_ACCOUT_SERVICE_OFFSET 0x707960 int GetSelfInfo(SelfInfoInner &out) { DWORD base = GetWeChatWinBase(); diff --git a/src/send_file.cc b/src/send_file.cc index 11f12e9..526210a 100644 --- a/src/send_file.cc +++ b/src/send_file.cc @@ -3,16 +3,16 @@ #include "common.h" #include "wechat_data.h" -#define WX_APP_MSG_MGR_OFFSET 0x65df50 -#define WX_SEND_FILE_OFFSET 0xa10190 -#define WX_INIT_CHAT_MSG_OFFSET 0xdbf380 -#define WX_FREE_CHAT_MSG_OFFSET 0x649ac0 +#define WX_APP_MSG_MGR_OFFSET 0x709bb0 +#define WX_SEND_FILE_OFFSET 0xb06240 +#define WX_INIT_CHAT_MSG_OFFSET 0xed3be0 +#define WX_FREE_CHAT_MSG_OFFSET 0x6f4ea0 int SendFile(wchar_t *wxid, wchar_t *file_path){ int success = 0; WeChatString to_user(wxid); WeChatString path(file_path); - char chat_msg[0x2A8] = {0}; + char chat_msg[0x2C4] = {0}; DWORD base = GetWeChatWinBase(); DWORD app_msg_mgr_addr = base + WX_APP_MSG_MGR_OFFSET; DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; diff --git a/src/send_image.cc b/src/send_image.cc index 24f40f1..00b6464 100644 --- a/src/send_image.cc +++ b/src/send_image.cc @@ -3,17 +3,17 @@ #include "common.h" #include "wechat_data.h" -#define WX_SEND_IMAGE_OFFSET 0xb6a3f0 -#define WX_SEND_MESSAGE_MGR_OFFSET 0x65b2a0 -#define WX_INIT_CHAT_MSG_OFFSET 0xdbf380 -#define WX_FREE_CHAT_MSG_OFFSET 0x649ac0 +#define WX_SEND_IMAGE_OFFSET 0xc71500 +#define WX_SEND_MESSAGE_MGR_OFFSET 0x706d30 +#define WX_INIT_CHAT_MSG_OFFSET 0xed3be0 +#define WX_FREE_CHAT_MSG_OFFSET 0x6f4ea0 int SendImage(wchar_t *wxid, wchar_t *image_path){ int success = 0; WeChatString to_user(wxid); WeChatString path(image_path); - char chat_msg[0x2A8] ={0}; + char chat_msg[0x2C4] ={0}; DWORD base = GetWeChatWinBase(); DWORD send_message_mgr_addr = base + WX_SEND_MESSAGE_MGR_OFFSET; DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; diff --git a/src/send_text.cc b/src/send_text.cc index 995fdd8..69bd08e 100644 --- a/src/send_text.cc +++ b/src/send_text.cc @@ -5,40 +5,43 @@ #include "common.h" #include "wechat_data.h" -#define WX_SEND_TEXT_OFFSET 0xb6a930 +#define WX_SEND_TEXT_OFFSET 0xc71a60 -#define WX_SEND_MESSAGE_MGR_OFFSET 0x65b2a0 +#define WX_SEND_MESSAGE_MGR_OFFSET 0x706d30 -#define WX_FREE_CHAT_MSG_OFFSET 0x649ac0 +#define WX_FREE_CHAT_MSG_OFFSET 0x6f4ea0 /// @brief 发生文本消息 /// @param wxid wxid /// @param msg 文本消息 /// @return 成功返回1 -int SendText(wchar_t* wxid, wchar_t* msg) { +int SendText(wchar_t* wxid, wchar_t* msg) { int success = 0; WeChatString to_user(wxid); WeChatString text_msg(msg); wchar_t **msg_pptr = &text_msg.ptr; - char chat_msg[0x2A8] ={0}; + DWORD base = GetWeChatWinBase(); DWORD send_message_mgr_addr = base + WX_SEND_MESSAGE_MGR_OFFSET; DWORD send_text_msg_addr = base + WX_SEND_TEXT_OFFSET; - DWORD free_msg_addr = base + WX_FREE_CHAT_MSG_OFFSET; + DWORD free_chat_msg_addr = base + WX_FREE_CHAT_MSG_OFFSET; + char chat_msg[0x2C4] ={0}; __asm{ PUSHAD + CALL send_message_mgr_addr + PUSH 0x0 PUSH 0x0 PUSH 0x0 PUSH 0x1 PUSH 0x0 - MOV EDI,msg_pptr - PUSH EDI + MOV EAX,msg_pptr + PUSH EAX LEA EDX,to_user LEA ECX,chat_msg - CALL send_text_msg_addr - ADD ESP,0x14 + CALL send_text_msg_addr MOV success,EAX - LEA ECX,chat_msg - CALL free_msg_addr + ADD ESP,0x18 + LEA ECX,chat_msg + CALL free_chat_msg_addr POPAD } return success; diff --git a/src/sns.cc b/src/sns.cc index e46e0cf..8c3ecd9 100644 --- a/src/sns.cc +++ b/src/sns.cc @@ -4,11 +4,9 @@ #include "common.h" #include "wechat_data.h" using namespace std; -#define WX_SNS_DATA_MGR_OFFSET 0xac66a0 -#define WX_SNS_GET_FIRST_PAGE_OFFSET 0x12e46c0 -#define WX_SNS_TIME_LINE_MGR_OFFSET 0x128e6a0 -#define WX_SNS_TRY_GET_FIRST_PAGE_SCENE_OFFSET 0x12ff300 -#define WX_SNS_GET_NEXT_PAGE_OFFSET 0x12e4760 +#define WX_SNS_DATA_MGR_OFFSET 0xbc4100 +#define WX_SNS_GET_FIRST_PAGE_OFFSET 0x1427be0 +#define WX_SNS_GET_NEXT_PAGE_OFFSET 0x1427c80 int GetFirstPage() { int success = -1; @@ -16,8 +14,6 @@ int GetFirstPage() { DWORD sns_data_mgr_addr = base + WX_SNS_DATA_MGR_OFFSET; DWORD get_first_page_addr = base + WX_SNS_GET_FIRST_PAGE_OFFSET; - DWORD time_line_mgr_addr = base + WX_SNS_TIME_LINE_MGR_OFFSET; - DWORD get_first_page_scene_addr = base + WX_SNS_TRY_GET_FIRST_PAGE_SCENE_OFFSET; char buff[0xB44] = {}; __asm { PUSHAD @@ -31,15 +27,6 @@ int GetFirstPage() { POPAD } -// __asm { -// PUSHAD -// CALL time_line_mgr_addr -// PUSH 0x1 -// MOV ECX,EAX -// CALL get_first_page_scene_addr -// MOV success, EAX -// POPAD -// } return success; } From 7be2903dd2f49ae683f90c58194e3503db3117e5 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Fri, 10 Feb 2023 15:52:55 +0800 Subject: [PATCH 26/59] =?UTF-8?q?3.9.0.28=E7=89=88=E6=9C=AC=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E5=92=8C=E7=BE=A4=E7=9B=B8=E5=85=B3=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 80 ++++++++++++++++++++++---------------------- src/chat_room.cc | 38 ++++++++++----------- src/forward.cc | 4 +-- src/get_db_handle.cc | 12 +++---- src/hook_img.cc | 6 ++-- src/new_sqlite3.h | 36 ++++++++++---------- 6 files changed, 88 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index fb1e9c6..bebb8af 100644 --- a/README.md +++ b/README.md @@ -109,20 +109,20 @@ vcpkg 6.发送文件 9.hook消息 10.取消hook消息 -~~11.hook图片~~ -~~12.取消hook图片~~ +11.hook图片 +12.取消hook图片 ~~17.删除好友~~ -~~25.获取群成员~~ -~~27.删除群成员~~ -~~28.增加群成员~~ -~~31.修改群昵称~~ -~~32.获取数据库句柄~~ -~~34.查询数据库~~ -~~40.转发消息~~ +25.获取群成员 +27.删除群成员 +28.增加群成员 +31.修改群昵称 +32.获取数据库句柄 +34.查询数据库 +40.转发消息 44.退出登录 ~~45.确认收款~~ 46.联系人列表 -~~47.获取群详情~~ +47.获取群详情 48.获取解密图片 ~~49.图片提取文字ocr~~ ~~50.拍一拍~~ @@ -226,7 +226,7 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|wxid |ture |string| 接收人wxid | +|wxid |true |string| 接收人wxid | |msg|true |string|消息文本内容| ###### 返回字段 @@ -265,7 +265,7 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|wxid |ture |string| 接收人wxid | +|wxid |true |string| 接收人wxid | |imagePath|true |string|图片路径| ###### 返回字段 @@ -303,7 +303,7 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|wxid |ture |string| 接收人wxid | +|wxid |true |string| 接收人wxid | |filePath|true |string|文件路径| ###### 返回字段 @@ -340,8 +340,8 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|port |ture |string| 本地服务端端口,用来接收消息内容 | -|ip |ture |string| 服务端ip地址,用来接收消息内容,可以是任意ip,即tcp客户端连接的服务端的ip (3.8.1.26版本)| +|port |true |string| 本地服务端端口,用来接收消息内容 | +|ip |true |string| 服务端ip地址,用来接收消息内容,可以是任意ip,即tcp客户端连接的服务端的ip (3.8.1.26版本)| ###### 返回字段 |返回字段|字段类型|说明 | @@ -410,7 +410,7 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|imgDir |ture |string| 图片保存的目录 | +|imgDir |true |string| 图片保存的目录 | ###### 返回字段 @@ -479,7 +479,7 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|wxid |ture |string| 好友wxid | +|wxid |true |string| 好友wxid | ###### 返回字段 @@ -516,7 +516,7 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|chatRoomId |ture |string| 群id | +|chatRoomId |true |string| 群id | ###### 返回字段 @@ -557,8 +557,8 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|chatRoomId |ture |string| 群id | -|memberIds |ture |string| 成员id,以,分割 | +|chatRoomId |true |string| 群id | +|memberIds |true |string| 成员id,以,分割 | ###### 返回字段 @@ -596,8 +596,8 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|chatRoomId |ture |string| 群id | -|memberIds |ture |string| 成员id,以,分割 | +|chatRoomId |true |string| 群id | +|memberIds |true |string| 成员id,以,分割 | ###### 返回字段 @@ -634,9 +634,9 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|chatRoomId |ture |string| 群id | -|wxid |ture |string| 自己的id,只能修改自己的群名片 | -|nickName |ture |string| 修改的昵称 | +|chatRoomId |true |string| 群id | +|wxid |true |string| 自己的id,只能修改自己的群名片 | +|nickName |true |string| 修改的昵称 | ###### 返回字段 |返回字段|字段类型|说明 | @@ -733,8 +733,8 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|dbHandle |ture |int| 句柄 | -|sql |ture |string| sql语句 | +|dbHandle |true |int| 句柄 | +|sql |true |string| sql语句 | ###### 返回字段 @@ -773,8 +773,8 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|wxid |ture |string| 消息接收人wxid | -|msgid |ture |number| 消息id,hook消息接口中返回的消息id | +|wxid |true |string| 消息接收人wxid | +|msgid |true |number| 消息id,hook消息接口中返回的消息id | ###### 返回字段 @@ -931,7 +931,7 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|chatRoomId |ture |string| 群id | +|chatRoomId |true |string| 群id | ###### 返回字段 |返回字段|字段类型|说明 | @@ -974,8 +974,8 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|imagePath |ture |string| 图片路径 | -|savePath |ture |string| 保存路径 | +|imagePath |true |string| 图片路径 | +|savePath |true |string| 保存路径 | ###### 返回字段 |返回字段|字段类型|说明 | @@ -1014,7 +1014,7 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|imagePath |ture |string| 图片路径 | +|imagePath |true |string| 图片路径 | ###### 返回字段 |返回字段|字段类型|说明 | @@ -1037,8 +1037,8 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|chatRoomId |ture |string| 微信群聊id | -|wxid |ture |string| 要拍的用户wxid,如果使用用户自定义的微信号,则不会显示群内昵称 | +|chatRoomId |true |string| 微信群聊id | +|wxid |true |string| 要拍的用户wxid,如果使用用户自定义的微信号,则不会显示群内昵称 | ###### 返回字段 |返回字段|字段类型|说明 | |---|---|---| @@ -1075,8 +1075,8 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|wxid |ture |string| 置顶消息的发送人wxid | -|msgid |ture |string| 消息id | +|wxid |true |string| 置顶消息的发送人wxid | +|msgid |true |string| 消息id | ###### 返回字段 |返回字段|字段类型|说明 | |---|---|---| @@ -1113,8 +1113,8 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|chatRoomId |ture |string| 微信群聊id | -|msgid |ture |string| 消息id | +|chatRoomId |true |string| 微信群聊id | +|msgid |true |string| 消息id | ###### 返回字段 |返回字段|字段类型|说明 | |---|---|---| @@ -1215,7 +1215,7 @@ vcpkg ###### 请求参数 |参数|必选|类型|说明| |---|---|---|---| -|snsId |ture |string| 朋友圈的snsId | +|snsId |true |string| 朋友圈的snsId | diff --git a/src/chat_room.cc b/src/chat_room.cc index ceed580..465984f 100644 --- a/src/chat_room.cc +++ b/src/chat_room.cc @@ -7,21 +7,21 @@ #include "base64.h" using namespace std; -#define WX_CHAT_ROOM_MGR_OFFSET 0x67ee70 -#define WX_GET_CHAT_ROOM_DETAIL_INFO_OFFSET 0xa73a80 -#define WX_NEW_CHAT_ROOM_INFO_OFFSET 0xd07010 -#define WX_FREE_CHAT_ROOM_INFO_OFFSET 0xd072f0 -#define WX_DEL_CHAT_ROOM_MEMBER_OFFSET 0xa69a50 -#define WX_INIT_CHAT_MSG_OFFSET 0xdbf380 -#define WX_FREE_CHAT_MSG_OFFSET 0x649ac0 -#define WX_ADD_MEMBER_TO_CHAT_ROOM_OFFSET 0xa69560 -#define WX_GET_MEMBER_FROM_CHAT_ROOM_OFFSET 0xa749b0 -#define WX_INIT_CHAT_ROOM_OFFSET 0xd04d80 -#define WX_FREE_CHAT_ROOM_OFFSET 0xa7c620 -#define WX_MOD_CHAT_ROOM_MEMBER_NICK_NAME_OFFSET 0xa6f8f0 -#define WX_NEW_CHAT_MSG_OFFSET 0x64adc0 -#define WX_TOP_MSG_OFFSET 0xa76e60 -#define WX_REMOVE_TOP_MSG_OFFSET 0xa76c50 +#define WX_CHAT_ROOM_MGR_OFFSET 0x72cf60 +#define WX_GET_CHAT_ROOM_DETAIL_INFO_OFFSET 0xb6f260 +#define WX_NEW_CHAT_ROOM_INFO_OFFSET 0xe15de0 +#define WX_FREE_CHAT_ROOM_INFO_OFFSET 0xe160b0 +#define WX_DEL_CHAT_ROOM_MEMBER_OFFSET 0xb64180 +#define WX_INIT_CHAT_MSG_OFFSET 0xed3be0 +#define WX_ADD_MEMBER_TO_CHAT_ROOM_OFFSET 0xb63c50 +#define WX_GET_MEMBER_FROM_CHAT_ROOM_OFFSET 0xB70260 +#define WX_INIT_CHAT_ROOM_OFFSET 0xe13b30 +#define WX_FREE_CHAT_ROOM_OFFSET 0xe13d50 +#define WX_MOD_CHAT_ROOM_MEMBER_NICK_NAME_OFFSET 0xb6adf0 +#define WX_NEW_CHAT_MSG_OFFSET 0x70e2a0 +#define WX_FREE_CHAT_MSG_OFFSET 0x6f4ea0 +#define WX_TOP_MSG_OFFSET 0xb727e0 +#define WX_REMOVE_TOP_MSG_OFFSET 0xb725a0 int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info) { int success = 0; @@ -182,8 +182,8 @@ int AddMemberToChatRoom(wchar_t* chat_room_id, wchar_t** wxids,int len){ int GetMemberFromChatRoom(wchar_t* chat_room_id,ChatRoomInner & out){ int success = 0; - WeChatString chat_room(chat_room_id); - DWORD chat_room_ptr = (DWORD) &chat_room; + WeChatString chat_room(chat_room_id); + DWORD chat_room_ptr = (DWORD) &chat_room; char buffer[0x1D4] = {0}; DWORD base = GetWeChatWinBase(); DWORD get_member_addr = base + WX_GET_MEMBER_FROM_CHAT_ROOM_OFFSET; @@ -261,12 +261,12 @@ int ModChatRoomMemberNickName(wchar_t* chat_room_id,wchar_t* wxid,wchar_t * nick int SetTopMsg(wchar_t* wxid,ULONG64 msg_id){ int success = -1; - char chat_msg[0x2A8] ={0}; + char chat_msg[0x2C4] ={0}; DWORD base = GetWeChatWinBase(); DWORD new_chat_msg_addr = base + WX_NEW_CHAT_MSG_OFFSET; DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; DWORD handle_top_msg_addr = base + WX_TOP_MSG_OFFSET; - DWORD free_addr = base + WX_FREE_CHAT_ROOM_OFFSET; + DWORD free_addr = base + WX_FREE_CHAT_MSG_OFFSET; vector local_msg = GetChatMsgByMsgId(msg_id); if(local_msg.empty()){ return -2; diff --git a/src/forward.cc b/src/forward.cc index 3ec3b0d..5d936c8 100644 --- a/src/forward.cc +++ b/src/forward.cc @@ -4,8 +4,8 @@ #include "common.h" #include "get_db_handle.h" #include "wechat_data.h" -#define WX_FORWARD_MSG_OFFSET 0xb6a4e0 -#define WX_INIT_CHAT_MSG_OFFSET 0xdbf380 +#define WX_FORWARD_MSG_OFFSET 0xc715f0 +#define WX_INIT_CHAT_MSG_OFFSET 0xed3be0 int ForwardMsg(wchar_t *wxid, unsigned long long msgid) { int success = 0; diff --git a/src/get_db_handle.cc b/src/get_db_handle.cc index 6a26f2d..d684f36 100644 --- a/src/get_db_handle.cc +++ b/src/get_db_handle.cc @@ -5,7 +5,7 @@ #include "new_sqlite3.h" #include "pch.h" #include "wechat_data.h" -#define CONTACT_G_PINSTANCE 0x2c42e78 +#define CONTACT_G_PINSTANCE_OFFSET 0x2e2d628 #define DB_MICRO_MSG_OFFSET 0x68 #define DB_CHAT_MSG_OFFSET 0x1C0 #define DB_MISC_OFFSET 0x3D8 @@ -15,10 +15,10 @@ #define DB_FUNCTION_MSG_OFFSET 0x11B0 #define DB_NAME_OFFSET 0x14 -#define PUBLIC_MSG_MGR_OFFSET 0x2c7ec88 -#define MULTI_DB_MSG_MGR_OFFSET 0x2c807d0 -#define FAVORITE_STORAGE_MGR_OFFSET 0x2c801f8 -#define FTS_FAVORITE_MGR_OFFSET 0x2c439b8 +#define PUBLIC_MSG_MGR_OFFSET 0x2e6ce20 +#define MULTI_DB_MSG_MGR_OFFSET 0x2e6ec84 +#define FAVORITE_STORAGE_MGR_OFFSET 0x2e6e630 +#define FTS_FAVORITE_MGR_OFFSET 0x2e2e168 using namespace std; map dbmap; @@ -64,7 +64,7 @@ std::vector GetDbHandles() { dbs.clear(); dbmap.clear(); DWORD base = GetWeChatWinBase(); - DWORD p_contact_addr = *(DWORD *)(base + CONTACT_G_PINSTANCE); + DWORD p_contact_addr = *(DWORD *)(base + CONTACT_G_PINSTANCE_OFFSET); DWORD micro_msg_db_addr = *(DWORD *)(p_contact_addr + DB_MICRO_MSG_OFFSET); DWORD chat_msg_db_addr = *(DWORD *)(p_contact_addr + DB_CHAT_MSG_OFFSET); DWORD misc_db_addr = *(DWORD *)(p_contact_addr + DB_MISC_OFFSET); diff --git a/src/hook_img.cc b/src/hook_img.cc index 7a052d4..4a1a6aa 100644 --- a/src/hook_img.cc +++ b/src/hook_img.cc @@ -6,9 +6,9 @@ using namespace std; // #define WX_HOOK_IMG_OFFSET 0xd7eaa5 // #define WX_HOOK_IMG_NEXT_OFFSET 0xda56e0 -#define WX_HOOK_IMG_OFFSET 0xc672cc -#define WX_HOOK_IMG_NEXT_OFFSET 0xd82370 -#define WX_SELF_ID_OFFSET 0x2C42A38 +#define WX_HOOK_IMG_OFFSET 0xd723dc +#define WX_HOOK_IMG_NEXT_OFFSET 0xe91d90 +#define WX_SELF_ID_OFFSET 0x2E2CD3C #define BUFSIZE 1024 #define JPEG0 0xFF diff --git a/src/new_sqlite3.h b/src/new_sqlite3.h index a27713f..c95d005 100644 --- a/src/new_sqlite3.h +++ b/src/new_sqlite3.h @@ -135,24 +135,24 @@ #define SQLITE_NULL 5 #define SQLITE_TEXT 3 -#define SQLITE3_EXEC_OFFSET 0x1ba9de0 -#define SQLITE3_BACKUP_INIT_OFFSET 0x1b6f760 -#define SQLITE3_PREPARE_OFFSET 0x1bb0730 -#define SQLITE3_OPEN_OFFSET 0x1bde730 -#define SQLITE3_BACKUP_STEP_OFFSET 0x1b6fb60 -#define SQLITE3_BACKUP_REMAINING_OFFSET 0x1b702a0 -#define SQLITE3_BACKUP_PAGECOUNT_OFFSET 0x1b702b0 -#define SQLITE3_BACKUP_FINISH_OFFSET 0x1b701a0 -#define SQLITE3_SLEEP_OFFSET 0x1bdef70 -#define SQLITE3_ERRCODE_OFFSET 0x1bdd3d0 -#define SQLITE3_CLOSE_OFFSET 0x1bdbb20 -#define SQLITE3_STEP_OFFSET 0x1b785d0 -#define SQLITE3_COLUMN_COUNT_OFFSET 0x1b78ae0 -#define SQLITE3_COLUMN_NAME_OFFSET 0x1b793d0 -#define SQLITE3_COLUMN_TYPE_OFFSET 0x1b79270 -#define SQLITE3_COLUMN_BLOB_OFFSET 0x1b78b20 -#define SQLITE3_COLUMN_BYTES_OFFSET 0x1b78c00 -#define SQLITE3_FINALIZE_OFFSET 0x1b775a0 +#define SQLITE3_EXEC_OFFSET 0x1d5cf70 +#define SQLITE3_BACKUP_INIT_OFFSET 0x1d228d0 +#define SQLITE3_PREPARE_OFFSET 0x1d638c0 +#define SQLITE3_OPEN_OFFSET 0x1d918b0 +#define SQLITE3_BACKUP_STEP_OFFSET 0x1d22ce0 +#define SQLITE3_BACKUP_REMAINING_OFFSET 0x1d23420 +#define SQLITE3_BACKUP_PAGECOUNT_OFFSET 0x1d23430 +#define SQLITE3_BACKUP_FINISH_OFFSET 0x1d23320 +#define SQLITE3_SLEEP_OFFSET 0x1d920f0 +#define SQLITE3_ERRCODE_OFFSET 0x1d90550 +#define SQLITE3_CLOSE_OFFSET 0x1d8ecd0 +#define SQLITE3_STEP_OFFSET 0x1d2b750 +#define SQLITE3_COLUMN_COUNT_OFFSET 0x1d2bc60 +#define SQLITE3_COLUMN_NAME_OFFSET 0x1d2c550 +#define SQLITE3_COLUMN_TYPE_OFFSET 0x1d2c3f0 +#define SQLITE3_COLUMN_BLOB_OFFSET 0x1d2bca0 +#define SQLITE3_COLUMN_BYTES_OFFSET 0x1d2bd80 +#define SQLITE3_FINALIZE_OFFSET 0x1d2a720 typedef int (*Sqlite3_callback)(void*, int, char**, char**); From 75d470935df51fa033c5a51b41ac22077e91caca Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Sat, 11 Feb 2023 16:26:05 +0800 Subject: [PATCH 27/59] =?UTF-8?q?=E5=B7=B2=E6=9C=89=E7=9A=84=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=85=A8=E9=83=A8=E6=9B=B4=E6=96=B0=E5=88=B03.9.0.28?= =?UTF-8?q?=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 18 ++++++++--------- src/chat_room.cc | 8 ++++++-- src/confirm_receipt.cc | 6 +++--- src/contact.cc | 45 +++++++++++++++++++++--------------------- src/ocr.cc | 11 +++++++---- src/pat.cc | 2 +- 6 files changed, 49 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index bebb8af..7165564 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ vcpkg 10.取消hook消息 11.hook图片 12.取消hook图片 -~~17.删除好友~~ +17.删除好友 25.获取群成员 27.删除群成员 28.增加群成员 @@ -120,14 +120,14 @@ vcpkg 34.查询数据库 40.转发消息 44.退出登录 -~~45.确认收款~~ +45.确认收款 46.联系人列表 47.获取群详情 48.获取解密图片 -~~49.图片提取文字ocr~~ -~~50.拍一拍~~ -~~51.群消息置顶消息~~ -~~52.群消息取消置顶~~ +49.图片提取文字ocr +50.拍一拍 +51.群消息置顶消息 +52.群消息取消置顶 53.朋友圈首页 54.朋友圈下一页 ### 接口文档: @@ -468,7 +468,7 @@ vcpkg #### 17.删除好友** ###### 接口功能 -> 删除好友 +> 删除好友,该接口不够完善,删除后,只会在通讯录里删除,如果点击聊天记录,又会重新加回来,删除的不彻底。 ###### 接口地址 > [/api/?type=17](/api/?type=17) @@ -485,7 +485,7 @@ vcpkg ###### 返回字段 |返回字段|字段类型|说明 | |---|---|---| -|code|int|返回状态,1成功, 0失败| +|code|int|返回状态,0成功, -1失败| |result|string|成功提示| @@ -498,7 +498,7 @@ vcpkg ``` 响应: ``` javascript -{"code":1,"result":"OK"} +{"code":0,"result":"OK"} ``` diff --git a/src/chat_room.cc b/src/chat_room.cc index 465984f..51164e7 100644 --- a/src/chat_room.cc +++ b/src/chat_room.cc @@ -22,6 +22,7 @@ using namespace std; #define WX_FREE_CHAT_MSG_OFFSET 0x6f4ea0 #define WX_TOP_MSG_OFFSET 0xb727e0 #define WX_REMOVE_TOP_MSG_OFFSET 0xb725a0 +#define WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET 0x6f5370 int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info) { int success = 0; @@ -266,7 +267,8 @@ int SetTopMsg(wchar_t* wxid,ULONG64 msg_id){ DWORD new_chat_msg_addr = base + WX_NEW_CHAT_MSG_OFFSET; DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; DWORD handle_top_msg_addr = base + WX_TOP_MSG_OFFSET; - DWORD free_addr = base + WX_FREE_CHAT_MSG_OFFSET; + // DWORD free_addr = base + WX_FREE_CHAT_MSG_OFFSET; + DWORD free_addr = base + WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET; vector local_msg = GetChatMsgByMsgId(msg_id); if(local_msg.empty()){ return -2; @@ -288,6 +290,7 @@ int SetTopMsg(wchar_t* wxid,ULONG64 msg_id){ WeChatString msg_content(w_content); WeChatString user_id(wxid); + __asm{ PUSHAD LEA ECX,chat_msg @@ -307,12 +310,13 @@ int SetTopMsg(wchar_t* wxid,ULONG64 msg_id){ __asm{ PUSHAD CALL get_chat_room_mgr_addr - PUSH 0x1 + PUSH 0x0 LEA EAX,chat_msg PUSH EAX CALL handle_top_msg_addr MOV success,EAX LEA ECX,chat_msg + PUSH 0x0 CALL free_addr POPAD } diff --git a/src/confirm_receipt.cc b/src/confirm_receipt.cc index 16cfa13..cb48b5b 100644 --- a/src/confirm_receipt.cc +++ b/src/confirm_receipt.cc @@ -4,9 +4,9 @@ #include "common.h" #include "wechat_data.h" -#define WX_NEW_WCPAYINFO_OFFSET 0x69d2b0 -#define WX_FREE_WCPAYINFO_OFFSET 0x68d610 -#define WX_CONFIRM_RECEIPT_OFFSET 0x13d5e00 +#define WX_NEW_WCPAYINFO_OFFSET 0x756340 +#define WX_FREE_WCPAYINFO_OFFSET 0x73c170 +#define WX_CONFIRM_RECEIPT_OFFSET 0x15287a0 int DoConfirmReceipt(wchar_t *wxid, wchar_t *transcationid, wchar_t *transferid) { diff --git a/src/contact.cc b/src/contact.cc index f4695d8..43ccccf 100644 --- a/src/contact.cc +++ b/src/contact.cc @@ -6,9 +6,11 @@ using namespace std; #define WX_CONTACT_MGR_INSTANCE_OFFSET 0x6f8990 #define WX_CONTACT_GET_LIST_OFFSET 0xb97550 -#define WX_CONTACT_DEL_OFFSET 0xa9ef40 -#define WX_INIT_CHAT_MSG_OFFSET 0xdbf380 -#define WX_DB_QUERY_OFFSET 0xa9ec40 +#define WX_CONTACT_DEL_OFFSET 0xb9b3b0 +#define WX_INIT_CHAT_MSG_OFFSET 0xed3be0 +#define WX_SYNC_MGR_OFFSET 0xa87fd0 +#define WX_SET_VALUE_OFFSET 0x1f80900 +#define WX_DO_DEL_CONTACT_OFFSET 0xca6480 int GetAllContact(vector &vec) { DWORD base = GetWeChatWinBase(); DWORD get_instance = base + WX_CONTACT_MGR_INSTANCE_OFFSET; @@ -66,33 +68,32 @@ int GetAllContact(vector &vec) { } return success; } -// todo +// note maybe not delete int DelContact(wchar_t *wxid) { - int success = 0; + int success = -1; WeChatString user_id(wxid); DWORD id_ptr = (DWORD) &user_id; DWORD base = GetWeChatWinBase(); - DWORD get_instance_addr = base + WX_CONTACT_MGR_INSTANCE_OFFSET; - DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; - DWORD del_addr = base + WX_CONTACT_DEL_OFFSET; - DWORD db_op_addr = base + WX_DB_QUERY_OFFSET; + DWORD sync_mgr_addr = base + WX_SYNC_MGR_OFFSET; + DWORD set_id_addr = base + WX_SET_VALUE_OFFSET; + DWORD del_contact_addr = base + WX_DO_DEL_CONTACT_OFFSET; + int len = user_id.length; + + string id_cstr = unicode_to_utf8(wxid); + char id_[0x20]={0}; + memcpy(id_,id_cstr.c_str(),id_cstr.size()+1); + char buff[0x10]={0}; __asm{ PUSHAD PUSHFD - CALL get_instance_addr - MOV ECX,dword ptr[id_ptr] - PUSH ECX + CALL sync_mgr_addr MOV ECX,EAX - MOV ESI,EAX - CALL db_op_addr - SUB ESP,0x14 - MOV EAX,dword ptr[id_ptr] - MOV ECX,ESP - PUSH EAX - CALL init_chat_msg_addr - MOV ECX,ESI - CALL del_addr - MOV success,EAX + LEA EAX,buff + MOV [ECX + 4],EAX + LEA EAX,id_ + Mov dword ptr[buff +0x4],EAX + CALL del_contact_addr + MOV success,EAX POPFD POPAD } diff --git a/src/ocr.cc b/src/ocr.cc index c22275e..4ac88d4 100644 --- a/src/ocr.cc +++ b/src/ocr.cc @@ -4,9 +4,9 @@ #include "common.h" #include "wechat_data.h" -#define WX_INIT_OBJ_OFFSET 0x6cbab0 -#define WX_OCR_MANAGER_OFFSET 0x6cff00 -#define WX_DO_OCR_TASK_OFFSET 0x11e3210 +#define WX_INIT_OBJ_OFFSET 0x7a98f0 +#define WX_OCR_MANAGER_OFFSET 0x7ae470 +#define WX_DO_OCR_TASK_OFFSET 0x13230c0 using namespace std; int DoOCRTask(wchar_t *img_path, std::string &result) { int success = -1; @@ -17,6 +17,7 @@ int DoOCRTask(wchar_t *img_path, std::string &result) { DWORD ocr_manager_addr = base + WX_OCR_MANAGER_OFFSET; DWORD do_ocr_task_addr = base + WX_DO_OCR_TASK_OFFSET; DWORD init_addr = base + WX_INIT_OBJ_OFFSET; + DWORD flag = 0; __asm { PUSHAD PUSHFD @@ -25,9 +26,11 @@ int DoOCRTask(wchar_t *img_path, std::string &result) { CALL ocr_manager_addr LEA ECX,null_obj PUSH ECX + LEA ECX,flag + PUSH ECX LEA ECX,ocr_result PUSH ECX - PUSH ECX + PUSH 0x0 LEA ECX,path PUSH ECX MOV ECX,EAX diff --git a/src/pat.cc b/src/pat.cc index 2fa6ec8..16a4757 100644 --- a/src/pat.cc +++ b/src/pat.cc @@ -5,7 +5,7 @@ #include "wechat_data.h" #define WX_PAT_MGR_OFFSET 0x8d0c00 -#define WX_SEND_PAT_MSG_OFFSET 0x1228510 +#define WX_SEND_PAT_MSG_OFFSET 0x1369850 #define WX_RET_OFFSET 0x1C94D34 int SendPatMsg(wchar_t* chat_room_id, wchar_t* wxid) { From 3d8ed813bed327749f3b59c09b8163fd0f438852 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Mon, 13 Feb 2023 11:30:33 +0800 Subject: [PATCH 28/59] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=98=B5=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 93 ++++++++++++++++++++++++++++++++++++++++++++---- src/api.cc | 15 +++++++- src/api.h | 1 + src/chat_room.cc | 63 ++++++++++++++++++++++++++++++++ src/chat_room.h | 2 ++ src/contact.cc | 37 ++++++++++++++++++- src/contact.h | 2 ++ 7 files changed, 205 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7165564..29d99ef 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26,3.9.0.28版 本项目是个人学习学习逆向的项目,主要参考 https://github.com/ljc545w/ComWeChatRobot ,在此基础上实现了微信的的其它版本的部分内容。 #### 使用说明: -支持的版本3.8.0.41,3.8.1.26,3.9.0.28。 -src:主要的dll代码 -tool:简单的注入工具,一个是控制台,一个是图形界面。 -python: 简单的服务器,用以接收消息内容。 -release:编译好的dll。 +支持的版本3.8.0.41,3.8.1.26, 3.9.0.28。 +src:主要的dll代码 +tool:简单的注入工具,一个是控制台,一个是图形界面。 +python: 简单的服务器,用以接收hook的消息内容。 + 0.首先安装对应的微信版本,主分支是3.8.0.41版本,分支对应相应的微信版本号. 1.通过cmake构建成功后,将wxhelper.dll注入到微信,本地启动tcp server,监听19088端口。 @@ -99,7 +99,9 @@ vcpkg 2023-02-08 : 新增朋友圈消息。 -2023-02-09 : 新增3.9.0.28版本基础功能。 +2023-02-09 : 新增3.9.0.28版本基础功能。 + +2023-02-13 : 新增查询昵称功能。 #### 功能预览: 0.检查是否登录 @@ -543,6 +545,43 @@ vcpkg ``` +#### 26.获取群成员昵称** +###### 接口功能 +> 获取群成员群内昵称 + +###### 接口地址 +> [/api/?type=26](/api/?type=26) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | +|memberId |true |string| 群成员id | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| +|nickname|string|昵称| + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"123@chatroom", + "memberId":"wxid_123" +} +``` +响应: +``` javascript +{"code":1,"nickname":"昵称","result":"OK"} +``` + #### 27.删除群成员** ###### 接口功能 @@ -1241,6 +1280,48 @@ vcpkg {"code":1,"result":"OK"} ``` + +#### 55.获取联系人或者群名称** +###### 接口功能 +> 根据wxid,获取联系人微信名称,传入群id获取群名称,传入群内非好友获取的是微信名称不是群内昵称。 + +###### 接口地址 +> [/api/?type=55](/api/?type=55) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|id |true |string| wxid或者群id | + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| +|name|string|名称| + + +###### 接口示例 +入参: +``` javascript +{ + "id":"wxid_123" + +} + +``` +响应: +``` javascript +{"code":1,"name":"文件助手","result":"OK"} +``` + + + #### 感谢 https://github.com/ljc545w/ComWeChatRobot diff --git a/src/api.cc b/src/api.cc index 37d6732..8e6a5c0 100644 --- a/src/api.cc +++ b/src/api.cc @@ -355,6 +355,12 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_CHATROOM_GET_MEMBER_NICKNAME: { + wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); + wstring member_id = get_http_req_param(hm, j_param, "memberId", is_post); + + wstring nickname = GetChatRoomMemberNickname(WS2LW(room_id),WS2LW(member_id)); + json ret_data = {{"code", 1}, {"result", "OK"},{"nickname",unicode_to_utf8(WS2LW(nickname))}}; + ret = ret_data.dump(); break; } case WECHAT_CHATROOM_DEL_MEMBER: { @@ -598,13 +604,20 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { ret = ret_data.dump(); break; } - case WECHAT_SNS_GET_NEXT_PAGE:{ + case WECHAT_SNS_GET_NEXT_PAGE: { ULONG64 snsid = get_http_param_ulong64(hm, j_param, "snsId", is_post); int success = GetNextPage(snsid); json ret_data = {{"code", success}, {"result", "OK"}}; ret = ret_data.dump(); break; } + case WECHAT_CONTACT_NAME:{ + wstring pri_id = get_http_req_param(hm, j_param, "id", is_post); + wstring name =GetContactOrChatRoomNickname(WS2LW(pri_id)); + json ret_data = {{"code", 1}, {"result", "OK"},{"name",unicode_to_utf8(WS2LW(name))}}; + ret = ret_data.dump(); + break; + } default: break; } diff --git a/src/api.h b/src/api.h index 7c04269..970d9e0 100644 --- a/src/api.h +++ b/src/api.h @@ -69,6 +69,7 @@ typedef enum WECHAT_HTTP_APISTag WECHAT_REMOVE_TOP_MSG, WECHAT_SNS_GET_FIRST_PAGE, WECHAT_SNS_GET_NEXT_PAGE, + WECHAT_CONTACT_NAME, } WECHAT_HTTP_APIS, *PWECHAT_HTTP_APIS; diff --git a/src/chat_room.cc b/src/chat_room.cc index 51164e7..90ac7fb 100644 --- a/src/chat_room.cc +++ b/src/chat_room.cc @@ -23,6 +23,10 @@ using namespace std; #define WX_TOP_MSG_OFFSET 0xb727e0 #define WX_REMOVE_TOP_MSG_OFFSET 0xb725a0 #define WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET 0x6f5370 +#define WX_GET_MEMBER_NICKNAME_OFFSET 0xb703f0 +#define WX_CONTACT_MGR_INSTANCE_OFFSET 0x6f8990 +#define WX_GET_CONTACT_OFFSET 0xb93b20 +#define WX_FREE_CONTACT_OFFSET 0xe23690 int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info) { int success = 0; @@ -352,4 +356,63 @@ int RemoveTopMsg(wchar_t* chat_room_id,ULONG64 msg_id){ } return success; +} + + + +std::wstring GetChatRoomMemberNickname(wchar_t* chat_room_id,wchar_t* wxid){ + WeChatString chat_room(chat_room_id); + WeChatString member_id(wxid); + WeChatString nickname(NULL); + DWORD base = GetWeChatWinBase(); + DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; + DWORD get_nickname_addr = base + WX_GET_MEMBER_NICKNAME_OFFSET; + DWORD contact_mgr_addr = base + WX_CONTACT_MGR_INSTANCE_OFFSET; + DWORD get_contact_addr = base + WX_GET_CONTACT_OFFSET; + DWORD free_contact_addr = base + WX_FREE_CONTACT_OFFSET; + __asm{ + PUSHAD + PUSHFD + CALL get_chat_room_mgr_addr + LEA ECX,nickname + PUSH ECX + LEA ECX,member_id + PUSH ECX + LEA ECX,chat_room + PUSH ECX + MOV ECX,EAX + CALL get_nickname_addr + POPFD + POPAD + } + wstring name = L""; + if (nickname.ptr) { + name += wstring(nickname.ptr); + }else { + char buff[0x440] = {0}; + __asm { + PUSHAD + PUSHFD + CALL contact_mgr_addr + LEA ECX,buff + PUSH ECX + LEA ECX,member_id + PUSH ECX + MOV ECX,EAX + CALL get_contact_addr + POPFD + POPAD + } + name += READ_WSTRING(buff, 0x6C); + + __asm{ + PUSHAD + PUSHFD + LEA ECX,buff + CALL free_contact_addr + POPFD + POPAD + } + } + return name; } \ No newline at end of file diff --git a/src/chat_room.h b/src/chat_room.h index e237b61..336aa0b 100644 --- a/src/chat_room.h +++ b/src/chat_room.h @@ -11,4 +11,6 @@ int ModChatRoomMemberNickName(wchar_t* chat_room_id,wchar_t* wxid,wchar_t * nick int SetTopMsg(wchar_t* wxid,ULONG64 msg_id); int RemoveTopMsg(wchar_t* chat_room_id,ULONG64 msg_id); + +std::wstring GetChatRoomMemberNickname(wchar_t* chat_room_id,wchar_t* wxid); #endif \ No newline at end of file diff --git a/src/contact.cc b/src/contact.cc index 43ccccf..0bf39fe 100644 --- a/src/contact.cc +++ b/src/contact.cc @@ -11,6 +11,9 @@ using namespace std; #define WX_SYNC_MGR_OFFSET 0xa87fd0 #define WX_SET_VALUE_OFFSET 0x1f80900 #define WX_DO_DEL_CONTACT_OFFSET 0xca6480 +#define WX_FREE_CONTACT_OFFSET 0xe23690 +#define WX_GET_CONTACT_OFFSET 0xb93b20 + int GetAllContact(vector &vec) { DWORD base = GetWeChatWinBase(); DWORD get_instance = base + WX_CONTACT_MGR_INSTANCE_OFFSET; @@ -100,4 +103,36 @@ int DelContact(wchar_t *wxid) { return success; } - +std::wstring GetContactOrChatRoomNickname(wchar_t *id) { + int success = -1; + char buff[0x440] = {0}; + WeChatString pri(id); + DWORD base = GetWeChatWinBase(); + DWORD contact_mgr_addr = base + WX_CONTACT_MGR_INSTANCE_OFFSET; + DWORD get_contact_addr = base + WX_GET_CONTACT_OFFSET; + DWORD free_contact_addr = base + WX_FREE_CONTACT_OFFSET; + wstring name = L""; + __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 + } + name += READ_WSTRING(buff, 0x6C); + __asm { + PUSHAD + PUSHFD + LEA ECX,buff + CALL free_contact_addr + POPFD + POPAD + } + return name; +} diff --git a/src/contact.h b/src/contact.h index 9ec6c76..0c1f981 100644 --- a/src/contact.h +++ b/src/contact.h @@ -8,4 +8,6 @@ int GetAllContact(std::vector &vec); int DelContact(wchar_t* wxid); + +std::wstring GetContactOrChatRoomNickname(wchar_t* id); #endif \ No newline at end of file From 4e34505cddcef6cabc5b1426049c5bc810df23a8 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Mon, 13 Feb 2023 11:37:34 +0800 Subject: [PATCH 29/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 29d99ef..da8101a 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ vcpkg 2023-02-09 : 新增3.9.0.28版本基础功能。 -2023-02-13 : 新增查询昵称功能。 +2023-02-13 : 新增查询联系人昵称功能。 #### 功能预览: 0.检查是否登录 From 3522f9187eb64a7826dc026d79ef412d2343c183 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Mon, 13 Feb 2023 11:44:45 +0800 Subject: [PATCH 30/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AE=8F=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E7=AC=94=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9112297..1191974 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(wxhelper VERSION 1.0.0) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D '0_UNICODE' /D 'UNICODE'") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D '_UNICODE' /D 'UNICODE'") file(GLOB CPP_FILES ${PROJECT_SOURCE_DIR}/src/*.cc ${PROJECT_SOURCE_DIR}/src/*.cpp) From c8544a9286b32b4cc6e7a33d2cd82e8aaa79f520 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Mon, 13 Feb 2023 13:33:15 +0800 Subject: [PATCH 31/59] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index da8101a..2f78068 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ vcpkg 12.取消hook图片 17.删除好友 25.获取群成员 +26.获取群成员昵称 27.删除群成员 28.增加群成员 31.修改群昵称 @@ -131,7 +132,8 @@ vcpkg 51.群消息置顶消息 52.群消息取消置顶 53.朋友圈首页 -54.朋友圈下一页 +54.朋友圈下一页 +55.获取联系人或者群名称 ### 接口文档: From db5051ae2f2c14e01c447e140f5c3e93ed008100 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Fri, 17 Feb 2023 08:44:51 +0800 Subject: [PATCH 32/59] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=A5=BD=E5=8F=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 44 ++++++++++++++++++++++++++++++++++++++-- src/api.cc | 4 ++++ src/contact.cc | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/contact.h | 2 ++ 4 files changed, 102 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2f78068..4096ab6 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,9 @@ vcpkg 2023-02-09 : 新增3.9.0.28版本基础功能。 -2023-02-13 : 新增查询联系人昵称功能。 +2023-02-13 : 新增查询联系人昵称功能。 + +2023-02-17 : 新增通过wxid添加好友。 #### 功能预览: 0.检查是否登录 @@ -113,7 +115,8 @@ vcpkg 10.取消hook消息 11.hook图片 12.取消hook图片 -17.删除好友 +17.删除好友 +20.通过wxid添加好友 25.获取群成员 26.获取群成员昵称 27.删除群成员 @@ -507,6 +510,43 @@ vcpkg + +#### 20.通过wxid添加好友** +###### 接口功能 +> 添加好友 + +###### 接口地址 +> [/api/?type=20](/api/?type=20) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 好友wxid | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "wxid":"wxid_o1112222" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + #### 25.获取群成员** ###### 接口功能 > 获取群成员 diff --git a/src/api.cc b/src/api.cc index 8e6a5c0..434827c 100644 --- a/src/api.cc +++ b/src/api.cc @@ -324,6 +324,10 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_CONTACT_ADD_BY_WXID: { + wstring user_id = get_http_req_param(hm, j_param, "wxid", is_post); + int success = AddFriendByWxid(WS2LW(user_id)); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); break; } case WECHAT_CONTACT_ADD_BY_V3: { diff --git a/src/contact.cc b/src/contact.cc index 0bf39fe..c37edbc 100644 --- a/src/contact.cc +++ b/src/contact.cc @@ -13,6 +13,7 @@ using namespace std; #define WX_DO_DEL_CONTACT_OFFSET 0xca6480 #define WX_FREE_CONTACT_OFFSET 0xe23690 #define WX_GET_CONTACT_OFFSET 0xb93b20 +#define WX_DO_VERIFY_USER_OFFSET 0xB91160 int GetAllContact(vector &vec) { DWORD base = GetWeChatWinBase(); @@ -136,3 +137,56 @@ std::wstring GetContactOrChatRoomNickname(wchar_t *id) { } return name; } + + +int AddFriendByWxid(wchar_t *wxid){ + int success = -1; + DWORD base = GetWeChatWinBase(); + DWORD contact_mgr_addr = base + WX_CONTACT_MGR_INSTANCE_OFFSET; + DWORD set_group_addr = base + 0x746E20; + DWORD fn2_addr = base + 0x285D968; + DWORD fn3_addr = base + 0x6F6050; + DWORD fn4_addr = base + 0xED3BE0; + DWORD do_verify_user_addr = base + WX_DO_VERIFY_USER_OFFSET; + + DWORD instance =0; + WeChatString chat_room(NULL); + WeChatString user_id(wxid); + __asm{ + PUSHAD + PUSHFD + CALL contact_mgr_addr + SUB ESP,0x18 + MOV dword ptr [instance],EAX + MOV ECX,ESP + PUSH ECX + LEA ECX,chat_room + CALL set_group_addr + MOV EAX,fn2_addr + SUB ESP,0x18 + MOV ECX,ESP + PUSH EAX + CALL fn3_addr + PUSH 0x0 + PUSH 0XE + SUB ESP,0x14 + MOV ESI,ESP + MOV dword ptr [ESI],0x0 + MOV dword ptr [ESI+0x4],0x0 + MOV dword ptr [ESI+0x8],0x0 + MOV dword ptr [ESI+0xC],0x0 + MOV dword ptr [ESI+0x10],0x0 + PUSH 0x1 + SUB ESP,0x14 + MOV ECX,ESP + LEA EAX,user_id + PUSH EAX + CALL fn4_addr + MOV ECX,dword ptr [instance] + CALL do_verify_user_addr + MOV success,EAX + POPFD + POPAD + } + return success; +} \ No newline at end of file diff --git a/src/contact.h b/src/contact.h index 0c1f981..924ee70 100644 --- a/src/contact.h +++ b/src/contact.h @@ -10,4 +10,6 @@ int GetAllContact(std::vector &vec); int DelContact(wchar_t* wxid); std::wstring GetContactOrChatRoomNickname(wchar_t* id); + +int AddFriendByWxid(wchar_t *wxid); #endif \ No newline at end of file From 9e5dce93f03a13574498ca9e29dddcca5ccc06f4 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Fri, 17 Feb 2023 14:46:49 +0800 Subject: [PATCH 33/59] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++ src/api.cc | 37 +++++++++++++++++++++++++++++ src/api.h | 3 ++- src/dllMain.cc | 1 + src/wechat_data.h | 26 ++++++++++++++++++++ 5 files changed, 126 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4096ab6..af99860 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ vcpkg 11.hook图片 12.取消hook图片 17.删除好友 +19.通过手机或qq查找微信 20.通过wxid添加好友 25.获取群成员 26.获取群成员昵称 @@ -508,6 +509,65 @@ vcpkg {"code":0,"result":"OK"} ``` +#### 19.通过手机或qq查找微信** +###### 接口功能 +> 通过手机或qq查找微信 + +###### 接口地址 +> [/api/?type=19](/api/?type=19) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|keyword |true |string| 手机或qq | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| +|userInfo|object|用户信息| +|  bigImage|string|大头像| +|  smallImage|string|小头像| +|  city|string|城市| +|  nation|string|民族| +|  nickname|string|昵称| +|  province|string|省| +|  sex|number|性别| +|  signature|string|签名| +|  v2|string|v2| +|  v3|string|v3| + +###### 接口示例 +入参: +``` javascript +{ + "keyword":"131111111" +} +``` +响应: +``` javascript +{ + "code": 1, + "result": "OK", + "userInfo": { + "bigImage": "http://wx.qlogo.cn/mmhead/ver_1/7NIHQAyXeaAPa7Vd7p122mKxgETJwoAiaERdk1sSyOyfnLLQOfElw4G9I32QkZzh7bGfZr2lg0OIQE1Az3cUwtWaLUM79Q/0", + "city": "", + "nation": "", + "nickname": "昵称", + "province": "", + "sex": 0, + "signature": "", + "smallImage": "http://wx.qlogo.cn/mmhead/ver_1/7NIHQAyXeaAPa7Vd7p4KR3vxiasmKxgETJwoAiaER23QE6G5mLBcdBQkZzh7bGfZr2lg0OIQE1Az3cUwtWaLUM79Q/132", + "v2": "wxid_12333", + "v3": "v3_020b3826fd0301000000000098ca23832239a3dba12f95f6b60a0536a1adb6b40fc4086288f46c0b89e6c4eb70c34f118c7b4b6a6845144843b088f0077e406507f821068571289b36c4158a8ac47ec41ae47bee65e9@stranger" + } +} +``` diff --git a/src/api.cc b/src/api.cc index 434827c..602524c 100644 --- a/src/api.cc +++ b/src/api.cc @@ -22,6 +22,7 @@ #include "pat.h" #include "confirm_receipt.h" #include "sns.h" +#include "search_contact.h" #pragma comment(lib, "ws2_32.lib") using namespace std; @@ -321,6 +322,26 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_CONTACT_SEARCH_BY_NET: { + wstring keyword = get_http_req_param(hm, j_param, "keyword", is_post); + UserInfo *user = nullptr; + int success = SearchContactNetScene(WS2LW(keyword), &user); + json ret_data = {{"code", success}, {"result", "OK"}}; + if (user) { + json info = { + {"bigImage", unicode_to_utf8(user->big_image)}, + {"smallImage", unicode_to_utf8(user->small_image)}, + {"city", unicode_to_utf8(user->city)}, + {"nation", unicode_to_utf8(user->nation)}, + {"nickname", unicode_to_utf8(user->nickname)}, + {"province", unicode_to_utf8(user->province)}, + {"sex", user->sex}, + {"signature", unicode_to_utf8(user->signature)}, + {"v2", unicode_to_utf8(user->v2)}, + {"v3", unicode_to_utf8(user->v3)}, + }; + ret_data["userInfo"] = info; + } + ret = ret_data.dump(); break; } case WECHAT_CONTACT_ADD_BY_WXID: { @@ -682,4 +703,20 @@ int http_start(int port) { kHttpThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)http_server, (LPVOID)port, NULL, 0); return 0; +} + +int http_close() { + if (!kHttpRuning) { + return 1; + } + kHttpRuning = false; + if (kHttpThread) { + WaitForSingleObject(kHttpThread, -1); + CloseHandle(kHttpThread); + kHttpThread = NULL; + } + UnHookRecvMsg(); + UnHookImg(); + UnHookSearchContact(); + return 0; } \ No newline at end of file diff --git a/src/api.h b/src/api.h index 970d9e0..94b9c50 100644 --- a/src/api.h +++ b/src/api.h @@ -74,5 +74,6 @@ typedef enum WECHAT_HTTP_APISTag *PWECHAT_HTTP_APIS; -int http_start(int port); +extern "C" __declspec(dllexport) int http_start(int port); +extern "C" __declspec(dllexport) int http_close(); #endif \ No newline at end of file diff --git a/src/dllMain.cc b/src/dllMain.cc index 42cbf5e..355e03d 100644 --- a/src/dllMain.cc +++ b/src/dllMain.cc @@ -16,6 +16,7 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, break; } case DLL_PROCESS_DETACH: { + http_close(); break; } } diff --git a/src/wechat_data.h b/src/wechat_data.h index 6fc8d18..6f0d63e 100644 --- a/src/wechat_data.h +++ b/src/wechat_data.h @@ -155,4 +155,30 @@ struct SelfInfoInner{ std::string data_save_path; std::string current_data_path; }; + +struct UserInfo { + int error_code; + wchar_t *keyword; + int keyword_len; + wchar_t *v3; + int v3_len; + wchar_t *nickname; + int nickname_len; + wchar_t *signature; + int signature_len; + wchar_t *v2; + int v2_len; + wchar_t *nation; + int nation_len; + wchar_t *province; + int province_len; + wchar_t *city; + int city_len; + wchar_t *big_image; + int big_image_len; + wchar_t *small_image; + int small_image_len; + DWORD sex; + BOOL over; +}; #endif From 3ed52a7dbcaa4fd737405eec520bb1d8d942ef52 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Fri, 17 Feb 2023 14:47:06 +0800 Subject: [PATCH 34/59] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/search_contact.cc | 275 ++++++++++++++++++++++++++++++++++++++++++ src/search_contact.h | 8 ++ 2 files changed, 283 insertions(+) create mode 100644 src/search_contact.cc create mode 100644 src/search_contact.h diff --git a/src/search_contact.cc b/src/search_contact.cc new file mode 100644 index 0000000..8eec925 --- /dev/null +++ b/src/search_contact.cc @@ -0,0 +1,275 @@ +#include "pch.h" +#include "search_contact.h" + +#include "common.h" + +#include "wechat_data.h" + +#define WX_SEARCH_CONTACT_ERROR_CODE_HOOK_OFFSeT 0xd94d1e +#define WX_SEARCH_CONTACT_ERROR_CODE_HOOK_NEXT_OFFSeT 0xed13a0 +#define WX_SEARCH_CONTACT_DETAIL_HOOK_OFFSeT 0xa2a260 +#define WX_SEARCH_CONTACT_DETAIL_HOOK_NEXT_OFFSeT 0xa2a4c0 + + +#define WX_SEARCH_CONTACT_OFFSeT 0xc5bb00 +#define WX_SEARCH_CONTACT_MGR_OFFSeT 0xa0d550 + + + +static BOOL kSearchContactHooked = false; +static char kHookSearchContactErrcodeOldAsm[5] = {0}; +static char kHookUserInfoOldAsm[5] = {0}; +static DWORD kWeChatWinBase = GetWeChatWinBase(); + +static DWORD kHookSearchContactErrcodeNextAddr = + kWeChatWinBase + WX_SEARCH_CONTACT_ERROR_CODE_HOOK_NEXT_OFFSeT; +static DWORD kHookSearchContactErrcodeAddr = + kWeChatWinBase + WX_SEARCH_CONTACT_ERROR_CODE_HOOK_OFFSeT; +static DWORD kHookSearchContactErrcodeJmpBackAddr = + kHookSearchContactErrcodeAddr + 0x5; + +static DWORD kHookUserInfoNextAddr = + kWeChatWinBase + WX_SEARCH_CONTACT_DETAIL_HOOK_NEXT_OFFSeT; +static DWORD kHookUserInfoAddr = + kWeChatWinBase + WX_SEARCH_CONTACT_DETAIL_HOOK_OFFSeT; +static DWORD kHookUserInfoJmpBackAddr = kHookUserInfoAddr + 0x5; + +static UserInfo userinfo; + +void SetErrorCode(int code) { userinfo.error_code = code; } + +void SetUserInfoDetail(DWORD address) { + DWORD length = *(DWORD *)(address + 0x8); + userinfo.keyword = new wchar_t[length + 1]; + userinfo.keyword_len = length; + if (length) { + memcpy(userinfo.keyword, (wchar_t *)(*(DWORD *)(address + 0x4)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.keyword, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x1C); + userinfo.v3 = new wchar_t[length + 1]; + userinfo.v3_len = length; + if (length) { + memcpy(userinfo.v3, (wchar_t *)(*(DWORD *)(address + 0x18)), + (length + 1) * sizeof(wchar_t)); + }else { + ZeroMemory(userinfo.v3, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x30); + userinfo.big_image = new wchar_t[length + 1]; + userinfo.big_image_len = length; + if (length) { + memcpy(userinfo.big_image, (wchar_t *)(*(DWORD *)(address + 0x2C)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.big_image, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0xC8); + userinfo.nickname = new wchar_t[length + 1]; + userinfo.nickname_len = length; + if (length) { + memcpy(userinfo.nickname, (wchar_t *)(*(DWORD *)(address + 0xC4)), + (length + 1) * sizeof(wchar_t)); + }else { + ZeroMemory(userinfo.nickname, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x108); + userinfo.v2 = new wchar_t[length + 1]; + userinfo.v2_len = length; + if (length) { + memcpy(userinfo.v2, (wchar_t *)(*(DWORD *)(address + 0x104)), + (length + 1) * sizeof(wchar_t)); + }else { + ZeroMemory(userinfo.v2, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x16C); + userinfo.small_image = new wchar_t[length + 1]; + userinfo.small_image_len = length; + if (length) { + memcpy(userinfo.small_image, (wchar_t *)(*(DWORD *)(address + 0x168)), + (length + 1) * sizeof(wchar_t)); + }else { + ZeroMemory(userinfo.small_image, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x1F8); + userinfo.signature = new wchar_t[length + 1]; + userinfo.signature_len = length; + if (length) { + memcpy(userinfo.signature, (wchar_t *)(*(DWORD *)(address + 0x1F4)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.signature, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x20C); + userinfo.nation = new wchar_t[length + 1]; + userinfo.nation_len = length; + if (length) { + memcpy(userinfo.nation, (wchar_t *)(*(DWORD *)(address + 0x208)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.nation, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x220); + userinfo.province = new wchar_t[length + 1]; + userinfo.province_len = length; + if (length) { + memcpy(userinfo.province, (wchar_t *)(*(DWORD *)(address + 0x21C)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.province, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x234); + userinfo.city = new wchar_t[length + 1]; + userinfo.city_len = length; + if (length) { + memcpy(userinfo.city, (wchar_t *)(*(DWORD *)(address + 0x230)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.city, (length + 1) * sizeof(wchar_t)); + } + + userinfo.sex = *(DWORD *)(address + 0x1BC); + userinfo.over = true; +} + +static void DeleteUserInfoCache() { + if (userinfo.keyword) { + delete userinfo.keyword; + } + if (userinfo.v2) { + delete userinfo.v2; + } + if (userinfo.v3) { + delete userinfo.v3; + } + if (userinfo.nickname) { + delete userinfo.nickname; + } + if (userinfo.nation) { + delete userinfo.nation; + } + if (userinfo.province) { + delete userinfo.province; + } + if (userinfo.city) { + delete userinfo.city; + } + if (userinfo.signature) { + delete userinfo.signature; + } + if (userinfo.small_image) { + delete userinfo.small_image; + } + if (userinfo.big_image) { + delete userinfo.big_image; + } + ZeroMemory(&userinfo, sizeof(UserInfo)); + userinfo.error_code = 1; +} + +__declspec(naked) void HandleErrorCode() { + __asm { + pushad; + pushfd; + push edi; + call SetErrorCode; + add esp, 0x4; + popfd; + popad; + call kHookSearchContactErrcodeNextAddr; + jmp kHookSearchContactErrcodeJmpBackAddr; + } +} + +__declspec(naked) void HandleUserInfoDetail() { + __asm { + pushad; + pushfd; + push dword ptr [ebp + 0x14]; + call SetUserInfoDetail; + add esp, 0x4; + popfd; + popad; + call kHookUserInfoNextAddr; + jmp kHookUserInfoJmpBackAddr; + } +} + +int SearchContactNetScene(wchar_t *keyword,UserInfo ** user) { + int success = -1; + HookSearchContact(); + DeleteUserInfoCache(); + DWORD base = GetWeChatWinBase(); + DWORD search_contact_mgr_addr = base + WX_SEARCH_CONTACT_MGR_OFFSeT; + DWORD search_contact_addr = base + WX_SEARCH_CONTACT_OFFSeT; + WeChatString key(keyword); + + __asm { + pushad; + pushfd; + call search_contact_mgr_addr; + lea ebx, key; + push ebx; + mov ecx, eax; + call search_contact_addr; + popfd; + popad; + } + success = 1; + while (userinfo.error_code == 1 && kSearchContactHooked) { + Sleep(20); + } + if (userinfo.error_code == 0) { + while (userinfo.over == false && kSearchContactHooked) { + Sleep(20); + } + } + *user= &userinfo; + return success; +} + +int HookSearchContact() { + kWeChatWinBase = GetWeChatWinBase(); + if (!kWeChatWinBase) { + return -1; + } + if (kSearchContactHooked) { + return 2; + } + kHookSearchContactErrcodeNextAddr = + kWeChatWinBase + WX_SEARCH_CONTACT_ERROR_CODE_HOOK_NEXT_OFFSeT; + kHookSearchContactErrcodeAddr = + kWeChatWinBase + WX_SEARCH_CONTACT_ERROR_CODE_HOOK_OFFSeT; + kHookSearchContactErrcodeJmpBackAddr = kHookSearchContactErrcodeAddr + 0x5; + + kHookUserInfoNextAddr = + kWeChatWinBase + WX_SEARCH_CONTACT_DETAIL_HOOK_NEXT_OFFSeT; + kHookUserInfoAddr = kWeChatWinBase + WX_SEARCH_CONTACT_DETAIL_HOOK_OFFSeT; + kHookUserInfoJmpBackAddr = kHookUserInfoAddr + 0x5; + HookAnyAddress(kHookSearchContactErrcodeAddr, + (LPVOID)HandleErrorCode, + kHookSearchContactErrcodeOldAsm); + HookAnyAddress(kHookUserInfoAddr, (LPVOID)HandleUserInfoDetail, kHookUserInfoOldAsm); + kSearchContactHooked = true; + return 1; +} + +int UnHookSearchContact() { + if (!kSearchContactHooked) return 2; + UnHookAnyAddress(kHookSearchContactErrcodeAddr, + kHookSearchContactErrcodeOldAsm); + UnHookAnyAddress(kHookUserInfoAddr, kHookUserInfoOldAsm); + kSearchContactHooked = false; + return 1; +} \ No newline at end of file diff --git a/src/search_contact.h b/src/search_contact.h new file mode 100644 index 0000000..5af1548 --- /dev/null +++ b/src/search_contact.h @@ -0,0 +1,8 @@ +#ifndef SEARCH_CONTACT_H_ +#define SEARCH_CONTACT_H_ +#include "wechat_data.h" +int SearchContactNetScene(wchar_t *keyword,UserInfo ** user); + +int HookSearchContact(); +int UnHookSearchContact(); +#endif \ No newline at end of file From 822e076a82588c6416ff6069c9379ab92f1bfe49 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Fri, 17 Feb 2023 14:49:58 +0800 Subject: [PATCH 35/59] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index af99860..7b109e0 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ vcpkg 2023-02-13 : 新增查询联系人昵称功能。 -2023-02-17 : 新增通过wxid添加好友。 +2023-02-17 : 新增通过wxid添加好友和查找微信。 #### 功能预览: 0.检查是否登录 From 04e79672367027784a9863fc35469f9621d2b609 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Mon, 20 Feb 2023 10:38:22 +0800 Subject: [PATCH 36/59] =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E7=9A=84=E5=B4=A9=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db_operation.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/db_operation.cc b/src/db_operation.cc index daecc1e..580d424 100644 --- a/src/db_operation.cc +++ b/src/db_operation.cc @@ -97,10 +97,15 @@ int Select(DWORD db_hanle, const char *sql, if (status == 0) { return 0; } + if(data.size() == 0){ + return 1; + } vector index; - for (size_t i = 0; i < data[0].size(); i++) + for (size_t i = 0; i < data[0].size(); i++){ index.push_back(data[0][i].column_name); + } query_result.push_back(index); + for (auto it : data) { vector item; for (size_t i = 0; i < it.size(); i++) { From 861a04e7a0a812acde3c0f1e19edfd0b9e8ec8dc Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Tue, 21 Feb 2023 16:24:39 +0800 Subject: [PATCH 37/59] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E5=8D=B8=E8=BD=BDdll?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api.cc | 2 +- src/common.cc | 7 +++++++ src/common.h | 1 + src/dllMain.cc | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/api.cc b/src/api.cc index 602524c..9c0a171 100644 --- a/src/api.cc +++ b/src/api.cc @@ -701,7 +701,7 @@ int http_start(int port) { #endif kHttpRuning = true; kHttpThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)http_server, - (LPVOID)port, NULL, 0); + (LPVOID)port, NULL, 0); return 0; } diff --git a/src/common.cc b/src/common.cc index 6d1288d..e60a3a1 100644 --- a/src/common.cc +++ b/src/common.cc @@ -135,4 +135,11 @@ BOOL FindOrCreateDirectoryW(const wchar_t *path) { return false; } return true; +} + +void CloseConsole(){ + fclose(stdin); + fclose(stdout); + fclose(stderr); + FreeConsole(); } \ No newline at end of file diff --git a/src/common.h b/src/common.h index f9eb55f..229b0fa 100644 --- a/src/common.h +++ b/src/common.h @@ -53,6 +53,7 @@ std::string Wstring2String(std::wstring wstr); /// @return BOOL FindOrCreateDirectoryW(const wchar_t *path); +void CloseConsole(); template std::vector split(T1 str, T2 letter) { diff --git a/src/dllMain.cc b/src/dllMain.cc index 355e03d..40a1cef 100644 --- a/src/dllMain.cc +++ b/src/dllMain.cc @@ -16,6 +16,7 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, break; } case DLL_PROCESS_DETACH: { + CloseConsole(); http_close(); break; } From 82a5ca28db33339d84db495dc170460e5ba55403 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 1 Mar 2023 14:11:06 +0800 Subject: [PATCH 38/59] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E8=8E=B7=E5=8F=96=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 ++- src/api.cc | 5 +- src/self_info.cc | 221 +++++++++++++++++++++++++--------------------- src/wechat_data.h | 5 +- 4 files changed, 128 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 7b109e0..cb013c6 100644 --- a/README.md +++ b/README.md @@ -197,17 +197,16 @@ vcpkg |result|string|成功提示| |data|object|响应内容| |account|string|账号| -|bigImage|string|头像大图| +|headImage|string|头像| |city|string|城市| |country|string|国家| -|currentDataPath|string|当前数据目录| -|dataRootPath|string|根目录| -|dataSavePath|string|保存目录| +|currentDataPath|string|当前数据目录,登录的账号目录| +|dataSavePath|string|微信保存目录| |mobile|string|手机| |name|string|昵称| |province|string|省| -|smallImage|string|小头像| |wxid|string|wxid| +|signature|string|个人签名| ###### 接口示例 入参: @@ -215,7 +214,7 @@ vcpkg ``` 响应: ``` javascript -{"code":1,"data":{"account":"xx","bigImage":"https://wx.qlogo.cn/mmhead/ver_1xx","city":"xx","country":"CN","currentDataPath":"xxx","dataRootPath":"C:\\xx","dataSavePath":"C:\\xx","mobie":"13949175447","name":"xx","province":"xx","smallImage":"xx","wxid":"xx"},"result":"OK"} +{"code":1,"data":{"account":"xx","headImage":"https://wx.qlogo.cn/mmhead/ver_1xx","city":"xx","country":"CN","currentDataPath":"C:\\xx\\wxid_xxxxx","dataSavePath":"C:\\xx","mobie":"13812345678","name":"xx","province":"xx","signature":"xx","wxid":"xx"},"result":"OK"} ``` diff --git a/src/api.cc b/src/api.cc index 9c0a171..9216e77 100644 --- a/src/api.cc +++ b/src/api.cc @@ -221,9 +221,8 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { {"account", self_info.account}, {"wxid", self_info.wxid}, {"mobile", self_info.mobile}, - {"smallImage", self_info.small_img}, - {"bigImage", self_info.big_img}, - {"dataRootPath",self_info.data_root_path}, + {"headImage", self_info.head_img}, + {"signature",self_info.signature}, {"dataSavePath",self_info.data_save_path}, {"currentDataPath",self_info.current_data_path}, }; diff --git a/src/self_info.cc b/src/self_info.cc index e34c7eb..8980a83 100644 --- a/src/self_info.cc +++ b/src/self_info.cc @@ -21,123 +21,140 @@ using namespace std; #define WX_CURRENT_DATA_PATH_OFFSET 0x2E4F290 #define WX_LOGOUT_OFFSET 0xdd5c90 -#define WX_ACCOUT_SERVICE_OFFSET 0x707960 - +#define WX_ACCOUNT_SERVICE_OFFSET 0x707960 +#define WX_GET_APP_DATA_SAVE_PATH_OFFSET 0xeb4b00 +#define WX_GET_CURRENT_DATA_PATH_OFFSET 0xc11140 int GetSelfInfo(SelfInfoInner &out) { DWORD base = GetWeChatWinBase(); -#ifdef _DEBUG - cout << "mobile:" << (char *)(base + WX_SELF_MOBILE_OFFSET) << endl; - cout << "name:" << (char *)(base + WX_SELF_NAME_OFFSET) << endl; - cout << "city:" << (char *)(base + WX_SELF_CITY_OFFSET) << endl; - cout << "city:" << (char *)(base + WX_SELF_CITY_OFFSET) << endl; - cout << "province:" << (char *)(base + WX_SELF_PROVINCE_OFFSET) << endl; - cout << "country:" << (char *)(base + WX_SELF_COUNTRY_OFFSET) << endl; - cout << "account:" << (char *)(base + WX_SELF_ACCOUNT_OFFSET) << endl; - cout << "wxid:" << (char *)(base + WX_SELF_ID_OFFSET) << endl; - cout << "small_img:" << (char *)(base + WX_SELF_SMALL_IMG_OFFSET) << endl; - cout << "big_img:" << (char *)(base + WX_SELF_BIG_IMG_OFFSET) << endl; -#endif - if (*(DWORD *)(base + WX_SELF_NAME_OFFSET) == 0 || - *(DWORD *)(base + WX_SELF_NAME_OFFSET + 0x10) == 0) { - out.name = string(); - } else { - out.name = string((char *)(base + WX_SELF_NAME_OFFSET), - *(DWORD *)(base + WX_SELF_NAME_OFFSET + 0x10)); + DWORD accout_service_addr = base + WX_ACCOUNT_SERVICE_OFFSET; + DWORD get_app_save_addr = base + WX_GET_APP_DATA_SAVE_PATH_OFFSET; + DWORD get_current_data_path_addr = base + WX_GET_CURRENT_DATA_PATH_OFFSET; + DWORD service_addr = NULL; + __asm { + PUSHAD + CALL accout_service_addr + MOV service_addr,EAX + POPAD } - if (*(DWORD *)(base + WX_SELF_MOBILE_OFFSET) == 0 || - *(DWORD *)(base + WX_SELF_MOBILE_OFFSET + 0x10) == 0) { - out.mobile = string(); - } else { - out.mobile = string((char *)(base + WX_SELF_MOBILE_OFFSET), - *(DWORD *)(base + WX_SELF_MOBILE_OFFSET + 0x10)); + if (service_addr) { + if (*(DWORD *)(service_addr + 0x44) == 0 || + *(DWORD *)(service_addr + 0x44 + 0x10) == 0) { + out.wxid = string(); + } else { + out.wxid = string(*(char **)(service_addr + 0x44), + *(DWORD *)(service_addr + 0x44 + 0x10)); + } + + if (*(DWORD *)(service_addr + 0xA8) == 0 || + *(DWORD *)(service_addr + 0xA8 + 0x10) == 0) { + out.account = string(); + } else { + out.account = string(*(char **)(service_addr + 0xA8), + *(DWORD *)(service_addr + 0xA8 + 0x10)); + } + + if (*(DWORD *)(service_addr + 0xC0) == 0 || + *(DWORD *)(service_addr + 0xC0 + 0x10) == 0) { + out.mobile = string(); + } else { + out.mobile = string((char *)(service_addr + 0xC0), + *(DWORD *)(service_addr + 0xC0 + 0x10)); + } + + if (*(DWORD *)(service_addr + 0xD8) == 0 || + *(DWORD *)(service_addr + 0xD8 + 0x10) == 0) { + out.signature = string(); + } else { + out.signature = string((char *)(service_addr + 0xD8), + *(DWORD *)(service_addr + 0xD8 + 0x10)); + } + + if (*(DWORD *)(service_addr + 0xF0) == 0 || + *(DWORD *)(service_addr + 0xF0 + 0x10) == 0) { + out.country = string(); + } else { + out.country = string((char *)(service_addr + 0xF0), + *(DWORD *)(service_addr + 0xF0 + 0x10)); + } + + if (*(DWORD *)(service_addr + 0x108) == 0 || + *(DWORD *)(service_addr + 0x108 + 0x10) == 0) { + out.province = string(); + } else { + out.province = string((char *)(service_addr + 0x108), + *(DWORD *)(service_addr + 0x108 + 0x10)); + } + + if (*(DWORD *)(service_addr + 0x120) == 0 || + *(DWORD *)(service_addr + 0x120 + 0x10) == 0) { + out.city = string(); + } else { + out.city = string((char *)(service_addr + 0x120), + *(DWORD *)(service_addr + 0x120 + 0x10)); + } + + if (*(DWORD *)(service_addr + 0x150) == 0 || + *(DWORD *)(service_addr + 0x150 + 0x10) == 0) { + out.name = string(); + } else { + out.name = string((char *)(service_addr + 0x150), + *(DWORD *)(service_addr + 0x150 + 0x10)); + } + + if (*(DWORD *)(service_addr + 0x304) == 0 || + *(DWORD *)(service_addr + 0x304 + 0x10) == 0) { + out.head_img = string(); + } else { + out.head_img = string(*(char **)(service_addr + 0x304), + *(DWORD *)(service_addr + 0x304 + 0x10)); + } } - if (*(DWORD *)(base + WX_SELF_CITY_OFFSET) == 0 || - *(DWORD *)(base + WX_SELF_CITY_OFFSET + 0x10) == 0) { - out.city = string(); - } else { - out.city = string((char *)(base + WX_SELF_CITY_OFFSET), - *(DWORD *)(base + WX_SELF_CITY_OFFSET + 0x10)); + WeChatString data_save_path; + WeChatString current_data_path; + + __asm { + PUSHAD + LEA ECX,data_save_path + CALL get_app_save_addr + LEA ECX,current_data_path + CALL get_current_data_path_addr + POPAD } - if (*(DWORD *)(base + WX_SELF_PROVINCE_OFFSET) == 0 || - *(DWORD *)(base + WX_SELF_PROVINCE_OFFSET + 0x10) == 0) { - out.province = string(); - } else { - out.province = string((char *)(base + WX_SELF_PROVINCE_OFFSET), - *(DWORD *)(base + WX_SELF_PROVINCE_OFFSET + 0x10)); - } - - if (*(DWORD *)(base + WX_SELF_COUNTRY_OFFSET) == 0 || - *(DWORD *)(base + WX_SELF_COUNTRY_OFFSET + 0x10) == 0) { - out.country = string(); - } else { - out.country = string((char *)(base + WX_SELF_COUNTRY_OFFSET), - *(DWORD *)(base + WX_SELF_COUNTRY_OFFSET + 0x10)); - } - - if (*(DWORD *)(base + WX_SELF_ACCOUNT_OFFSET) == 0 || - *(DWORD *)(base + WX_SELF_ACCOUNT_OFFSET + 0x10) == 0) { - out.account = string(); - } else { - out.account = string(*(char **)(base + WX_SELF_ACCOUNT_OFFSET), - *(DWORD *)(base + WX_SELF_ACCOUNT_OFFSET + 0x10)); - } - - if (*(DWORD *)(base + WX_SELF_ID_OFFSET) == 0 || - *(DWORD *)(base + WX_SELF_ID_OFFSET + 0x10) == 0) { - out.wxid = string(); - } else { - out.wxid = string(*(char **)(base + WX_SELF_ID_OFFSET), - *(DWORD *)(base + WX_SELF_ID_OFFSET + 0x10)); - } - - if (*(DWORD *)(base + WX_SELF_SMALL_IMG_OFFSET) == 0 || - *(DWORD *)(base + WX_SELF_SMALL_IMG_OFFSET + 0x10) == 0) { - out.small_img = string(); - } else { - out.small_img = string(*(char **)(base + WX_SELF_SMALL_IMG_OFFSET), - *(DWORD *)(base + WX_SELF_SMALL_IMG_OFFSET + 0x10)); - } - - if (*(DWORD *)(base + WX_SELF_BIG_IMG_OFFSET) == 0 || - *(DWORD *)(base + WX_SELF_BIG_IMG_OFFSET + 0x10) == 0) { - out.big_img = string(); - } else { - out.big_img = string(*(char **)(base + WX_SELF_BIG_IMG_OFFSET), - *(DWORD *)(base + WX_SELF_BIG_IMG_OFFSET + 0x10)); - } - - if (*(DWORD *)(base + WX_APP_DATA_ROOT_PATH_OFFSET) == 0 || - *(DWORD *)(base + WX_APP_DATA_ROOT_PATH_OFFSET + 0x4) == 0) { - out.data_root_path = string(); - } else { - out.data_root_path = Wstring2String(wstring(*(wchar_t **)(base + WX_APP_DATA_ROOT_PATH_OFFSET), - *(DWORD *)(base + WX_APP_DATA_ROOT_PATH_OFFSET + 0x4))); - } - - if (*(DWORD *)(base + WX_APP_DATA_SAVE_PATH_OFFSET) == 0 || - *(DWORD *)(base + WX_APP_DATA_SAVE_PATH_OFFSET + 0x4) == 0) { + if (data_save_path.ptr) { + out.data_save_path = + Wstring2String(wstring(data_save_path.ptr, data_save_path.length)); + }else { out.data_save_path = string(); - } else { - out.data_save_path = Wstring2String(wstring(*(wchar_t **)(base + WX_APP_DATA_SAVE_PATH_OFFSET), - *(DWORD *)(base + WX_APP_DATA_SAVE_PATH_OFFSET + 0x4))); - } - if (*(DWORD *)(base + WX_CURRENT_DATA_PATH_OFFSET) == 0 || - *(DWORD *)(base + WX_CURRENT_DATA_PATH_OFFSET + 0x4) == 0) { - out.current_data_path = string(); - } else { - out.current_data_path = Wstring2String(wstring(*(wchar_t **)(base + WX_CURRENT_DATA_PATH_OFFSET), - *(DWORD *)(base + WX_CURRENT_DATA_PATH_OFFSET + 0x4))); } + if (current_data_path.ptr) { + out.current_data_path = + Wstring2String(wstring(current_data_path.ptr, current_data_path.length)); + }else { + out.current_data_path = string(); + } return 1; } int CheckLogin(){ DWORD base = GetWeChatWinBase(); - return *(DWORD*) (base + WX_LOGIN_STATUS_OFFSET); + DWORD accout_service_addr = base + WX_ACCOUNT_SERVICE_OFFSET; + DWORD service_addr = NULL; + __asm { + PUSHAD + CALL accout_service_addr + MOV service_addr,EAX + POPAD + } + if (service_addr) { + return *(DWORD *)(service_addr + 0x4C8); + } + else { + return 0; + } } @@ -147,7 +164,7 @@ int Logout(){ return success; } DWORD base = GetWeChatWinBase(); - DWORD account_service_addr = base + WX_ACCOUT_SERVICE_OFFSET; + DWORD account_service_addr = base + WX_ACCOUNT_SERVICE_OFFSET; DWORD logout_addr = base + WX_LOGOUT_OFFSET; __asm{ PUSHAD diff --git a/src/wechat_data.h b/src/wechat_data.h index 6f0d63e..15c08b0 100644 --- a/src/wechat_data.h +++ b/src/wechat_data.h @@ -149,10 +149,9 @@ struct SelfInfoInner{ std::string account; std::string wxid; std::string mobile; - std::string small_img; - std::string big_img; - std::string data_root_path; + std::string head_img; std::string data_save_path; + std::string signature; std::string current_data_path; }; From 7e89c6ef2d0091193902645818d4f494ce950f3a Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 1 Mar 2023 17:02:15 +0800 Subject: [PATCH 39/59] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E4=B8=AA=E4=BA=BA=E4=BF=A1=E6=81=AF=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/self_info.cc | 106 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 31 deletions(-) diff --git a/src/self_info.cc b/src/self_info.cc index 8980a83..3dd47bf 100644 --- a/src/self_info.cc +++ b/src/self_info.cc @@ -20,10 +20,10 @@ using namespace std; #define WX_APP_DATA_SAVE_PATH_OFFSET 0x2E52DB0 #define WX_CURRENT_DATA_PATH_OFFSET 0x2E4F290 -#define WX_LOGOUT_OFFSET 0xdd5c90 +#define WX_LOGOUT_OFFSET 0xdd5c90 #define WX_ACCOUNT_SERVICE_OFFSET 0x707960 #define WX_GET_APP_DATA_SAVE_PATH_OFFSET 0xeb4b00 -#define WX_GET_CURRENT_DATA_PATH_OFFSET 0xc11140 +#define WX_GET_CURRENT_DATA_PATH_OFFSET 0xc11140 int GetSelfInfo(SelfInfoInner &out) { DWORD base = GetWeChatWinBase(); DWORD accout_service_addr = base + WX_ACCOUNT_SERVICE_OFFSET; @@ -41,72 +41,117 @@ int GetSelfInfo(SelfInfoInner &out) { *(DWORD *)(service_addr + 0x44 + 0x10) == 0) { out.wxid = string(); } else { - out.wxid = string(*(char **)(service_addr + 0x44), - *(DWORD *)(service_addr + 0x44 + 0x10)); + if (*(DWORD *)(service_addr + 0x44 + 0x14) == 0xF) { + out.wxid = string((char *)(service_addr + 0x44), + *(DWORD *)(service_addr + 0x44 + 0x10)); + } else { + out.wxid = string(*(char **)(service_addr + 0x44), + *(DWORD *)(service_addr + 0x44 + 0x10)); + } } if (*(DWORD *)(service_addr + 0xA8) == 0 || *(DWORD *)(service_addr + 0xA8 + 0x10) == 0) { out.account = string(); } else { - out.account = string(*(char **)(service_addr + 0xA8), - *(DWORD *)(service_addr + 0xA8 + 0x10)); + if (*(DWORD *)(service_addr + 0xA8 + 0x14) == 0xF) { + out.account = string((char *)(service_addr + 0xA8), + *(DWORD *)(service_addr + 0xA8 + 0x10)); + } else { + out.account = string(*(char **)(service_addr + 0xA8), + *(DWORD *)(service_addr + 0xA8 + 0x10)); + } } if (*(DWORD *)(service_addr + 0xC0) == 0 || *(DWORD *)(service_addr + 0xC0 + 0x10) == 0) { out.mobile = string(); } else { - out.mobile = string((char *)(service_addr + 0xC0), - *(DWORD *)(service_addr + 0xC0 + 0x10)); + if (*(DWORD *)(service_addr + 0xC0 + 0x14) == 0xF) { + out.mobile = string((char *)(service_addr + 0xC0), + *(DWORD *)(service_addr + 0xC0 + 0x10)); + } else { + out.mobile = string(*(char **)(service_addr + 0xC0), + *(DWORD *)(service_addr + 0xC0 + 0x10)); + } } if (*(DWORD *)(service_addr + 0xD8) == 0 || *(DWORD *)(service_addr + 0xD8 + 0x10) == 0) { out.signature = string(); } else { - out.signature = string((char *)(service_addr + 0xD8), - *(DWORD *)(service_addr + 0xD8 + 0x10)); + if (*(DWORD *)(service_addr + 0xD8 + 0x14) == 0xF) { + out.signature = string((char *)(service_addr + 0xD8), + *(DWORD *)(service_addr + 0xD8 + 0x10)); + } else { + out.signature = string(*(char **)(service_addr + 0xD8), + *(DWORD *)(service_addr + 0xD8 + 0x10)); + } } if (*(DWORD *)(service_addr + 0xF0) == 0 || *(DWORD *)(service_addr + 0xF0 + 0x10) == 0) { out.country = string(); } else { - out.country = string((char *)(service_addr + 0xF0), - *(DWORD *)(service_addr + 0xF0 + 0x10)); + if (*(DWORD *)(service_addr + 0xF0 + 0x14) == 0xF) { + out.country = string((char *)(service_addr + 0xF0), + *(DWORD *)(service_addr + 0xF0 + 0x10)); + } else { + out.country = string(*(char **)(service_addr + 0xF0), + *(DWORD *)(service_addr + 0xF0 + 0x10)); + } } if (*(DWORD *)(service_addr + 0x108) == 0 || *(DWORD *)(service_addr + 0x108 + 0x10) == 0) { out.province = string(); } else { - out.province = string((char *)(service_addr + 0x108), - *(DWORD *)(service_addr + 0x108 + 0x10)); + if (*(DWORD *)(service_addr + 0x108 + 0x14) == 0xF) { + out.province = string((char *)(service_addr + 0x108), + *(DWORD *)(service_addr + 0x108 + 0x10)); + } else { + out.province = string(*(char **)(service_addr + 0x108), + *(DWORD *)(service_addr + 0x108 + 0x10)); + } } if (*(DWORD *)(service_addr + 0x120) == 0 || *(DWORD *)(service_addr + 0x120 + 0x10) == 0) { out.city = string(); } else { - out.city = string((char *)(service_addr + 0x120), - *(DWORD *)(service_addr + 0x120 + 0x10)); + if (*(DWORD *)(service_addr + 0x120 + 0x14) == 0xF) { + out.city = string((char *)(service_addr + 0x120), + *(DWORD *)(service_addr + 0x120 + 0x10)); + } else { + out.city = string(*(char **)(service_addr + 0x120), + *(DWORD *)(service_addr + 0x120 + 0x10)); + } } if (*(DWORD *)(service_addr + 0x150) == 0 || *(DWORD *)(service_addr + 0x150 + 0x10) == 0) { out.name = string(); } else { - out.name = string((char *)(service_addr + 0x150), - *(DWORD *)(service_addr + 0x150 + 0x10)); + if (*(DWORD *)(service_addr + 0x150 + 0x14) == 0xF) { + out.name = string((char *)(service_addr + 0x150), + *(DWORD *)(service_addr + 0x150 + 0x10)); + } else { + out.name = string(*(char **)(service_addr + 0x150), + *(DWORD *)(service_addr + 0x150 + 0x10)); + } } if (*(DWORD *)(service_addr + 0x304) == 0 || *(DWORD *)(service_addr + 0x304 + 0x10) == 0) { out.head_img = string(); } else { - out.head_img = string(*(char **)(service_addr + 0x304), - *(DWORD *)(service_addr + 0x304 + 0x10)); + if (*(DWORD *)(service_addr + 0x304 + 0x14) == 0xF) { + out.head_img = string((char *)(service_addr + 0x304), + *(DWORD *)(service_addr + 0x304 + 0x10)); + } else { + out.head_img = string(*(char **)(service_addr + 0x304), + *(DWORD *)(service_addr + 0x304 + 0x10)); + } } } @@ -125,21 +170,21 @@ int GetSelfInfo(SelfInfoInner &out) { if (data_save_path.ptr) { out.data_save_path = Wstring2String(wstring(data_save_path.ptr, data_save_path.length)); - }else { + } + else { out.data_save_path = string(); } - if (current_data_path.ptr) { - out.current_data_path = - Wstring2String(wstring(current_data_path.ptr, current_data_path.length)); - }else { + if (current_data_path.ptr) { + out.current_data_path = Wstring2String( + wstring(current_data_path.ptr, current_data_path.length)); + } else { out.current_data_path = string(); } return 1; } - -int CheckLogin(){ +int CheckLogin() { DWORD base = GetWeChatWinBase(); DWORD accout_service_addr = base + WX_ACCOUNT_SERVICE_OFFSET; DWORD service_addr = NULL; @@ -157,16 +202,15 @@ int CheckLogin(){ } } - -int Logout(){ +int Logout() { int success = 0; - if(!CheckLogin()){ + if (!CheckLogin()) { return success; } DWORD base = GetWeChatWinBase(); DWORD account_service_addr = base + WX_ACCOUNT_SERVICE_OFFSET; DWORD logout_addr = base + WX_LOGOUT_OFFSET; - __asm{ + __asm { PUSHAD CALL account_service_addr PUSH 0x0 From b8dc0325258a27c3808d821e3016d3cde8f1ed3f Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Thu, 2 Mar 2023 11:38:47 +0800 Subject: [PATCH 40/59] =?UTF-8?q?=E5=8F=91=E9=80=81@=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 41 +++++++++++++++++++++++++++ src/api.cc | 10 +++++++ src/send_text.cc | 70 +++++++++++++++++++++++++++++++++++++++++++++++ src/send_text.h | 2 +- src/wechat_data.h | 6 ++++ 5 files changed, 128 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cb013c6..b22233b 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,47 @@ vcpkg +#### 3.发送@文本消息** +###### 接口功能 +> 发送@文本消息 + +###### 接口地址 +> [/api/?type=3](/api/?type=3) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | +|msg|true |string|消息文本内容| +|wxids |true |string| @的用户微信id用,号分隔, @所有人 传 notify@all ,区分大小写 | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,不为0成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 + +入参: +``` javascript +{ + "chatRoomId": "123333@chatroom", + "wxids":"notify@all,wxid_122221", + "msg": "12333" +} +``` +响应: +``` javascript +{"code":345686720,"result":"OK"} +``` + + + #### 5.发送图片消息** ###### 接口功能 > 发送图片消息 diff --git a/src/api.cc b/src/api.cc index 9216e77..ae13bef 100644 --- a/src/api.cc +++ b/src/api.cc @@ -240,6 +240,16 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_MSG_SEND_AT: { + wstring chat_room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); + vector wxids = get_http_param_array(hm, j_param, "wxids", is_post); + wstring msg = get_http_req_param(hm, j_param, "msg", is_post); + vector wxid_list; + for (unsigned int i = 0; i < wxids.size(); i++){ + wxid_list.push_back(WS2LW(wxids[i])); + } + int success = SendAtText(WS2LW(chat_room_id), wxid_list.data(), wxid_list.size(),WS2LW(msg)); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); break; } case WECHAT_MSG_SEND_CARD: { diff --git a/src/send_text.cc b/src/send_text.cc index 69bd08e..265e8d4 100644 --- a/src/send_text.cc +++ b/src/send_text.cc @@ -4,12 +4,14 @@ #include "common.h" #include "wechat_data.h" +#include "contact.h" #define WX_SEND_TEXT_OFFSET 0xc71a60 #define WX_SEND_MESSAGE_MGR_OFFSET 0x706d30 #define WX_FREE_CHAT_MSG_OFFSET 0x6f4ea0 +using namespace std; /// @brief 发生文本消息 /// @param wxid wxid /// @param msg 文本消息 @@ -45,4 +47,72 @@ int SendText(wchar_t* wxid, wchar_t* msg) { POPAD } return success; +} + + + +int SendAtText(wchar_t* chat_room_id,wchar_t** wxids,int len,wchar_t* msg){ + int success = -1; + WeChatString * at_users = new WeChatString[len+1]; + wstring at_msg = L""; + int number =0; + for (int i = 0; i < len; i++) { + wstring nickname; + if (!lstrcmpiW((wchar_t *)wxids[i], (wchar_t *)L"notify@all")) { + nickname = L""; + } else { + nickname = GetContactOrChatRoomNickname(wxids[i]); + } + if (nickname.length() == 0) { + continue; + } + + WeChatString temp = {0}; + temp.ptr = (wchar_t *)wxids[i]; + temp.length = wcslen((wchar_t *)wxids[i]); + temp.max_length = wcslen((wchar_t *)wxids[i]) * 2; + memcpy(&at_users[number], &temp, sizeof(WeChatString)); + at_msg = at_msg + L"@" + nickname + L" "; + number++; + } + if (number < 1){ + return success; + } + wstring origin(msg); + at_msg += origin; + AtInner at_list = {0}; + at_list.start = (DWORD)at_users; + at_list.finsh = (DWORD)&at_users[number]; + at_list.end = (DWORD)&at_users[number]; + WeChatString to_user(chat_room_id); + WeChatString text_msg((wchar_t *)at_msg.c_str()); + wchar_t **msg_pptr = &text_msg.ptr; + + DWORD base = GetWeChatWinBase(); + DWORD send_message_mgr_addr = base + WX_SEND_MESSAGE_MGR_OFFSET; + DWORD send_text_msg_addr = base + WX_SEND_TEXT_OFFSET; + DWORD free_chat_msg_addr = base + WX_FREE_CHAT_MSG_OFFSET; + char chat_msg[0x2C4] ={0}; + + __asm{ + PUSHAD + CALL send_message_mgr_addr + PUSH 0x0 + PUSH 0x0 + PUSH 0x0 + PUSH 0x1 + LEA EAX,at_list + PUSH EAX + MOV EAX,msg_pptr + PUSH EAX + LEA EDX,to_user + LEA ECX,chat_msg + CALL send_text_msg_addr + MOV success,EAX + ADD ESP,0x18 + LEA ECX,chat_msg + CALL free_chat_msg_addr + POPAD + } + return success; } \ No newline at end of file diff --git a/src/send_text.h b/src/send_text.h index 57005dd..c7b1a76 100644 --- a/src/send_text.h +++ b/src/send_text.h @@ -2,5 +2,5 @@ #define SEND_TEXT_H_ int SendText(wchar_t* wxid, wchar_t* msg); - +int SendAtText(wchar_t* chat_room_id,wchar_t** wxids,int len,wchar_t* msg); #endif \ No newline at end of file diff --git a/src/wechat_data.h b/src/wechat_data.h index 15c08b0..f15b8e3 100644 --- a/src/wechat_data.h +++ b/src/wechat_data.h @@ -180,4 +180,10 @@ struct UserInfo { DWORD sex; BOOL over; }; + +struct AtInner{ + DWORD start; + DWORD finsh; + DWORD end; +}; #endif From 4eab2a2414975c61cb5b452def1ac62fe8792adb Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Sat, 4 Mar 2023 11:30:59 +0800 Subject: [PATCH 41/59] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E9=99=84=E4=BB=B6=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 41 +++++++++++ src/api.cc | 8 ++ src/api.h | 1 + src/common.cc | 2 +- src/download.cc | 183 ++++++++++++++++++++++++++++++++++++++++++++++ src/download.h | 5 ++ src/wechat_data.h | 147 +++++++++++++++++++++++++++++++++++++ 7 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 src/download.cc create mode 100644 src/download.h diff --git a/README.md b/README.md index b22233b..70272e9 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ vcpkg 53.朋友圈首页 54.朋友圈下一页 55.获取联系人或者群名称 +56.获取消息附件(图片,视频,文件) ### 接口文档: @@ -1464,6 +1465,46 @@ vcpkg +#### 56.获取消息附件** +###### 接口功能 +> 根据消息id,下载消息附件(图片,视频,文件) + +###### 接口地址 +> [/api/?type=56](/api/?type=56) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|msgId |true |string| 消息id | + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,0成功, 非0失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "msgId": 3224560917391784099 + +} + +``` +响应: +``` javascript +{"code":0,"result":"OK"} +``` + + #### 感谢 https://github.com/ljc545w/ComWeChatRobot diff --git a/src/api.cc b/src/api.cc index ae13bef..4183138 100644 --- a/src/api.cc +++ b/src/api.cc @@ -23,6 +23,7 @@ #include "confirm_receipt.h" #include "sns.h" #include "search_contact.h" +#include "download.h" #pragma comment(lib, "ws2_32.lib") using namespace std; @@ -652,6 +653,13 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { ret = ret_data.dump(); break; } + case WECHAT_ATTACH_DOWNLOAD:{ + ULONG64 msg_id = get_http_param_ulong64(hm, j_param, "msgId", is_post); + int success = DoDownloadTask(msg_id); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } default: break; } diff --git a/src/api.h b/src/api.h index 94b9c50..3b44f4a 100644 --- a/src/api.h +++ b/src/api.h @@ -70,6 +70,7 @@ typedef enum WECHAT_HTTP_APISTag WECHAT_SNS_GET_FIRST_PAGE, WECHAT_SNS_GET_NEXT_PAGE, WECHAT_CONTACT_NAME, + WECHAT_ATTACH_DOWNLOAD, } WECHAT_HTTP_APIS, *PWECHAT_HTTP_APIS; diff --git a/src/common.cc b/src/common.cc index e60a3a1..d3e0ce1 100644 --- a/src/common.cc +++ b/src/common.cc @@ -126,7 +126,7 @@ string Wstring2String(wstring wstr) { BOOL FindOrCreateDirectoryW(const wchar_t *path) { WIN32_FIND_DATAW fd; HANDLE hFind = ::FindFirstFileW(path, &fd); - if (hFind != INVALID_HANDLE_VALUE) { + if (hFind != INVALID_HANDLE_VALUE && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { FindClose(hFind); return true; } diff --git a/src/download.cc b/src/download.cc new file mode 100644 index 0000000..f22a6d0 --- /dev/null +++ b/src/download.cc @@ -0,0 +1,183 @@ +#include "pch.h" +#include "download.h" + +#include "common.h" +#include "get_db_handle.h" + +#include "wechat_data.h" + +#define WX_NEW_CHAT_MSG_OFFSET 0x70e2a0 +#define WX_GET_PRE_DOWNLOAD_MGR_OFFSET 0x7ae310 +#define WX_PUSH_ATTACH_TASK_OFFSET 0x7c94a0 +#define WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET 0x6f5370 +#define WX_FREE_CHAT_MSG_OFFSET 0x6f4ea0 +#define WX_CHAT_MGR_OFFSET 0x732660 +#define WX_GET_MGR_BY_PREFIX_LOCAL_ID_OFFSET 0xb54950 +#define WX_GET_CURRENT_DATA_PATH_OFFSET 0xc11140 +#define WX_APP_MSG_INFO_OFFSET 0x7571d0 +#define WX_GET_APP_MSG_XML_OFFSET 0xddef80 +#define WX_FREE_APP_MSG_INFO_OFFSET 0x73d820 +#define WX_PUSH_THUMB_TASK_OFFSET 0x7c93a0 +#define WX_VIDEO_MGR_OFFSET 0x7c7300 +#define WX_DOWNLOAD_VIDEO_IMG_OFFSET 0xcc6d80 + +using namespace std; + +int DoDownloadTask(ULONG64 msg_id) { + int success = -1; + int db_index = 0; + int local_id = GetLocalIdByMsgId(msg_id, db_index); + if (local_id < 1) { + return -2; + } + + char chat_msg[0x2C4] = {0}; + DWORD base = GetWeChatWinBase(); + DWORD new_chat_msg_addr = base + WX_NEW_CHAT_MSG_OFFSET; + DWORD get_chat_mgr_addr = base + WX_CHAT_MGR_OFFSET; + DWORD pre_download_mgr_addr = base + WX_GET_PRE_DOWNLOAD_MGR_OFFSET; + DWORD push_attach_task_addr = base + WX_PUSH_ATTACH_TASK_OFFSET; + DWORD free_addr = base + WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET; + DWORD get_by_local_Id_addr = base + WX_GET_MGR_BY_PREFIX_LOCAL_ID_OFFSET; + DWORD get_current_data_path_addr = base + WX_GET_CURRENT_DATA_PATH_OFFSET; + DWORD free_app_msg_info_addr = base + WX_FREE_APP_MSG_INFO_OFFSET; + DWORD push_thumb_task_addr = base + WX_PUSH_THUMB_TASK_OFFSET; + DWORD video_mgr_addr = base + WX_VIDEO_MGR_OFFSET; + DWORD download_video_image_addr = base + WX_VIDEO_MGR_OFFSET; + + WeChatString current_data_path; + + __asm { + PUSHAD + PUSHFD + LEA ECX,current_data_path + CALL get_current_data_path_addr + + LEA ECX,chat_msg + CALL new_chat_msg_addr + + CALL get_chat_mgr_addr + PUSH dword ptr [db_index] + LEA ECX,chat_msg + PUSH dword ptr [local_id] + CALL get_by_local_Id_addr + ADD ESP,0x8 + POPFD + POPAD + } + wstring save_path = L""; + wstring thumb_path = L""; + if (current_data_path.length > 0) { + save_path += current_data_path.ptr; + save_path += L"wxhelper"; + } else { + return -1; + } + + if (!FindOrCreateDirectoryW(save_path.c_str())) { + return -3; + } + DWORD type = *(DWORD *)(chat_msg + 0x38); + wchar_t *content = *(wchar_t **)(chat_msg + 0x70); + + switch (type) { + case 0x3: { + save_path += L"\\image"; + if (!FindOrCreateDirectoryW(save_path.c_str())) { + return -3; + } + save_path = save_path +L"\\"+ to_wstring(msg_id) + L".png"; + break; + } + case 0x3E: + case 0x2B: { + save_path += L"\\video"; + if (!FindOrCreateDirectoryW(save_path.c_str())) { + return -3; + } + thumb_path = save_path + L"\\"+ to_wstring(msg_id) + L".jpg"; + save_path = save_path + L"\\"+ to_wstring(msg_id) + L".mp4"; + + break; + } + case 0x31: { + save_path += L"\\file"; + wcout << save_path << endl; + if (!FindOrCreateDirectoryW(save_path.c_str())) { + return -3; + } + char xml_app_msg[0xC80] = {0}; + DWORD new_app_msg_addr = base + WX_APP_MSG_INFO_OFFSET; + DWORD get_xml_addr = base + WX_GET_APP_MSG_XML_OFFSET; + WeChatString w_content(content); + + __asm { + PUSHAD + PUSHFD + LEA ECX,xml_app_msg + CALL new_app_msg_addr + PUSH 0x1 + LEA EAX,w_content + PUSH EAX + LEA ECX,xml_app_msg + CALL get_xml_addr + MOV success,EAX + LEA ECX,xml_app_msg + CALL free_app_msg_info_addr + POPFD + POPAD + } + if (success != 1) { + return -4; + } + WeChatString *file_name = (WeChatString *)((DWORD)xml_app_msg + 0x44); + save_path = save_path +L"\\" + to_wstring(msg_id) + L"_" + + wstring(file_name->ptr, file_name->length); + break; + } + default: + break; + } + WeChatString w_save_path(save_path); + WeChatString w_thumb_path(thumb_path); + int temp =1; + memcpy(&chat_msg[0x19C], &w_thumb_path, sizeof(w_thumb_path)); + memcpy(&chat_msg[0x1B0], &w_save_path, sizeof(w_save_path)); + memcpy(&chat_msg[0x290], &temp, sizeof(temp)); + // note: the image has been downloaded and will not be downloaded again + // use low-level method + // this function does not work, need to modify chatmsg. + // if (type == 0x3E || type == 0x2B){ + // __asm{ + // PUSHAD + // PUSHFD + // CALL video_mgr_addr + // LEA ECX,chat_msg + // PUSH ECX + // MOV ECX,EAX + // CALL download_video_image_addr + // POPFD + // POPAD + // } + // } + + __asm { + PUSHAD + PUSHFD + CALL pre_download_mgr_addr + PUSH 0x1 + PUSH 0x0 + LEA ECX,chat_msg + PUSH ECX + MOV ECX,EAX + CALL push_attach_task_addr + MOV success,EAX + LEA ECX,chat_msg + PUSH 0x0 + CALL free_addr + POPFD + POPAD + } + + return success; +} \ No newline at end of file diff --git a/src/download.h b/src/download.h new file mode 100644 index 0000000..fcd15b9 --- /dev/null +++ b/src/download.h @@ -0,0 +1,5 @@ +#ifndef DOWNLOAD_H_ +#define DOWNLOAD_H_ + +int DoDownloadTask(ULONG64 msg_id); +#endif \ No newline at end of file diff --git a/src/wechat_data.h b/src/wechat_data.h index f15b8e3..cf4f997 100644 --- a/src/wechat_data.h +++ b/src/wechat_data.h @@ -186,4 +186,151 @@ struct AtInner{ DWORD finsh; DWORD end; }; + +struct ChatMsg { + DWORD **field0_0x0; + DWORD field1_0x4; + ULONG64 sequence; + DWORD field3_0x10; + DWORD field4_0x14; + ULONG64 msgSequence; + DWORD localId; + DWORD field7_0x24; + DWORD field8_0x28; + DWORD field9_0x2c; + ULONG64 msgId; + DWORD type; + DWORD isSendMsg; + DWORD msgStatus; + DWORD timestamp; + WeChatString talker; + DWORD field16_0x5c; + DWORD field17_0x60; + DWORD field18_0x64; + DWORD field19_0x68; + DWORD field20_0x6c; + WeChatString content; + DWORD field22_0x84; + DWORD field23_0x88; + DWORD field24_0x8c; + DWORD field25_0x90; + DWORD field26_0x94; + DWORD field27_0x98; + DWORD field28_0x9c; + DWORD field29_0xa0; + DWORD field30_0xa4; + DWORD field31_0xa8; + DWORD field32_0xac; + DWORD field33_0xb0; + DWORD field34_0xb4; + DWORD field35_0xb8; + DWORD field36_0xbc; + DWORD field37_0xc0; + DWORD field38_0xc4; + DWORD field39_0xc8; + DWORD field40_0xcc; + DWORD field41_0xd0; + DWORD field42_0xd4; + DWORD field43_0xd8; + DWORD field44_0xdc; + DWORD field45_0xe0; + DWORD field46_0xe4; + DWORD field47_0xe8; + DWORD field48_0xec; + DWORD field49_0xf0; + DWORD field50_0xf4; + DWORD field51_0xf8; + DWORD field52_0xfc; + DWORD field53_0x100; + DWORD field54_0x104; + DWORD field55_0x108; + DWORD field56_0x10c; + DWORD field57_0x110; + DWORD field58_0x114; + DWORD field59_0x118; + DWORD field60_0x11c; + DWORD field61_0x120; + DWORD field62_0x124; + DWORD field63_0x128; + DWORD field64_0x12c; + DWORD field65_0x130; + DWORD field66_0x134; + DWORD field67_0x138; + DWORD field68_0x13c; + DWORD field69_0x140; + DWORD field70_0x144; + DWORD field71_0x148; + DWORD field72_0x14c; + DWORD field73_0x150; + DWORD field74_0x154; + DWORD field75_0x158; + DWORD field76_0x15c; + DWORD field77_0x160; + DWORD field78_0x164; + DWORD field79_0x168; + DWORD field80_0x16c; + DWORD field81_0x170; + WeChatString fromGroup; + WeChatString sign; + WeChatString thumbPath; + WeChatString path; + DWORD field86_0x1c4; + DWORD field87_0x1c8; + DWORD field88_0x1cc; + DWORD field89_0x1d0; + DWORD field90_0x1d4; + DWORD field91_0x1d8; + DWORD field92_0x1dc; + DWORD field93_0x1e0; + DWORD field94_0x1e4; + DWORD field95_0x1e8; + DWORD field96_0x1ec; + WeChatString signature; + DWORD field98_0x204; + DWORD field99_0x208; + DWORD field100_0x20c; + DWORD field101_0x210; + DWORD field102_0x214; + DWORD field103_0x218; + DWORD field104_0x21c; + DWORD field105_0x220; + DWORD field106_0x224; + DWORD field107_0x228; + DWORD field108_0x22c; + DWORD field109_0x230; + DWORD field110_0x234; + DWORD field111_0x238; + DWORD field112_0x23c; + DWORD field113_0x240; + DWORD field114_0x244; + DWORD field115_0x248; + DWORD field116_0x24c; + DWORD field117_0x250; + DWORD field118_0x254; + DWORD field119_0x258; + DWORD field120_0x25c; + DWORD field121_0x260; + DWORD field122_0x264; + DWORD field123_0x268; + DWORD field124_0x26c; + DWORD field125_0x270; + DWORD field126_0x274; + DWORD field127_0x278; + DWORD field128_0x27c; + DWORD field129_0x280; + DWORD field130_0x284; + DWORD field131_0x288; + DWORD field132_0x28c; + DWORD field133_0x290; + DWORD field134_0x294; + DWORD field135_0x298; + DWORD field136_0x29c; + DWORD field137_0x2a0; + DWORD field138_0x2a4; + DWORD field139_0x2a8; + DWORD field140_0x2ac; + DWORD field141_0x2b0; + int field142_0x2b4; +}; + #endif From 9f7b1dd2735c3ba0e7d63e0488d8751a37bba00a Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Thu, 16 Mar 2023 12:02:44 +0800 Subject: [PATCH 42/59] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=97=A5=E5=BF=97hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api.cc | 7 +++++ src/hook_log.cc | 78 +++++++++++++++++++++++++++++++++++++++++++++++++ src/hook_log.h | 8 +++++ 3 files changed, 93 insertions(+) create mode 100644 src/hook_log.cc create mode 100644 src/hook_log.h diff --git a/src/api.cc b/src/api.cc index 4183138..97a8a0a 100644 --- a/src/api.cc +++ b/src/api.cc @@ -24,6 +24,7 @@ #include "sns.h" #include "search_contact.h" #include "download.h" +#include "hook_log.h" #pragma comment(lib, "ws2_32.lib") using namespace std; @@ -486,9 +487,15 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_LOG_START_HOOK: { + int success = HookLog(); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); break; } case WECHAT_LOG_STOP_HOOK: { + int success = UnHookLog(); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); break; } case WECHAT_BROWSER_OPEN_WITH_URL: { diff --git a/src/hook_log.cc b/src/hook_log.cc new file mode 100644 index 0000000..bffe7b0 --- /dev/null +++ b/src/hook_log.cc @@ -0,0 +1,78 @@ +#include "pch.h" +#include "hook_log.h" + +#include "common.h" + +using namespace std; + +#define WX_HOOK_LOG_OFFSET 0xed1675 +#define WX_HOOK_LOG_NEXT_OFFSET 0x2344832 + +static int kLogHooked = FALSE; +static DWORD kWeChatWinBase = GetWeChatWinBase(); +static char kOriginLogAsmCode[5] = {0}; + +static DWORD kHookLogAddress = kWeChatWinBase + WX_HOOK_LOG_OFFSET; +static DWORD kHookLogNextAddress = kWeChatWinBase + WX_HOOK_LOG_NEXT_OFFSET; +static DWORD kHookLogJmpBackAddress = kWeChatWinBase + WX_HOOK_LOG_OFFSET + 0x5; + +void log_print(DWORD addr) { + if (!addr) { + return; + } + DWORD dwId = 0; + char *msg = (char *)addr; + int size = MultiByteToWideChar(CP_UTF8, 0, msg, -1, 0, 0); + wchar_t *w_msg = new wchar_t[size + 1]; + memset(w_msg, 0, (size + 1) * 2); + MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, size); + size = WideCharToMultiByte(CP_ACP, 0, w_msg, -1, 0, 0, 0, 0); + char *ansi_message = new char[size + 1]; + memset(ansi_message, 0, size + 1); + WideCharToMultiByte(CP_ACP, 0, w_msg, -1, ansi_message, size, 0, 0); + delete[] w_msg; + w_msg = NULL; + cout << ansi_message; + delete[] ansi_message; + ansi_message = NULL; +} + +_declspec(naked) void handle_log() { + __asm { + PUSHAD + PUSHFD + PUSH EAX + CALL log_print + ADD ESP, 0x4 + POPFD + POPAD + CALL kHookLogNextAddress + JMP kHookLogJmpBackAddress + } +} + +int HookLog() { + kWeChatWinBase = GetWeChatWinBase(); + if (!kWeChatWinBase) { + return -1; + } + if (kLogHooked) { + return 2; + } + kHookLogAddress = kWeChatWinBase + WX_HOOK_LOG_OFFSET; + kHookLogNextAddress = kWeChatWinBase + WX_HOOK_LOG_NEXT_OFFSET; + kHookLogJmpBackAddress = kHookLogAddress + 0x5; + HookAnyAddress(kHookLogAddress, (LPVOID)handle_log, kOriginLogAsmCode); + kLogHooked = TRUE; + return 1; +} + +int UnHookLog() { + if (!kLogHooked) { + return 1; + } + DWORD hook_img_addr = kWeChatWinBase + WX_HOOK_LOG_OFFSET; + UnHookAnyAddress(hook_img_addr, kOriginLogAsmCode); + kLogHooked = FALSE; + return 1; +} \ No newline at end of file diff --git a/src/hook_log.h b/src/hook_log.h new file mode 100644 index 0000000..5dcad93 --- /dev/null +++ b/src/hook_log.h @@ -0,0 +1,8 @@ +#ifndef HOOK_LOG_H_ +#define HOOK_LOG_H_ +#include "windows.h" + +int HookLog(); +int UnHookLog(); + +#endif \ No newline at end of file From 67949e16fc140ac06b669869952fbbe191470431 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Thu, 16 Mar 2023 12:06:56 +0800 Subject: [PATCH 43/59] =?UTF-8?q?=E6=97=A5=E5=BF=97hook=E9=80=80=E5=87=BA?= =?UTF-8?q?=E8=BF=98=E5=8E=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/api.cc | 1 + 2 files changed, 70 insertions(+) diff --git a/README.md b/README.md index 70272e9..4f6e2e6 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,8 @@ vcpkg 31.修改群昵称 32.获取数据库句柄 34.查询数据库 +35.hook日志 +36.关闭hook日志 40.转发消息 44.退出登录 45.确认收款 @@ -942,6 +944,73 @@ vcpkg ``` + +#### 35.hook日志** +###### 接口功能 +> hook微信日志,在控制台打印日志,方便调试 + +###### 接口地址 +> [/api/?type=35](/api/?type=35) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 36.取消hook日志** +###### 接口功能 +> 取消hook日志 + +###### 接口地址 +> [/api/?type=36](/api/?type=36) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + #### 40.转发消息** ###### 接口功能 > 直接转发消息 diff --git a/src/api.cc b/src/api.cc index 97a8a0a..db5fe42 100644 --- a/src/api.cc +++ b/src/api.cc @@ -742,5 +742,6 @@ int http_close() { UnHookRecvMsg(); UnHookImg(); UnHookSearchContact(); + UnHookLog(); return 0; } \ No newline at end of file From 19d146db9943ebe0c985a23e00f19af0fe9b23e9 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Tue, 21 Mar 2023 12:32:17 +0800 Subject: [PATCH 44/59] =?UTF-8?q?hook=E8=AF=AD=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 180 +++++++++++++++++++--------------------------- src/api.cc | 8 +++ src/hook_voice.cc | 88 +++++++++++++++++++++++ src/hook_voice.h | 8 +++ 4 files changed, 177 insertions(+), 107 deletions(-) create mode 100644 src/hook_voice.cc create mode 100644 src/hook_voice.h diff --git a/README.md b/README.md index 4f6e2e6..484e7c7 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,3 @@ -# wxhelper -wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26,3.9.0.28版本。 -#### 免责声明: -本仓库发布的内容,仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关! - -#### 项目说明: -本项目是个人学习学习逆向的项目,主要参考 https://github.com/ljc545w/ComWeChatRobot ,在此基础上实现了微信的的其它版本的部分内容。 - -#### 使用说明: -支持的版本3.8.0.41,3.8.1.26, 3.9.0.28。 -src:主要的dll代码 -tool:简单的注入工具,一个是控制台,一个是图形界面。 -python: 简单的服务器,用以接收hook的消息内容。 - - -0.首先安装对应的微信版本,主分支是3.8.0.41版本,分支对应相应的微信版本号. -1.通过cmake构建成功后,将wxhelper.dll注入到微信,本地启动tcp server,监听19088端口。 -2.通过http协议与dll通信,方便客户端操作。 -3.接口的url为http://127.0.0.1:19088,注入成功后,直接进行调用即可。 -4.特别注意数据库查询接口需要先调用获取到句柄之后,才能进行查询。 -5.相关功能只在win11环境下进行简单测试,其他环境无法保证。 -6.注意个别接口在3.8.0.41版本没有实现,具体参考源码。 -7.对应分支接口文档都是支持指定版本的,其他版本不支持,请特别注意版本。 -8.相应分支的文档对应相应版本,带有删除线的接口表示该版本的暂未实现,其他版本有实现。后续会继续实现。 - - -#### 编译环境 - -Visual Studio 2022(x86) - -Visual Studio code - -cmake - -vcpkg -#### 构建步骤 -以下是在vscode中操作,vs中的操作类似。 -1.安装vcpkg,cmake,vscode - -2.安装相应的库,如果安装的版本不同,则根据vcpkg安装成功后提示的find_package修改CMakeLists.txt内容即可。或者自己编译。 -``` - vcpkg install mongoose - vcpkg install nlohmann-json -``` -3.vscode 配置CMakePresets.json,主要设置CMAKE_C_COMPILER 和CMAKE_CXX_COMPILER 为cl.exe.参考如下 -``` - { - "name": "x86-release", - "displayName": "x86-release", - "description": "Sets Ninja generator, build and install directory", - "generator": "Ninja", - "binaryDir": "${sourceDir}/out/build/${presetName}", - "architecture":{ - "value": "x86", - "strategy": "external" - }, - "cacheVariables": { - "CMAKE_C_COMPILER": "cl.exe", - "CMAKE_CXX_COMPILER": "cl.exe", - "CMAKE_BUILD_TYPE": "Release", - "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", - "CMAKE_TOOLCHAIN_FILE": { - "value": "C:/soft/vcpkg/scripts/buildsystems/vcpkg.cmake", - "type": "FILEPATH" - } - }, - "environment": { - - } - - } -``` -4.vscode中右键configure all projects,在Terminal中点击Run Task,如没有先配置build任务,然后运行即可 - -5.命令行注入工具,注入命令 -``` javascript - //-i 注入程序名 -p 注入dll路径 - // -u 卸载程序名 -d 卸载dll名称 - //注入 - ConsoleInject.exe -i demo.exe -p E:\testInject.dll - //卸载 - ConsoleInject.exe -u demo.exe -d testInject.dll -``` - -#### 更新说明 -2022-12-26 : 增加3.8.1.26版本支持。 - -2022-12-29 : 新增提取文字功能。 - -2023-01-02 : 退出微信登录。 - -2023-01-31 : 新增修改群昵称(仅支持3.8.1.26)。 - -2023-02-01 : 新增拍一拍(仅支持3.8.1.26)。 - -2023-02-04 : 新增群消息置顶和取消置顶。 - -2023-02-06 : 新增确认收款。 - -2023-02-08 : 新增朋友圈消息。 - -2023-02-09 : 新增3.9.0.28版本基础功能。 - -2023-02-13 : 新增查询联系人昵称功能。 - -2023-02-17 : 新增通过wxid添加好友和查找微信。 #### 功能预览: 0.检查是否登录 @@ -114,7 +8,9 @@ vcpkg 9.hook消息 10.取消hook消息 11.hook图片 -12.取消hook图片 +12.取消hook图片 +13.hook语音 +14.取消hook语音 17.删除好友 19.通过手机或qq查找微信 20.通过wxid添加好友 @@ -515,7 +411,77 @@ vcpkg 响应: ``` javascript {"code":1,"result":"OK"} +``` + +#### 13.hook语音** +###### 接口功能 +> hook语音 + +###### 接口地址 +> [/api/?type=13](/api/?type=13) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|voiceDir |true |string| 语音保存的目录 | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "voiceDir":"C:\\other" +} ``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 14.取消hook语音** +###### 接口功能 +> 取消hook语音 + +###### 接口地址 +> [/api/?type=14](/api/?type=14) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + #### 17.删除好友** ###### 接口功能 diff --git a/src/api.cc b/src/api.cc index db5fe42..e66e0e3 100644 --- a/src/api.cc +++ b/src/api.cc @@ -25,6 +25,7 @@ #include "search_contact.h" #include "download.h" #include "hook_log.h" +#include "hook_voice.h" #pragma comment(lib, "ws2_32.lib") using namespace std; @@ -310,9 +311,16 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_MSG_START_VOICE_HOOK: { + wstring voice_dir = get_http_req_param(hm, j_param, "voiceDir", is_post); + int success = HookVoice(voice_dir); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); break; } case WECHAT_MSG_STOP_VOICE_HOOK: { + int success = UnHookVoice(); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); break; } case WECHAT_CONTACT_GET_LIST: { diff --git a/src/hook_voice.cc b/src/hook_voice.cc new file mode 100644 index 0000000..e8b8d00 --- /dev/null +++ b/src/hook_voice.cc @@ -0,0 +1,88 @@ +#include "pch.h" +#include "hook_voice.h" + +#include "common.h" + +using namespace std; + +#define WX_HOOK_VOICE_OFFSET 0xccd561 +#define WX_HOOK_VOICE_NEXT_OFFSET 0x1f74560 +#define WX_SELF_ID_OFFSET 0x2E2CD3C + +static wstring kVoiceStorePath = L""; +static int kVoiceHooked = FALSE; +static DWORD kWeChatWinBase = GetWeChatWinBase(); +static char kOriginVoiceAsmCode[5] = {0}; + +static DWORD kHookVoiceNextAddress = kWeChatWinBase + WX_HOOK_VOICE_NEXT_OFFSET; +static DWORD kHookVoiceJmpBackAddress = + kWeChatWinBase + WX_HOOK_VOICE_OFFSET + 0x5; + +void OnHookVoice(DWORD buff,int len , DWORD msg_addr) { + DWORD wxid_addr = GetWeChatWinBase() + WX_SELF_ID_OFFSET; + string wxid = string(*(char **)wxid_addr, *(DWORD *)(wxid_addr + 0x10)); + wstring self_id = utf8_to_unicode(wxid.c_str()); + wstring save_path = kVoiceStorePath + self_id; + if (!FindOrCreateDirectoryW(save_path.c_str())) { + return; + } + unsigned long long msgid = *(unsigned long long *)(msg_addr + 0x30); + save_path = save_path + L"\\" + to_wstring(msgid) + L".amr"; + HANDLE file_handle = CreateFileW(save_path.c_str(), GENERIC_ALL, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) { + return; + } + DWORD bytes_write = 0; + WriteFile(file_handle, (LPCVOID)buff, len, &bytes_write, 0); + CloseHandle(file_handle); +} + +/// @brief hook voice implement +_declspec(naked) void handle_voice() { + __asm { + PUSHAD + PUSHFD + PUSH EDI + PUSH EDX + PUSH EAX + CALL OnHookVoice + ADD ESP, 0xC + POPFD + POPAD + CALL kHookVoiceNextAddress + JMP kHookVoiceJmpBackAddress + } +} + +int HookVoice(std::wstring save_path) { + kWeChatWinBase = GetWeChatWinBase(); + if (!kWeChatWinBase) { + return -1; + } + if (kVoiceHooked) { + return 2; + } + kVoiceStorePath = save_path; + if (kVoiceStorePath.back() != '\\') { + kVoiceStorePath += L"\\"; + } + wstring createpath = kVoiceStorePath.substr(0, kVoiceStorePath.length() - 1); + if (!FindOrCreateDirectoryW(createpath.c_str())) { + return -2; + } + DWORD hook_voice_addr = kWeChatWinBase + WX_HOOK_VOICE_OFFSET; + kHookVoiceNextAddress = kWeChatWinBase + WX_HOOK_VOICE_NEXT_OFFSET; + static DWORD kHookVoiceJmpBackAddress = hook_voice_addr + 0x5; + HookAnyAddress(hook_voice_addr, (LPVOID)handle_voice, kOriginVoiceAsmCode); + kVoiceHooked = TRUE; + return 1; +} + +int UnHookVoice() { + if (!kVoiceHooked) return 1; + DWORD hook_voice_addr = kWeChatWinBase + WX_HOOK_VOICE_OFFSET; + UnHookAnyAddress(hook_voice_addr, kOriginVoiceAsmCode); + kVoiceHooked = FALSE; + return 1; +} diff --git a/src/hook_voice.h b/src/hook_voice.h new file mode 100644 index 0000000..429221a --- /dev/null +++ b/src/hook_voice.h @@ -0,0 +1,8 @@ +#ifndef HOOK_VOICE_H_ +#define HOOK_VOICE_H_ +#include + +int HookVoice(std::wstring save_path); + +int UnHookVoice(); +#endif \ No newline at end of file From e52a9b87ed6c0d34dc47b896e36b49f5e9242e29 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 22 Mar 2023 17:52:51 +0800 Subject: [PATCH 45/59] =?UTF-8?q?3.9.2.23=20=E6=B5=8B=E8=AF=95=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++---- src/send_file.cc | 10 +++++----- src/send_image.cc | 11 +++++------ src/send_text.cc | 10 +++++----- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 484e7c7..f1fdce6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ #### 功能预览: -0.检查是否登录 -1.获取登录微信信息 + + 2.发送文本 5.发送图片 6.发送文件 -9.hook消息 + ### 接口文档: diff --git a/src/send_file.cc b/src/send_file.cc index 526210a..258a650 100644 --- a/src/send_file.cc +++ b/src/send_file.cc @@ -3,16 +3,16 @@ #include "common.h" #include "wechat_data.h" -#define WX_APP_MSG_MGR_OFFSET 0x709bb0 -#define WX_SEND_FILE_OFFSET 0xb06240 -#define WX_INIT_CHAT_MSG_OFFSET 0xed3be0 -#define WX_FREE_CHAT_MSG_OFFSET 0x6f4ea0 +#define WX_APP_MSG_MGR_OFFSET 0x76ae20 +#define WX_SEND_FILE_OFFSET 0xb6d1f0 +#define WX_INIT_CHAT_MSG_OFFSET 0xf59e40 +#define WX_FREE_CHAT_MSG_OFFSET 0x756960 int SendFile(wchar_t *wxid, wchar_t *file_path){ int success = 0; WeChatString to_user(wxid); WeChatString path(file_path); - char chat_msg[0x2C4] = {0}; + char chat_msg[0x2D8] = {0}; DWORD base = GetWeChatWinBase(); DWORD app_msg_mgr_addr = base + WX_APP_MSG_MGR_OFFSET; DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; diff --git a/src/send_image.cc b/src/send_image.cc index 00b6464..f30ef47 100644 --- a/src/send_image.cc +++ b/src/send_image.cc @@ -3,17 +3,16 @@ #include "common.h" #include "wechat_data.h" -#define WX_SEND_IMAGE_OFFSET 0xc71500 -#define WX_SEND_MESSAGE_MGR_OFFSET 0x706d30 -#define WX_INIT_CHAT_MSG_OFFSET 0xed3be0 -#define WX_FREE_CHAT_MSG_OFFSET 0x6f4ea0 - +#define WX_SEND_IMAGE_OFFSET 0xce6640 +#define WX_SEND_MESSAGE_MGR_OFFSET 0x768140 +#define WX_INIT_CHAT_MSG_OFFSET 0xf59e40 +#define WX_FREE_CHAT_MSG_OFFSET 0x756960 int SendImage(wchar_t *wxid, wchar_t *image_path){ int success = 0; WeChatString to_user(wxid); WeChatString path(image_path); - char chat_msg[0x2C4] ={0}; + char chat_msg[0x2D8] ={0}; DWORD base = GetWeChatWinBase(); DWORD send_message_mgr_addr = base + WX_SEND_MESSAGE_MGR_OFFSET; DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; diff --git a/src/send_text.cc b/src/send_text.cc index 265e8d4..96c349e 100644 --- a/src/send_text.cc +++ b/src/send_text.cc @@ -6,11 +6,11 @@ #include "wechat_data.h" #include "contact.h" -#define WX_SEND_TEXT_OFFSET 0xc71a60 +#define WX_SEND_TEXT_OFFSET 0xce6c80 -#define WX_SEND_MESSAGE_MGR_OFFSET 0x706d30 +#define WX_SEND_MESSAGE_MGR_OFFSET 0x768140 -#define WX_FREE_CHAT_MSG_OFFSET 0x6f4ea0 +#define WX_FREE_CHAT_MSG_OFFSET 0x756960 using namespace std; /// @brief 发生文本消息 /// @param wxid wxid @@ -26,7 +26,7 @@ int SendText(wchar_t* wxid, wchar_t* msg) { DWORD send_message_mgr_addr = base + WX_SEND_MESSAGE_MGR_OFFSET; DWORD send_text_msg_addr = base + WX_SEND_TEXT_OFFSET; DWORD free_chat_msg_addr = base + WX_FREE_CHAT_MSG_OFFSET; - char chat_msg[0x2C4] ={0}; + char chat_msg[0x2D8] ={0}; __asm{ PUSHAD CALL send_message_mgr_addr @@ -59,7 +59,7 @@ int SendAtText(wchar_t* chat_room_id,wchar_t** wxids,int len,wchar_t* msg){ for (int i = 0; i < len; i++) { wstring nickname; if (!lstrcmpiW((wchar_t *)wxids[i], (wchar_t *)L"notify@all")) { - nickname = L""; + nickname = L"������"; } else { nickname = GetContactOrChatRoomNickname(wxids[i]); } From b8db52d52250f0ba6fa24f998c51f8b0f050876c Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 22 Mar 2023 17:55:07 +0800 Subject: [PATCH 46/59] =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f1fdce6..a1525d6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ +3.9.2.23 测试版,临时测试 #### 功能预览: @@ -25,7 +26,7 @@ 36.关闭hook日志 40.转发消息 44.退出登录 -45.确认收款 +5.确认收款 46.联系人列表 47.获取群详情 48.获取解密图片 From 8ea5cbec8a8bda48d261611b2cc82d0888ecd9de Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Thu, 23 Mar 2023 11:04:58 +0800 Subject: [PATCH 47/59] =?UTF-8?q?hook=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++---- src/hook_recv_msg.cc | 6 +++--- src/self_info.cc | 22 +++++----------------- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index a1525d6..8d4da7b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ 3.9.2.23 测试版,临时测试 #### 功能预览: - - +0.检查是否登录 +1.获取登录微信信息 2.发送文本 5.发送图片 6.发送文件 - 46.联系人列表 -47.获取群详情 + +56.获取消息附件(图片,视频,文件) --> ### 接口文档: diff --git a/src/contact.cc b/src/contact.cc index c37edbc..346286a 100644 --- a/src/contact.cc +++ b/src/contact.cc @@ -4,8 +4,8 @@ #include "common.h" #include "wechat_data.h" using namespace std; -#define WX_CONTACT_MGR_INSTANCE_OFFSET 0x6f8990 -#define WX_CONTACT_GET_LIST_OFFSET 0xb97550 +#define WX_CONTACT_MGR_INSTANCE_OFFSET 0x75a4a0 +#define WX_CONTACT_GET_LIST_OFFSET 0xc089f0 #define WX_CONTACT_DEL_OFFSET 0xb9b3b0 #define WX_INIT_CHAT_MSG_OFFSET 0xed3be0 #define WX_SYNC_MGR_OFFSET 0xa87fd0 From a097ae334a60869d020475cc017bfd56831a26f0 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Mon, 27 Mar 2023 11:40:57 +0800 Subject: [PATCH 49/59] =?UTF-8?q?sqlite3=20=E5=81=8F=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++++------- src/get_db_handle.cc | 12 ++++++------ src/new_sqlite3.h | 36 ++++++++++++++++++------------------ 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 6c219b3..c4f25d1 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ 6.发送文件 9.hook消息 10.取消hook消息 - 32.获取数据库句柄 34.查询数据库 -35.hook日志 + 44.退出登录 -5.确认收款 --> -46.联系人列表 + 44.退出登录 - + +46.联系人列表 +36.关闭hook日志 --> +40.转发消息 44.退出登录 46.联系人列表 diff --git a/src/forward.cc b/src/forward.cc index 5d936c8..0c94400 100644 --- a/src/forward.cc +++ b/src/forward.cc @@ -4,8 +4,8 @@ #include "common.h" #include "get_db_handle.h" #include "wechat_data.h" -#define WX_FORWARD_MSG_OFFSET 0xc715f0 -#define WX_INIT_CHAT_MSG_OFFSET 0xed3be0 +#define WX_FORWARD_MSG_OFFSET 0xce6730 +#define WX_INIT_CHAT_MSG_OFFSET 0xf59e40 int ForwardMsg(wchar_t *wxid, unsigned long long msgid) { int success = 0; From 1a1a1fe964efe411fce47f46f323b16ad745b3dc Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Mon, 27 Mar 2023 11:58:04 +0800 Subject: [PATCH 52/59] =?UTF-8?q?hook=E8=AF=AD=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hook_voice.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hook_voice.cc b/src/hook_voice.cc index e8b8d00..1fa3205 100644 --- a/src/hook_voice.cc +++ b/src/hook_voice.cc @@ -5,9 +5,9 @@ using namespace std; -#define WX_HOOK_VOICE_OFFSET 0xccd561 -#define WX_HOOK_VOICE_NEXT_OFFSET 0x1f74560 -#define WX_SELF_ID_OFFSET 0x2E2CD3C +#define WX_HOOK_VOICE_OFFSET 0xd4d8d8 +#define WX_HOOK_VOICE_NEXT_OFFSET 0x203d130 +#define WX_SELF_ID_OFFSET 0x2FFD484 static wstring kVoiceStorePath = L""; static int kVoiceHooked = FALSE; @@ -41,11 +41,11 @@ void OnHookVoice(DWORD buff,int len , DWORD msg_addr) { /// @brief hook voice implement _declspec(naked) void handle_voice() { __asm { - PUSHAD + PUSHAD PUSHFD PUSH EDI - PUSH EDX - PUSH EAX + PUSH EDX + PUSH EAX CALL OnHookVoice ADD ESP, 0xC POPFD From 789b4b6e75b3d1c713ded83f5a28316a6116defd Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Mon, 27 Mar 2023 17:52:30 +0800 Subject: [PATCH 53/59] =?UTF-8?q?3.9.2.23=E9=83=A8=E5=88=86=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 +++++++++---------- src/api.cc | 1 + src/chat_room.cc | 8 ++++---- src/common.cc | 47 +++++++++++++++++++++++++++++++++++++++++++- src/common.h | 8 ++++++++ src/forward.cc | 3 ++- src/hook_recv_msg.cc | 4 ++-- src/pat.cc | 6 +++--- src/self_info.cc | 9 +++++++++ src/sns.cc | 6 +++--- src/wechat_data.h | 1 + 11 files changed, 89 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 10a2d44..4e868d3 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,13 @@ 10.取消hook消息 11.hook图片 12.取消hook图片 - + + 25.获取群成员 -26.获取群成员昵称 + @@ -28,15 +28,15 @@ 44.退出登录 46.联系人列表 - 48.获取解密图片 -49.图片提取文字ocr + 50.拍一拍 -51.群消息置顶消息 -52.群消息取消置顶 + 53.朋友圈首页 54.朋友圈下一页 -55.获取联系人或者群名称 + ### 接口文档: diff --git a/src/api.cc b/src/api.cc index e66e0e3..7543187 100644 --- a/src/api.cc +++ b/src/api.cc @@ -228,6 +228,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { {"signature",self_info.signature}, {"dataSavePath",self_info.data_save_path}, {"currentDataPath",self_info.current_data_path}, + {"dbKey",self_info.db_key}, }; ret_data["data"] = j_info; } diff --git a/src/chat_room.cc b/src/chat_room.cc index 90ac7fb..7a8ea57 100644 --- a/src/chat_room.cc +++ b/src/chat_room.cc @@ -7,16 +7,16 @@ #include "base64.h" using namespace std; -#define WX_CHAT_ROOM_MGR_OFFSET 0x72cf60 +#define WX_CHAT_ROOM_MGR_OFFSET 0x78cf20 #define WX_GET_CHAT_ROOM_DETAIL_INFO_OFFSET 0xb6f260 #define WX_NEW_CHAT_ROOM_INFO_OFFSET 0xe15de0 #define WX_FREE_CHAT_ROOM_INFO_OFFSET 0xe160b0 #define WX_DEL_CHAT_ROOM_MEMBER_OFFSET 0xb64180 #define WX_INIT_CHAT_MSG_OFFSET 0xed3be0 #define WX_ADD_MEMBER_TO_CHAT_ROOM_OFFSET 0xb63c50 -#define WX_GET_MEMBER_FROM_CHAT_ROOM_OFFSET 0xB70260 -#define WX_INIT_CHAT_ROOM_OFFSET 0xe13b30 -#define WX_FREE_CHAT_ROOM_OFFSET 0xe13d50 +#define WX_GET_MEMBER_FROM_CHAT_ROOM_OFFSET 0xbdf260 +#define WX_INIT_CHAT_ROOM_OFFSET 0xe97890 +#define WX_FREE_CHAT_ROOM_OFFSET 0xe97ab0 #define WX_MOD_CHAT_ROOM_MEMBER_NICK_NAME_OFFSET 0xb6adf0 #define WX_NEW_CHAT_MSG_OFFSET 0x70e2a0 #define WX_FREE_CHAT_MSG_OFFSET 0x6f4ea0 diff --git a/src/common.cc b/src/common.cc index d3e0ce1..36a31dd 100644 --- a/src/common.cc +++ b/src/common.cc @@ -142,4 +142,49 @@ void CloseConsole(){ fclose(stdout); fclose(stderr); FreeConsole(); -} \ No newline at end of file +} + +std::string EncodeHexString(const std::string &str) { + const std::string hex_table = "0123456789abcdef"; + string sb; + for (int i = 0; i < str.length(); i++) { + sb += hex_table.at((str[i] & 0xf0) >> 4); + sb += hex_table.at((str[i] & 0x0f) >> 0); + } + return sb; +} + +std::string Hex2String(const std::string &hex_str) { + std::string ret; + const std::string hex_table = "0123456789abcdef"; + for (int i = 0; i < hex_str.length(); i += 2) { + ret += BYTE(hex_table.find(hex_str.at(i)) << 4 | + hex_table.find(hex_str.at(i + 1))); + } + return ret; +} + +std::string Bytes2Hex(const BYTE *bytes, const int length) { + if (bytes == NULL) { + return ""; + } + std::string buff; + const int len = length; + for (int j = 0; j < len; j++) { + int high = bytes[j] / 16, low = bytes[j] % 16; + buff += (high < 10) ? ('0' + high) : ('a' + high - 10); + buff += (low < 10) ? ('0' + low) : ('a' + low - 10); + } + return buff; +} + +void Hex2Bytes(const std::string &hex, BYTE *bytes) { + int byte_len = hex.length() / 2; + std::string str; + unsigned int n; + for (int i = 0; i < byte_len; i++) { + str = hex.substr(i * 2, 2); + sscanf_s(str.c_str(), "%x", &n); + bytes[i] = n; + } +} diff --git a/src/common.h b/src/common.h index 229b0fa..0cd0f4f 100644 --- a/src/common.h +++ b/src/common.h @@ -55,6 +55,14 @@ BOOL FindOrCreateDirectoryW(const wchar_t *path); void CloseConsole(); +std::string EncodeHexString(const std::string &str); + +std::string Hex2String(const std::string &hex_str); + +std::string Bytes2Hex(const BYTE *bytes, const int length); + +void Hex2Bytes(const std::string &hex, BYTE *bytes); + template std::vector split(T1 str, T2 letter) { vector arr; diff --git a/src/forward.cc b/src/forward.cc index 0c94400..b014607 100644 --- a/src/forward.cc +++ b/src/forward.cc @@ -29,7 +29,8 @@ int ForwardMsg(wchar_t *wxid, unsigned long long msgid) { MOV ECX,ESP LEA ESI,to_user PUSH ESI - CALL init_chat_msg_addr + CALL init_chat_msg_addr + XOR ECX,ECX CALL forward_msg_addr MOVZX EAX,AL MOV success,EAX diff --git a/src/hook_recv_msg.cc b/src/hook_recv_msg.cc index fe27f66..c57e58c 100644 --- a/src/hook_recv_msg.cc +++ b/src/hook_recv_msg.cc @@ -12,8 +12,8 @@ using namespace nlohmann; using namespace std; #define WX_RECV_MSG_HOOK_OFFSET 0xd19a0b #define WX_RECV_MSG_HOOK_NEXT_OFFSET 0x756960 -#define WX_SNS_HOOK_OFFSET 0x143ef09 -#define WX_SNS_HOOK_NEXT_OFFSET 0x143f1b0 +#define WX_SNS_HOOK_OFFSET 0x14f9e15 +#define WX_SNS_HOOK_NEXT_OFFSET 0x14fa0a0 // SyncMgr::addMsgListToDB // #define WX_RECV_MSG_HOOK_OFFSET 0xB9C919 diff --git a/src/pat.cc b/src/pat.cc index 16a4757..bd609a2 100644 --- a/src/pat.cc +++ b/src/pat.cc @@ -4,9 +4,9 @@ #include "common.h" #include "wechat_data.h" -#define WX_PAT_MGR_OFFSET 0x8d0c00 -#define WX_SEND_PAT_MSG_OFFSET 0x1369850 -#define WX_RET_OFFSET 0x1C94D34 +#define WX_PAT_MGR_OFFSET 0x931730 +#define WX_SEND_PAT_MSG_OFFSET 0x1421940 +#define WX_RET_OFFSET 0x1D58751 int SendPatMsg(wchar_t* chat_room_id, wchar_t* wxid) { int success = -1; diff --git a/src/self_info.cc b/src/self_info.cc index 27666a9..9ab002a 100644 --- a/src/self_info.cc +++ b/src/self_info.cc @@ -141,6 +141,15 @@ int GetSelfInfo(SelfInfoInner &out) { *(DWORD *)(service_addr + 0x304 + 0x10)); } } + + if (*(DWORD *)(service_addr + 0x4CC) == 0 || + *(DWORD *)(service_addr +0x4D0) == 0) { + out.db_key = string(); + } else { + DWORD byte_addr = *(DWORD *)(service_addr + 0x4CC); + DWORD len = *(DWORD *)(service_addr +0x4D0); + out.db_key = Bytes2Hex((BYTE *)byte_addr,len); + } } WeChatString data_save_path; diff --git a/src/sns.cc b/src/sns.cc index 8c3ecd9..d4a6f91 100644 --- a/src/sns.cc +++ b/src/sns.cc @@ -4,9 +4,9 @@ #include "common.h" #include "wechat_data.h" using namespace std; -#define WX_SNS_DATA_MGR_OFFSET 0xbc4100 -#define WX_SNS_GET_FIRST_PAGE_OFFSET 0x1427be0 -#define WX_SNS_GET_NEXT_PAGE_OFFSET 0x1427c80 +#define WX_SNS_DATA_MGR_OFFSET 0xc39680 +#define WX_SNS_GET_FIRST_PAGE_OFFSET 0x14e2140 +#define WX_SNS_GET_NEXT_PAGE_OFFSET 0x14e21e0 int GetFirstPage() { int success = -1; diff --git a/src/wechat_data.h b/src/wechat_data.h index cf4f997..404bfb8 100644 --- a/src/wechat_data.h +++ b/src/wechat_data.h @@ -153,6 +153,7 @@ struct SelfInfoInner{ std::string data_save_path; std::string signature; std::string current_data_path; + std::string db_key; }; struct UserInfo { From 4a3ef64467aa5479d605ef42d031ce6d036d269a Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Tue, 28 Mar 2023 10:01:55 +0800 Subject: [PATCH 54/59] =?UTF-8?q?3.9.2.23=E7=89=88=E6=9C=AC=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +++++---- src/api.cc | 18 ++++++++++++++++++ src/download.cc | 32 ++++++++++++++++---------------- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 4e868d3..d578087 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -3.9.2.23 测试版,临时测试 +## 3.9.2.23版本,预览功能没有的接口,不能使用,文档仅供参考。 #### 功能预览: 0.检查是否登录 1.获取登录微信信息 @@ -36,8 +36,8 @@ 52.群消息取消置顶 --> 53.朋友圈首页 54.朋友圈下一页 - + +56.获取消息附件(图片,视频,文件) ### 接口文档: @@ -107,6 +107,7 @@ |province|string|省| |wxid|string|wxid| |signature|string|个人签名| +|dbKey|string|数据库的SQLCipher的加密key,可以使用该key配合decrypt.py解密数据库 ###### 接口示例 入参: @@ -114,7 +115,7 @@ ``` 响应: ``` javascript -{"code":1,"data":{"account":"xx","headImage":"https://wx.qlogo.cn/mmhead/ver_1xx","city":"xx","country":"CN","currentDataPath":"C:\\xx\\wxid_xxxxx","dataSavePath":"C:\\xx","mobie":"13812345678","name":"xx","province":"xx","signature":"xx","wxid":"xx"},"result":"OK"} +{"code":1,"data":{"account":"xx","headImage":"https://wx.qlogo.cn/mmhead/ver_1xx","city":"xx","country":"CN","currentDataPath":"C:\\xx\\wxid_xxxxx","dataSavePath":"C:\\xx","mobie":"13812345678","name":"xx","province":"xx","signature":"xx","wxid":"xx","dbKey":"aaa2222"},"result":"OK"} ``` diff --git a/src/api.cc b/src/api.cc index 7543187..0e3da12 100644 --- a/src/api.cc +++ b/src/api.cc @@ -244,6 +244,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_MSG_SEND_AT: { + break; wstring chat_room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); vector wxids = get_http_param_array(hm, j_param, "wxids", is_post); wstring msg = get_http_req_param(hm, j_param, "msg", is_post); @@ -299,6 +300,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_MSG_START_IMAGE_HOOK: { + break; wstring img_dir = get_http_req_param(hm, j_param, "imgDir", is_post); int success = HookImg(img_dir); json ret_data = {{"code", success}, {"result", "OK"}}; @@ -306,6 +308,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_MSG_STOP_IMAGE_HOOK: { + break; int success = UnHookImg(); json ret_data = {{"code", success}, {"result", "OK"}}; ret = ret_data.dump(); @@ -332,6 +335,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_CONTACT_DEL: { + break; wstring user_id = get_http_req_param(hm, j_param, "wxid", is_post); int success = DelContact(WS2LW(user_id)); json ret_data = {{"code", success}, {"result", "OK"}}; @@ -342,6 +346,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_CONTACT_SEARCH_BY_NET: { + break; wstring keyword = get_http_req_param(hm, j_param, "keyword", is_post); UserInfo *user = nullptr; int success = SearchContactNetScene(WS2LW(keyword), &user); @@ -365,6 +370,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_CONTACT_ADD_BY_WXID: { + break; wstring user_id = get_http_req_param(hm, j_param, "wxid", is_post); int success = AddFriendByWxid(WS2LW(user_id)); json ret_data = {{"code", success}, {"result", "OK"}}; @@ -400,6 +406,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_CHATROOM_GET_MEMBER_NICKNAME: { + break; wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); wstring member_id = get_http_req_param(hm, j_param, "memberId", is_post); @@ -409,6 +416,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_CHATROOM_DEL_MEMBER: { + break; wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); vector wxids = get_http_param_array(hm, j_param, "memberIds", is_post); vector wxid_list; @@ -421,6 +429,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_CHATROOM_ADD_MEMBER: { + break; wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); vector wxids = get_http_param_array(hm, j_param, "memberIds", is_post); vector wxid_list; @@ -439,6 +448,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_CHATROOM_SET_SELF_NICKNAME: { + break; wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); wstring wxid = get_http_req_param(hm, j_param, "wxid", is_post); wstring nick = get_http_req_param(hm, j_param, "nickName", is_post); @@ -496,12 +506,14 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_LOG_START_HOOK: { + break; int success = HookLog(); json ret_data = {{"code", success}, {"result", "OK"}}; ret = ret_data.dump(); break; } case WECHAT_LOG_STOP_HOOK: { + break; int success = UnHookLog(); json ret_data = {{"code", success}, {"result", "OK"}}; ret = ret_data.dump(); @@ -537,6 +549,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_GET_TRANSFER: { + break; wstring wxid = get_http_req_param(hm, j_param, "wxid", is_post); wstring transcationid = get_http_req_param(hm, j_param, "transcationId", is_post); wstring transferid = get_http_req_param(hm, j_param, "transferId", is_post); @@ -583,6 +596,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_GET_CHATROOM_INFO: { + break; wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); ChatRoomInfoInner chat_room_detail{0}; int success = GetChatRoomDetailInfo(WS2LW(room_id), chat_room_detail); @@ -618,6 +632,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_DO_OCR:{ + break; wstring image_path = get_http_req_param(hm, j_param, "imagePath", is_post); string text(""); int success = DoOCRTask(WS2LW(image_path),text); @@ -634,6 +649,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_SET_TOP_MSG:{ + break; wstring wxid = get_http_req_param(hm, j_param, "wxid", is_post); ULONG64 msgid = get_http_param_ulong64(hm, j_param, "msgid", is_post); int success = SetTopMsg(WS2LW(wxid),msgid); @@ -642,6 +658,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_REMOVE_TOP_MSG:{ + break; wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); ULONG64 msgid = get_http_param_ulong64(hm, j_param, "msgid", is_post); int success = RemoveTopMsg(WS2LW(room_id),msgid); @@ -663,6 +680,7 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { break; } case WECHAT_CONTACT_NAME:{ + break; wstring pri_id = get_http_req_param(hm, j_param, "id", is_post); wstring name =GetContactOrChatRoomNickname(WS2LW(pri_id)); json ret_data = {{"code", 1}, {"result", "OK"},{"name",unicode_to_utf8(WS2LW(name))}}; diff --git a/src/download.cc b/src/download.cc index f22a6d0..d3d5182 100644 --- a/src/download.cc +++ b/src/download.cc @@ -6,20 +6,20 @@ #include "wechat_data.h" -#define WX_NEW_CHAT_MSG_OFFSET 0x70e2a0 -#define WX_GET_PRE_DOWNLOAD_MGR_OFFSET 0x7ae310 -#define WX_PUSH_ATTACH_TASK_OFFSET 0x7c94a0 -#define WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET 0x6f5370 -#define WX_FREE_CHAT_MSG_OFFSET 0x6f4ea0 -#define WX_CHAT_MGR_OFFSET 0x732660 -#define WX_GET_MGR_BY_PREFIX_LOCAL_ID_OFFSET 0xb54950 -#define WX_GET_CURRENT_DATA_PATH_OFFSET 0xc11140 -#define WX_APP_MSG_INFO_OFFSET 0x7571d0 -#define WX_GET_APP_MSG_XML_OFFSET 0xddef80 -#define WX_FREE_APP_MSG_INFO_OFFSET 0x73d820 -#define WX_PUSH_THUMB_TASK_OFFSET 0x7c93a0 -#define WX_VIDEO_MGR_OFFSET 0x7c7300 -#define WX_DOWNLOAD_VIDEO_IMG_OFFSET 0xcc6d80 +#define WX_NEW_CHAT_MSG_OFFSET 0x76f010 +#define WX_GET_PRE_DOWNLOAD_MGR_OFFSET 0x80f110 +#define WX_PUSH_ATTACH_TASK_OFFSET 0x82bb40 +#define WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET 0x756e30 +#define WX_FREE_CHAT_MSG_OFFSET 0x756960 +#define WX_CHAT_MGR_OFFSET 0x792700 +#define WX_GET_MGR_BY_PREFIX_LOCAL_ID_OFFSET 0xbc0370 +#define WX_GET_CURRENT_DATA_PATH_OFFSET 0xc872c0 +#define WX_APP_MSG_INFO_OFFSET 0x7b3d20 +#define WX_GET_APP_MSG_XML_OFFSET 0xe628a0 +#define WX_FREE_APP_MSG_INFO_OFFSET 0x79d900 +#define WX_PUSH_THUMB_TASK_OFFSET 0x82ba40 +#define WX_VIDEO_MGR_OFFSET 0x829820 +#define WX_DOWNLOAD_VIDEO_IMG_OFFSET 0xd46c30 using namespace std; @@ -31,7 +31,7 @@ int DoDownloadTask(ULONG64 msg_id) { return -2; } - char chat_msg[0x2C4] = {0}; + char chat_msg[0x2D8] = {0}; DWORD base = GetWeChatWinBase(); DWORD new_chat_msg_addr = base + WX_NEW_CHAT_MSG_OFFSET; DWORD get_chat_mgr_addr = base + WX_CHAT_MGR_OFFSET; @@ -143,7 +143,7 @@ int DoDownloadTask(ULONG64 msg_id) { int temp =1; memcpy(&chat_msg[0x19C], &w_thumb_path, sizeof(w_thumb_path)); memcpy(&chat_msg[0x1B0], &w_save_path, sizeof(w_save_path)); - memcpy(&chat_msg[0x290], &temp, sizeof(temp)); + memcpy(&chat_msg[0x29C], &temp, sizeof(temp)); // note: the image has been downloaded and will not be downloaded again // use low-level method // this function does not work, need to modify chatmsg. From c590a9542564459e5d989a5ca51745d08756a4e5 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 29 Mar 2023 09:30:03 +0800 Subject: [PATCH 55/59] =?UTF-8?q?=E5=90=88=E5=B9=B6=E6=B3=A8=E5=85=A5?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E5=92=8C=E8=A7=A3=E5=AF=86=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- python/client.py | 595 ++++++++++++++++++++++ python/decrypt.py | 51 ++ source/CMakeLists.txt | 20 + source/ConsoleApplication.cc | 962 +++++++++++++++++++++++++++++++++++ source/getopt.h | 659 ++++++++++++++++++++++++ 6 files changed, 2288 insertions(+), 1 deletion(-) create mode 100644 python/client.py create mode 100644 python/decrypt.py create mode 100644 source/CMakeLists.txt create mode 100644 source/ConsoleApplication.cc create mode 100644 source/getopt.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1191974..adcb78d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ file(GLOB CPP_FILES ${PROJECT_SOURCE_DIR}/src/*.cc ${PROJECT_SOURCE_DIR}/src/* include_directories(${VCPKG_INSTALLED_DIR}/x86-windows/include) # add_subdirectory(3rd) - +add_subdirectory(source) find_package(nlohmann_json CONFIG REQUIRED) find_package(unofficial-mongoose CONFIG REQUIRED) diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..9188e10 --- /dev/null +++ b/python/client.py @@ -0,0 +1,595 @@ +import requests +import json + + +def check_login(): + """ + 0.检查是否登录 + :return: + """ + url = "127.0.0.1:19088/api/?type=0" + payload = {} + headers = {} + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def user_info(): + """ + 登录用户信息 + :return: + """ + url = "127.0.0.1:19088/api/?type=8" + payload = {} + headers = {} + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def send_text(): + """ + 发送文本 + :return: + """ + url = "127.0.0.1:19088/api/?type=2" + payload = json.dumps({ + "wxid": "filehelper", + "msg": "123" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def send_at(): + """ + 发送@消息 + :return: + """ + url = "127.0.0.1:19088/api/?type=3" + payload = json.dumps({ + "chatRoomId": "12333@chatroom", + "wxids": "notify@all", + "msg": "12333" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def send_img(): + """ + 发送图片 + :return: + """ + url = "127.0.0.1:19088/api/?type=5" + payload = json.dumps({ + "wxid": "filehelper", + "imagePath": "C:/123.png" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def send_file(): + """ + 发送文件 + :return: + """ + url = "127.0.0.1:19088/api/?type=6" + payload = json.dumps({ + "wxid": "filehelper", + "filePath": "C:/test.txt" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def hook_msg(): + """ + hook 消息 + :return: + """ + url = "127.0.0.1:19088/api/?type=9" + payload = json.dumps({ + "port": "19099", + "ip": "127.0.0.1" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def unhook_msg(): + """ + 取消消息hook + :return: + """ + url = "127.0.0.1:19088/api/?type=10" + payload = {} + headers = {} + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def hook_img(): + """ + hook 图片 + :return: + """ + url = "127.0.0.1:19088/api/?type=11" + payload = json.dumps({ + "imgDir": "C:\\img" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def unhook_img(): + """ + 取消hook 图片 + :return: + """ + url = "127.0.0.1:19088/api/?type=12" + payload = json.dumps({ + "imgDir": "C:\\img" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def hook_voice(): + """ + hook 语音 + :return: + """ + url = "127.0.0.1:19088/api/?type=56" + payload = json.dumps({ + "msgId": 322456091115784000 + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def unhook_voice(): + """ + 取消hook 语音 + :return: + """ + url = "127.0.0.1:19088/api/?type=14" + payload = {} + headers = {} + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def del_friend(): + """ + 删除好友 + :return: + """ + url = "127.0.0.1:19088/api/?type=17" + payload = json.dumps({ + "wxid": "wxid_1124423322" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def search_friend(): + """ + 网络搜素用户 + :return: + """ + url = "127.0.0.1:19088/api/?type=19" + payload = json.dumps({ + "keyword": "13812345678" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def add_friend(): + """ + 添加好友 + :return: + """ + url = "127.0.0.1:19088/api/?type=20" + payload = json.dumps({ + "wxid": "wxid_o11222334422" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def fetch_chat_room_members(): + """ + 群成员 + :return: + """ + url = "127.0.0.1:19088/api/?type=25" + payload = json.dumps({ + "chatRoomId": "2112222004@chatroom" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def get_member_nickname(): + """ + 群成员昵称 + :return: + """ + url = "127.0.0.1:19088/api/?type=26" + payload = json.dumps({ + "chatRoomId": "322333384@chatroom", + "memberId": "wxid_4m1112222u22" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def del_member(): + """ + 删除群成员 + :return: + """ + url = "127.0.0.1:19088/api/?type=27" + payload = json.dumps({ + "chatRoomId": "31122263384@chatroom", + "memberIds": "wxid_12223334422" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def add_member(): + """ + 增加群成员 + :return: + """ + url = "127.0.0.1:19088/api/?type=28" + payload = json.dumps({ + "chatRoomId": "1111163384@chatroom", + "memberIds": "wxid_o12222222" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def modify_room_name(): + """ + 修改群昵称 + :return: + """ + url = "127.0.0.1:19088/api/?type=31" + payload = json.dumps({ + "chatRoomId": "222285428@chatroom", + "wxid": "wxid_222222512", + "nickName": "qqq" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def get_db_handlers(): + """ + 获取sqlite3的操作句柄 + :return: + """ + url = "127.0.0.1:19088/api/?type=32" + payload = {} + headers = {} + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def query_db_by_sql(): + """ + 查询数据库 + :return: + """ + url = "127.0.0.1:19088/api/?type=34" + payload = json.dumps({ + "dbHandle": 116201928, + "sql": "select localId from MSG where MsgSvrID= 7533111101686156" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def hook_log(): + """ + hook 日志 + :return: + """ + url = "127.0.0.1:19088/api/?type=36" + payload = {} + headers = {} + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def unhook_log(): + """ + 取消hook日志 + :return: + """ + url = "127.0.0.1:19088/api/?type=37" + payload = {} + headers = {} + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def forward(): + """ + 转发消息 + :return: + """ + url = "127.0.0.1:19088/api/?type=40" + payload = json.dumps({ + "wxid": "filehelper", + "msgid": "705117679011122708" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def logout(): + """ + 退出登录 + :return: + """ + url = "127.0.0.1:19088/api/?type=44" + payload = {} + headers = {} + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def confirm_receipt(): + """ + 确认收款 + :return: + """ + url = "127.0.0.1:19088/api/?type=45" + payload = json.dumps({ + "wxid": "wxid_1111112622", + "transcationId": "10000500012312222212243388865912", + "transferId": "100005000120212222173123036" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def contact_list(): + """ + 好友列表 + :return: + """ + url = "127.0.0.1:19088/api/?type=46" + payload = {} + headers = {} + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def room_detail(): + """ + 群详情 + :return: + """ + url = "127.0.0.1:19088/api/?type=47" + payload = json.dumps({ + "chatRoomId": "199134446111@chatroom" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def ocr(): + """ + ocr提取文字 + :return: + """ + url = "127.0.0.1:19088/api/?type=49" + payload = json.dumps({ + "imagePath": "C:\\WeChat Files\\b23e84997144dd12f21554b0.dat" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def pat(): + """ + 拍一拍 + :return: + """ + url = "127.0.0.1:19088/api/?type=50" + payload = json.dumps({ + "chatRoomId": "211111121004@chatroom", + "wxid": "wxid_111111111422" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def top_msg(): + """ + 消息置顶 + :return: + """ + url = "127.0.0.1:19088/api/?type=51" + payload = json.dumps({ + "wxid": "wxid_o11114422", + "msgid": 3728307145189195000 + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def close_top_msg(): + """ + 取消置顶 + :return: + """ + url = "127.0.0.1:19088/api/?type=52" + payload = json.dumps({ + "chatRoomId": "213222231004@chatroom", + "msgid": 3728307145189195000 + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def sns_first(): + """ + 朋友圈首页 + :return: + """ + url = "127.0.0.1:19088/api/?type=53" + payload = {} + headers = {} + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def sns_next(): + """ + 朋友圈下一页 + :return: + """ + url = "127.0.0.1:19088/api/?type=54" + payload = json.dumps({ + "snsId": "14091988153735844377" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def query_nickname(): + """ + 查询联系人或群名称 + :return: + """ + url = "127.0.0.1:19088/api/?type=55" + + payload = json.dumps({ + "id": "wxid_1112p4422" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def download_msg_attach(): + """ + 下载消息附件 + :return: + """ + url = "127.0.0.1:19088/api/?type=56" + payload = json.dumps({ + "msgId": 6080100336053626000 + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +def get_member_info(): + """ + 获取群/群成员信息 + :return: + """ + url = "127.0.0.1:19088/api/?type=57" + payload = json.dumps({ + "wxid": "wxid_tx8k6tu21112" + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + + +if __name__ == '__main__': + check_login() + user_info() + send_text() \ No newline at end of file diff --git a/python/decrypt.py b/python/decrypt.py new file mode 100644 index 0000000..eae7a97 --- /dev/null +++ b/python/decrypt.py @@ -0,0 +1,51 @@ +import ctypes +import hashlib +import hmac + +# pip install pycryptodome +from Crypto.Cipher import AES + + +def decrypt(password, input_file, out_file): + password = bytes.fromhex(password.replace(' ', '')) + with open(input_file, 'rb') as (f): + blist = f.read() + print(len(blist)) + salt = blist[:16] + key = hashlib.pbkdf2_hmac('sha1', password, salt, DEFAULT_ITER, KEY_SIZE) + first = blist[16:DEFAULT_PAGESIZE] + mac_salt = bytes([x ^ 58 for x in salt]) + mac_key = hashlib.pbkdf2_hmac('sha1', key, mac_salt, 2, KEY_SIZE) + hash_mac = hmac.new(mac_key, digestmod='sha1') + hash_mac.update(first[:-32]) + hash_mac.update(bytes(ctypes.c_int(1))) + if hash_mac.digest() == first[-32:-12]: + print('decrypt success') + else: + print('password error') + return + blist = [blist[i:i + DEFAULT_PAGESIZE] for i in range(DEFAULT_PAGESIZE, len(blist), DEFAULT_PAGESIZE)] + with open(out_file, 'wb') as (f): + f.write(SQLITE_FILE_HEADER) + t = AES.new(key, AES.MODE_CBC, first[-48:-32]) + f.write(t.decrypt(first[:-48])) + f.write(first[-48:]) + for i in blist: + t = AES.new(key, AES.MODE_CBC, i[-48:-32]) + f.write(t.decrypt(i[:-48])) + f.write(i[-48:]) + + +def main(): + password = '565735E30E474DA09250CB5AA047E3940FFA1C6F767C4263B13ABB512933DA49' + input_file = 'C:/var/Applet.db' + out_file = 'c:/var/out/Applet.db' + decrypt(password, input_file, out_file) + + +if __name__ == '__main__': + SQLITE_FILE_HEADER = bytes('SQLite format 3', encoding='ASCII') + bytes(1) + KEY_SIZE = 32 + DEFAULT_PAGESIZE = 4096 + DEFAULT_ITER = 64000 + main() \ No newline at end of file diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt new file mode 100644 index 0000000..9e36448 --- /dev/null +++ b/source/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.0.0) +project(ConsoleApplication VERSION 1.0.0) + + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D '_UNICODE' /D 'UNICODE'") + +file(GLOB INJECT_CPP_FILES ${PROJECT_SOURCE_DIR}/*.cc ${PROJECT_SOURCE_DIR}/*.cpp) + +add_executable (ConsoleApplication ${INJECT_CPP_FILES}) + +SET_TARGET_PROPERTIES(ConsoleApplication PROPERTIES LINKER_LANGUAGE C + ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin + LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin + RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin + OUTPUT_NAME "ConsoleApplication" + PREFIX "") + \ No newline at end of file diff --git a/source/ConsoleApplication.cc b/source/ConsoleApplication.cc new file mode 100644 index 0000000..f746b94 --- /dev/null +++ b/source/ConsoleApplication.cc @@ -0,0 +1,962 @@ +// ConsoleApplication.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 +// https://github.com/yihleego/handle-tools + +#include +#include +#include +#include "getopt.h" +#include + +#include "ntstatus.h" + + +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) + +bool endsWith(const std::string& str, const std::string suffix) { + if (suffix.length() > str.length()) { return false; } + return (str.rfind(suffix) == (str.length() - suffix.length())); +} + +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING, * PUNICODE_STRING; + +typedef struct _SYSTEM_HANDLE { + PVOID Object; + HANDLE UniqueProcessId; + HANDLE HandleValue; + ULONG GrantedAccess; + USHORT CreatorBackTraceIndex; + USHORT ObjectTypeIndex; + ULONG HandleAttributes; + ULONG Reserved; +} SYSTEM_HANDLE, * PSYSTEM_HANDLE; + +typedef struct _SYSTEM_HANDLE_INFORMATION_EX { + ULONG_PTR HandleCount; + ULONG_PTR Reserved; + SYSTEM_HANDLE Handles[1]; +} SYSTEM_HANDLE_INFORMATION_EX, * PSYSTEM_HANDLE_INFORMATION_EX; + +typedef struct _OBJECT_BASIC_INFORMATION { + ULONG Attributes; + ACCESS_MASK GrantedAccess; + ULONG HandleCount; + ULONG PointerCount; + ULONG PagedPoolCharge; + ULONG NonPagedPoolCharge; + ULONG Reserved[3]; + ULONG NameInfoSize; + ULONG TypeInfoSize; + ULONG SecurityDescriptorSize; + LARGE_INTEGER CreationTime; +} OBJECT_BASIC_INFORMATION, * POBJECT_BASIC_INFORMATION; + +typedef struct _OBJECT_NAME_INFORMATION { + UNICODE_STRING Name; +} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION; + +typedef struct _OBJECT_TYPE_INFORMATION { + UNICODE_STRING TypeName; + ULONG Reserved[22]; // reserved for internal use +} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION; + +typedef enum _SYSTEM_INFORMATION_CLASS { + SystemBasicInformation, // q: SYSTEM_BASIC_INFORMATION + SystemProcessorInformation, // q: SYSTEM_PROCESSOR_INFORMATION + SystemPerformanceInformation, // q: SYSTEM_PERFORMANCE_INFORMATION + SystemTimeOfDayInformation, // q: SYSTEM_TIMEOFDAY_INFORMATION + SystemPathInformation, // not implemented + SystemProcessInformation, // q: SYSTEM_PROCESS_INFORMATION + SystemCallCountInformation, // q: SYSTEM_CALL_COUNT_INFORMATION + SystemDeviceInformation, // q: SYSTEM_DEVICE_INFORMATION + SystemProcessorPerformanceInformation, // q: SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION + SystemFlagsInformation, // q: SYSTEM_FLAGS_INFORMATION + SystemCallTimeInformation, // not implemented // SYSTEM_CALL_TIME_INFORMATION // 10 + SystemModuleInformation, // q: RTL_PROCESS_MODULES + SystemLocksInformation, // q: RTL_PROCESS_LOCKS + SystemStackTraceInformation, // q: RTL_PROCESS_BACKTRACES + SystemPagedPoolInformation, // not implemented + SystemNonPagedPoolInformation, // not implemented + SystemHandleInformation, // q: SYSTEM_HANDLE_INFORMATION + SystemObjectInformation, // q: SYSTEM_OBJECTTYPE_INFORMATION mixed with SYSTEM_OBJECT_INFORMATION + SystemPageFileInformation, // q: SYSTEM_PAGEFILE_INFORMATION + SystemVdmInstemulInformation, // q: SYSTEM_VDM_INSTEMUL_INFO + SystemVdmBopInformation, // not implemented // 20 + SystemFileCacheInformation, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (info for WorkingSetTypeSystemCache) + SystemPoolTagInformation, // q: SYSTEM_POOLTAG_INFORMATION + SystemInterruptInformation, // q: SYSTEM_INTERRUPT_INFORMATION + SystemDpcBehaviorInformation, // q: SYSTEM_DPC_BEHAVIOR_INFORMATION; s: SYSTEM_DPC_BEHAVIOR_INFORMATION (requires SeLoadDriverPrivilege) + SystemFullMemoryInformation, // not implemented // SYSTEM_MEMORY_USAGE_INFORMATION + SystemLoadGdiDriverInformation, // s (kernel-mode only) + SystemUnloadGdiDriverInformation, // s (kernel-mode only) + SystemTimeAdjustmentInformation, // q: SYSTEM_QUERY_TIME_ADJUST_INFORMATION; s: SYSTEM_SET_TIME_ADJUST_INFORMATION (requires SeSystemtimePrivilege) + SystemSummaryMemoryInformation, // not implemented // SYSTEM_MEMORY_USAGE_INFORMATION + SystemMirrorMemoryInformation, // s (requires license value "Kernel-MemoryMirroringSupported") (requires SeShutdownPrivilege) // 30 + SystemPerformanceTraceInformation, // q; s: (type depends on EVENT_TRACE_INFORMATION_CLASS) + SystemObsolete0, // not implemented + SystemExceptionInformation, // q: SYSTEM_EXCEPTION_INFORMATION + SystemCrashDumpStateInformation, // s: SYSTEM_CRASH_DUMP_STATE_INFORMATION (requires SeDebugPrivilege) + SystemKernelDebuggerInformation, // q: SYSTEM_KERNEL_DEBUGGER_INFORMATION + SystemContextSwitchInformation, // q: SYSTEM_CONTEXT_SWITCH_INFORMATION + SystemRegistryQuotaInformation, // q: SYSTEM_REGISTRY_QUOTA_INFORMATION; s (requires SeIncreaseQuotaPrivilege) + SystemExtendServiceTableInformation, // s (requires SeLoadDriverPrivilege) // loads win32k only + SystemPrioritySeperation, // s (requires SeTcbPrivilege) + SystemVerifierAddDriverInformation, // s (requires SeDebugPrivilege) // 40 + SystemVerifierRemoveDriverInformation, // s (requires SeDebugPrivilege) + SystemProcessorIdleInformation, // q: SYSTEM_PROCESSOR_IDLE_INFORMATION + SystemLegacyDriverInformation, // q: SYSTEM_LEGACY_DRIVER_INFORMATION + SystemCurrentTimeZoneInformation, // q; s: RTL_TIME_ZONE_INFORMATION + SystemLookasideInformation, // q: SYSTEM_LOOKASIDE_INFORMATION + SystemTimeSlipNotification, // s: HANDLE (NtCreateEvent) (requires SeSystemtimePrivilege) + SystemSessionCreate, // not implemented + SystemSessionDetach, // not implemented + SystemSessionInformation, // not implemented (SYSTEM_SESSION_INFORMATION) + SystemRangeStartInformation, // q: SYSTEM_RANGE_START_INFORMATION // 50 + SystemVerifierInformation, // q: SYSTEM_VERIFIER_INFORMATION; s (requires SeDebugPrivilege) + SystemVerifierThunkExtend, // s (kernel-mode only) + SystemSessionProcessInformation, // q: SYSTEM_SESSION_PROCESS_INFORMATION + SystemLoadGdiDriverInSystemSpace, // s: SYSTEM_GDI_DRIVER_INFORMATION (kernel-mode only) (same as SystemLoadGdiDriverInformation) + SystemNumaProcessorMap, // q: SYSTEM_NUMA_INFORMATION + SystemPrefetcherInformation, // q; s: PREFETCHER_INFORMATION // PfSnQueryPrefetcherInformation + SystemExtendedProcessInformation, // q: SYSTEM_PROCESS_INFORMATION + SystemRecommendedSharedDataAlignment, // q: ULONG // KeGetRecommendedSharedDataAlignment + SystemComPlusPackage, // q; s: ULONG + SystemNumaAvailableMemory, // q: SYSTEM_NUMA_INFORMATION // 60 + SystemProcessorPowerInformation, // q: SYSTEM_PROCESSOR_POWER_INFORMATION + SystemEmulationBasicInformation, // q: SYSTEM_BASIC_INFORMATION + SystemEmulationProcessorInformation, // q: SYSTEM_PROCESSOR_INFORMATION + SystemExtendedHandleInformation, // q: SYSTEM_HANDLE_INFORMATION_EX + SystemLostDelayedWriteInformation, // q: ULONG + SystemBigPoolInformation, // q: SYSTEM_BIGPOOL_INFORMATION + SystemSessionPoolTagInformation, // q: SYSTEM_SESSION_POOLTAG_INFORMATION + SystemSessionMappedViewInformation, // q: SYSTEM_SESSION_MAPPED_VIEW_INFORMATION + SystemHotpatchInformation, // q; s: SYSTEM_HOTPATCH_CODE_INFORMATION + SystemObjectSecurityMode, // q: ULONG // 70 + SystemWatchdogTimerHandler, // s: SYSTEM_WATCHDOG_HANDLER_INFORMATION // (kernel-mode only) + SystemWatchdogTimerInformation, // q: SYSTEM_WATCHDOG_TIMER_INFORMATION // (kernel-mode only) + SystemLogicalProcessorInformation, // q: SYSTEM_LOGICAL_PROCESSOR_INFORMATION + SystemWow64SharedInformationObsolete, // not implemented + SystemRegisterFirmwareTableInformationHandler, // s: SYSTEM_FIRMWARE_TABLE_HANDLER // (kernel-mode only) + SystemFirmwareTableInformation, // SYSTEM_FIRMWARE_TABLE_INFORMATION + SystemModuleInformationEx, // q: RTL_PROCESS_MODULE_INFORMATION_EX + SystemVerifierTriageInformation, // not implemented + SystemSuperfetchInformation, // q; s: SUPERFETCH_INFORMATION // PfQuerySuperfetchInformation + SystemMemoryListInformation, // q: SYSTEM_MEMORY_LIST_INFORMATION; s: SYSTEM_MEMORY_LIST_COMMAND (requires SeProfileSingleProcessPrivilege) // 80 + SystemFileCacheInformationEx, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (same as SystemFileCacheInformation) + SystemThreadPriorityClientIdInformation, // s: SYSTEM_THREAD_CID_PRIORITY_INFORMATION (requires SeIncreaseBasePriorityPrivilege) + SystemProcessorIdleCycleTimeInformation, // q: SYSTEM_PROCESSOR_IDLE_CYCLE_TIME_INFORMATION[] + SystemVerifierCancellationInformation, // SYSTEM_VERIFIER_CANCELLATION_INFORMATION // name:wow64:whNT32QuerySystemVerifierCancellationInformation + SystemProcessorPowerInformationEx, // not implemented + SystemRefTraceInformation, // q; s: SYSTEM_REF_TRACE_INFORMATION // ObQueryRefTraceInformation + SystemSpecialPoolInformation, // q; s: SYSTEM_SPECIAL_POOL_INFORMATION (requires SeDebugPrivilege) // MmSpecialPoolTag, then MmSpecialPoolCatchOverruns != 0 + SystemProcessIdInformation, // q: SYSTEM_PROCESS_ID_INFORMATION + SystemErrorPortInformation, // s (requires SeTcbPrivilege) + SystemBootEnvironmentInformation, // q: SYSTEM_BOOT_ENVIRONMENT_INFORMATION // 90 + SystemHypervisorInformation, // q: SYSTEM_HYPERVISOR_QUERY_INFORMATION + SystemVerifierInformationEx, // q; s: SYSTEM_VERIFIER_INFORMATION_EX + SystemTimeZoneInformation, // q; s: RTL_TIME_ZONE_INFORMATION (requires SeTimeZonePrivilege) + SystemImageFileExecutionOptionsInformation, // s: SYSTEM_IMAGE_FILE_EXECUTION_OPTIONS_INFORMATION (requires SeTcbPrivilege) + SystemCoverageInformation, // q: COVERAGE_MODULES s: COVERAGE_MODULE_REQUEST // ExpCovQueryInformation (requires SeDebugPrivilege) + SystemPrefetchPatchInformation, // SYSTEM_PREFETCH_PATCH_INFORMATION + SystemVerifierFaultsInformation, // s: SYSTEM_VERIFIER_FAULTS_INFORMATION (requires SeDebugPrivilege) + SystemSystemPartitionInformation, // q: SYSTEM_SYSTEM_PARTITION_INFORMATION + SystemSystemDiskInformation, // q: SYSTEM_SYSTEM_DISK_INFORMATION + SystemProcessorPerformanceDistribution, // q: SYSTEM_PROCESSOR_PERFORMANCE_DISTRIBUTION // 100 + SystemNumaProximityNodeInformation, // q; s: SYSTEM_NUMA_PROXIMITY_MAP + SystemDynamicTimeZoneInformation, // q; s: RTL_DYNAMIC_TIME_ZONE_INFORMATION (requires SeTimeZonePrivilege) + SystemCodeIntegrityInformation, // q: SYSTEM_CODEINTEGRITY_INFORMATION // SeCodeIntegrityQueryInformation + SystemProcessorMicrocodeUpdateInformation, // s: SYSTEM_PROCESSOR_MICROCODE_UPDATE_INFORMATION + SystemProcessorBrandString, // q: CHAR[] // HaliQuerySystemInformation -> HalpGetProcessorBrandString, info class 23 + SystemVirtualAddressInformation, // q: SYSTEM_VA_LIST_INFORMATION[]; s: SYSTEM_VA_LIST_INFORMATION[] (requires SeIncreaseQuotaPrivilege) // MmQuerySystemVaInformation + SystemLogicalProcessorAndGroupInformation, // q: SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX // since WIN7 // KeQueryLogicalProcessorRelationship + SystemProcessorCycleTimeInformation, // q: SYSTEM_PROCESSOR_CYCLE_TIME_INFORMATION[] + SystemStoreInformation, // q; s: SYSTEM_STORE_INFORMATION (requires SeProfileSingleProcessPrivilege) // SmQueryStoreInformation + SystemRegistryAppendString, // s: SYSTEM_REGISTRY_APPEND_STRING_PARAMETERS // 110 + SystemAitSamplingValue, // s: ULONG (requires SeProfileSingleProcessPrivilege) + SystemVhdBootInformation, // q: SYSTEM_VHD_BOOT_INFORMATION + SystemCpuQuotaInformation, // q; s: PS_CPU_QUOTA_QUERY_INFORMATION + SystemNativeBasicInformation, // q: SYSTEM_BASIC_INFORMATION + SystemErrorPortTimeouts, // SYSTEM_ERROR_PORT_TIMEOUTS + SystemLowPriorityIoInformation, // q: SYSTEM_LOW_PRIORITY_IO_INFORMATION + SystemTpmBootEntropyInformation, // q: TPM_BOOT_ENTROPY_NT_RESULT // ExQueryTpmBootEntropyInformation + SystemVerifierCountersInformation, // q: SYSTEM_VERIFIER_COUNTERS_INFORMATION + SystemPagedPoolInformationEx, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (info for WorkingSetTypePagedPool) + SystemSystemPtesInformationEx, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (info for WorkingSetTypeSystemPtes) // 120 + SystemNodeDistanceInformation, + SystemAcpiAuditInformation, // q: SYSTEM_ACPI_AUDIT_INFORMATION // HaliQuerySystemInformation -> HalpAuditQueryResults, info class 26 + SystemBasicPerformanceInformation, // q: SYSTEM_BASIC_PERFORMANCE_INFORMATION // name:wow64:whNtQuerySystemInformation_SystemBasicPerformanceInformation + SystemQueryPerformanceCounterInformation, // q: SYSTEM_QUERY_PERFORMANCE_COUNTER_INFORMATION // since WIN7 SP1 + SystemSessionBigPoolInformation, // q: SYSTEM_SESSION_POOLTAG_INFORMATION // since WIN8 + SystemBootGraphicsInformation, // q; s: SYSTEM_BOOT_GRAPHICS_INFORMATION (kernel-mode only) + SystemScrubPhysicalMemoryInformation, // q; s: MEMORY_SCRUB_INFORMATION + SystemBadPageInformation, + SystemProcessorProfileControlArea, // q; s: SYSTEM_PROCESSOR_PROFILE_CONTROL_AREA + SystemCombinePhysicalMemoryInformation, // s: MEMORY_COMBINE_INFORMATION, MEMORY_COMBINE_INFORMATION_EX, MEMORY_COMBINE_INFORMATION_EX2 // 130 + SystemEntropyInterruptTimingInformation, // q; s: SYSTEM_ENTROPY_TIMING_INFORMATION + SystemConsoleInformation, // q: SYSTEM_CONSOLE_INFORMATION + SystemPlatformBinaryInformation, // q: SYSTEM_PLATFORM_BINARY_INFORMATION (requires SeTcbPrivilege) + SystemPolicyInformation, // q: SYSTEM_POLICY_INFORMATION + SystemHypervisorProcessorCountInformation, // q: SYSTEM_HYPERVISOR_PROCESSOR_COUNT_INFORMATION + SystemDeviceDataInformation, // q: SYSTEM_DEVICE_DATA_INFORMATION + SystemDeviceDataEnumerationInformation, // q: SYSTEM_DEVICE_DATA_INFORMATION + SystemMemoryTopologyInformation, // q: SYSTEM_MEMORY_TOPOLOGY_INFORMATION + SystemMemoryChannelInformation, // q: SYSTEM_MEMORY_CHANNEL_INFORMATION + SystemBootLogoInformation, // q: SYSTEM_BOOT_LOGO_INFORMATION // 140 + SystemProcessorPerformanceInformationEx, // q: SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION_EX // since WINBLUE + SystemCriticalProcessErrorLogInformation, + SystemSecureBootPolicyInformation, // q: SYSTEM_SECUREBOOT_POLICY_INFORMATION + SystemPageFileInformationEx, // q: SYSTEM_PAGEFILE_INFORMATION_EX + SystemSecureBootInformation, // q: SYSTEM_SECUREBOOT_INFORMATION + SystemEntropyInterruptTimingRawInformation, + SystemPortableWorkspaceEfiLauncherInformation, // q: SYSTEM_PORTABLE_WORKSPACE_EFI_LAUNCHER_INFORMATION + SystemFullProcessInformation, // q: SYSTEM_PROCESS_INFORMATION with SYSTEM_PROCESS_INFORMATION_EXTENSION (requires admin) + SystemKernelDebuggerInformationEx, // q: SYSTEM_KERNEL_DEBUGGER_INFORMATION_EX + SystemBootMetadataInformation, // 150 + SystemSoftRebootInformation, // q: ULONG + SystemElamCertificateInformation, // s: SYSTEM_ELAM_CERTIFICATE_INFORMATION + SystemOfflineDumpConfigInformation, // q: OFFLINE_CRASHDUMP_CONFIGURATION_TABLE_V2 + SystemProcessorFeaturesInformation, // q: SYSTEM_PROCESSOR_FEATURES_INFORMATION + SystemRegistryReconciliationInformation, // s: NULL (requires admin) (flushes registry hives) + SystemEdidInformation, // q: SYSTEM_EDID_INFORMATION + SystemManufacturingInformation, // q: SYSTEM_MANUFACTURING_INFORMATION // since THRESHOLD + SystemEnergyEstimationConfigInformation, // q: SYSTEM_ENERGY_ESTIMATION_CONFIG_INFORMATION + SystemHypervisorDetailInformation, // q: SYSTEM_HYPERVISOR_DETAIL_INFORMATION + SystemProcessorCycleStatsInformation, // q: SYSTEM_PROCESSOR_CYCLE_STATS_INFORMATION // 160 + SystemVmGenerationCountInformation, + SystemTrustedPlatformModuleInformation, // q: SYSTEM_TPM_INFORMATION + SystemKernelDebuggerFlags, // SYSTEM_KERNEL_DEBUGGER_FLAGS + SystemCodeIntegrityPolicyInformation, // q: SYSTEM_CODEINTEGRITYPOLICY_INFORMATION + SystemIsolatedUserModeInformation, // q: SYSTEM_ISOLATED_USER_MODE_INFORMATION + SystemHardwareSecurityTestInterfaceResultsInformation, + SystemSingleModuleInformation, // q: SYSTEM_SINGLE_MODULE_INFORMATION + SystemAllowedCpuSetsInformation, + SystemVsmProtectionInformation, // q: SYSTEM_VSM_PROTECTION_INFORMATION (previously SystemDmaProtectionInformation) + SystemInterruptCpuSetsInformation, // q: SYSTEM_INTERRUPT_CPU_SET_INFORMATION // 170 + SystemSecureBootPolicyFullInformation, // q: SYSTEM_SECUREBOOT_POLICY_FULL_INFORMATION + SystemCodeIntegrityPolicyFullInformation, + SystemAffinitizedInterruptProcessorInformation, // (requires SeIncreaseBasePriorityPrivilege) + SystemRootSiloInformation, // q: SYSTEM_ROOT_SILO_INFORMATION + SystemCpuSetInformation, // q: SYSTEM_CPU_SET_INFORMATION // since THRESHOLD2 + SystemCpuSetTagInformation, // q: SYSTEM_CPU_SET_TAG_INFORMATION + SystemWin32WerStartCallout, + SystemSecureKernelProfileInformation, // q: SYSTEM_SECURE_KERNEL_HYPERGUARD_PROFILE_INFORMATION + SystemCodeIntegrityPlatformManifestInformation, // q: SYSTEM_SECUREBOOT_PLATFORM_MANIFEST_INFORMATION // since REDSTONE + SystemInterruptSteeringInformation, // SYSTEM_INTERRUPT_STEERING_INFORMATION_INPUT // 180 + SystemSupportedProcessorArchitectures, // in: HANDLE, out: SYSTEM_SUPPORTED_PROCESSOR_ARCHITECTURES_INFORMATION[] (Max 5 structs) // NtQuerySystemInformationEx + SystemMemoryUsageInformation, // q: SYSTEM_MEMORY_USAGE_INFORMATION + SystemCodeIntegrityCertificateInformation, // q: SYSTEM_CODEINTEGRITY_CERTIFICATE_INFORMATION + SystemPhysicalMemoryInformation, // q: SYSTEM_PHYSICAL_MEMORY_INFORMATION // since REDSTONE2 + SystemControlFlowTransition, + SystemKernelDebuggingAllowed, // s: ULONG + SystemActivityModerationExeState, // SYSTEM_ACTIVITY_MODERATION_EXE_STATE + SystemActivityModerationUserSettings, // SYSTEM_ACTIVITY_MODERATION_USER_SETTINGS + SystemCodeIntegrityPoliciesFullInformation, + SystemCodeIntegrityUnlockInformation, // SYSTEM_CODEINTEGRITY_UNLOCK_INFORMATION // 190 + SystemIntegrityQuotaInformation, + SystemFlushInformation, // q: SYSTEM_FLUSH_INFORMATION + SystemProcessorIdleMaskInformation, // q: ULONG_PTR // since REDSTONE3 + SystemSecureDumpEncryptionInformation, + SystemWriteConstraintInformation, // SYSTEM_WRITE_CONSTRAINT_INFORMATION + SystemKernelVaShadowInformation, // SYSTEM_KERNEL_VA_SHADOW_INFORMATION + SystemHypervisorSharedPageInformation, // SYSTEM_HYPERVISOR_SHARED_PAGE_INFORMATION // since REDSTONE4 + SystemFirmwareBootPerformanceInformation, + SystemCodeIntegrityVerificationInformation, // SYSTEM_CODEINTEGRITYVERIFICATION_INFORMATION + SystemFirmwarePartitionInformation, // SYSTEM_FIRMWARE_PARTITION_INFORMATION // 200 + SystemSpeculationControlInformation, // SYSTEM_SPECULATION_CONTROL_INFORMATION // (CVE-2017-5715) REDSTONE3 and above. + SystemDmaGuardPolicyInformation, // SYSTEM_DMA_GUARD_POLICY_INFORMATION + SystemEnclaveLaunchControlInformation, // SYSTEM_ENCLAVE_LAUNCH_CONTROL_INFORMATION + SystemWorkloadAllowedCpuSetsInformation, // SYSTEM_WORKLOAD_ALLOWED_CPU_SET_INFORMATION // since REDSTONE5 + SystemCodeIntegrityUnlockModeInformation, + SystemLeapSecondInformation, // SYSTEM_LEAP_SECOND_INFORMATION + SystemFlags2Information, // q: SYSTEM_FLAGS_INFORMATION + SystemSecurityModelInformation, // SYSTEM_SECURITY_MODEL_INFORMATION // since 19H1 + SystemCodeIntegritySyntheticCacheInformation, + SystemFeatureConfigurationInformation, // SYSTEM_FEATURE_CONFIGURATION_INFORMATION // since 20H1 // 210 + SystemFeatureConfigurationSectionInformation, // SYSTEM_FEATURE_CONFIGURATION_SECTIONS_INFORMATION + SystemFeatureUsageSubscriptionInformation, // SYSTEM_FEATURE_USAGE_SUBSCRIPTION_DETAILS + SystemSecureSpeculationControlInformation, // SECURE_SPECULATION_CONTROL_INFORMATION + SystemSpacesBootInformation, // since 20H2 + SystemFwRamdiskInformation, // SYSTEM_FIRMWARE_RAMDISK_INFORMATION + SystemWheaIpmiHardwareInformation, + SystemDifSetRuleClassInformation, + SystemDifClearRuleClassInformation, + SystemDifApplyPluginVerificationOnDriver, + SystemDifRemovePluginVerificationOnDriver, // 220 + SystemShadowStackInformation, // SYSTEM_SHADOW_STACK_INFORMATION + SystemBuildVersionInformation, // SYSTEM_BUILD_VERSION_INFORMATION + SystemPoolLimitInformation, // SYSTEM_POOL_LIMIT_INFORMATION + SystemCodeIntegrityAddDynamicStore, + SystemCodeIntegrityClearDynamicStores, + SystemDifPoolTrackingInformation, + SystemPoolZeroingInformation, // SYSTEM_POOL_ZEROING_INFORMATION + MaxSystemInfoClass +} SYSTEM_INFORMATION_CLASS; + +typedef enum _OBJECT_INFORMATION_CLASS { + ObjectBasicInformation = 0, // q: OBJECT_BASIC_INFORMATION + ObjectNameInformation = 1, // q: OBJECT_NAME_INFORMATION + ObjectTypeInformation = 2, // q: OBJECT_TYPE_INFORMATION + ObjectTypesInformation, // q: OBJECT_TYPES_INFORMATION + ObjectHandleFlagInformation, // qs: OBJECT_HANDLE_FLAG_INFORMATION + ObjectSessionInformation, // s: void // change object session // (requires SeTcbPrivilege) + ObjectSessionObjectInformation, // s: void // change object session // (requires SeTcbPrivilege) + MaxObjectInfoClass +} OBJECT_INFORMATION_CLASS; + +typedef NTSTATUS(WINAPI* PNtQuerySystemInformation)( + _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, + _Inout_ PVOID SystemInformation, + _In_ ULONG SystemInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +typedef NTSTATUS(WINAPI* PNtQueryObject)( + _In_opt_ HANDLE Handle, + _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass, + _Out_opt_ PVOID ObjectInformation, + _In_ ULONG ObjectInformationLength, + _Out_opt_ PULONG ReturnLength); + +typedef NTSTATUS(WINAPI* PNtDuplicateObject)( + _In_ HANDLE SourceProcessHandle, + _In_ HANDLE SourceHandle, + _In_opt_ HANDLE TargetProcessHandle, + _Out_opt_ PHANDLE TargetHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG HandleAttributes, + _In_ ULONG Options + ); + +int FindHandles(ULONG pid, LPSTR handleName, BOOL closeHandle, BOOL suffix) { + HMODULE ntdll = GetModuleHandle(TEXT("ntdll.dll")); + if (NULL == ntdll) { + printf("Failed to load 'ntdll.dll'\n"); + return 0; + } + PNtQuerySystemInformation pQuerySystemInformation = (PNtQuerySystemInformation)GetProcAddress(ntdll, "NtQuerySystemInformation"); + PNtQueryObject pQueryObject = (PNtQueryObject)GetProcAddress(ntdll, "NtQueryObject"); + PNtDuplicateObject pDuplicateObject = (PNtDuplicateObject)GetProcAddress(ntdll, "NtDuplicateObject"); + if (NULL == pQuerySystemInformation || NULL == pQueryObject || NULL == pDuplicateObject) { + printf("Failed to call 'GetProcAddress()'\n"); + return 0; + } + + ULONG len = 0x10000; + NTSTATUS status; + PSYSTEM_HANDLE_INFORMATION_EX pHandleInfo = NULL; + do { + if (len > 0x4000000) { + return 0; + } + len *= 2; + pHandleInfo = (PSYSTEM_HANDLE_INFORMATION_EX)GlobalAlloc(GMEM_ZEROINIT, len); + status = pQuerySystemInformation(SystemExtendedHandleInformation, pHandleInfo, len, &len); + } while (status == STATUS_INFO_LENGTH_MISMATCH); + + if (!NT_SUCCESS(status)) { + printf("Failed to call 'NtQuerySystemInformation()' with error code 0x%X\n", status); + return 0; + } + + HANDLE currentProcess = GetCurrentProcess(); + for (int i = 0; i < pHandleInfo->HandleCount; i++) { + SYSTEM_HANDLE handle = pHandleInfo->Handles[i]; + PVOID object = handle.Object; + HANDLE handleValue = handle.HandleValue; + HANDLE uniqueProcessId = handle.UniqueProcessId; + if (NULL != pid && HandleToLong(uniqueProcessId) != pid) { + continue; + } + LPSTR pName = NULL; + LPSTR pType = NULL; + HANDLE sourceProcess = OpenProcess(PROCESS_ALL_ACCESS | PROCESS_DUP_HANDLE | PROCESS_SUSPEND_RESUME, FALSE, HandleToULong(uniqueProcessId)); + HANDLE targetHandle = NULL; + NTSTATUS status = pDuplicateObject(sourceProcess, handleValue, currentProcess, &targetHandle, 0, FALSE, DUPLICATE_SAME_ACCESS); + if (NT_SUCCESS(status)) { + //printf("Failed to call 'NtDuplicateObject()' with error code 0x%X\n", status); + POBJECT_NAME_INFORMATION pNameInfo = (POBJECT_NAME_INFORMATION)GlobalAlloc(GMEM_ZEROINIT, len); + POBJECT_TYPE_INFORMATION pTypeInfo = (POBJECT_TYPE_INFORMATION)GlobalAlloc(GMEM_ZEROINIT, len); + if (NT_SUCCESS(pQueryObject(targetHandle, ObjectNameInformation, pNameInfo, len, NULL))) { + pName = (LPSTR)GlobalAlloc(GMEM_ZEROINIT, pNameInfo->Name.Length); + WideCharToMultiByte(CP_ACP, 0, pNameInfo->Name.Buffer, -1, pName, pNameInfo->Name.Length, NULL, NULL); + } + if (NT_SUCCESS(pQueryObject(targetHandle, ObjectTypeInformation, pTypeInfo, len, NULL))) { + pType = (LPSTR)GlobalAlloc(GMEM_ZEROINIT, pTypeInfo->TypeName.Length); + WideCharToMultiByte(CP_ACP, 0, pTypeInfo->TypeName.Buffer, -1, pType, pTypeInfo->TypeName.Length, NULL, NULL); + } + } + if (NULL != handleName) { + if (suffix) { + if (NULL == pName || !endsWith(std::string(pName), std::string(handleName))) { + continue; + } + } + else { + if (NULL == pName || 0 != strcmp(pName, handleName)) { + continue; + } + } + + if (TRUE == closeHandle) { + HANDLE hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, HandleToLong(uniqueProcessId)); + DuplicateHandle(hProcess, handleValue, 0, 0, 0, 0, DUPLICATE_CLOSE_SOURCE); + CloseHandle(hProcess); + } + } + printf("PID: %-6d\t", uniqueProcessId); + printf("Handle: 0x%-3x\t", handleValue); + printf("Object: 0x%-8X\t", object); + printf("Type: %-20s\t", NULL != pType ? pType : ""); + printf("Name: %-30s\t", NULL != pName ? pName : ""); + printf("\n"); + } + return 1; +} + +int DisplayHandles() { + return FindHandles(NULL, NULL, FALSE, FALSE); +} + +int DisplayHandles(ULONG pid) { + return FindHandles(pid, NULL, FALSE, FALSE); +} + +int FindHandle(ULONG pid, LPSTR handleName) { + return FindHandles(pid, handleName, FALSE, FALSE); +} + +int CloseHandle(ULONG pid, LPSTR handleName) { + return FindHandles(pid, handleName, TRUE, FALSE); +} + +HANDLE FindHandleByName(ULONG pid, LPSTR handleName) { + HMODULE ntdll = GetModuleHandle(TEXT("ntdll.dll")); + if (NULL == ntdll) { + printf("Failed to load 'ntdll.dll'\n"); + return 0; + } + PNtQuerySystemInformation pQuerySystemInformation = (PNtQuerySystemInformation)GetProcAddress(ntdll, "NtQuerySystemInformation"); + PNtQueryObject pQueryObject = (PNtQueryObject)GetProcAddress(ntdll, "NtQueryObject"); + PNtDuplicateObject pDuplicateObject = (PNtDuplicateObject)GetProcAddress(ntdll, "NtDuplicateObject"); + if (NULL == pQuerySystemInformation || NULL == pQueryObject || NULL == pDuplicateObject) { + printf("Failed to call 'GetProcAddress()'\n"); + return 0; + } + + ULONG len = 0x10000; + NTSTATUS status; + PSYSTEM_HANDLE_INFORMATION_EX pHandleInfo = NULL; + do { + if (len > 0x4000000) { + return 0; + } + len *= 2; + pHandleInfo = (PSYSTEM_HANDLE_INFORMATION_EX)GlobalAlloc(GMEM_ZEROINIT, len); + status = pQuerySystemInformation(SystemExtendedHandleInformation, pHandleInfo, len, &len); + } while (status == STATUS_INFO_LENGTH_MISMATCH); + + if (!NT_SUCCESS(status)) { + printf("Failed to call 'NtQuerySystemInformation()' with error code 0x%X\n", status); + return 0; + } + + HANDLE currentProcess = GetCurrentProcess(); + for (int i = 0; i < pHandleInfo->HandleCount; i++) { + SYSTEM_HANDLE handle = pHandleInfo->Handles[i]; + PVOID object = handle.Object; + HANDLE handleValue = handle.HandleValue; + HANDLE uniqueProcessId = handle.UniqueProcessId; + if (NULL != pid && HandleToLong(uniqueProcessId) != pid) { + continue; + } + LPSTR pName = NULL; + LPSTR pType = NULL; + HANDLE sourceProcess = OpenProcess(PROCESS_ALL_ACCESS | PROCESS_DUP_HANDLE | PROCESS_SUSPEND_RESUME, FALSE, HandleToULong(uniqueProcessId)); + HANDLE targetHandle = NULL; + NTSTATUS status = pDuplicateObject(sourceProcess, handleValue, currentProcess, &targetHandle, 0, FALSE, DUPLICATE_SAME_ACCESS); + if (NT_SUCCESS(status)) { + //printf("Failed to call 'NtDuplicateObject()' with error code 0x%X\n", status); + POBJECT_NAME_INFORMATION pNameInfo = (POBJECT_NAME_INFORMATION)GlobalAlloc(GMEM_ZEROINIT, len); + POBJECT_TYPE_INFORMATION pTypeInfo = (POBJECT_TYPE_INFORMATION)GlobalAlloc(GMEM_ZEROINIT, len); + if (NT_SUCCESS(pQueryObject(targetHandle, ObjectNameInformation, pNameInfo, len, NULL))) { + pName = (LPSTR)GlobalAlloc(GMEM_ZEROINIT, pNameInfo->Name.Length); + WideCharToMultiByte(CP_ACP, 0, pNameInfo->Name.Buffer, -1, pName, pNameInfo->Name.Length, NULL, NULL); + } + if (NT_SUCCESS(pQueryObject(targetHandle, ObjectTypeInformation, pTypeInfo, len, NULL))) { + pType = (LPSTR)GlobalAlloc(GMEM_ZEROINIT, pTypeInfo->TypeName.Length); + WideCharToMultiByte(CP_ACP, 0, pTypeInfo->TypeName.Buffer, -1, pType, pTypeInfo->TypeName.Length, NULL, NULL); + } + } + if (NULL != handleName) { + + if (NULL == pName || 0 != strcmp(pName, handleName)) { + continue; + } + return handleValue; + } + + } +} + + +std::wstring Utf8ToUnicode(const char* buffer) { + int c_size = MultiByteToWideChar(CP_UTF8, 0, buffer, -1, NULL, 0); + if (c_size > 0) { + wchar_t* temp = new wchar_t[c_size + 1]; + MultiByteToWideChar(CP_UTF8, 0, buffer, -1, temp, c_size); + temp[c_size] = L'\0'; + std::wstring ret(temp); + delete[] temp; + temp = NULL; + return ret; + } + return std::wstring(); +} + +DWORD GetPIDForProcess(wchar_t* process) +{ + HANDLE hSnapshot; + DWORD dPid = 0; + PROCESSENTRY32W pe32; + int working; + hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (!hSnapshot) { + return 0; + } + pe32.dwSize = sizeof(PROCESSENTRY32); + for (working = Process32FirstW(hSnapshot, &pe32); working; working = Process32NextW(hSnapshot, &pe32)) + { + if (!wcscmp(pe32.szExeFile, process)) + { + dPid = pe32.th32ProcessID; + break; + } + } + CloseHandle(hSnapshot); + return dPid; +} + +HMODULE GetDLLHandle(wchar_t* wDllName, DWORD dPid) +{ + HMODULE result; + tagMODULEENTRY32W me32; + void* snapMod; + + if (!dPid) { + return 0; + } + + snapMod = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dPid); + me32.dwSize = sizeof(tagMODULEENTRY32W); + if (Module32FirstW(snapMod, &me32)) + { + while (wcscmp(wDllName, me32.szModule)) + { + if (!Module32NextW(snapMod, &me32)) + goto error; + } + CloseHandle(snapMod); + result = me32.hModule; + } + else + { + error: + CloseHandle(snapMod); + result = 0; + } + return result; +} + +BOOL EnableDebugPrivilege() +{ + HANDLE TokenHandle = NULL; + TOKEN_PRIVILEGES TokenPrivilege; + + LUID uID; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle)) { + if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &uID)) { + TokenPrivilege.PrivilegeCount = 1; + TokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + TokenPrivilege.Privileges[0].Luid = uID; + if (AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivilege, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) { + CloseHandle(TokenHandle); + TokenHandle = INVALID_HANDLE_VALUE; + return TRUE; + } + else + goto fail; + + } + else + goto fail; + } + else + goto fail; + +fail: + CloseHandle(TokenHandle); + TokenHandle = INVALID_HANDLE_VALUE; + return FALSE; +} + + +static unsigned char GetProcAddressAsmCode[] = { + 0x55, // push ebp; + 0x8B, 0xEC, // mov ebp, esp; + 0x83, 0xEC, 0x40, // sub esp, 0x40; + 0x57, // push edi; + 0x51, // push ecx; + 0x8B, 0x7D, 0x08, // mov edi, dword ptr[ebp + 0x8]; + 0x8B, 0x07, // mov eax,dword ptr[edi]; + 0x50, // push eax; + 0xE8, 0x00, 0x00, 0x00, 0x00, // call GetModuleHandleW; + 0x83, 0xC4, 0x04, // add esp,0x4; + 0x83, 0xC7, 0x04, // add edi,0x4; + 0x8B, 0x0F, // mov ecx, dword ptr[edi]; + 0x51, // push ecx; + 0x50, // push eax; + 0xE8, 0x00, 0x00, 0x00, 0x00, // call GetProcAddress; + 0x83, 0xC4, 0x08, // add esp, 0x8; + 0x59, // pop ecx; + 0x5F, // pop edi; + 0x8B, 0xE5, // mov esp, ebp; + 0x5D, // pop ebp; + 0xC3 // retn; +}; + +LPVOID FillAsmCode(HANDLE handle) { + DWORD pGetModuleHandleW = (DWORD)GetModuleHandleW; + DWORD pGetProcAddress = (DWORD)GetProcAddress; + PVOID fillCall1 = (PVOID)&GetProcAddressAsmCode[15]; + PVOID fillCall2 = (PVOID)&GetProcAddressAsmCode[30]; + LPVOID pAsmFuncAddr = VirtualAllocEx(handle, NULL, 1, MEM_COMMIT, PAGE_EXECUTE); + if (!pAsmFuncAddr) { + return 0; + } + *(DWORD*)fillCall1 = pGetModuleHandleW - (DWORD)pAsmFuncAddr - 14 - 5; + *(DWORD*)fillCall2 = pGetProcAddress - (DWORD)pAsmFuncAddr - 29 - 5; + //*(DWORD*)fillCall1 = pGetModuleHandleW ; + //*(DWORD*)fillCall2 = pGetProcAddress; + SIZE_T dwWriteSize; + WriteProcessMemory(handle, pAsmFuncAddr, GetProcAddressAsmCode, sizeof(GetProcAddressAsmCode), &dwWriteSize); + return pAsmFuncAddr; + +} + + +int InjectDllAndStartHttp(wchar_t* szPName, wchar_t* szDllPath, DWORD port) +{ + if(!EnableDebugPrivilege()){ + return 0; + } + int result = 0; + HANDLE hRemoteThread = NULL; + LPTHREAD_START_ROUTINE lpSysLibAddr = NULL; + HINSTANCE__* hKernelModule = NULL; + LPVOID lpRemoteDllBase = NULL; + HANDLE hProcess; + unsigned int dwPid; + size_t ulDllLength; + wchar_t* dllName = (wchar_t*)L"wxhelper.dll"; + size_t dllNameLen = wcslen(dllName) * 2 + 2; + char* funcName = (char* )"http_start"; + size_t funcNameLen = strlen(funcName) + 1; + + HANDLE hStartHttp = NULL; + LPVOID portAddr = NULL; + HANDLE getProcThread = NULL; + + LPVOID paramsAddr = NULL; + LPVOID param1Addr = NULL; + LPVOID param2Addr = NULL; + LPVOID GetProcFuncAddr = NULL; + + DWORD params[2] = { 0 }; + + dwPid = GetPIDForProcess(szPName); + ulDllLength = (wcslen(szDllPath) + 1) * sizeof(wchar_t); + hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwPid); + if (!hProcess) { + goto error; + } + + lpRemoteDllBase = VirtualAllocEx(hProcess, NULL, ulDllLength, MEM_COMMIT, PAGE_READWRITE); + if (lpRemoteDllBase) + { + if (WriteProcessMemory(hProcess, lpRemoteDllBase, szDllPath, ulDllLength, NULL) + && (hKernelModule = GetModuleHandleW(L"kernel32.dll")) != 0 + && (lpSysLibAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernelModule, "LoadLibraryW")) != 0 + && (hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, lpSysLibAddr, lpRemoteDllBase, 0, NULL)) != 0) + { + WaitForSingleObject(hRemoteThread, INFINITE); + GetProcFuncAddr = FillAsmCode(hProcess); + param1Addr = VirtualAllocEx(hProcess, NULL, dllNameLen, MEM_COMMIT, PAGE_READWRITE); + if (param1Addr) { + SIZE_T dwWriteSize; + BOOL bRet = WriteProcessMemory(hProcess, (LPVOID)param1Addr, dllName, dllNameLen, &dwWriteSize); + if (!bRet) { + goto error; + } + } + param2Addr = VirtualAllocEx(hProcess, NULL, funcNameLen, MEM_COMMIT, PAGE_READWRITE); + if (param2Addr) { + SIZE_T dwWriteSize; + BOOL bRet = WriteProcessMemory(hProcess, (LPVOID)param2Addr, funcName, funcNameLen, &dwWriteSize); + if (!bRet) { + goto error; + } + } + + params[0] = (DWORD)param1Addr; + params[1] = (DWORD)param2Addr; + + paramsAddr = VirtualAllocEx(hProcess, NULL, sizeof(params), MEM_COMMIT, PAGE_READWRITE); + if (paramsAddr) { + SIZE_T dwWriteSize; + BOOL bRet = WriteProcessMemory(hProcess, (LPVOID)paramsAddr, ¶ms[0], sizeof(params), &dwWriteSize); + if (!bRet) { + goto error; + } + } + + DWORD dwRet = 0; + getProcThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcFuncAddr, paramsAddr, 0, NULL); + + if (getProcThread) + { + WaitForSingleObject(getProcThread, INFINITE); + GetExitCodeThread(getProcThread, &dwRet); + if (dwRet) { + hStartHttp = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)dwRet, (LPVOID)port, 0, NULL); + WaitForSingleObject(hStartHttp, INFINITE); + result = 1; + } + } + } + } +error: + if (hRemoteThread) { + CloseHandle(hRemoteThread); + } + if (getProcThread) { + CloseHandle(getProcThread); + } + if (hStartHttp) { + CloseHandle(hStartHttp); + } + + if (lpRemoteDllBase) { + VirtualFreeEx(hProcess, lpRemoteDllBase, ulDllLength, MEM_DECOMMIT | MEM_RELEASE); + } + if (param1Addr) { + VirtualFreeEx(hProcess, param1Addr, dllNameLen, MEM_DECOMMIT | MEM_RELEASE); + } + + if (param2Addr) { + VirtualFreeEx(hProcess, param1Addr, funcNameLen, MEM_DECOMMIT | MEM_RELEASE); + } + + if (paramsAddr) { + VirtualFreeEx(hProcess, param1Addr, sizeof(params), MEM_DECOMMIT | MEM_RELEASE); + } + + if (GetProcFuncAddr) { + VirtualFreeEx(hProcess, GetProcFuncAddr, sizeof(GetProcAddressAsmCode), MEM_DECOMMIT | MEM_RELEASE); + } + + CloseHandle(hProcess); + return result; +} + +int InjectDll(wchar_t* szPName, wchar_t* szDllPath) +{ + if(!EnableDebugPrivilege()){ + return 0; + } + int result = 0; + HANDLE hRemoteThread; + LPTHREAD_START_ROUTINE lpSysLibAddr; + HINSTANCE__* hKernelModule; + LPVOID lpRemoteDllBase; + HANDLE hProcess; + unsigned int dwPid; + size_t ulDllLength; + + dwPid = GetPIDForProcess(szPName); + ulDllLength = (wcslen(szDllPath) + 1) * sizeof(wchar_t); + hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwPid); + if (!hProcess) { + return 0; + } + + lpRemoteDllBase = VirtualAllocEx(hProcess, NULL, ulDllLength, MEM_COMMIT, PAGE_READWRITE); + if (lpRemoteDllBase) + { + if (WriteProcessMemory(hProcess, lpRemoteDllBase, szDllPath, ulDllLength, NULL) + && (hKernelModule = GetModuleHandleW(L"kernel32.dll")) != 0 + && (lpSysLibAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernelModule, "LoadLibraryW")) != 0 + && (hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, lpSysLibAddr, lpRemoteDllBase, 0, NULL)) != 0) + { + WaitForSingleObject(hRemoteThread, INFINITE); + VirtualFreeEx(hProcess, lpRemoteDllBase, ulDllLength, MEM_DECOMMIT | MEM_RELEASE); + CloseHandle(hRemoteThread); + CloseHandle(hProcess); + OutputDebugStringA("[DBG] dll inject success"); + printf("dll inject success"); + printf("dll path : %s ", szDllPath); + printf("dll path : %d ", dwPid); + result = 1; + } + else + { + VirtualFreeEx(hProcess, lpRemoteDllBase, ulDllLength, MEM_DECOMMIT | MEM_RELEASE); + CloseHandle(hProcess); + result = 0; + } + } + else + { + CloseHandle(hProcess); + result = 0; + } + return result; +} + +int UnInjectDll(wchar_t* szPName, wchar_t* szDName) +{ + HMODULE hDll; + HANDLE lpFreeLibAddr; + HINSTANCE__* hK32; + HANDLE hProcess; + unsigned int dwPID; + + dwPID = GetPIDForProcess(szPName); + hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwPID); + if (!hProcess) { + return 0; + } + + hK32 = GetModuleHandleW(L"Kernel32.dll"); + if (!hK32) { + return 0; + } + + lpFreeLibAddr = GetProcAddress(hK32, "FreeLibraryAndExitThread"); + //lpFreeLibAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hK32, "FreeLibrary"); + hDll = GetDLLHandle(szDName, dwPID); + if (hDll) { + HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)lpFreeLibAddr, hDll, NULL, NULL); + if (hThread == NULL) { + int errorCode = GetLastError(); + return 0; + } + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + CloseHandle(hProcess); + return 1; + } + + CloseHandle(hProcess); + return 0; +} + +FARPROC ShellCode(DWORD param[]) { + return GetProcAddress(GetModuleHandleW((LPCWSTR)param[0]), (LPCSTR)param[1]); +} + + +int main(int argc, char** argv) +{ + int param; + char cInjectprogram[MAX_PATH] = { 0 }; + char cUnInjectprogram[MAX_PATH] = { 0 }; + char cDllPath[MAX_PATH] = { 0 }; + char cDllName[MAX_PATH] = { 0 }; + int port = 0; + + ULONG pid = 0; + + while ((param = getopt(argc, argv, "i:p:u:d:m:P:h")) != -1) + { + switch (param) + { + case 'i': + strcpy(cInjectprogram, optarg); + break; + case 'p': + strcpy(cDllPath, optarg); + break; + case 'u': + strcpy(cUnInjectprogram, optarg); + case 'd': + strcpy(cDllName, optarg); + break; + case 'h': + printf("Usage: %s [-i/u] [-p/d] [-m]\n", argv[0]); + printf("Options:\n"); + printf(" -h Print this help message.\n"); + printf(" -i Name of the running program to be injected.\n"); + printf(" -u Name of the running program to be uninstalled.\n"); + printf(" -p Full path of injection file.\n"); + printf(" -d Name of injection file.\n"); + printf(" -m WeChat.exe pid.\n"); + printf("\n"); + printf("Examples:\n"); + printf(" window> %s -i test.exe -p c:/inject.dll \n", argv[0]); + printf(" window> %s -u test.exe -d inject.dll \n", argv[0]); + printf(" window> %s -m 1988 \n", argv[0]); + exit(0); + break; + case 'm': + pid = std::stol(optarg); + break; + case 'P': + port = std::atoi(optarg); + break; + default: + abort(); + break; + } + } + + if (pid) { + FindHandles(pid, (LPSTR)"_WeChat_App_Instance_Identity_Mutex_Name", TRUE, TRUE); + } + + if (cInjectprogram[0] != 0 && cDllPath[0] != 0) + { + if (cInjectprogram[0] != '\0' && cDllPath[0] != '\0') + { + if (port == 0) { + std::wstring wsProgram = Utf8ToUnicode(cInjectprogram); + std::wstring wsPath = Utf8ToUnicode(cDllPath); + int ret = InjectDll((wchar_t*)wsProgram.c_str(), (wchar_t*)wsPath.c_str()); + printf(" 注入结果:%i \n", ret); + } + else + { + std::wstring wsProgram = Utf8ToUnicode(cInjectprogram); + std::wstring wsPath = Utf8ToUnicode(cDllPath); + int ret = InjectDllAndStartHttp((wchar_t*)wsProgram.c_str(), (wchar_t*)wsPath.c_str(), port); + printf(" 注入结果:%i \n", ret); + } + } + } + + if (cUnInjectprogram[0] != 0 && cDllName[0] != 0) + { + if (cUnInjectprogram[0] != '\0' && cDllName[0] != '\0') + { + std::wstring wsUnInjectProgram = Utf8ToUnicode(cUnInjectprogram); + std::wstring wsName = Utf8ToUnicode(cDllName); + int ret = UnInjectDll((wchar_t*)wsUnInjectProgram.c_str(), (wchar_t*)wsName.c_str()); + printf(" 卸载结果:%i \n", ret); + } + + } + + return 0; +} diff --git a/source/getopt.h b/source/getopt.h new file mode 100644 index 0000000..54e9208 --- /dev/null +++ b/source/getopt.h @@ -0,0 +1,659 @@ +#ifndef __GETOPT_H__ +/** + * DISCLAIMER + * This file is part of the mingw-w64 runtime package. + * + * The mingw-w64 runtime package and its code is distributed in the hope that it + * will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR + * IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to + * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + /* + * Copyright (c) 2002 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + /*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma warning(disable:4996); + +#define __GETOPT_H__ + + /* All the headers include this file. */ +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */ + +#ifdef REPLACE_GETOPT + int opterr = 1; /* if error message should be printed */ + int optind = 1; /* index into parent argv vector */ + int optopt = '?'; /* character checked for validity */ +#undef optreset /* see getopt.h */ +#define optreset __mingw_optreset + int optreset; /* reset getopt */ + char* optarg; /* argument associated with option */ +#endif + + //extern int optind; /* index of first non-option in argv */ + //extern int optopt; /* single option character, as parsed */ + //extern int opterr; /* flag to enable built-in diagnostics... */ + // /* (user may set to zero, to suppress) */ + // + //extern char *optarg; /* pointer to argument of current option */ + +#define PRINT_ERROR ((opterr) && (*options != ':')) + +#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ +#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ +#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +#ifndef __CYGWIN__ +#define __progname __argv[0] +#else + extern char __declspec(dllimport)* __progname; +#endif + +#ifdef __CYGWIN__ + static char EMSG[] = ""; +#else +#define EMSG "" +#endif + + static int getopt_internal(int, char* const*, const char*, + const struct option*, int*, int); + static int parse_long_options(char* const*, const char*, + const struct option*, int*, int); + static int gcd(int, int); + static void permute_args(int, int, int, char* const*); + + static char* place = EMSG; /* option letter processing */ + + /* XXX: set optreset to 1 rather than these two */ + static int nonopt_start = -1; /* first non option argument (for permute) */ + static int nonopt_end = -1; /* first option after non options (for permute) */ + + /* Error messages */ + static const char recargchar[] = "option requires an argument -- %c"; + static const char recargstring[] = "option requires an argument -- %s"; + static const char ambig[] = "ambiguous option -- %.*s"; + static const char noarg[] = "option doesn't take an argument -- %.*s"; + static const char illoptchar[] = "unknown option -- %c"; + static const char illoptstring[] = "unknown option -- %s"; + + static void + _vwarnx(const char* fmt, va_list ap) + { + (void)fprintf(stderr, "%s: ", __progname); + if (fmt != NULL) + (void)vfprintf(stderr, fmt, ap); + (void)fprintf(stderr, "\n"); + } + + static void + warnx(const char* fmt, ...) + { + va_list ap; + va_start(ap, fmt); + _vwarnx(fmt, ap); + va_end(ap); + } + + /* + * Compute the greatest common divisor of a and b. + */ + static int + gcd(int a, int b) + { + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return (b); + } + + /* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ + static void + permute_args(int panonopt_start, int panonopt_end, int opt_end, + char* const* nargv) + { + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char* swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end + i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + /* LINTED const cast */ + ((char**)nargv)[pos] = nargv[cstart]; + /* LINTED const cast */ + ((char**)nargv)[cstart] = swap; + } + } + } + +#ifdef REPLACE_GETOPT + /* + * getopt -- + * Parse argc/argv argument vector. + * + * [eventually this will replace the BSD getopt] + */ + int + getopt(int nargc, char* const* nargv, const char* options) + { + + /* + * We don't pass FLAG_PERMUTE to getopt_internal() since + * the BSD getopt(3) (unlike GNU) has never done this. + * + * Furthermore, since many privileged programs call getopt() + * before dropping privileges it makes sense to keep things + * as simple (and bug-free) as possible. + */ + return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); + } +#endif /* REPLACE_GETOPT */ + + //extern int getopt(int nargc, char * const *nargv, const char *options); + +#ifdef _BSD_SOURCE +/* + * BSD adds the non-standard `optreset' feature, for reinitialisation + * of `getopt' parsing. We support this feature, for applications which + * proclaim their BSD heritage, before including this header; however, + * to maintain portability, developers are advised to avoid it. + */ +# define optreset __mingw_optreset + extern int optreset; +#endif +#ifdef __cplusplus +} +#endif +/* + * POSIX requires the `getopt' API to be specified in `unistd.h'; + * thus, `unistd.h' includes this header. However, we do not want + * to expose the `getopt_long' or `getopt_long_only' APIs, when + * included in this manner. Thus, close the standard __GETOPT_H__ + * declarations block, and open an additional __GETOPT_LONG_H__ + * specific block, only when *not* __UNISTD_H_SOURCED__, in which + * to declare the extended API. + */ +#endif /* !defined(__GETOPT_H__) */ + +#if !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) +#define __GETOPT_LONG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + + struct option /* specification for a long form option... */ + { + const char* name; /* option name, without leading hyphens */ + int has_arg; /* does it take an argument? */ + int* flag; /* where to save its status, or NULL */ + int val; /* its associated status value */ + }; + + enum /* permitted values for its `has_arg' field... */ + { + no_argument = 0, /* option never takes an argument */ + required_argument, /* option always requires an argument */ + optional_argument /* option may take an argument */ + }; + + /* + * parse_long_options -- + * Parse long options in argc/argv argument vector. + * Returns -1 if short_too is set and the option does not match long_options. + */ + static int + parse_long_options(char* const* nargv, const char* options, + const struct option* long_options, int* idx, int short_too) + { + char* current_argv, * has_equal; + size_t current_argv_len; + int i, ambiguous, match; + +#define IDENTICAL_INTERPRETATION(_x, _y) \ + (long_options[(_x)].has_arg == long_options[(_y)].has_arg && \ + long_options[(_x)].flag == long_options[(_y)].flag && \ + long_options[(_x)].val == long_options[(_y)].val) + + current_argv = place; + match = -1; + ambiguous = 0; + + optind++; + + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } + else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == current_argv_len) { + /* exact match */ + match = i; + ambiguous = 0; + break; + } + /* + * If this is a known short option, don't allow + * a partial match of a single character. + */ + if (short_too && current_argv_len == 1) + continue; + + if (match == -1) /* partial match */ + match = i; + else if (!IDENTICAL_INTERPRETATION(i, match)) + ambiguous = 1; + } + if (ambiguous) { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + warnx(ambig, (int)current_argv_len, + current_argv); + optopt = 0; + return (BADCH); + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) + warnx(noarg, (int)current_argv_len, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + return (BADARG); + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use next nargv + */ + optarg = nargv[optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (optarg == NULL)) { + /* + * Missing argument; leading ':' indicates no error + * should be generated. + */ + if (PRINT_ERROR) + warnx(recargstring, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + --optind; + return (BADARG); + } + } + else { /* unknown option */ + if (short_too) { + --optind; + return (-1); + } + if (PRINT_ERROR) + warnx(illoptstring, current_argv); + optopt = 0; + return (BADCH); + } + if (idx) + *idx = match; + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + return (0); + } + else + return (long_options[match].val); +#undef IDENTICAL_INTERPRETATION + } + + /* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + */ + static int + getopt_internal(int nargc, char* const* nargv, const char* options, + const struct option* long_options, int* idx, int flags) + { + char* oli; /* option letter list index */ + int optchar, short_too; + static int posixly_correct = -1; + + if (options == NULL) + return (-1); + + /* + * XXX Some GNU programs (like cvs) set optind to 0 instead of + * XXX using optreset. Work around this braindamage. + */ + if (optind == 0) + optind = optreset = 1; + + /* + * Disable GNU extensions if POSIXLY_CORRECT is set or options + * string begins with a '+'. + * + * CV, 2009-12-14: Check POSIXLY_CORRECT anew if optind == 0 or + * optreset != 0 for GNU compatibility. + */ + if (posixly_correct == -1 || optreset != 0) + posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); + if (*options == '-') + flags |= FLAG_ALLARGS; + else if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; + if (*options == '+' || *options == '-') + options++; + + optarg = NULL; + if (optreset) + nonopt_start = nonopt_end = -1; + start: + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + else if (nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + if (*(place = nargv[optind]) != '-' || + (place[1] == '\0' && strchr(options, '-') == NULL)) { + place = EMSG; /* found non-option */ + if (flags & FLAG_ALLARGS) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + optarg = nargv[optind++]; + return (INORDER); + } + if (!(flags & FLAG_PERMUTE)) { + /* + * If no permutation wanted, stop parsing + * at first non-option. + */ + return (-1); + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + nonopt_start = optind - + (nonopt_end - nonopt_start); + nonopt_end = -1; + } + optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = optind; + + /* + * If we have "-" do nothing, if "--" we are done. + */ + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + optind++; + place = EMSG; + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + } + + /* + * Check long options if: + * 1) we were passed some + * 2) the arg is not just "-" + * 3) either the arg starts with -- we are getopt_long_only() + */ + if (long_options != NULL && place != nargv[optind] && + (*place == '-' || (flags & FLAG_LONGONLY))) { + short_too = 0; + if (*place == '-') + place++; /* --foo long option */ + else if (*place != ':' && strchr(options, *place) != NULL) + short_too = 1; /* could be short option too */ + + optchar = parse_long_options(nargv, options, long_options, + idx, short_too); + if (optchar != -1) { + place = EMSG; + return (optchar); + } + } + + if ((optchar = (int)*place++) == (int)':' || + (optchar == (int)'-' && *place != '\0') || + (oli = (char*)strchr(options, optchar)) == NULL) { + /* + * If the user specified "-" and '-' isn't listed in + * options, return -1 (non-option) as per POSIX. + * Otherwise, it is an unknown option character (or ':'). + */ + if (optchar == (int)'-' && *place == '\0') + return (-1); + if (!*place) + ++optind; + if (PRINT_ERROR) + warnx(illoptchar, optchar); + optopt = optchar; + return (BADCH); + } + if (long_options != NULL && optchar == 'W' && oli[1] == ';') { + /* -W long-option */ + if (*place) /* no space */ + /* NOTHING */; + else if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } + else /* white space */ + place = nargv[optind]; + optchar = parse_long_options(nargv, options, long_options, + idx, 0); + place = EMSG; + return (optchar); + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++optind; + } + else { /* takes (optional) argument */ + optarg = NULL; + if (*place) /* no white space */ + optarg = place; + else if (oli[1] != ':') { /* arg not optional */ + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } + else + optarg = nargv[optind]; + } + place = EMSG; + ++optind; + } + /* dump back option letter */ + return (optchar); + } + + /* + * getopt_long -- + * Parse argc/argv argument vector. + */ + int + getopt_long(int nargc, char* const* nargv, const char* options, + const struct option* long_options, int* idx) + { + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE)); + } + + /* + * getopt_long_only -- + * Parse argc/argv argument vector. + */ + int + getopt_long_only(int nargc, char* const* nargv, const char* options, + const struct option* long_options, int* idx) + { + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE | FLAG_LONGONLY)); + } + + //extern int getopt_long(int nargc, char * const *nargv, const char *options, + // const struct option *long_options, int *idx); + //extern int getopt_long_only(int nargc, char * const *nargv, const char *options, + // const struct option *long_options, int *idx); + /* + * Previous MinGW implementation had... + */ +#ifndef HAVE_DECL_GETOPT + /* + * ...for the long form API only; keep this for compatibility. + */ +# define HAVE_DECL_GETOPT 1 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) */ \ No newline at end of file From 61c9e6f8c1f76698dfd1b59ed94dd4ea7fd0283d Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Thu, 30 Mar 2023 23:08:15 +0800 Subject: [PATCH 56/59] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=AF=AD=E9=9F=B3?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 44 +++++++++++++++- src/api.cc | 8 +++ src/api.h | 1 + src/download.cc | 30 +++++++++++ src/download.h | 2 + src/get_db_handle.cc | 120 +++++++++++++++++++++++++++++++++++++++++++ src/get_db_handle.h | 2 + 7 files changed, 206 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d578087..bad1057 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,8 @@ 53.朋友圈首页 54.朋友圈下一页 -56.获取消息附件(图片,视频,文件) +56.获取消息附件(图片,视频,文件) +57.获取消息语音文件 ### 接口文档: @@ -1542,6 +1543,47 @@ ``` +#### 57.获取语音文件** +###### 接口功能 +> 根据消息id,获取该语音消息的语音文件,文件为silk3格式,可以自行转换mp3. + +###### 接口地址 +> [/api/?type=57](/api/?type=57) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|msgId |true |string| 消息id | +|voiceDir |true |string| 语音文件保存的目录,文件名称为 (msgid).amr | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 非0失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "msgId": 3224560917391784099, + "voiceDir" : "c:\\voice" + +} + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + #### 感谢 https://github.com/ljc545w/ComWeChatRobot diff --git a/src/api.cc b/src/api.cc index 0e3da12..a4785fe 100644 --- a/src/api.cc +++ b/src/api.cc @@ -694,6 +694,14 @@ void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { ret = ret_data.dump(); break; } + case WECHAT_GET_VOICE:{ + ULONG64 msg_id = get_http_param_ulong64(hm, j_param, "msgId", is_post); + wstring voice_dir = get_http_req_param(hm, j_param, "voiceDir", is_post); + int success = GetVoice(msg_id,WS2LW(voice_dir)); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } default: break; } diff --git a/src/api.h b/src/api.h index 3b44f4a..951bc67 100644 --- a/src/api.h +++ b/src/api.h @@ -71,6 +71,7 @@ typedef enum WECHAT_HTTP_APISTag WECHAT_SNS_GET_NEXT_PAGE, WECHAT_CONTACT_NAME, WECHAT_ATTACH_DOWNLOAD, + WECHAT_GET_VOICE, } WECHAT_HTTP_APIS, *PWECHAT_HTTP_APIS; diff --git a/src/download.cc b/src/download.cc index d3d5182..7544d8c 100644 --- a/src/download.cc +++ b/src/download.cc @@ -5,6 +5,7 @@ #include "get_db_handle.h" #include "wechat_data.h" +#include "base64.h" #define WX_NEW_CHAT_MSG_OFFSET 0x76f010 #define WX_GET_PRE_DOWNLOAD_MGR_OFFSET 0x80f110 @@ -179,5 +180,34 @@ int DoDownloadTask(ULONG64 msg_id) { POPAD } + return success; +} + +int GetVoice(ULONG64 msg_id, wchar_t *dir) { + int success = -1; + string buff = GetVoiceBuffByMsgId(msg_id); + if (buff.size() == 0) { + success = 0; + return success; + } + wstring save_path = wstring(dir); + if (!FindOrCreateDirectoryW(save_path.c_str())) { + success = -2; + return success; + } + save_path = save_path + L"\\" + to_wstring(msg_id) + L".amr"; + HANDLE file_handle = CreateFileW(save_path.c_str(), GENERIC_ALL, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) { + #ifdef _DEBUG + wcout <<" save_path =" < dbmap; std::vector dbs; @@ -180,6 +209,60 @@ std::vector GetDbHandles() { DWORD *)(p_contact_addr + DB_FUNCTION_MSG_OFFSET + DB_NAME_OFFSET))); dbmap[bizchat_msg_name] = bizchat_msg_db; } + // Storage + DWORD storage_start = *(DWORD *)(p_contact_addr + STORAGE_START_OFFSET); + DWORD storage_end = *(DWORD *)(p_contact_addr + STORAGE_END_OFFSET); + + // do { + // DWORD vtable_ptr = *(DWORD *)(storage_start); + + // if(vtable_ptr == base + OP_LOG_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + CHAT_MSG_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + CHAT_CR_MSG_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + SESSION_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + APP_INFO_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + HEAD_IMG_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + HEAD_IMG_URL_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + BIZ_INFO_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + TICKET_INFO_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + CHAT_ROOM_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + CHAT_ROOM_INFO_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + MEDIA_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + NAME_2_ID_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + EMOTION_PACKAGE_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + EMOTION_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + BUFINFO_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + CUSTOM_EMOTION_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + DEL_SESSIONINFO_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + FUNCTION_MSG_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + FUNCTION_MSG_TASK_STORAGE_VFTABLE){ + + // }else if(vtable_ptr == base + REVOKE_MSG_STORAGE_VFTABLE){ + + // } + + // storage_start = storage_start + 0x4; + // } while (storage_start != storage_end); + DWORD multi_db_mgr_addr = base + MULTI_DB_MSG_MGR_OFFSET; DWORD public_msg_mgr_addr = base + PUBLIC_MSG_MGR_OFFSET; DWORD favorite_storage_mgr_addr = base + FAVORITE_STORAGE_MGR_OFFSET; @@ -207,6 +290,21 @@ std::vector GetDbHandles() { dbs.push_back(msg0_db); wstring msg_db_name = wstring((wchar_t *)(*(DWORD *)(db_addr))); dbmap[msg_db_name] = msg0_db; + + // BufInfoStorage + DWORD buf_info_addr = *(DWORD *)(db_addr + 0x14); + + DWORD buf_info_handle = *(DWORD *)(buf_info_addr + 0x38); + DatabaseInfo media_msg0_db{0}; + media_msg0_db.db_name = (wchar_t *)(*(DWORD *)(buf_info_addr + 0x4C)); + media_msg0_db.db_name_len = *(DWORD *)(buf_info_addr + 0x50); + media_msg0_db.handle = buf_info_handle; + ExecuteSQL(buf_info_handle, + "select * from sqlite_master where type=\"table\";", + (DWORD)GetDbInfo, &media_msg0_db); + dbs.push_back(media_msg0_db); + wstring media_msg_db_name = wstring((wchar_t *)(*(DWORD *)(buf_info_addr + 0x4C))); + dbmap[media_msg_db_name] = media_msg0_db; } } @@ -315,4 +413,26 @@ vector GetChatMsgByMsgId(ULONG64 msgid){ return result[1]; } return {}; +} + +std::string GetVoiceBuffByMsgId(ULONG64 msgid) { + char sql[260] = {0}; + sprintf_s(sql, "SELECT Buf from Media WHERE Reserved0=%llu;", msgid); + wchar_t dbname[20] = {0}; + for (int i = 0;; i++) { + swprintf_s(dbname, L"MediaMSG%d.db", i); + DWORD handle = GetDbHandleByDbName(dbname); + #ifdef _DEBUG + cout <<" handle =" <> result; + int ret = Select(handle, (const char *)sql, result); + #ifdef _DEBUG + cout <<" size =" < GetDbHandles(); DWORD GetDbHandleByDbName(wchar_t *dbname); unsigned int GetLocalIdByMsgId(ULONG64 msgid, int &dbIndex); std::vector GetChatMsgByMsgId(ULONG64 msgid); + +std::string GetVoiceBuffByMsgId(ULONG64 msgid); #endif \ No newline at end of file From 384aade1a88ec6f1eab886abeea3c7dd860445a6 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Fri, 31 Mar 2023 21:20:57 +0800 Subject: [PATCH 57/59] =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- src/api.cc | 2 +- src/common.cc | 13 +++++++ src/common.h | 4 ++- src/dllMain.cc | 7 ++-- src/http_server.cc | 86 ++++++++++++++++++++++++++++++++++++++++++++++ src/http_server.h | 36 +++++++++++++++++++ src/wxhelper.cc | 24 +++++++++++++ src/wxhelper.h | 21 +++++++++++ 9 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 src/http_server.cc create mode 100644 src/http_server.h create mode 100644 src/wxhelper.cc create mode 100644 src/wxhelper.h diff --git a/CMakeLists.txt b/CMakeLists.txt index adcb78d..85f13b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ file(GLOB CPP_FILES ${PROJECT_SOURCE_DIR}/src/*.cc ${PROJECT_SOURCE_DIR}/src/* include_directories(${VCPKG_INSTALLED_DIR}/x86-windows/include) # add_subdirectory(3rd) -add_subdirectory(source) +# add_subdirectory(source) find_package(nlohmann_json CONFIG REQUIRED) find_package(unofficial-mongoose CONFIG REQUIRED) diff --git a/src/api.cc b/src/api.cc index a4785fe..d935d56 100644 --- a/src/api.cc +++ b/src/api.cc @@ -31,7 +31,7 @@ using namespace std; using namespace nlohmann; -#define STR2INT(str) (is_digit(str) ? stoi(str) : 0) +// #define STR2INT(str) (is_digit(str) ? stoi(str) : 0) #define WS2LW(wstr) (LPWSTR) wstr.c_str() static bool kHttpRuning = false; diff --git a/src/common.cc b/src/common.cc index 36a31dd..5a1e218 100644 --- a/src/common.cc +++ b/src/common.cc @@ -188,3 +188,16 @@ void Hex2Bytes(const std::string &hex, BYTE *bytes) { bytes[i] = n; } } + + +bool IsDigit(string str) { + if (str.length() == 0) { + return false; + } + for (auto it : str) { + if (it < '0' || it > '9') { + return false; + } + } + return true; +} \ No newline at end of file diff --git a/src/common.h b/src/common.h index 0cd0f4f..482ce56 100644 --- a/src/common.h +++ b/src/common.h @@ -2,7 +2,7 @@ #define COMMON_H_ #include #define READ_WSTRING(addr, offset) ((*(DWORD *)(addr + offset + 0x4) == 0) ? std::wstring(L"") : std::wstring((wchar_t *)(*(DWORD *)(addr + offset)), *(DWORD *)(addr + offset + 0x4))) - +#define STR2INT(str) (IsDigit(str) ? stoi(str) : 0) /// @brief utf8 转换成unicode /// @param buffer utf8 /// @return unicode @@ -63,6 +63,8 @@ std::string Bytes2Hex(const BYTE *bytes, const int length); void Hex2Bytes(const std::string &hex, BYTE *bytes); +bool IsDigit(std::string str); + template std::vector split(T1 str, T2 letter) { vector arr; diff --git a/src/dllMain.cc b/src/dllMain.cc index 40a1cef..57880ea 100644 --- a/src/dllMain.cc +++ b/src/dllMain.cc @@ -1,12 +1,14 @@ #include "pch.h" #include "api.h" #include "common.h" +#include "wxhelper.h" BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { - http_start(19088); + // http_start(19088); + wxhelper::WXHelper::GetInstance().http_start(19088); break; } case DLL_THREAD_ATTACH: { @@ -17,7 +19,8 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, } case DLL_PROCESS_DETACH: { CloseConsole(); - http_close(); + // http_close(); + wxhelper::WXHelper::GetInstance().http_close(); break; } } diff --git a/src/http_server.cc b/src/http_server.cc new file mode 100644 index 0000000..9e57bc1 --- /dev/null +++ b/src/http_server.cc @@ -0,0 +1,86 @@ +#include "http_server.h" + +#include + +#include "api_route.h" +#include "common.h" +#include "pch.h" +#pragma comment(lib, "ws2_32.lib") +using namespace std; +using namespace nlohmann; + +namespace wxhelper { +HttpServer& HttpServer::GetInstance() { + static HttpServer p; + return p; +} + +void HttpServer::Init(int port) { + port_ = port; + running_ = false; + http_handler_ = new HttpHandler(); + mg_mgr_init(&mgr_); + +} + +HttpServer::~HttpServer() { + mg_mgr_free(&mgr_); + delete http_handler_; +} + +bool HttpServer::HttpStart() { + if (running_) { + return true; + } +#ifdef _DEBUG + CreateConsole(); +#endif + running_ = true; + CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)StartHttpServer, NULL, + NULL, 0); + return false; +} + +void HttpServer::StartHttpServer() { + string lsten_addr = "http://0.0.0.0:" + to_string(GetInstance().port_); + mg_http_listen(&GetInstance().mgr_, lsten_addr.c_str(), EventHandler, + &GetInstance().mgr_); + for (;;) mg_mgr_poll(&GetInstance().mgr_, 1000); +} + +void HttpServer::EventHandler(struct mg_connection *c, + int ev, void *ev_data, void *fn_data) { + if (ev == MG_EV_OPEN) { + // c->is_hexdumping = 1; + } else if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message *hm = (struct mg_http_message *)ev_data; + if (mg_http_match_uri(hm, "/websocket")) { + mg_ws_upgrade(c, hm, NULL); + } else if (mg_http_match_uri(hm, "/api/")) { + GetInstance().HandleHttpRequest(c,hm); + } else { + mg_http_reply(c, 500, NULL, "%s", "Invalid URI"); + } + } else if (ev == MG_EV_WS_MSG) { + + GetInstance().HandleWebsocketRequest(c,ev_data); + } + (void)fn_data; +} + +void HttpServer::HandleHttpRequest(struct mg_connection *c, + void *ev_data) { + + http_handler_->HandlerRequest(c,ev_data); +} + +void HttpServer::HandleWebsocketRequest(struct mg_connection *c, + void *ev_data) { + // Got websocket frame. Received data is wm->data. Echo it back! + struct mg_ws_message *wm = (struct mg_ws_message *)ev_data; + mg_ws_send(c, wm->data.ptr, wm->data.len, WEBSOCKET_OP_TEXT); +} + +bool HttpServer::HttpClose() { return false; } + +} // namespace wxhelper \ No newline at end of file diff --git a/src/http_server.h b/src/http_server.h new file mode 100644 index 0000000..89df650 --- /dev/null +++ b/src/http_server.h @@ -0,0 +1,36 @@ +#ifndef WXHELPER_HTTP_SERVER_H_ +#define WXHELPER_HTTP_SERVER_H_ + +#include +#include "http_handler.h" +namespace wxhelper { +class HttpServer { + + + public: + + static HttpServer &GetInstance(); + bool HttpStart(); + bool HttpClose(); + void Init(int port); + +private: + HttpServer(){} + HttpServer(const HttpServer &) = delete; + HttpServer &operator=(const HttpServer &) = delete; + ~HttpServer(); + static void StartHttpServer(); + static void EventHandler(struct mg_connection *c, int ev, void *ev_data, void *fn_data); + void HandleHttpRequest(struct mg_connection *c, void *ev_data); + void HandleWebsocketRequest(struct mg_connection *c, void *ev_data); + + + private: + int port_; + bool running_; + struct mg_mgr mgr_; + HttpHandler* http_handler_; +}; +} // namespace wxhelper + +#endif diff --git a/src/wxhelper.cc b/src/wxhelper.cc new file mode 100644 index 0000000..73e9397 --- /dev/null +++ b/src/wxhelper.cc @@ -0,0 +1,24 @@ +#include "wxhelper.h" + +#include + +#include "pch.h" + +namespace wxhelper { +WXHelper& WXHelper::GetInstance() { + static WXHelper p; + return p; +} + +int WXHelper::http_start(int port) { + HttpServer::GetInstance().Init(port); + bool ret = HttpServer::GetInstance().HttpStart(); + return ret == true ? 1 : 0; +} + +int WXHelper::http_close() { + bool ret = HttpServer::GetInstance().HttpClose(); + return ret == true ? 1 : 0; +} + +} // namespace wxhelper \ No newline at end of file diff --git a/src/wxhelper.h b/src/wxhelper.h new file mode 100644 index 0000000..d0f60be --- /dev/null +++ b/src/wxhelper.h @@ -0,0 +1,21 @@ +#ifndef WXHELPER_WXHELPER_H_ +#define WXHELPER_WXHELPER_H_ +#include "http_server.h" +namespace wxhelper { + +class WXHelper { + public: + static WXHelper &GetInstance(); + + int http_start(int port); + int http_close(); + + private: + WXHelper(){}; + WXHelper(const WXHelper &) = delete; + WXHelper &operator=(const WXHelper &) = delete; + ~WXHelper(){}; +}; +} // namespace wxhelper + +#endif \ No newline at end of file From bfe5b0ee12e616ccdbd2bedc30b4e7dbf6278d06 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Fri, 31 Mar 2023 21:21:35 +0800 Subject: [PATCH 58/59] =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 5 + README.md | 18 +- src/{self_info.cc => account_mgr.cc} | 67 +- src/account_mgr.h | 19 + src/api.cc | 782 ---- src/api.h | 81 - src/api_route.h | 78 + src/base_mgr.cc | 13 + src/base_mgr.h | 13 + src/chat_room.h | 16 - src/{chat_room.cc => chat_room_mgr.cc} | 269 +- src/chat_room_mgr.h | 28 + src/common.h | 93 - src/config.cc | 6 + src/config.h | 15 + src/confirm_receipt.cc | 49 - src/confirm_receipt.h | 6 - src/contact.cc | 192 - src/contact.h | 15 - src/contact_mgr.cc | 193 + src/contact_mgr.h | 20 + src/{get_db_handle.cc => db.cc} | 384 +- src/db.h | 38 + src/db_operation.cc | 160 - src/db_operation.h | 22 - src/dllMain.cc | 22 +- src/download.cc | 213 -- src/download.h | 7 - src/easylogging++.cc | 3120 ++++++++++++++++ src/easylogging++.h | 4576 ++++++++++++++++++++++++ src/forward.cc | 42 - src/forward.h | 5 - src/get_db_handle.h | 13 - src/global_context.cc | 38 + src/global_context.h | 43 + src/handler.h | 10 + src/hide_module.cc | 64 + src/hide_module.h | 48 + src/hook_img.cc | 231 -- src/hook_img.h | 9 - src/hook_log.cc | 78 - src/hook_log.h | 8 - src/hook_recv_msg.cc | 301 -- src/hook_recv_msg.h | 10 - src/hook_voice.cc | 88 - src/hook_voice.h | 8 - src/hooks.cc | 533 +++ src/hooks.h | 23 + src/http_handler.cc | 594 +++ src/http_handler.h | 17 + src/http_server.cc | 26 +- src/http_server.h | 27 +- src/log.cc | 37 + src/log.h | 18 + src/misc_mgr.cc | 436 +++ src/misc_mgr.h | 24 + src/ocr.cc | 64 - src/ocr.h | 5 - src/pat.cc | 34 - src/pat.h | 4 - src/pch.h | 4 + src/search_contact.cc | 275 -- src/search_contact.h | 8 - src/self_info.h | 9 - src/send_file.cc | 67 - src/send_file.h | 5 - src/send_image.cc | 46 - src/send_image.h | 5 - src/send_message_mgr.cc | 178 + src/send_message_mgr.h | 18 + src/send_text.cc | 118 - src/send_text.h | 6 - src/singleton.h | 21 + src/sns.h | 6 - src/{sns.cc => sns_mgr.cc} | 35 +- src/sns_mgr.h | 14 + src/{common.cc => utils.cc} | 228 +- src/utils.h | 77 + src/wechat_data.h | 337 -- src/wechat_function.h | 770 ++++ src/wxhelper.cc | 24 - src/wxhelper.h | 21 - 82 files changed, 11652 insertions(+), 3978 deletions(-) rename src/{self_info.cc => account_mgr.cc} (81%) create mode 100644 src/account_mgr.h delete mode 100644 src/api.cc delete mode 100644 src/api.h create mode 100644 src/api_route.h create mode 100644 src/base_mgr.cc create mode 100644 src/base_mgr.h delete mode 100644 src/chat_room.h rename src/{chat_room.cc => chat_room_mgr.cc} (53%) create mode 100644 src/chat_room_mgr.h delete mode 100644 src/common.h create mode 100644 src/config.cc create mode 100644 src/config.h delete mode 100644 src/confirm_receipt.cc delete mode 100644 src/confirm_receipt.h delete mode 100644 src/contact.cc delete mode 100644 src/contact.h create mode 100644 src/contact_mgr.cc create mode 100644 src/contact_mgr.h rename src/{get_db_handle.cc => db.cc} (55%) create mode 100644 src/db.h delete mode 100644 src/db_operation.cc delete mode 100644 src/db_operation.h delete mode 100644 src/download.cc delete mode 100644 src/download.h create mode 100644 src/easylogging++.cc create mode 100644 src/easylogging++.h delete mode 100644 src/forward.cc delete mode 100644 src/forward.h delete mode 100644 src/get_db_handle.h create mode 100644 src/global_context.cc create mode 100644 src/global_context.h create mode 100644 src/handler.h create mode 100644 src/hide_module.cc create mode 100644 src/hide_module.h delete mode 100644 src/hook_img.cc delete mode 100644 src/hook_img.h delete mode 100644 src/hook_log.cc delete mode 100644 src/hook_log.h delete mode 100644 src/hook_recv_msg.cc delete mode 100644 src/hook_recv_msg.h delete mode 100644 src/hook_voice.cc delete mode 100644 src/hook_voice.h create mode 100644 src/hooks.cc create mode 100644 src/hooks.h create mode 100644 src/http_handler.cc create mode 100644 src/http_handler.h create mode 100644 src/log.cc create mode 100644 src/log.h create mode 100644 src/misc_mgr.cc create mode 100644 src/misc_mgr.h delete mode 100644 src/ocr.cc delete mode 100644 src/ocr.h delete mode 100644 src/pat.cc delete mode 100644 src/pat.h delete mode 100644 src/search_contact.cc delete mode 100644 src/search_contact.h delete mode 100644 src/self_info.h delete mode 100644 src/send_file.cc delete mode 100644 src/send_file.h delete mode 100644 src/send_image.cc delete mode 100644 src/send_image.h create mode 100644 src/send_message_mgr.cc create mode 100644 src/send_message_mgr.h delete mode 100644 src/send_text.cc delete mode 100644 src/send_text.h create mode 100644 src/singleton.h delete mode 100644 src/sns.h rename src/{sns.cc => sns_mgr.cc} (54%) create mode 100644 src/sns_mgr.h rename src/{common.cc => utils.cc} (54%) create mode 100644 src/utils.h delete mode 100644 src/wechat_data.h create mode 100644 src/wechat_function.h delete mode 100644 src/wxhelper.cc delete mode 100644 src/wxhelper.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 85f13b1..aebd708 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,9 @@ include_directories(${VCPKG_INSTALLED_DIR}/x86-windows/include) find_package(nlohmann_json CONFIG REQUIRED) find_package(unofficial-mongoose CONFIG REQUIRED) +# find_package(spdlog CONFIG REQUIRED) +# find_package(minhook CONFIG REQUIRED) + add_library(wxhelper SHARED ${CPP_FILES} ) @@ -26,6 +29,8 @@ add_library(wxhelper SHARED ${CPP_FILES} ) target_link_libraries(wxhelper PRIVATE nlohmann_json::nlohmann_json) target_link_libraries(wxhelper PRIVATE unofficial::mongoose::mongoose) +# target_link_libraries(wxhelper PRIVATE spdlog::spdlog spdlog::spdlog_header_only) +# target_link_libraries(wxhelper PRIVATE minhook::minhook) SET_TARGET_PROPERTIES(wxhelper PROPERTIES LINKER_LANGUAGE C ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib diff --git a/README.md b/README.md index bad1057..a813e39 100644 --- a/README.md +++ b/README.md @@ -13,30 +13,30 @@ 13.hook语音 14.取消hook语音 - +19.通过手机或qq查找微信 25.获取群成员 - +31.修改群昵称 32.获取数据库句柄 34.查询数据库 - +35.hook日志 +36.关闭hook日志 40.转发消息 44.退出登录 46.联系人列表 - +47.获取群详情 48.获取解密图片 50.拍一拍 - +51.群消息置顶消息 +52.群消息取消置顶 53.朋友圈首页 54.朋友圈下一页 - +55.获取联系人或者群名称 56.获取消息附件(图片,视频,文件) 57.获取消息语音文件 ### 接口文档: diff --git a/src/self_info.cc b/src/account_mgr.cc similarity index 81% rename from src/self_info.cc rename to src/account_mgr.cc index 9ab002a..e8579ee 100644 --- a/src/self_info.cc +++ b/src/account_mgr.cc @@ -1,22 +1,22 @@ -#include "pch.h" -#include "self_info.h" +#include "pch.h" +#include "account_mgr.h" +#include "easylogging++.h" -#include "common.h" -#include "wechat_data.h" +#include "wechat_function.h" + using namespace std; +namespace wxhelper { + AccountMgr::AccountMgr(DWORD base):BaseMgr(base){ + } + AccountMgr::~AccountMgr(){ - -#define WX_LOGOUT_OFFSET 0xe58870 -#define WX_ACCOUNT_SERVICE_OFFSET 0x768c80 -#define WX_GET_APP_DATA_SAVE_PATH_OFFSET 0xf3a610 -#define WX_GET_CURRENT_DATA_PATH_OFFSET 0xc872c0 -int GetSelfInfo(SelfInfoInner &out) { - DWORD base = GetWeChatWinBase(); - DWORD accout_service_addr = base + WX_ACCOUNT_SERVICE_OFFSET; - DWORD get_app_save_addr = base + WX_GET_APP_DATA_SAVE_PATH_OFFSET; - DWORD get_current_data_path_addr = base + WX_GET_CURRENT_DATA_PATH_OFFSET; + } +int AccountMgr::GetSelfInfo(SelfInfoInner &out) { + DWORD accout_service_addr = base_addr_ + WX_ACCOUNT_SERVICE_OFFSET; + DWORD get_app_save_addr = base_addr_ + WX_GET_APP_DATA_SAVE_PATH_OFFSET; + DWORD get_current_data_path_addr = base_addr_ + WX_GET_CURRENT_DATA_PATH_OFFSET; DWORD service_addr = NULL; __asm { PUSHAD @@ -143,12 +143,12 @@ int GetSelfInfo(SelfInfoInner &out) { } if (*(DWORD *)(service_addr + 0x4CC) == 0 || - *(DWORD *)(service_addr +0x4D0) == 0) { + *(DWORD *)(service_addr + 0x4D0) == 0) { out.db_key = string(); } else { - DWORD byte_addr = *(DWORD *)(service_addr + 0x4CC); - DWORD len = *(DWORD *)(service_addr +0x4D0); - out.db_key = Bytes2Hex((BYTE *)byte_addr,len); + DWORD byte_addr = *(DWORD *)(service_addr + 0x4CC); + DWORD len = *(DWORD *)(service_addr + 0x4D0); + out.db_key = Utils::Bytes2Hex((BYTE *)byte_addr, len); } } @@ -165,15 +165,15 @@ int GetSelfInfo(SelfInfoInner &out) { } if (data_save_path.ptr) { - out.data_save_path = - Wstring2String(wstring(data_save_path.ptr, data_save_path.length)); + out.data_save_path = Utils::WstringToUTF8( + wstring(data_save_path.ptr, data_save_path.length)); } else { out.data_save_path = string(); } if (current_data_path.ptr) { - out.current_data_path = Wstring2String( + out.current_data_path = Utils::WstringToUTF8( wstring(current_data_path.ptr, current_data_path.length)); } else { out.current_data_path = string(); @@ -181,9 +181,9 @@ int GetSelfInfo(SelfInfoInner &out) { return 1; } -int CheckLogin() { - DWORD base = GetWeChatWinBase(); - DWORD accout_service_addr = base + WX_ACCOUNT_SERVICE_OFFSET; +int AccountMgr::CheckLogin() { + int success = -1; + DWORD accout_service_addr = base_addr_ + WX_ACCOUNT_SERVICE_OFFSET; DWORD service_addr = NULL; __asm { PUSHAD @@ -192,21 +192,18 @@ int CheckLogin() { POPAD } if (service_addr) { - return *(DWORD *)(service_addr + 0x4C8); - } - else { - return 0; + success = *(DWORD *)(service_addr + 0x4C8); } + return success; } -int Logout() { - int success = 0; +int AccountMgr::Logout() { + int success = -1; if (!CheckLogin()) { return success; } - DWORD base = GetWeChatWinBase(); - DWORD account_service_addr = base + WX_ACCOUNT_SERVICE_OFFSET; - DWORD logout_addr = base + WX_LOGOUT_OFFSET; + DWORD account_service_addr = base_addr_ + WX_ACCOUNT_SERVICE_OFFSET; + DWORD logout_addr = base_addr_ + WX_LOGOUT_OFFSET; __asm { PUSHAD CALL account_service_addr @@ -217,4 +214,6 @@ int Logout() { POPAD } return success; -} \ No newline at end of file +} + +} // namespace wxhelper \ No newline at end of file diff --git a/src/account_mgr.h b/src/account_mgr.h new file mode 100644 index 0000000..67da58d --- /dev/null +++ b/src/account_mgr.h @@ -0,0 +1,19 @@ +#ifndef WXHELPER_ACCOUNT_MGR_H_ +#define WXHELPER_ACCOUNT_MGR_H_ +#include "wechat_function.h" +#include"base_mgr.h" +namespace wxhelper{ + class AccountMgr: public BaseMgr + { + public: + explicit AccountMgr(DWORD base); + ~AccountMgr(); + int GetSelfInfo(SelfInfoInner& out); + + int CheckLogin(); + + int Logout(); + }; + +} +#endif \ No newline at end of file diff --git a/src/api.cc b/src/api.cc deleted file mode 100644 index d935d56..0000000 --- a/src/api.cc +++ /dev/null @@ -1,782 +0,0 @@ -#include "pch.h" -#include "api.h" -#include - -#include - - -#include "send_text.h" -#include "common.h" -#include "send_image.h" -#include "send_file.h" -#include "hook_recv_msg.h" -#include "get_db_handle.h" -#include "wechat_data.h" -#include "forward.h" -#include "db_operation.h" -#include "contact.h" -#include "chat_room.h" -#include "self_info.h" -#include "hook_img.h" -#include "ocr.h" -#include "pat.h" -#include "confirm_receipt.h" -#include "sns.h" -#include "search_contact.h" -#include "download.h" -#include "hook_log.h" -#include "hook_voice.h" - -#pragma comment(lib, "ws2_32.lib") -using namespace std; -using namespace nlohmann; - -// #define STR2INT(str) (is_digit(str) ? stoi(str) : 0) -#define WS2LW(wstr) (LPWSTR) wstr.c_str() - -static bool kHttpRuning = false; -static HANDLE kHttpThread = NULL; - -bool is_digit(string str) { - if (str.length() == 0) { - return false; - } - - for (auto it : str) { - if (it < '0' || it > '9') { - return false; - } - } - return true; -} - -string get_var(mg_http_message *hm, string name) { - string ret; - char *buffer = new char[hm->query.len + 1]; - ZeroMemory(buffer, hm->query.len + 1); - int len = mg_http_get_var(&hm->query, name.c_str(), buffer, hm->query.len); - if (len > 0) ret = string(buffer, len); - delete[] buffer; - buffer = NULL; - return ret; -} -/// @brief 获取request中的请求参数int类型 -/// @param hm 消息 -/// @param data json数据 -/// @param key key -/// @param method 是否是post,暂时全部用post -/// @return int -static int get_http_req_param_int(mg_http_message *hm, json data, string key, int method){ - int result; - switch (method) { - case 0: { - result = STR2INT(get_var(hm,key).c_str()); - break; - } - case 1: { - try { - result = data[key].get(); - } catch (json::exception) { - result = STR2INT(data[key].get()); - } - break; - } - default: - break; - } - return result; -} - -/// @brief 获取request中的请求参数 -/// @param hm 消息 -/// @param data json数据 -/// @param key key -/// @param method 是否是post,暂时全部用post -/// @return -static wstring get_http_req_param(mg_http_message *hm, json data, string key, int method){ - wstring result; - switch (method) { - case 0: { - result = utf8_to_unicode(get_var(hm,key).c_str()); - break; - } - case 1: { - result = utf8_to_unicode(data[key].get().c_str()); - break; - } - default: - break; - } - return result; -} - -static unsigned long long get_http_param_ulong64(mg_http_message *hm, - json j_data, string key, - int method) { - unsigned long long result = 0; - switch (method) { - case 0: { - string value = get_var(hm, key); - istringstream is(value); - is >> result; - break; - } - case 1: { - try { - result = j_data[key].get(); - } catch (json::exception) { - string value = j_data[key].get(); - istringstream is(value); - is >> result; - } - break; - } - default: - break; - } - return result; -} - -static int get_http_param_int(mg_http_message *hm, json j_data, string key, - int method) { - int result = 0; - switch (method) { - case 0: { - result = STR2INT(get_var(hm, key)); - break; - } - case 1: { - try { - result = j_data[key].get(); - } catch (json::exception) { - result = STR2INT(j_data[key].get()); - } - break; - } - default: - break; - } - return result; -} - -static vector get_http_param_array(mg_http_message *hm, json j_data, - string key, int method) { - vector result; - switch (method) { - case 0: { - result = split(utf8_to_unicode(get_var(hm, key).c_str()), L','); - break; - } - case 1: { - result = split(utf8_to_unicode(j_data[key].get().c_str()), L','); - break; - } - default: - break; - } - return result; -} - -/// @brief api接口入口解析 -/// @param hm mg_http_message -/// @param c connection -/// @param ret json数据 -void api_handle(mg_http_message *hm, struct mg_connection *c, string &ret) { - int is_post = 0; - - if (mg_vcasecmp(&hm->method, "POST") == 0) { - is_post = 1; - } - #ifdef _DEBUG - printf("method:%s body: %s", hm->method.ptr,hm->body.ptr); - #endif - if (is_post == 0){ - json ret_data = {{"result", "ERROR"}, {"msg", "not support method"}}; - ret = ret_data.dump(); - return; - } - - json j_param = - json::parse(hm->body.ptr, hm->body.ptr + hm->body.len, nullptr, false); - if (hm->body.len != 0 && j_param.is_discarded() == true) { - json ret_data = {{"result", "ERROR"}, {"msg", "json string is invalid."}}; - ret = ret_data.dump(); - return; - } - int api_number = STR2INT(get_var(hm, "type")); - switch (api_number) { - case WECHAT_IS_LOGIN: { - int success = CheckLogin(); - json ret_data = {{"result", "OK"}, {"code", success}}; - ret = ret_data.dump(); - break; - } - case WECHAT_GET_SELF_INFO: { - SelfInfoInner self_info; - int success = GetSelfInfo(self_info); - json ret_data = {{"result", "OK"}, {"code", success}}; - if (success) { - json j_info = { - {"name", self_info.name}, - {"city", self_info.city}, - {"province", self_info.province}, - {"country", self_info.country}, - {"account", self_info.account}, - {"wxid", self_info.wxid}, - {"mobile", self_info.mobile}, - {"headImage", self_info.head_img}, - {"signature",self_info.signature}, - {"dataSavePath",self_info.data_save_path}, - {"currentDataPath",self_info.current_data_path}, - {"dbKey",self_info.db_key}, - }; - ret_data["data"] = j_info; - } - ret = ret_data.dump(); - break; - } - case WECHAT_MSG_SEND_TEXT: { - wstring wxid = get_http_req_param(hm, j_param, "wxid", is_post); - wstring msg = get_http_req_param(hm, j_param, "msg", is_post); - int success = SendText(WS2LW(wxid), WS2LW(msg)); - json ret_data = {{"result", "OK"}, {"code", success}}; - ret = ret_data.dump(); - break; - } - case WECHAT_MSG_SEND_AT: { - break; - wstring chat_room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); - vector wxids = get_http_param_array(hm, j_param, "wxids", is_post); - wstring msg = get_http_req_param(hm, j_param, "msg", is_post); - vector wxid_list; - for (unsigned int i = 0; i < wxids.size(); i++){ - wxid_list.push_back(WS2LW(wxids[i])); - } - int success = SendAtText(WS2LW(chat_room_id), wxid_list.data(), wxid_list.size(),WS2LW(msg)); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_MSG_SEND_CARD: { - break; - } - case WECHAT_MSG_SEND_IMAGE: { - wstring receiver = get_http_req_param(hm, j_param, "wxid", is_post); - wstring img_path = get_http_req_param(hm, j_param, "imagePath", is_post); - int success = SendImage(WS2LW(receiver), WS2LW(img_path)); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_MSG_SEND_FILE: { - wstring receiver = get_http_req_param(hm, j_param, "wxid", is_post); - wstring file_path = get_http_req_param(hm, j_param, "filePath", is_post); - int success = SendFile(WS2LW(receiver), WS2LW(file_path)); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_MSG_SEND_ARTICLE: { - break; - } - case WECHAT_MSG_SEND_APP: { - break; - } - case WECHAT_MSG_START_HOOK: { - int port = get_http_req_param_int(hm, j_param, "port", is_post); - wstring ip = get_http_req_param(hm, j_param, "ip", is_post); - string client_ip = Wstring2String(ip); - char ip_cstr[16]; - strcpy_s(ip_cstr,client_ip.c_str()); - int success = HookRecvMsg(ip_cstr,port); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_MSG_STOP_HOOK: { - int success = UnHookRecvMsg(); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_MSG_START_IMAGE_HOOK: { - break; - wstring img_dir = get_http_req_param(hm, j_param, "imgDir", is_post); - int success = HookImg(img_dir); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_MSG_STOP_IMAGE_HOOK: { - break; - int success = UnHookImg(); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_MSG_START_VOICE_HOOK: { - wstring voice_dir = get_http_req_param(hm, j_param, "voiceDir", is_post); - int success = HookVoice(voice_dir); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_MSG_STOP_VOICE_HOOK: { - int success = UnHookVoice(); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_CONTACT_GET_LIST: { - - break; - } - case WECHAT_CONTACT_CHECK_STATUS: { - break; - } - case WECHAT_CONTACT_DEL: { - break; - wstring user_id = get_http_req_param(hm, j_param, "wxid", is_post); - int success = DelContact(WS2LW(user_id)); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_CONTACT_SEARCH_BY_CACHE: { - break; - } - case WECHAT_CONTACT_SEARCH_BY_NET: { - break; - wstring keyword = get_http_req_param(hm, j_param, "keyword", is_post); - UserInfo *user = nullptr; - int success = SearchContactNetScene(WS2LW(keyword), &user); - json ret_data = {{"code", success}, {"result", "OK"}}; - if (user) { - json info = { - {"bigImage", unicode_to_utf8(user->big_image)}, - {"smallImage", unicode_to_utf8(user->small_image)}, - {"city", unicode_to_utf8(user->city)}, - {"nation", unicode_to_utf8(user->nation)}, - {"nickname", unicode_to_utf8(user->nickname)}, - {"province", unicode_to_utf8(user->province)}, - {"sex", user->sex}, - {"signature", unicode_to_utf8(user->signature)}, - {"v2", unicode_to_utf8(user->v2)}, - {"v3", unicode_to_utf8(user->v3)}, - }; - ret_data["userInfo"] = info; - } - ret = ret_data.dump(); - break; - } - case WECHAT_CONTACT_ADD_BY_WXID: { - break; - wstring user_id = get_http_req_param(hm, j_param, "wxid", is_post); - int success = AddFriendByWxid(WS2LW(user_id)); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_CONTACT_ADD_BY_V3: { - break; - } - case WECHAT_CONTACT_ADD_BY_PUBLIC_ID: { - break; - } - case WECHAT_CONTACT_VERIFY_APPLY: { - break; - } - case WECHAT_CONTACT_EDIT_REMARK: { - break; - } - case WECHAT_CHATROOM_GET_MEMBER_LIST: { - wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); - ChatRoomInner out{0}; - int success = GetMemberFromChatRoom(WS2LW(room_id),out); - json ret_data = {{"code", success}, {"result", "OK"}}; - if (success){ - json member_info ={ - {"admin",unicode_to_utf8(out.admin)}, - {"chatRoomId",unicode_to_utf8(out.chat_room)}, - {"members",out.members}, - }; - ret_data["data"] = member_info; - } - ret = ret_data.dump(); - break; - } - case WECHAT_CHATROOM_GET_MEMBER_NICKNAME: { - break; - wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); - wstring member_id = get_http_req_param(hm, j_param, "memberId", is_post); - - wstring nickname = GetChatRoomMemberNickname(WS2LW(room_id),WS2LW(member_id)); - json ret_data = {{"code", 1}, {"result", "OK"},{"nickname",unicode_to_utf8(WS2LW(nickname))}}; - ret = ret_data.dump(); - break; - } - case WECHAT_CHATROOM_DEL_MEMBER: { - break; - wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); - vector wxids = get_http_param_array(hm, j_param, "memberIds", is_post); - vector wxid_list; - for (unsigned int i = 0; i < wxids.size(); i++){ - wxid_list.push_back(WS2LW(wxids[i])); - } - int success = DelMemberFromChatRoom(WS2LW(room_id), wxid_list.data(),wxid_list.size()); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_CHATROOM_ADD_MEMBER: { - break; - wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); - vector wxids = get_http_param_array(hm, j_param, "memberIds", is_post); - vector wxid_list; - for (unsigned int i = 0; i < wxids.size(); i++){ - wxid_list.push_back(WS2LW(wxids[i])); - } - int success = AddMemberToChatRoom(WS2LW(room_id), wxid_list.data(),wxid_list.size()); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_CHATROOM_SET_ANNOUNCEMENT: { - break; - } - case WECHAT_CHATROOM_SET_CHATROOM_NAME: { - break; - } - case WECHAT_CHATROOM_SET_SELF_NICKNAME: { - break; - wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); - wstring wxid = get_http_req_param(hm, j_param, "wxid", is_post); - wstring nick = get_http_req_param(hm, j_param, "nickName", is_post); - int success = ModChatRoomMemberNickName(WS2LW(room_id),WS2LW(wxid),WS2LW(nick)); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_DATABASE_GET_HANDLES: { - vector v_ptr = GetDbHandles(); - json ret_data = {{"data", json::array()}, {"result", "OK"}}; - for (unsigned int i = 0; i < v_ptr.size(); i++) { - json db_info; - db_info["tables"] = json::array(); - DatabaseInfo *db = reinterpret_cast(v_ptr[i]); - db_info["handle"] = db->handle; - db_info["databaseName"] = unicode_to_utf8(db->db_name); - for (auto table : db->tables) { - json table_info = {{"name", table.name}, - {"tableName", table.table_name}, - {"sql", table.sql}, - {"rootpage", table.rootpage}}; - db_info["tables"].push_back(table_info); - } - ret_data["data"].push_back(db_info); - } - ret = ret_data.dump(); - break; - } - case WECHAT_DATABASE_BACKUP: { - break; - } - case WECHAT_DATABASE_QUERY: { - DWORD db_handle = get_http_param_int(hm, j_param, "dbHandle", is_post); - wstring sql = get_http_req_param(hm, j_param, "sql", is_post); - string sql_str = unicode_to_utf8(WS2LW(sql)); - vector> items; - int success = Select(db_handle, sql_str.c_str(),items); - json ret_data = {{"data", json::array()}, {"code",success},{"result", "OK"}}; - if(success == 0){ - ret = ret_data.dump(); - break; - } - for (auto it : items) { - json temp_arr = json::array(); - for (size_t i = 0; i < it.size(); i++) { - temp_arr.push_back(it[i]); - } - ret_data["data"].push_back(temp_arr); - } - ret = ret_data.dump(); - break; - } - case WECHAT_SET_VERSION: { - break; - } - case WECHAT_LOG_START_HOOK: { - break; - int success = HookLog(); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_LOG_STOP_HOOK: { - break; - int success = UnHookLog(); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_BROWSER_OPEN_WITH_URL: { - break; - } - case WECHAT_GET_PUBLIC_MSG: { - break; - } - case WECHAT_MSG_FORWARD_MESSAGE: { - wstring wxid = get_http_req_param(hm, j_param, "wxid", is_post); - ULONG64 msgid = get_http_param_ulong64(hm, j_param, "msgid", is_post); - int success = ForwardMsg(WS2LW(wxid), msgid); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_GET_QRCODE_IMAGE: { - break; - } - case WECHAT_GET_A8KEY: { - break; - } - case WECHAT_MSG_SEND_XML: { - break; - } - case WECHAT_LOGOUT: { - int success = Logout(); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_GET_TRANSFER: { - break; - wstring wxid = get_http_req_param(hm, j_param, "wxid", is_post); - wstring transcationid = get_http_req_param(hm, j_param, "transcationId", is_post); - wstring transferid = get_http_req_param(hm, j_param, "transferId", is_post); - BOOL response = DoConfirmReceipt(WS2LW(wxid), WS2LW(transcationid), WS2LW(transferid)); - json ret_data = {{"msg", response}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_GET_CONTACT_ALL: { - vector vec; - int success = GetAllContact(vec); - json ret_data = { - {"data", json::array()}, {"code", success}, {"result", "OK"}}; - - for (unsigned int i = 0; i < vec.size(); i++) { - #ifdef _DEBUG - cout << "vector :" < 0 - ? vec[i].custom_account.ptr != nullptr - ? unicode_to_utf8(vec[i].custom_account.ptr) - : string() - : string()}, - {"delFlag", vec[i].del_flag}, - {"userName", vec[i].encrypt_name.length > 0 - ? vec[i].encrypt_name.ptr != nullptr - ? unicode_to_utf8(vec[i].encrypt_name.ptr) - : string() - : string()}, - {"type", vec[i].type}, - {"verifyFlag", vec[i].verify_flag}, - {"verifyFlag", vec[i].verify_flag}, - {"wxid", vec[i].wxid.length > 0 ? unicode_to_utf8(vec[i].wxid.ptr) - : string()}, - }; - ret_data["data"].push_back(item); - } - ret = ret_data.dump(); - break; - } - case WECHAT_GET_CHATROOM_INFO: { - break; - wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); - ChatRoomInfoInner chat_room_detail{0}; - int success = GetChatRoomDetailInfo(WS2LW(room_id), chat_room_detail); - json ret_data = {{"code", success}, {"result", "OK"}}; - if(!success){ - break; - } - json detail = { - {"chatRoomId", - chat_room_detail.chat_room_id.length > 0 - ? unicode_to_utf8(chat_room_detail.chat_room_id.ptr) - : string()}, - {"notice", chat_room_detail.notice.length > 0 - ? unicode_to_utf8(chat_room_detail.notice.ptr) - : string()}, - {"admin", chat_room_detail.admin.length > 0 - ? unicode_to_utf8(chat_room_detail.admin.ptr) - : string()}, - {"xml", chat_room_detail.xml.length > 0 - ? unicode_to_utf8(chat_room_detail.xml.ptr) - : string()}, - }; - ret_data["data"]=detail; - ret = ret_data.dump(); - break; - } - case WECHAT_GET_IMG_BY_NAME: { - wstring image_path = get_http_req_param(hm, j_param, "imagePath", is_post); - wstring save_path = get_http_req_param(hm, j_param, "savePath", is_post); - int success = GetImgByName(WS2LW(image_path),WS2LW(save_path)); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_DO_OCR:{ - break; - wstring image_path = get_http_req_param(hm, j_param, "imagePath", is_post); - string text(""); - int success = DoOCRTask(WS2LW(image_path),text); - json ret_data = {{"code", success}, {"result", "OK"},{"text",text}}; - ret = ret_data.dump(); - break; - } - case WECHAT_SEND_PAT_MSG:{ - wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); - wstring wxid = get_http_req_param(hm, j_param, "wxid", is_post); - int success = SendPatMsg(WS2LW(room_id),WS2LW(wxid)); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_SET_TOP_MSG:{ - break; - wstring wxid = get_http_req_param(hm, j_param, "wxid", is_post); - ULONG64 msgid = get_http_param_ulong64(hm, j_param, "msgid", is_post); - int success = SetTopMsg(WS2LW(wxid),msgid); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_REMOVE_TOP_MSG:{ - break; - wstring room_id = get_http_req_param(hm, j_param, "chatRoomId", is_post); - ULONG64 msgid = get_http_param_ulong64(hm, j_param, "msgid", is_post); - int success = RemoveTopMsg(WS2LW(room_id),msgid); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_SNS_GET_FIRST_PAGE:{ - int success = GetFirstPage(); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_SNS_GET_NEXT_PAGE: { - ULONG64 snsid = get_http_param_ulong64(hm, j_param, "snsId", is_post); - int success = GetNextPage(snsid); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_CONTACT_NAME:{ - break; - wstring pri_id = get_http_req_param(hm, j_param, "id", is_post); - wstring name =GetContactOrChatRoomNickname(WS2LW(pri_id)); - json ret_data = {{"code", 1}, {"result", "OK"},{"name",unicode_to_utf8(WS2LW(name))}}; - ret = ret_data.dump(); - break; - } - case WECHAT_ATTACH_DOWNLOAD:{ - ULONG64 msg_id = get_http_param_ulong64(hm, j_param, "msgId", is_post); - int success = DoDownloadTask(msg_id); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - case WECHAT_GET_VOICE:{ - ULONG64 msg_id = get_http_param_ulong64(hm, j_param, "msgId", is_post); - wstring voice_dir = get_http_req_param(hm, j_param, "voiceDir", is_post); - int success = GetVoice(msg_id,WS2LW(voice_dir)); - json ret_data = {{"code", success}, {"result", "OK"}}; - ret = ret_data.dump(); - break; - } - default: - break; - } -} - -/// @brief 事件回调函数 -/// @param c 链接 -/// @param ev 事件 -/// @param ev_data 事件数据 -/// @param fn_data 回调数据 -static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { - struct mg_http_serve_opts opts = {0}; - if (ev == MG_EV_HTTP_MSG) { - struct mg_http_message *hm = (struct mg_http_message *)ev_data; - string ret = R"({"result":"OK"})"; - if (mg_http_match_uri(hm, "/api/")) { - try { - api_handle(hm, c, ret); - } catch (json::exception &e) { - json res = {{"result", "ERROR"}, {"msg", e.what()}}; - ret = res.dump(); - } - if (ret != "") { - mg_http_reply(c, 200, "", ret.c_str(), 0, 0); - } - } else { - mg_http_reply(c, 500, NULL, "%s", "Invalid URI"); - } - } - (void)fn_data; -} -/// @brief http server -/// @param port 端口 -void http_server(int port) { - string lsten_addr = "http://0.0.0.0:" + to_string(port); - struct mg_mgr mgr; - // Init manager - mg_mgr_init(&mgr); - // Setup listener - mg_http_listen(&mgr, lsten_addr.c_str(), fn, &mgr); - // Event loop - for (;;) mg_mgr_poll(&mgr, 1000); - // Cleanup - mg_mgr_free(&mgr); -} - -/// @brief 启动http服务 -/// @param port 端口 -/// @return 成功返回0 -int http_start(int port) { - if (kHttpRuning) { - return 1; - } - #ifdef _DEBUG - CreateConsole(); - #endif - kHttpRuning = true; - kHttpThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)http_server, - (LPVOID)port, NULL, 0); - return 0; -} - -int http_close() { - if (!kHttpRuning) { - return 1; - } - kHttpRuning = false; - if (kHttpThread) { - WaitForSingleObject(kHttpThread, -1); - CloseHandle(kHttpThread); - kHttpThread = NULL; - } - UnHookRecvMsg(); - UnHookImg(); - UnHookSearchContact(); - UnHookLog(); - return 0; -} \ No newline at end of file diff --git a/src/api.h b/src/api.h deleted file mode 100644 index 951bc67..0000000 --- a/src/api.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef API_H_ -#define API_H_ - - -typedef enum WECHAT_HTTP_APISTag -{ - // login check - WECHAT_IS_LOGIN = 0, - // self info - WECHAT_GET_SELF_INFO, - // send message - WECHAT_MSG_SEND_TEXT, - WECHAT_MSG_SEND_AT, - WECHAT_MSG_SEND_CARD, - WECHAT_MSG_SEND_IMAGE, - WECHAT_MSG_SEND_FILE, - WECHAT_MSG_SEND_ARTICLE, - WECHAT_MSG_SEND_APP, - // receive message - WECHAT_MSG_START_HOOK, - WECHAT_MSG_STOP_HOOK, - WECHAT_MSG_START_IMAGE_HOOK, - WECHAT_MSG_STOP_IMAGE_HOOK, - WECHAT_MSG_START_VOICE_HOOK, - WECHAT_MSG_STOP_VOICE_HOOK, - // contact - WECHAT_CONTACT_GET_LIST, - WECHAT_CONTACT_CHECK_STATUS, - WECHAT_CONTACT_DEL, - WECHAT_CONTACT_SEARCH_BY_CACHE, - WECHAT_CONTACT_SEARCH_BY_NET, - WECHAT_CONTACT_ADD_BY_WXID, - WECHAT_CONTACT_ADD_BY_V3, - WECHAT_CONTACT_ADD_BY_PUBLIC_ID, - WECHAT_CONTACT_VERIFY_APPLY, - WECHAT_CONTACT_EDIT_REMARK, - // chatroom - WECHAT_CHATROOM_GET_MEMBER_LIST, - WECHAT_CHATROOM_GET_MEMBER_NICKNAME, - WECHAT_CHATROOM_DEL_MEMBER, - WECHAT_CHATROOM_ADD_MEMBER, - WECHAT_CHATROOM_SET_ANNOUNCEMENT, - WECHAT_CHATROOM_SET_CHATROOM_NAME, - WECHAT_CHATROOM_SET_SELF_NICKNAME, - // database - WECHAT_DATABASE_GET_HANDLES, - WECHAT_DATABASE_BACKUP, - WECHAT_DATABASE_QUERY, - // version - WECHAT_SET_VERSION, - // log - WECHAT_LOG_START_HOOK, - WECHAT_LOG_STOP_HOOK, - // browser - WECHAT_BROWSER_OPEN_WITH_URL, - WECHAT_GET_PUBLIC_MSG, - WECHAT_MSG_FORWARD_MESSAGE, - WECHAT_GET_QRCODE_IMAGE, - WECHAT_GET_A8KEY, - WECHAT_MSG_SEND_XML, - WECHAT_LOGOUT, - WECHAT_GET_TRANSFER, - WECHAT_GET_CONTACT_ALL, - WECHAT_GET_CHATROOM_INFO, - WECHAT_GET_IMG_BY_NAME, - WECHAT_DO_OCR, - WECHAT_SEND_PAT_MSG, - WECHAT_SET_TOP_MSG, - WECHAT_REMOVE_TOP_MSG, - WECHAT_SNS_GET_FIRST_PAGE, - WECHAT_SNS_GET_NEXT_PAGE, - WECHAT_CONTACT_NAME, - WECHAT_ATTACH_DOWNLOAD, - WECHAT_GET_VOICE, -} WECHAT_HTTP_APIS, - *PWECHAT_HTTP_APIS; - - -extern "C" __declspec(dllexport) int http_start(int port); -extern "C" __declspec(dllexport) int http_close(); -#endif \ No newline at end of file diff --git a/src/api_route.h b/src/api_route.h new file mode 100644 index 0000000..224b7fe --- /dev/null +++ b/src/api_route.h @@ -0,0 +1,78 @@ +#ifndef WXHELPER_API_ROUTINES_H_ +#define WXHELPER_API_ROUTINES_H_ +namespace wxhelper { + +typedef enum HTTP_API_ROUTE { + // login check + WECHAT_IS_LOGIN = 0, + // self info + WECHAT_GET_SELF_INFO, + // send message + WECHAT_MSG_SEND_TEXT, + WECHAT_MSG_SEND_AT, + WECHAT_MSG_SEND_CARD, + WECHAT_MSG_SEND_IMAGE, + WECHAT_MSG_SEND_FILE, + WECHAT_MSG_SEND_ARTICLE, + WECHAT_MSG_SEND_APP, + // receive message + WECHAT_MSG_START_HOOK, + WECHAT_MSG_STOP_HOOK, + WECHAT_MSG_START_IMAGE_HOOK, + WECHAT_MSG_STOP_IMAGE_HOOK, + WECHAT_MSG_START_VOICE_HOOK, + WECHAT_MSG_STOP_VOICE_HOOK, + // contact + WECHAT_CONTACT_GET_LIST, + WECHAT_CONTACT_CHECK_STATUS, + WECHAT_CONTACT_DEL, + WECHAT_CONTACT_SEARCH_BY_CACHE, + WECHAT_CONTACT_SEARCH_BY_NET, + WECHAT_CONTACT_ADD_BY_WXID, + WECHAT_CONTACT_ADD_BY_V3, + WECHAT_CONTACT_ADD_BY_PUBLIC_ID, + WECHAT_CONTACT_VERIFY_APPLY, + WECHAT_CONTACT_EDIT_REMARK, + // chatroom + WECHAT_CHATROOM_GET_MEMBER_LIST, + WECHAT_CHATROOM_GET_MEMBER_NICKNAME, + WECHAT_CHATROOM_DEL_MEMBER, + WECHAT_CHATROOM_ADD_MEMBER, + WECHAT_CHATROOM_SET_ANNOUNCEMENT, + WECHAT_CHATROOM_SET_CHATROOM_NAME, + WECHAT_CHATROOM_SET_SELF_NICKNAME, + // database + WECHAT_DATABASE_GET_HANDLES, + WECHAT_DATABASE_BACKUP, + WECHAT_DATABASE_QUERY, + // version + WECHAT_SET_VERSION, + // log + WECHAT_LOG_START_HOOK, + WECHAT_LOG_STOP_HOOK, + // browser + WECHAT_BROWSER_OPEN_WITH_URL, + WECHAT_GET_PUBLIC_MSG, + WECHAT_MSG_FORWARD_MESSAGE, + WECHAT_GET_QRCODE_IMAGE, + WECHAT_GET_A8KEY, + WECHAT_MSG_SEND_XML, + WECHAT_LOGOUT, + WECHAT_GET_TRANSFER, + WECHAT_GET_CONTACT_ALL, + WECHAT_GET_CHATROOM_INFO, + WECHAT_GET_IMG_BY_NAME, + WECHAT_DO_OCR, + WECHAT_SEND_PAT_MSG, + WECHAT_SET_TOP_MSG, + WECHAT_REMOVE_TOP_MSG, + WECHAT_SNS_GET_FIRST_PAGE, + WECHAT_SNS_GET_NEXT_PAGE, + WECHAT_CONTACT_NAME, + WECHAT_ATTACH_DOWNLOAD, + WECHAT_GET_VOICE, +} WECHAT_HTTP_APIS, + *PWECHAT_HTTP_APIS; + +} +#endif \ No newline at end of file diff --git a/src/base_mgr.cc b/src/base_mgr.cc new file mode 100644 index 0000000..4131e0e --- /dev/null +++ b/src/base_mgr.cc @@ -0,0 +1,13 @@ +#include "base_mgr.h" + +namespace wxhelper{ + + BaseMgr::BaseMgr(DWORD base):base_addr_(base) + { + } + + BaseMgr::~BaseMgr() + { + } + +} \ No newline at end of file diff --git a/src/base_mgr.h b/src/base_mgr.h new file mode 100644 index 0000000..86f4608 --- /dev/null +++ b/src/base_mgr.h @@ -0,0 +1,13 @@ +#ifndef WXHELPER_BASE_MGR_H_ +#define WXHELPER_BASE_MGR_H_ +#include +namespace wxhelper{ + class BaseMgr{ + public: + explicit BaseMgr(DWORD base); + ~BaseMgr(); + protected: + DWORD base_addr_; + }; +} +#endif \ No newline at end of file diff --git a/src/chat_room.h b/src/chat_room.h deleted file mode 100644 index 336aa0b..0000000 --- a/src/chat_room.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef CHAT_ROOM_H_ -#define CHAT_ROOM_H_ -#include "wechat_data.h" - -int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info); -int DelMemberFromChatRoom(wchar_t* chat_room_id,wchar_t** wxids,int len); -int AddMemberToChatRoom(wchar_t* chat_room_id, wchar_t** wxids,int len); - -int GetMemberFromChatRoom(wchar_t* chat_room_id,ChatRoomInner & out); -int ModChatRoomMemberNickName(wchar_t* chat_room_id,wchar_t* wxid,wchar_t * nick); - -int SetTopMsg(wchar_t* wxid,ULONG64 msg_id); -int RemoveTopMsg(wchar_t* chat_room_id,ULONG64 msg_id); - -std::wstring GetChatRoomMemberNickname(wchar_t* chat_room_id,wchar_t* wxid); -#endif \ No newline at end of file diff --git a/src/chat_room.cc b/src/chat_room_mgr.cc similarity index 53% rename from src/chat_room.cc rename to src/chat_room_mgr.cc index 7a8ea57..a0c6a12 100644 --- a/src/chat_room.cc +++ b/src/chat_room_mgr.cc @@ -1,41 +1,25 @@ -#include "pch.h" -#include "chat_room.h" +#include "pch.h" +#include "chat_room_mgr.h" + +#include "db.h" -#include "common.h" -#include "get_db_handle.h" -#include "wechat_data.h" -#include "base64.h" using namespace std; -#define WX_CHAT_ROOM_MGR_OFFSET 0x78cf20 -#define WX_GET_CHAT_ROOM_DETAIL_INFO_OFFSET 0xb6f260 -#define WX_NEW_CHAT_ROOM_INFO_OFFSET 0xe15de0 -#define WX_FREE_CHAT_ROOM_INFO_OFFSET 0xe160b0 -#define WX_DEL_CHAT_ROOM_MEMBER_OFFSET 0xb64180 -#define WX_INIT_CHAT_MSG_OFFSET 0xed3be0 -#define WX_ADD_MEMBER_TO_CHAT_ROOM_OFFSET 0xb63c50 -#define WX_GET_MEMBER_FROM_CHAT_ROOM_OFFSET 0xbdf260 -#define WX_INIT_CHAT_ROOM_OFFSET 0xe97890 -#define WX_FREE_CHAT_ROOM_OFFSET 0xe97ab0 -#define WX_MOD_CHAT_ROOM_MEMBER_NICK_NAME_OFFSET 0xb6adf0 -#define WX_NEW_CHAT_MSG_OFFSET 0x70e2a0 -#define WX_FREE_CHAT_MSG_OFFSET 0x6f4ea0 -#define WX_TOP_MSG_OFFSET 0xb727e0 -#define WX_REMOVE_TOP_MSG_OFFSET 0xb725a0 -#define WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET 0x6f5370 -#define WX_GET_MEMBER_NICKNAME_OFFSET 0xb703f0 -#define WX_CONTACT_MGR_INSTANCE_OFFSET 0x6f8990 -#define WX_GET_CONTACT_OFFSET 0xb93b20 -#define WX_FREE_CONTACT_OFFSET 0xe23690 +namespace wxhelper { -int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info) { - int success = 0; +ChatRoomMgr::ChatRoomMgr(DWORD base) : BaseMgr(base) {} + +ChatRoomMgr::~ChatRoomMgr() {} + +int ChatRoomMgr::GetChatRoomDetailInfo(wchar_t* chat_room_id, + ChatRoomInfoInner& room_info) { + int success = -1; WeChatString chat_room(chat_room_id); - DWORD base = GetWeChatWinBase(); - DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; - DWORD get_chat_room_detail_addr = base + WX_GET_CHAT_ROOM_DETAIL_INFO_OFFSET; - DWORD create_chat_room_info_addr = base + WX_NEW_CHAT_ROOM_INFO_OFFSET; - DWORD free_chat_room_info_addr = base + WX_FREE_CHAT_ROOM_INFO_OFFSET; + DWORD get_chat_room_mgr_addr = base_addr_ + WX_CHAT_ROOM_MGR_OFFSET; + DWORD get_chat_room_detail_addr = + base_addr_ + WX_GET_CHAT_ROOM_DETAIL_INFO_OFFSET; + DWORD create_chat_room_info_addr = base_addr_ + WX_NEW_CHAT_ROOM_INFO_OFFSET; + DWORD free_chat_room_info_addr = base_addr_ + WX_FREE_CHAT_ROOM_INFO_OFFSET; char chat_room_info[0xDC] = {0}; __asm { PUSHAD @@ -54,21 +38,20 @@ int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info) { } DWORD room_id_len = *(DWORD*)(chat_room_info + 0x8); DWORD room_id_max_len = *(DWORD*)(chat_room_info + 0xC); - wchar_t * room_id = new wchar_t[room_id_len + 1]; - wmemcpy(room_id,*(wchar_t**)(chat_room_info + 0x4),room_id_len + 1); + wchar_t* room_id = new wchar_t[room_id_len + 1]; + wmemcpy(room_id, *(wchar_t**)(chat_room_info + 0x4), room_id_len + 1); room_info.chat_room_id.ptr = room_id; room_info.chat_room_id.length = room_id_len; room_info.chat_room_id.max_length = room_id_max_len; - DWORD notice_len = *(DWORD*)(chat_room_info + 0x1C); DWORD notice_max_len = *(DWORD*)(chat_room_info + 0x20); wchar_t* notice_ptr = *(wchar_t**)(chat_room_info + 0x18); - if(notice_len <= 0){ + if (notice_len <= 0) { room_info.notice.ptr = nullptr; - }else{ - wchar_t * notice = new wchar_t[notice_len + 1]; - wmemcpy(notice,notice_ptr,notice_len+1); + } else { + wchar_t* notice = new wchar_t[notice_len + 1]; + wmemcpy(notice, notice_ptr, notice_len + 1); room_info.notice.ptr = notice; } room_info.notice.length = notice_len; @@ -77,11 +60,11 @@ int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info) { DWORD admin_len = *(DWORD*)(chat_room_info + 0x30); DWORD admin_max_len = *(DWORD*)(chat_room_info + 0x34); wchar_t* admin_ptr = *(wchar_t**)(chat_room_info + 0x2C); - if(admin_len <= 0){ + if (admin_len <= 0) { room_info.admin.ptr = nullptr; - }else{ - wchar_t * admin = new wchar_t[admin_len + 1]; - wmemcpy(admin,admin_ptr,admin_len+1); + } else { + wchar_t* admin = new wchar_t[admin_len + 1]; + wmemcpy(admin, admin_ptr, admin_len + 1); room_info.admin.ptr = admin; } room_info.admin.length = admin_len; @@ -90,11 +73,11 @@ int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info) { DWORD xml_len = *(DWORD*)(chat_room_info + 0x54); DWORD xml_max_len = *(DWORD*)(chat_room_info + 0x58); wchar_t* xml_ptr = *(wchar_t**)(chat_room_info + 0x50); - if (xml_len <= 0){ + if (xml_len <= 0) { room_info.xml.ptr = nullptr; - }else{ - wchar_t * xml = new wchar_t[xml_len + 1]; - wmemcpy(xml,xml_ptr,xml_len+1); + } else { + wchar_t* xml = new wchar_t[xml_len + 1]; + wmemcpy(xml, xml_ptr, xml_len + 1); room_info.xml.ptr = xml; } room_info.xml.length = xml_len; @@ -109,20 +92,20 @@ int GetChatRoomDetailInfo(wchar_t* chat_room_id, ChatRoomInfoInner& room_info) { return success; } -int DelMemberFromChatRoom(wchar_t* chat_room_id, wchar_t** wxids,int len) { +int ChatRoomMgr::DelMemberFromChatRoom(wchar_t* chat_room_id, wchar_t** wxids, + int len) { int success = 0; WeChatString chat_room(chat_room_id); vector members; - VectorInner *list = (VectorInner *)&members; + VectorInner* list = (VectorInner*)&members; DWORD members_ptr = (DWORD)&list->start; for (int i = 0; i < len; i++) { WeChatString pwxid(wxids[i]); members.push_back(pwxid); } - DWORD base = GetWeChatWinBase(); - DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; - DWORD del_member_addr = base + WX_DEL_CHAT_ROOM_MEMBER_OFFSET; - DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; + DWORD get_chat_room_mgr_addr = base_addr_ + WX_CHAT_ROOM_MGR_OFFSET; + DWORD del_member_addr = base_addr_ + WX_DEL_CHAT_ROOM_MEMBER_OFFSET; + DWORD init_chat_msg_addr = base_addr_ + WX_INIT_CHAT_MSG_OFFSET; __asm { PUSHAD CALL get_chat_room_mgr_addr @@ -142,22 +125,21 @@ int DelMemberFromChatRoom(wchar_t* chat_room_id, wchar_t** wxids,int len) { return success; } - -int AddMemberToChatRoom(wchar_t* chat_room_id, wchar_t** wxids,int len){ - int success = 0; +int ChatRoomMgr::AddMemberToChatRoom(wchar_t* chat_room_id, wchar_t** wxids, + int len) { + int success = -1; WeChatString chat_room(chat_room_id); vector members; - VectorInner *list = (VectorInner *)&members; + VectorInner* list = (VectorInner*)&members; DWORD members_ptr = (DWORD)&list->start; for (int i = 0; i < len; i++) { WeChatString pwxid(wxids[i]); members.push_back(pwxid); } - DWORD base = GetWeChatWinBase(); - DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; - DWORD add_member_addr = base + WX_ADD_MEMBER_TO_CHAT_ROOM_OFFSET; - DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; - DWORD temp=0; + DWORD get_chat_room_mgr_addr = base_addr_ + WX_CHAT_ROOM_MGR_OFFSET; + DWORD add_member_addr = base_addr_ + WX_ADD_MEMBER_TO_CHAT_ROOM_OFFSET; + DWORD init_chat_msg_addr = base_addr_ + WX_INIT_CHAT_MSG_OFFSET; + DWORD temp = 0; __asm { PUSHAD PUSHFD @@ -184,17 +166,16 @@ int AddMemberToChatRoom(wchar_t* chat_room_id, wchar_t** wxids,int len){ return success; } - -int GetMemberFromChatRoom(wchar_t* chat_room_id,ChatRoomInner & out){ - int success = 0; +int ChatRoomMgr::GetMemberFromChatRoom(wchar_t* chat_room_id, + ChatRoomInner& out) { + int success = -1; WeChatString chat_room(chat_room_id); - DWORD chat_room_ptr = (DWORD) &chat_room; + DWORD chat_room_ptr = (DWORD)&chat_room; char buffer[0x1D4] = {0}; - DWORD base = GetWeChatWinBase(); - DWORD get_member_addr = base + WX_GET_MEMBER_FROM_CHAT_ROOM_OFFSET; - DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; - DWORD create_chat_room_addr = base + WX_INIT_CHAT_ROOM_OFFSET; - DWORD free_chat_room_addr = base + WX_FREE_CHAT_ROOM_OFFSET; + DWORD get_member_addr = base_addr_ + WX_GET_MEMBER_FROM_CHAT_ROOM_OFFSET; + DWORD get_chat_room_mgr_addr = base_addr_ + WX_CHAT_ROOM_MGR_OFFSET; + DWORD create_chat_room_addr = base_addr_ + WX_INIT_CHAT_ROOM_OFFSET; + DWORD free_chat_room_addr = base_addr_ + WX_FREE_CHAT_ROOM_OFFSET; __asm { PUSHAD LEA ECX,buffer @@ -208,36 +189,37 @@ int GetMemberFromChatRoom(wchar_t* chat_room_id,ChatRoomInner & out){ MOV success,EAX POPAD } - char* members = *(char **)(buffer +0x1c); - wchar_t* room = *(wchar_t **)(buffer +0x8); - wchar_t* admin = *(wchar_t **)(buffer +0x4c); - + char* members = *(char**)(buffer + 0x1c); + wchar_t* room = *(wchar_t**)(buffer + 0x8); + wchar_t* admin = *(wchar_t**)(buffer + 0x4c); + out.members = new char[strlen(members) + 1]; memcpy(out.members, members, strlen(members) + 1); - out.chat_room = new wchar_t[wcslen(room)+1]; - wmemcpy(out.chat_room ,room,wcslen(room)+1); + out.chat_room = new wchar_t[wcslen(room) + 1]; + wmemcpy(out.chat_room, room, wcslen(room) + 1); - out.admin = new wchar_t[wcslen(admin)+1]; - wmemcpy(out.admin ,admin,wcslen(admin)+1); + out.admin = new wchar_t[wcslen(admin) + 1]; + wmemcpy(out.admin, admin, wcslen(admin) + 1); - __asm{ + __asm { LEA ECX,buffer CALL free_chat_room_addr - } + } return success; } -int ModChatRoomMemberNickName(wchar_t* chat_room_id,wchar_t* wxid,wchar_t * nick){ - int success = 0; +int ChatRoomMgr::ModChatRoomMemberNickName(wchar_t* chat_room_id, wchar_t* wxid, + wchar_t* nick) { + int success = -1; WeChatString chat_room(chat_room_id); WeChatString self_wxid(wxid); WeChatString new_nick(nick); - DWORD base = GetWeChatWinBase(); - DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; - DWORD mod_member_nick_name_addr = base + WX_MOD_CHAT_ROOM_MEMBER_NICK_NAME_OFFSET; - DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; - __asm{ + DWORD get_chat_room_mgr_addr = base_addr_ + WX_CHAT_ROOM_MGR_OFFSET; + DWORD mod_member_nick_name_addr = + base_addr_ + WX_MOD_CHAT_ROOM_MEMBER_NICK_NAME_OFFSET; + DWORD init_chat_msg_addr = base_addr_ + WX_INIT_CHAT_MSG_OFFSET; + __asm { PUSHAD CALL get_chat_room_mgr_addr SUB ESP,0x14 @@ -263,57 +245,34 @@ int ModChatRoomMemberNickName(wchar_t* chat_room_id,wchar_t* wxid,wchar_t * nick return success; } - -int SetTopMsg(wchar_t* wxid,ULONG64 msg_id){ +int ChatRoomMgr::SetTopMsg(wchar_t* wxid, ULONG64 msg_id) { int success = -1; - char chat_msg[0x2C4] ={0}; - DWORD base = GetWeChatWinBase(); - DWORD new_chat_msg_addr = base + WX_NEW_CHAT_MSG_OFFSET; - DWORD get_chat_room_mgr_addr = base + WX_CHAT_ROOM_MGR_OFFSET; - DWORD handle_top_msg_addr = base + WX_TOP_MSG_OFFSET; - // DWORD free_addr = base + WX_FREE_CHAT_MSG_OFFSET; - DWORD free_addr = base + WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET; - vector local_msg = GetChatMsgByMsgId(msg_id); - if(local_msg.empty()){ + char chat_msg[0x2D8] = {0}; + DWORD new_chat_msg_addr = base_addr_ + WX_NEW_CHAT_MSG_OFFSET; + DWORD get_chat_room_mgr_addr = base_addr_ + WX_CHAT_ROOM_MGR_OFFSET; + DWORD handle_top_msg_addr = base_addr_ + WX_TOP_MSG_OFFSET; + DWORD free_addr = base_addr_ + WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET; + DWORD get_chat_mgr_addr = base_addr_ + WX_CHAT_MGR_OFFSET; + DWORD get_by_local_Id_addr = base_addr_ + WX_GET_MGR_BY_PREFIX_LOCAL_ID_OFFSET; + + + int db_index = 0; + int local_id = DB::GetInstance().GetLocalIdByMsgId(msg_id, db_index); + if (local_id < 1) { return -2; } - string type = local_msg[3]; - string status = local_msg[10]; - string talker = local_msg[13]; - string content = local_msg[14]; - wstring w_talker = String2Wstring(talker); - wstring w_content = String2Wstring(content); - int msg_type =stoi(type); - int msg_status =stoi(status); - - #ifdef _DEBUG - wcout << "w_talker:" < -#define READ_WSTRING(addr, offset) ((*(DWORD *)(addr + offset + 0x4) == 0) ? std::wstring(L"") : std::wstring((wchar_t *)(*(DWORD *)(addr + offset)), *(DWORD *)(addr + offset + 0x4))) -#define STR2INT(str) (IsDigit(str) ? stoi(str) : 0) -/// @brief utf8 转换成unicode -/// @param buffer utf8 -/// @return unicode -std::wstring utf8_to_unicode(const char *buffer); - -/// @brief unicode转换utf8 -/// @param wstr unicode -/// @return utf8 -std::string unicode_to_utf8(wchar_t *wstr); - -/// @brief 获取WeChatWin.dll基址 -/// @return 基址 -DWORD GetWeChatWinBase(); -/// @brief 创建窗口 -/// @param void -/// @return 创建结果 -BOOL CreateConsole(void); -/// @brief hook任意地址 -/// @param hook_addr 被hook的地址 -/// @param jmp_addr 被hook的函数的地址 -/// @param origin 原始code -void HookAnyAddress(DWORD hook_addr, LPVOID jmp_addr, char *origin); - -/// @brief 取消hook -/// @param hook_addr 被hook的地址 -/// @param origin 原始code -void UnHookAnyAddress(DWORD hook_addr, char *origin); - -/// @brief get timeW -/// @param timestamp timestamp -/// @return str -std::wstring GetTimeW(long long timestamp); -/// @brief unicode trans utf8 -/// @param str unicode str -/// @return utf8 str -std::string UnicodeToUtf8(const wchar_t *str); -/// @brief string convert wstring -/// @param str -/// @return -std::wstring String2Wstring(std::string str); -/// @brief wstring convert string -/// @param str -/// @return -std::string Wstring2String(std::wstring wstr); - -/// @brief create dir -/// @param path -/// @return -BOOL FindOrCreateDirectoryW(const wchar_t *path); - -void CloseConsole(); - -std::string EncodeHexString(const std::string &str); - -std::string Hex2String(const std::string &hex_str); - -std::string Bytes2Hex(const BYTE *bytes, const int length); - -void Hex2Bytes(const std::string &hex, BYTE *bytes); - -bool IsDigit(std::string str); - -template -std::vector split(T1 str, T2 letter) { - vector arr; - size_t pos; - while ((pos = str.find_first_of(letter)) != T1::npos) { - T1 str1 = str.substr(0, pos); - arr.push_back(str1); - str = str.substr(pos + 1, str.length() - pos - 1); - } - arr.push_back(str); - return arr; -} - -template -T1 replace(T1 source, T2 replaced, T1 replaceto) { - vector v_arr = split(source, replaced); - if (v_arr.size() < 2) return source; - T1 temp; - for (unsigned int i = 0; i < v_arr.size() - 1; i++) { - temp += v_arr[i]; - temp += replaceto; - } - temp += v_arr[v_arr.size() - 1]; - return temp; -} -#endif \ No newline at end of file diff --git a/src/config.cc b/src/config.cc new file mode 100644 index 0000000..2f86227 --- /dev/null +++ b/src/config.cc @@ -0,0 +1,6 @@ +#include "config.h" +namespace wxhelper { +Config::Config(/* args */) {} + +Config::~Config() {} +} // namespace wxhelper \ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..5df279e --- /dev/null +++ b/src/config.h @@ -0,0 +1,15 @@ +#ifndef WXHELPER_CONFIG_H_ +#define WXHELPER_CONFIG_H_ + +namespace wxhelper{ + + class Config + { + private: + /* data */ + public: + Config(/* args */); + ~Config(); + }; +} +#endif \ No newline at end of file diff --git a/src/confirm_receipt.cc b/src/confirm_receipt.cc deleted file mode 100644 index cb48b5b..0000000 --- a/src/confirm_receipt.cc +++ /dev/null @@ -1,49 +0,0 @@ -#include "pch.h" -#include "confirm_receipt.h" - -#include "common.h" -#include "wechat_data.h" - -#define WX_NEW_WCPAYINFO_OFFSET 0x756340 -#define WX_FREE_WCPAYINFO_OFFSET 0x73c170 -#define WX_CONFIRM_RECEIPT_OFFSET 0x15287a0 - -int DoConfirmReceipt(wchar_t *wxid, wchar_t *transcationid, - wchar_t *transferid) { - int success = -1; - WeChatString recv_id(wxid); - WeChatString transcation_id(transcationid); - WeChatString transfer_id(transferid); - char pay_info[0x134] = {0}; - DWORD base = GetWeChatWinBase(); - DWORD new_pay_info_addr = base + WX_NEW_WCPAYINFO_OFFSET; - DWORD free_pay_info_addr = base + WX_FREE_WCPAYINFO_OFFSET; - DWORD do_confirm_addr = base + WX_CONFIRM_RECEIPT_OFFSET; - __asm { - PUSHAD - LEA ECX,pay_info - CALL new_pay_info_addr - MOV dword ptr [pay_info + 0x4], 0x1 - MOV dword ptr [pay_info + 0x4C], 0x1 - POPAD - } - memcpy(&pay_info[0x1c], &transcation_id, sizeof(transcation_id)); - memcpy(&pay_info[0x38], &transfer_id, sizeof(transfer_id)); - - __asm { - PUSHAD - PUSH 0x1 - SUB ESP,0x8 - LEA EDX,recv_id - LEA ECX,pay_info - CALL do_confirm_addr - MOV success,EAX - ADD ESP,0xC - PUSH 0x0 - LEA ECX,pay_info - CALL free_pay_info_addr - POPAD - } - - return success; -} \ No newline at end of file diff --git a/src/confirm_receipt.h b/src/confirm_receipt.h deleted file mode 100644 index 0fdee8f..0000000 --- a/src/confirm_receipt.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef CONFIRM_RECEIPT_H -#define CONFIRM_RECEIPT_H - -int DoConfirmReceipt(wchar_t* wxid,wchar_t *transcationid, wchar_t *transferid); - -#endif \ No newline at end of file diff --git a/src/contact.cc b/src/contact.cc deleted file mode 100644 index 346286a..0000000 --- a/src/contact.cc +++ /dev/null @@ -1,192 +0,0 @@ -#include "pch.h" -#include "contact.h" - -#include "common.h" -#include "wechat_data.h" -using namespace std; -#define WX_CONTACT_MGR_INSTANCE_OFFSET 0x75a4a0 -#define WX_CONTACT_GET_LIST_OFFSET 0xc089f0 -#define WX_CONTACT_DEL_OFFSET 0xb9b3b0 -#define WX_INIT_CHAT_MSG_OFFSET 0xed3be0 -#define WX_SYNC_MGR_OFFSET 0xa87fd0 -#define WX_SET_VALUE_OFFSET 0x1f80900 -#define WX_DO_DEL_CONTACT_OFFSET 0xca6480 -#define WX_FREE_CONTACT_OFFSET 0xe23690 -#define WX_GET_CONTACT_OFFSET 0xb93b20 -#define WX_DO_VERIFY_USER_OFFSET 0xB91160 - -int GetAllContact(vector &vec) { - DWORD base = GetWeChatWinBase(); - DWORD get_instance = base + WX_CONTACT_MGR_INSTANCE_OFFSET; - DWORD contact_get_list = base + WX_CONTACT_GET_LIST_OFFSET; - // char contact[0xc] = {0}; - DWORD* contact[3] = {0, 0, 0}; - int success = 0; - __asm { - PUSHAD - CALL get_instance - LEA ECX,contact - PUSH ECX - MOV ECX,EAX - CALL contact_get_list - MOVZX EAX,AL - MOV success,EAX - POPAD - } - DWORD start = (DWORD)contact[0]; - DWORD end = (DWORD)contact[2]; -#ifdef _DEBUG - cout << "address = " << &contact << endl; - cout << "refresh contact = " << success << endl; - cout << "start = " << start << endl; - cout << "end = " << end << endl; -#endif - while (start < end) { - Contact temp{0}; - - temp.wxid.ptr = *(wchar_t **)(start + 0x10); - temp.wxid.length = *(DWORD *)(start + 0x14); - temp.wxid.max_length = *(DWORD *)(start + 0x18); - - temp.custom_account.ptr = *(wchar_t **)(start + 0x24); - temp.custom_account.length = *(DWORD *)(start + 0x28); - temp.custom_account.max_length = *(DWORD *)(start + 0x2C); - - temp.encrypt_name.ptr = *(wchar_t **)(start + 0x6c); - temp.encrypt_name.length = *(DWORD *)(start + 0x70); - temp.encrypt_name.max_length = *(DWORD *)(start + 0x74); - - temp.pinyin.ptr = *(wchar_t **)(start + 0xAC); - temp.pinyin.length = *(DWORD *)(start + 0xB0); - temp.pinyin.max_length = *(DWORD *)(start + 0xB4); - - temp.pinyin_all.ptr = *(wchar_t **)(start + 0xC0); - temp.pinyin_all.length = *(DWORD *)(start + 0xC4); - temp.pinyin_all.max_length = *(DWORD *)(start + 0xC8); - - temp.del_flag = *(DWORD *)(start + 0x4c); - temp.type = *(DWORD *)(start + 0x50); - temp.verify_flag = *(DWORD *)(start + 0x54); - vec.push_back(temp); - start += 0x438; - } - return success; -} -// note maybe not delete -int DelContact(wchar_t *wxid) { - int success = -1; - WeChatString user_id(wxid); - DWORD id_ptr = (DWORD) &user_id; - DWORD base = GetWeChatWinBase(); - DWORD sync_mgr_addr = base + WX_SYNC_MGR_OFFSET; - DWORD set_id_addr = base + WX_SET_VALUE_OFFSET; - DWORD del_contact_addr = base + WX_DO_DEL_CONTACT_OFFSET; - int len = user_id.length; - - string id_cstr = unicode_to_utf8(wxid); - char id_[0x20]={0}; - memcpy(id_,id_cstr.c_str(),id_cstr.size()+1); - char buff[0x10]={0}; - __asm{ - PUSHAD - PUSHFD - CALL sync_mgr_addr - MOV ECX,EAX - LEA EAX,buff - MOV [ECX + 4],EAX - LEA EAX,id_ - Mov dword ptr[buff +0x4],EAX - CALL del_contact_addr - MOV success,EAX - POPFD - POPAD - } - return success; -} - -std::wstring GetContactOrChatRoomNickname(wchar_t *id) { - int success = -1; - char buff[0x440] = {0}; - WeChatString pri(id); - DWORD base = GetWeChatWinBase(); - DWORD contact_mgr_addr = base + WX_CONTACT_MGR_INSTANCE_OFFSET; - DWORD get_contact_addr = base + WX_GET_CONTACT_OFFSET; - DWORD free_contact_addr = base + WX_FREE_CONTACT_OFFSET; - wstring name = L""; - __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 - } - name += READ_WSTRING(buff, 0x6C); - __asm { - PUSHAD - PUSHFD - LEA ECX,buff - CALL free_contact_addr - POPFD - POPAD - } - return name; -} - - -int AddFriendByWxid(wchar_t *wxid){ - int success = -1; - DWORD base = GetWeChatWinBase(); - DWORD contact_mgr_addr = base + WX_CONTACT_MGR_INSTANCE_OFFSET; - DWORD set_group_addr = base + 0x746E20; - DWORD fn2_addr = base + 0x285D968; - DWORD fn3_addr = base + 0x6F6050; - DWORD fn4_addr = base + 0xED3BE0; - DWORD do_verify_user_addr = base + WX_DO_VERIFY_USER_OFFSET; - - DWORD instance =0; - WeChatString chat_room(NULL); - WeChatString user_id(wxid); - __asm{ - PUSHAD - PUSHFD - CALL contact_mgr_addr - SUB ESP,0x18 - MOV dword ptr [instance],EAX - MOV ECX,ESP - PUSH ECX - LEA ECX,chat_room - CALL set_group_addr - MOV EAX,fn2_addr - SUB ESP,0x18 - MOV ECX,ESP - PUSH EAX - CALL fn3_addr - PUSH 0x0 - PUSH 0XE - SUB ESP,0x14 - MOV ESI,ESP - MOV dword ptr [ESI],0x0 - MOV dword ptr [ESI+0x4],0x0 - MOV dword ptr [ESI+0x8],0x0 - MOV dword ptr [ESI+0xC],0x0 - MOV dword ptr [ESI+0x10],0x0 - PUSH 0x1 - SUB ESP,0x14 - MOV ECX,ESP - LEA EAX,user_id - PUSH EAX - CALL fn4_addr - MOV ECX,dword ptr [instance] - CALL do_verify_user_addr - MOV success,EAX - POPFD - POPAD - } - return success; -} \ No newline at end of file diff --git a/src/contact.h b/src/contact.h deleted file mode 100644 index 924ee70..0000000 --- a/src/contact.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef CONTACT_H_ -#define CONTACT_H_ -#include -#include "wechat_data.h" - -int GetAllContact(std::vector &vec); - - - -int DelContact(wchar_t* wxid); - -std::wstring GetContactOrChatRoomNickname(wchar_t* id); - -int AddFriendByWxid(wchar_t *wxid); -#endif \ No newline at end of file diff --git a/src/contact_mgr.cc b/src/contact_mgr.cc new file mode 100644 index 0000000..4b2582a --- /dev/null +++ b/src/contact_mgr.cc @@ -0,0 +1,193 @@ +#include "pch.h" +#include "contact_mgr.h" + + +#include "wechat_function.h" + +using namespace std; +namespace wxhelper { +ContactMgr::ContactMgr(DWORD base) : BaseMgr(base) {} +ContactMgr::~ContactMgr() {} +int ContactMgr::GetAllContact(vector &vec) { + DWORD get_instance = base_addr_ + WX_CONTACT_MGR_OFFSET; + DWORD contact_get_list = base_addr_ + WX_CONTACT_GET_LIST_OFFSET; + DWORD *contact[3] = {0, 0, 0}; + int success = 0; + __asm { + PUSHAD + CALL get_instance + LEA ECX,contact + PUSH ECX + MOV ECX,EAX + CALL contact_get_list + MOVZX EAX,AL + MOV success,EAX + POPAD + } + DWORD start = (DWORD)contact[0]; + DWORD end = (DWORD)contact[2]; + while (start < end) { + Contact temp{0}; + + temp.wxid.ptr = *(wchar_t **)(start + 0x10); + temp.wxid.length = *(DWORD *)(start + 0x14); + temp.wxid.max_length = *(DWORD *)(start + 0x18); + + temp.custom_account.ptr = *(wchar_t **)(start + 0x24); + temp.custom_account.length = *(DWORD *)(start + 0x28); + temp.custom_account.max_length = *(DWORD *)(start + 0x2C); + + temp.encrypt_name.ptr = *(wchar_t **)(start + 0x6c); + temp.encrypt_name.length = *(DWORD *)(start + 0x70); + temp.encrypt_name.max_length = *(DWORD *)(start + 0x74); + + temp.pinyin.ptr = *(wchar_t **)(start + 0xAC); + temp.pinyin.length = *(DWORD *)(start + 0xB0); + temp.pinyin.max_length = *(DWORD *)(start + 0xB4); + + temp.pinyin_all.ptr = *(wchar_t **)(start + 0xC0); + temp.pinyin_all.length = *(DWORD *)(start + 0xC4); + temp.pinyin_all.max_length = *(DWORD *)(start + 0xC8); + + temp.del_flag = *(DWORD *)(start + 0x4c); + temp.type = *(DWORD *)(start + 0x50); + temp.verify_flag = *(DWORD *)(start + 0x54); + vec.push_back(temp); + start += 0x438; + } + return success; +} +int ContactMgr::DelContact(wchar_t *wxid) { + int success = -1; + WeChatString user_id(wxid); + DWORD id_ptr = (DWORD)&user_id; + DWORD sync_mgr_addr = base_addr_ + WX_SYNC_MGR_OFFSET; + DWORD set_id_addr = base_addr_ + WX_SET_VALUE_OFFSET; + DWORD del_contact_addr = base_addr_ + WX_DO_DEL_CONTACT_OFFSET; + int len = user_id.length; + wstring ws_wxid(wxid); + + string id_cstr = Utils::WstringToUTF8(ws_wxid); + char id_[0x20] = {0}; + memcpy(id_, id_cstr.c_str(), id_cstr.size() + 1); + char buff[0x10] = {0}; + __asm { + PUSHAD + PUSHFD + CALL sync_mgr_addr + MOV ECX,EAX + LEA EAX,buff + MOV [ECX + 4],EAX + LEA EAX,id_ + Mov dword ptr[buff +0x4],EAX + CALL del_contact_addr + MOV success,EAX + POPFD + POPAD + } + return success; +} +wstring ContactMgr::GetContactOrChatRoomNickname(wchar_t *id) { + int success = -1; + char buff[0x440] = {0}; + WeChatString pri(id); + DWORD contact_mgr_addr = base_addr_ + WX_CONTACT_MGR_OFFSET; + DWORD get_contact_addr = base_addr_ + WX_GET_CONTACT_OFFSET; + DWORD free_contact_addr = base_addr_ + WX_FREE_CONTACT_OFFSET; + wstring name = L""; + __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 + } + name += READ_WSTRING(buff, 0x6C); + __asm { + PUSHAD + PUSHFD + LEA ECX,buff + CALL free_contact_addr + POPFD + POPAD + } + return name; +} + +int ContactMgr::AddFriendByWxid(wchar_t *wxid,wchar_t* msg) { + int success = -1; + DWORD contact_mgr_addr = base_addr_ + WX_CONTACT_MGR_OFFSET; + DWORD verify_msg_addr = base_addr_ + WX_VERIFY_MSG_OFFSET; + DWORD set_value_addr = base_addr_ + WX_INIT_CHAT_MSG_OFFSET; + DWORD do_verify_user_addr = base_addr_ + WX_DO_VERIFY_USER_OFFSET; + DWORD fn1_addr = base_addr_ + 0x758720; + WeChatString user_id(wxid); + WeChatString w_msg(msg); + DWORD null_ptr = 0; + DWORD instance =0; + Unkown null_obj={}; + null_obj.field6 = 0xF; + __asm{ + PUSHAD + PUSHFD + CALL contact_mgr_addr + MOV dword ptr [instance],EAX + SUB ESP,0x18 + MOV EAX,ESP + SUB ESP,0x18 + LEA EAX,null_obj + MOV ECX,ESP + PUSH EAX + CALL fn1_addr + PUSH 0x0 + PUSH 0x6 + MOV EAX,w_msg + SUB ESP,0x14 + MOV ECX,ESP + PUSH -0x1 + PUSH EAX + CALL verify_msg_addr + PUSH 0x2 + LEA EAX,user_id + SUB ESP,0x14 + MOV ECX,ESP + PUSH EAX + CALL set_value_addr + MOV ECX,dword ptr [instance] + CALL do_verify_user_addr + MOV success,EAX + POPFD + POPAD + } + + // __asm { + // PUSHAD + // PUSHFD + // SUB ESP,0x14 + // MOV ECX,ESP + // PUSH -0x1 + // PUSH w_msg + // CALL verify_msg_addr + // PUSH 0x2 + // LEA EAX,user_id + // SUB ESP,0x14 + // MOV ECX,ESP + // PUSH EAX + // CALL set_value_addr + // CALL contact_mgr_addr + // MOV ECX,EAX + // CALL do_verify_user_addr + // MOV success,EAX + // POPFD + // POPAD + // } + return success; +} + +} // namespace wxhelper \ No newline at end of file diff --git a/src/contact_mgr.h b/src/contact_mgr.h new file mode 100644 index 0000000..b522427 --- /dev/null +++ b/src/contact_mgr.h @@ -0,0 +1,20 @@ +#ifndef WXHELPER_CONTACT_MGR_H_ +#define WXHELPER_CONTACT_MGR_H_ +#include +#include + +#include "base_mgr.h" +#include "wechat_function.h" +namespace wxhelper { +class ContactMgr : public BaseMgr { + public: + explicit ContactMgr(DWORD base); + ~ContactMgr(); + int GetAllContact(std::vector& vec); + int DelContact(wchar_t* wxid); + std::wstring GetContactOrChatRoomNickname(wchar_t* id); + int AddFriendByWxid(wchar_t* wxid,wchar_t* msg); +}; +} // namespace wxhelper + +#endif; \ No newline at end of file diff --git a/src/get_db_handle.cc b/src/db.cc similarity index 55% rename from src/get_db_handle.cc rename to src/db.cc index cd3779f..1737688 100644 --- a/src/get_db_handle.cc +++ b/src/db.cc @@ -1,57 +1,163 @@ -#include "get_db_handle.h" +#include "pch.h" +#include "db.h" -#include "common.h" -#include "db_operation.h" -#include "new_sqlite3.h" -#include "pch.h" -#include "wechat_data.h" -#define CONTACT_G_PINSTANCE_OFFSET 0x2ffddc8 -#define DB_MICRO_MSG_OFFSET 0x68 -#define DB_CHAT_MSG_OFFSET 0x1C0 -#define DB_MISC_OFFSET 0x3D8 -#define DB_EMOTION_OFFSET 0x558 -#define DB_MEDIA_OFFSET 0x9B8 -#define DB_BIZCHAT_MSG_OFFSET 0x1120 -#define DB_FUNCTION_MSG_OFFSET 0x11B0 -#define DB_NAME_OFFSET 0x14 - -#define STORAGE_START_OFFSET 0x13f8 -#define STORAGE_END_OFFSET 0x13fc - -#define PUBLIC_MSG_MGR_OFFSET 0x303df74 -#define MULTI_DB_MSG_MGR_OFFSET 0x30403b8 -#define FAVORITE_STORAGE_MGR_OFFSET 0x303fd40 -#define FTS_FAVORITE_MGR_OFFSET 0x2ffe908 - -#define OP_LOG_STORAGE_VFTABLE 0x2AD3A20 -#define CHAT_MSG_STORAGE_VFTABLE 0x2AC10F0 -#define CHAT_CR_MSG_STORAGE_VFTABLE 0x2ABEF14 -#define SESSION_STORAGE_VFTABLE 0x2AD3578 -#define APP_INFO_STORAGE_VFTABLE 0x2ABCC58 -#define HEAD_IMG_STORAGE_VFTABLE 0x2ACD9DC -#define HEAD_IMG_URL_STORAGE_VFTABLE 0x2ACDF70 - -#define BIZ_INFO_STORAGE_VFTABLE 0x2ABD718 -#define TICKET_INFO_STORAGE_VFTABLE 0x2AD5400 -#define CHAT_ROOM_STORAGE_VFTABLE 0x2AC299C -#define CHAT_ROOM_INFO_STORAGE_VFTABLE 0x2AC245C -#define MEDIA_STORAGE_VFTABLE 0x2ACE998 -#define NAME_2_ID_STORAGE_VFTABLE 0x2AD222C -#define EMOTION_PACKAGE_STORAGE_VFTABLE 0x2AC6400 - -#define EMOTION_STORAGE_VFTABLE 0x2AC7018 -#define BUFINFO_STORAGE_VFTABLE 0x2AC3178 - -#define CUSTOM_EMOTION_STORAGE_VFTABLE 0x2AC4E90 -#define DEL_SESSIONINFO_STORAGE_VFTABLE 0x2AC5F98 -#define FUNCTION_MSG_STORAGE_VFTABLE 0x2ACD10C - -#define FUNCTION_MSG_TASK_STORAGE_VFTABLE 0x2ACC5C8 -#define REVOKE_MSG_STORAGE_VFTABLE 0x2AD27BC +#include "base64.h" +#include "easylogging++.h" +#include "wechat_function.h" using namespace std; -map dbmap; -std::vector dbs; + +namespace wxhelper { + +void DB::init(DWORD base) { + base_addr_ = base; + dbmap_ = {}; + dbs_ = {}; +} + +void FreeResult(vector> &data) { + if (data.size() == 0) { + return; + } + for (unsigned int i = 0; i < data.size(); i++) { + for (unsigned j = 0; j < data[i].size(); j++) { + SqlResult *sr = (SqlResult *)&data[i][j]; + if (sr->column_name) { + delete[] sr->column_name; + sr->column_name = NULL; + } + if (sr->content) { + delete[] sr->content; + sr->content = NULL; + } + } + data[i].clear(); + } + data.clear(); +} + +int DB::SelectDataInner(DWORD db, const char *sql, + vector> &data) { + Sqlite3_prepare p_Sqlite3_prepare = + (Sqlite3_prepare)(base_addr_ + SQLITE3_PREPARE_OFFSET); + Sqlite3_step p_Sqlite3_step = + (Sqlite3_step)(base_addr_ + SQLITE3_STEP_OFFSET); + Sqlite3_column_count p_Sqlite3_column_count = + (Sqlite3_column_count)(base_addr_ + SQLITE3_COLUMN_COUNT_OFFSET); + Sqlite3_column_name p_Sqlite3_column_name = + (Sqlite3_column_name)(base_addr_ + SQLITE3_COLUMN_NAME_OFFSET); + Sqlite3_column_type p_Sqlite3_column_type = + (Sqlite3_column_type)(base_addr_ + SQLITE3_COLUMN_TYPE_OFFSET); + Sqlite3_column_blob p_Sqlite3_column_blob = + (Sqlite3_column_blob)(base_addr_ + SQLITE3_COLUMN_BLOB_OFFSET); + Sqlite3_column_bytes p_Sqlite3_column_bytes = + (Sqlite3_column_bytes)(base_addr_ + SQLITE3_COLUMN_BYTES_OFFSET); + Sqlite3_finalize p_Sqlite3_finalize = + (Sqlite3_finalize)(base_addr_ + SQLITE3_FINALIZE_OFFSET); + DWORD *stmt; + int rc = p_Sqlite3_prepare(db, sql, -1, &stmt, 0); + if (rc != SQLITE_OK) return NULL; + while (p_Sqlite3_step(stmt) == SQLITE_ROW) { + int col_count = p_Sqlite3_column_count(stmt); + vector tempStruct; + for (int i = 0; i < col_count; i++) { + SqlResult temp = {0}; + const char *ColName = p_Sqlite3_column_name(stmt, i); + int nType = p_Sqlite3_column_type(stmt, i); + const void *pReadBlobData = p_Sqlite3_column_blob(stmt, i); + int nLength = p_Sqlite3_column_bytes(stmt, i); + temp.column_name = new char[strlen(ColName) + 1]; + memcpy(temp.column_name, ColName, strlen(ColName) + 1); + temp.column_name_len = strlen(ColName); + temp.content_len = nLength; + switch (nType) { + case SQLITE_BLOB: { + temp.content = new char[nLength]; + memcpy(temp.content, pReadBlobData, nLength); + temp.is_blob = true; + break; + } + default: { + if (nLength != 0) { + temp.content = new char[nLength + 1]; + memcpy(temp.content, pReadBlobData, nLength + 1); + } else { + temp.content = new char[2]; + ZeroMemory(temp.content, 2); + } + temp.is_blob = false; + break; + } + } + tempStruct.push_back(temp); + } + data.push_back(tempStruct); + } + p_Sqlite3_finalize(stmt); + return 1; +} + +int DB::Select(DWORD db_hanle, const char *sql, + vector> &query_result) { + vector> data; + int status = SelectDataInner(db_hanle, sql, data); + if (status == 0) { + return 0; + } + if (data.size() == 0) { + return 1; + } + vector index; + for (size_t i = 0; i < data[0].size(); i++) { + index.push_back(data[0][i].column_name); + } + query_result.push_back(index); + + for (auto it : data) { + vector item; + for (size_t i = 0; i < it.size(); i++) { + if (!it[i].is_blob) { + string content(it[i].content); + item.push_back(content); + } else { + string b64_str = + base64_encode((BYTE *)it[i].content, it[i].content_len); + item.push_back(b64_str); + } + } + query_result.push_back(item); + } + FreeResult(data); + return 1; +} + +int SelectDbInfo(void *data, int argc, char **argv, char **name) { + vector result; + for (int i = 0; i < argc; i++) { + SqlResult temp = {0}; + temp.column_name = new char[strlen(name[i]) + 1]; + memcpy(temp.column_name, name[i], strlen(name[i]) + 1); + temp.column_name_len = strlen(name[i]); + if (argv[i]) { + temp.content = new char[strlen(argv[i]) + 1]; + memcpy(temp.content, argv[i], strlen(argv[i]) + 1); + temp.content_len = strlen(argv[i]); + } else { + temp.content = new char[2]; + ZeroMemory(temp.content, 2); + temp.content_len = 0; + } + result.push_back(temp); + } + return 1; +} + +int DB::ExecuteSQL(DWORD db, const char *sql, DWORD callback, void *data) { + DWORD sqlite3_exec_addr = base_addr_ + SQLITE3_EXEC_OFFSET; + Sqlite3_exec fn_sqlite3_exec = (Sqlite3_exec)sqlite3_exec_addr; + int status = fn_sqlite3_exec(db, sql, (Sqlite3_callback)callback, data, 0); + return status; +} int GetDbInfo(void *data, int argc, char **argv, char **name) { DatabaseInfo *pdata = (DatabaseInfo *)data; @@ -89,11 +195,10 @@ int GetDbInfo(void *data, int argc, char **argv, char **name) { return 0; } -std::vector GetDbHandles() { - dbs.clear(); - dbmap.clear(); - DWORD base = GetWeChatWinBase(); - DWORD p_contact_addr = *(DWORD *)(base + CONTACT_G_PINSTANCE_OFFSET); +std::vector DB::GetDbHandles() { + dbs_.clear(); + dbmap_.clear(); + DWORD p_contact_addr = *(DWORD *)(base_addr_ + CONTACT_G_PINSTANCE_OFFSET); DWORD micro_msg_db_addr = *(DWORD *)(p_contact_addr + DB_MICRO_MSG_OFFSET); DWORD chat_msg_db_addr = *(DWORD *)(p_contact_addr + DB_CHAT_MSG_OFFSET); DWORD misc_db_addr = *(DWORD *)(p_contact_addr + DB_MISC_OFFSET); @@ -114,10 +219,10 @@ std::vector GetDbHandles() { ExecuteSQL(micro_msg_db_addr, "select * from sqlite_master where type=\"table\";", (DWORD)GetDbInfo, µ_msg_db); - dbs.push_back(micro_msg_db); + dbs_.push_back(micro_msg_db); wstring micro_msg_name = wstring((wchar_t *)(*( DWORD *)(p_contact_addr + DB_MICRO_MSG_OFFSET + DB_NAME_OFFSET))); - dbmap[micro_msg_name] = micro_msg_db; + dbmap_[micro_msg_name] = micro_msg_db; // chatMsg.db DatabaseInfo chat_msg_db{0}; @@ -129,10 +234,10 @@ std::vector GetDbHandles() { ExecuteSQL(chat_msg_db_addr, "select * from sqlite_master where type=\"table\";", (DWORD)GetDbInfo, &chat_msg_db); - dbs.push_back(chat_msg_db); + dbs_.push_back(chat_msg_db); wstring chat_msg_name = wstring((wchar_t *)(*( DWORD *)(p_contact_addr + DB_CHAT_MSG_OFFSET + DB_NAME_OFFSET))); - dbmap[chat_msg_name] = chat_msg_db; + dbmap_[chat_msg_name] = chat_msg_db; // misc.db DatabaseInfo misc_db{0}; @@ -143,10 +248,10 @@ std::vector GetDbHandles() { misc_db.handle = misc_db_addr; ExecuteSQL(misc_db_addr, "select * from sqlite_master where type=\"table\";", (DWORD)GetDbInfo, &misc_db); - dbs.push_back(misc_db); + dbs_.push_back(misc_db); wstring misc_name = wstring(( wchar_t *)(*(DWORD *)(p_contact_addr + DB_MISC_OFFSET + DB_NAME_OFFSET))); - dbmap[misc_name] = misc_db; + dbmap_[misc_name] = misc_db; // emotion.db DatabaseInfo emotion_db{0}; @@ -158,10 +263,10 @@ std::vector GetDbHandles() { ExecuteSQL(emotion_db_addr, "select * from sqlite_master where type=\"table\";", (DWORD)GetDbInfo, &emotion_db); - dbs.push_back(emotion_db); + dbs_.push_back(emotion_db); wstring emotion_name = wstring((wchar_t *)(*( DWORD *)(p_contact_addr + DB_EMOTION_OFFSET + DB_NAME_OFFSET))); - dbmap[emotion_name] = emotion_db; + dbmap_[emotion_name] = emotion_db; // media.db DatabaseInfo media_db{0}; @@ -172,10 +277,10 @@ std::vector GetDbHandles() { media_db.handle = media_db_addr; ExecuteSQL(media_db_addr, "select * from sqlite_master where type=\"table\";", (DWORD)GetDbInfo, &media_db); - dbs.push_back(media_db); + dbs_.push_back(media_db); wstring media_name = wstring((wchar_t *)(*( DWORD *)(p_contact_addr + DB_MEDIA_OFFSET + DB_NAME_OFFSET))); - dbmap[media_name] = media_db; + dbmap_[media_name] = media_db; // functionMsg.db DatabaseInfo function_msg_db{0}; @@ -187,10 +292,10 @@ std::vector GetDbHandles() { ExecuteSQL(function_msg_db_addr, "select * from sqlite_master where type=\"table\";", (DWORD)GetDbInfo, &function_msg_db); - dbs.push_back(function_msg_db); + dbs_.push_back(function_msg_db); wstring function_msg_name = wstring((wchar_t *)(*( DWORD *)(p_contact_addr + DB_FUNCTION_MSG_OFFSET + DB_NAME_OFFSET))); - dbmap[function_msg_name] = function_msg_db; + dbmap_[function_msg_name] = function_msg_db; if (bizchat_msg_db_addr) { // functionMsg.db maybe null @@ -204,18 +309,18 @@ std::vector GetDbHandles() { ExecuteSQL(bizchat_msg_db_addr, "select * from sqlite_master where type=\"table\";", (DWORD)GetDbInfo, &bizchat_msg_db); - dbs.push_back(bizchat_msg_db); + dbs_.push_back(bizchat_msg_db); wstring bizchat_msg_name = wstring((wchar_t *)(*( DWORD *)(p_contact_addr + DB_FUNCTION_MSG_OFFSET + DB_NAME_OFFSET))); - dbmap[bizchat_msg_name] = bizchat_msg_db; + dbmap_[bizchat_msg_name] = bizchat_msg_db; } - // Storage + // Storage DWORD storage_start = *(DWORD *)(p_contact_addr + STORAGE_START_OFFSET); DWORD storage_end = *(DWORD *)(p_contact_addr + STORAGE_END_OFFSET); // do { // DWORD vtable_ptr = *(DWORD *)(storage_start); - + // if(vtable_ptr == base + OP_LOG_STORAGE_VFTABLE){ // }else if(vtable_ptr == base + CHAT_MSG_STORAGE_VFTABLE){ @@ -259,37 +364,37 @@ std::vector GetDbHandles() { // }else if(vtable_ptr == base + REVOKE_MSG_STORAGE_VFTABLE){ // } - + // storage_start = storage_start + 0x4; // } while (storage_start != storage_end); - DWORD multi_db_mgr_addr = base + MULTI_DB_MSG_MGR_OFFSET; - DWORD public_msg_mgr_addr = base + PUBLIC_MSG_MGR_OFFSET; - DWORD favorite_storage_mgr_addr = base + FAVORITE_STORAGE_MGR_OFFSET; - DWORD fts_favorite_mgr_addr = base + FTS_FAVORITE_MGR_OFFSET; + DWORD multi_db_mgr_addr = base_addr_ + MULTI_DB_MSG_MGR_OFFSET; + DWORD public_msg_mgr_addr = base_addr_ + PUBLIC_MSG_MGR_OFFSET; + DWORD favorite_storage_mgr_addr = base_addr_ + FAVORITE_STORAGE_MGR_OFFSET; + DWORD fts_favorite_mgr_addr = base_addr_ + FTS_FAVORITE_MGR_OFFSET; // MsgX.db DWORD wrap_ptr = *(DWORD *)(multi_db_mgr_addr); - DWORD db_num = *(DWORD *)(wrap_ptr+0x30); - DWORD current_db_num = *(DWORD *)(wrap_ptr+0x38); + DWORD db_num = *(DWORD *)(wrap_ptr + 0x30); + DWORD current_db_num = *(DWORD *)(wrap_ptr + 0x38); DWORD begin_ptr = *(DWORD *)(wrap_ptr + 0x2c); for (unsigned int i = 0; i < current_db_num; i++) { DWORD next_addr = begin_ptr + i * 0x4; - DWORD db_addr = *(DWORD *) next_addr; + DWORD db_addr = *(DWORD *)next_addr; if (db_addr) { DWORD msg0_db_addr = *(DWORD *)(db_addr + 0x60); DatabaseInfo msg0_db{0}; msg0_db.db_name = (wchar_t *)(*(DWORD *)(db_addr)); msg0_db.db_name_len = *(DWORD *)(db_addr + 0x4); msg0_db.handle = msg0_db_addr; - msg0_db.extrainfo = *(DWORD *) (*(DWORD *)(db_addr + 0x18) +0x144); - ExecuteSQL( - msg0_db_addr, "select * from sqlite_master where type=\"table\";", - (DWORD)GetDbInfo, &msg0_db); - dbs.push_back(msg0_db); + msg0_db.extrainfo = *(DWORD *)(*(DWORD *)(db_addr + 0x18) + 0x144); + ExecuteSQL(msg0_db_addr, + "select * from sqlite_master where type=\"table\";", + (DWORD)GetDbInfo, &msg0_db); + dbs_.push_back(msg0_db); wstring msg_db_name = wstring((wchar_t *)(*(DWORD *)(db_addr))); - dbmap[msg_db_name] = msg0_db; + dbmap_[msg_db_name] = msg0_db; // BufInfoStorage DWORD buf_info_addr = *(DWORD *)(db_addr + 0x14); @@ -302,137 +407,134 @@ std::vector GetDbHandles() { ExecuteSQL(buf_info_handle, "select * from sqlite_master where type=\"table\";", (DWORD)GetDbInfo, &media_msg0_db); - dbs.push_back(media_msg0_db); - wstring media_msg_db_name = wstring((wchar_t *)(*(DWORD *)(buf_info_addr + 0x4C))); - dbmap[media_msg_db_name] = media_msg0_db; + dbs_.push_back(media_msg0_db); + wstring media_msg_db_name = + wstring((wchar_t *)(*(DWORD *)(buf_info_addr + 0x4C))); + dbmap_[media_msg_db_name] = media_msg0_db; } } // publicMsg.db - DWORD public_msg_ptr = *(DWORD *) (*(DWORD *)(public_msg_mgr_addr) + 0x8); + DWORD public_msg_ptr = *(DWORD *)(*(DWORD *)(public_msg_mgr_addr) + 0x8); if (public_msg_ptr) { DWORD public_msg_db_addr = *(DWORD *)(public_msg_ptr + 0x38); DatabaseInfo public_msg_db{0}; public_msg_db.db_name = (wchar_t *)(*(DWORD *)(public_msg_ptr + 0x4C)); - public_msg_db.db_name_len =*(DWORD *)(public_msg_ptr + 0x50); + public_msg_db.db_name_len = *(DWORD *)(public_msg_ptr + 0x50); public_msg_db.handle = public_msg_db_addr; ExecuteSQL(public_msg_db_addr, "select * from sqlite_master where type=\"table\";", (DWORD)GetDbInfo, &public_msg_db); - dbs.push_back(public_msg_db); - wstring public_msg_db_name =wstring((wchar_t *)(*(DWORD *)(public_msg_ptr + 0x4C))); - dbmap[public_msg_db_name] = public_msg_db; + dbs_.push_back(public_msg_db); + wstring public_msg_db_name = + wstring((wchar_t *)(*(DWORD *)(public_msg_ptr + 0x4C))); + dbmap_[public_msg_db_name] = public_msg_db; } // Favorite.db - DWORD favItems_ptr = *(DWORD *)(*(DWORD *)(*(DWORD *)(favorite_storage_mgr_addr) + 0x8) + 0x4); + DWORD favItems_ptr = + *(DWORD *)(*(DWORD *)(*(DWORD *)(favorite_storage_mgr_addr) + 0x8) + 0x4); if (favItems_ptr) { DWORD favorite_db_addr = *(DWORD *)(favItems_ptr + 0x38); DatabaseInfo favorite_db{0}; - favorite_db.db_name =(wchar_t *)(*(DWORD *)(favItems_ptr + 0x4C)); + favorite_db.db_name = (wchar_t *)(*(DWORD *)(favItems_ptr + 0x4C)); favorite_db.db_name_len = *(DWORD *)(favItems_ptr + 0x50); favorite_db.handle = favorite_db_addr; ExecuteSQL(favorite_db_addr, "select * from sqlite_master where type=\"table\";", (DWORD)GetDbInfo, &favorite_db); - dbs.push_back(favorite_db); - wstring public_msg_db_name =wstring((wchar_t *)(*(DWORD *)(favItems_ptr + 0x4C))); - dbmap[public_msg_db_name] = favorite_db; + dbs_.push_back(favorite_db); + wstring public_msg_db_name = + wstring((wchar_t *)(*(DWORD *)(favItems_ptr + 0x4C))); + dbmap_[public_msg_db_name] = favorite_db; } DatabaseInfo db_end = {0}; - dbs.push_back(db_end); + dbs_.push_back(db_end); #ifdef _DEBUG - for (unsigned int i = 0; i < dbs.size() - 1; i++) { - printf("dbname = %ws,handle = 0x%08X,table_count:%d\n", dbs[i].db_name, - dbs[i].handle, dbs[i].tables.size()); - for (unsigned int j = 0; j < dbs[i].tables.size(); j++) { - cout << "name = " << dbs[i].tables[j].name << endl; - cout << "tbl_name = " << dbs[i].tables[j].table_name << endl; - cout << "rootpage = " << dbs[i].tables[j].rootpage << endl; - cout << "sql = " << dbs[i].tables[j].sql << endl; - cout << endl; - } - cout << endl; + for (unsigned int i = 0; i < dbs_.size() - 1; i++) { + LOG(INFO) << "dbname =" << dbs_[i].db_name; + LOG(INFO) << "handle =" << dbs_[i].handle; + LOG(INFO) << "table_count =" << dbs_[i].tables.size(); } #endif vector ret_array; - for (unsigned int i = 0; i < dbs.size() - 1; i++) - ret_array.push_back(&dbs[i]); + for (unsigned int i = 0; i < dbs_.size() - 1; i++) + ret_array.push_back(&dbs_[i]); return ret_array; } -DWORD GetDbHandleByDbName(wchar_t *dbname) { - if (dbmap.size() == 0) { +DWORD DB::GetDbHandleByDbName(wchar_t *dbname) { + if (dbmap_.size() == 0) { GetDbHandles(); } - if (dbmap.find(dbname) != dbmap.end()) { - return dbmap[dbname].handle; + if (dbmap_.find(dbname) != dbmap_.end()) { + return dbmap_[dbname].handle; } return 0; } -unsigned int GetLocalIdByMsgId(ULONG64 msgid, int &dbIndex) { +unsigned int DB::GetLocalIdByMsgId(ULONG64 msgid, int &dbIndex) { char sql[260] = {0}; sprintf_s(sql, "select localId from MSG where MsgSvrID=%llu;", msgid); wchar_t dbname[20] = {0}; for (int i = 0;; i++) { swprintf_s(dbname, L"MSG%d.db", i); DWORD handle = GetDbHandleByDbName(dbname); - #ifdef _DEBUG - cout <<" handle =" <> result; int ret = Select(handle, (const char *)sql, result); - #ifdef _DEBUG - cout <<" size =" < GetChatMsgByMsgId(ULONG64 msgid){ +vector DB::GetChatMsgByMsgId(ULONG64 msgid) { char sql[260] = {0}; - sprintf_s(sql, "select localId,TalkerId,MsgSvrID,Type,SubType,IsSender,CreateTime,Sequence,StatusEx,FlagEx,Status,MsgServerSeq,MsgSequence,StrTalker,StrContent,BytesExtra from MSG where MsgSvrID=%llu;", msgid); + sprintf_s(sql, + "select " + "localId,TalkerId,MsgSvrID,Type,SubType,IsSender,CreateTime," + "Sequence,StatusEx,FlagEx,Status,MsgServerSeq,MsgSequence," + "StrTalker,StrContent,BytesExtra from MSG where MsgSvrID=%llu;", + msgid); wchar_t dbname[20] = {0}; for (int i = 0;; i++) { swprintf_s(dbname, L"MSG%d.db", i); DWORD handle = GetDbHandleByDbName(dbname); - if (handle == 0) return {}; + if (handle == 0) { + LOG(INFO) << "MSG db handle is null"; + return {}; + } vector> result; int ret = Select(handle, (const char *)sql, result); - #ifdef _DEBUG - cout <<" size =" <> result; int ret = Select(handle, (const char *)sql, result); - #ifdef _DEBUG - cout <<" size =" < +#include + +#include "base_mgr.h" +#include "wechat_function.h" +#include "windows.h" +#include "singleton.h" +namespace wxhelper { +class DB :public Singleton{ + public: + void init(DWORD base); + int ExecuteSQL(DWORD db, const char *sql, DWORD callback, void *data); + + int Select(DWORD db_hanle, const char *sql, + std::vector> &query_result); + + std::vector GetDbHandles(); + DWORD GetDbHandleByDbName(wchar_t *dbname); + unsigned int GetLocalIdByMsgId(ULONG64 msgid, int &dbIndex); + std::vector GetChatMsgByMsgId(ULONG64 msgid); + + std::string GetVoiceBuffByMsgId(ULONG64 msgid); + + private: + int SelectDataInner(DWORD db, const char *sql, + std::vector> &data); + + private: + std::map dbmap_; + std::vector dbs_; + DWORD base_addr_; +}; + +} // namespace wxhelper + +#endif \ No newline at end of file diff --git a/src/db_operation.cc b/src/db_operation.cc deleted file mode 100644 index 580d424..0000000 --- a/src/db_operation.cc +++ /dev/null @@ -1,160 +0,0 @@ -#include "pch.h" -#include "db_operation.h" - -#include "base64.h" -#include "common.h" -#include "new_sqlite3.h" -using namespace std; - -/// @brief free data -void FreeResult(vector> &data) { - if (data.size() == 0) { - return; - } - for (unsigned int i = 0; i < data.size(); i++) { - for (unsigned j = 0; j < data[i].size(); j++) { - SqlResult *sr = (SqlResult *)&data[i][j]; - if (sr->column_name) { - delete[] sr->column_name; - sr->column_name = NULL; - } - if (sr->content) { - delete[] sr->content; - sr->content = NULL; - } - } - data[i].clear(); - } - data.clear(); -} - -int SelectDataInner(DWORD db, const char *sql, - vector> &data) { - DWORD wxBaseAddress = GetWeChatWinBase(); - Sqlite3_prepare p_Sqlite3_prepare = - (Sqlite3_prepare)(wxBaseAddress + SQLITE3_PREPARE_OFFSET); - Sqlite3_step p_Sqlite3_step = - (Sqlite3_step)(wxBaseAddress + SQLITE3_STEP_OFFSET); - Sqlite3_column_count p_Sqlite3_column_count = - (Sqlite3_column_count)(wxBaseAddress + SQLITE3_COLUMN_COUNT_OFFSET); - Sqlite3_column_name p_Sqlite3_column_name = - (Sqlite3_column_name)(wxBaseAddress + SQLITE3_COLUMN_NAME_OFFSET); - Sqlite3_column_type p_Sqlite3_column_type = - (Sqlite3_column_type)(wxBaseAddress + SQLITE3_COLUMN_TYPE_OFFSET); - Sqlite3_column_blob p_Sqlite3_column_blob = - (Sqlite3_column_blob)(wxBaseAddress + SQLITE3_COLUMN_BLOB_OFFSET); - Sqlite3_column_bytes p_Sqlite3_column_bytes = - (Sqlite3_column_bytes)(wxBaseAddress + SQLITE3_COLUMN_BYTES_OFFSET); - Sqlite3_finalize p_Sqlite3_finalize = - (Sqlite3_finalize)(wxBaseAddress + SQLITE3_FINALIZE_OFFSET); - DWORD *stmt; - int rc = p_Sqlite3_prepare(db, sql, -1, &stmt, 0); - if (rc != SQLITE_OK) return NULL; - while (p_Sqlite3_step(stmt) == SQLITE_ROW) { - int col_count = p_Sqlite3_column_count(stmt); - vector tempStruct; - for (int i = 0; i < col_count; i++) { - SqlResult temp = {0}; - const char *ColName = p_Sqlite3_column_name(stmt, i); - int nType = p_Sqlite3_column_type(stmt, i); - const void *pReadBlobData = p_Sqlite3_column_blob(stmt, i); - int nLength = p_Sqlite3_column_bytes(stmt, i); - temp.column_name = new char[strlen(ColName) + 1]; - memcpy(temp.column_name, ColName, strlen(ColName) + 1); - temp.column_name_len = strlen(ColName); - temp.content_len = nLength; - switch (nType) { - case SQLITE_BLOB: { - temp.content = new char[nLength]; - memcpy(temp.content, pReadBlobData, nLength); - temp.is_blob = true; - break; - } - default: { - if (nLength != 0) { - temp.content = new char[nLength + 1]; - memcpy(temp.content, pReadBlobData, nLength + 1); - } else { - temp.content = new char[2]; - ZeroMemory(temp.content, 2); - } - temp.is_blob = false; - break; - } - } - tempStruct.push_back(temp); - } - data.push_back(tempStruct); - } - p_Sqlite3_finalize(stmt); - return 1; -} - -int Select(DWORD db_hanle, const char *sql, - vector> &query_result) { - vector> data; - int status = SelectDataInner(db_hanle, sql, data); - if (status == 0) { - return 0; - } - if(data.size() == 0){ - return 1; - } - vector index; - for (size_t i = 0; i < data[0].size(); i++){ - index.push_back(data[0][i].column_name); - } - query_result.push_back(index); - - for (auto it : data) { - vector item; - for (size_t i = 0; i < it.size(); i++) { - if (!it[i].is_blob) { - string content(it[i].content); - item.push_back(content); - } else { - string b64_str = - base64_encode((BYTE *)it[i].content, it[i].content_len); - item.push_back(b64_str); - } - } - query_result.push_back(item); - } - FreeResult(data); - return 1; -} - -int SelectDbInfo(void *data, int argc, char **argv, char **name) { - vector result; - for (int i = 0; i < argc; i++) { - SqlResult temp = {0}; - temp.column_name = new char[strlen(name[i]) + 1]; - memcpy(temp.column_name, name[i], strlen(name[i]) + 1); - temp.column_name_len = strlen(name[i]); - if (argv[i]) { - temp.content = new char[strlen(argv[i]) + 1]; - memcpy(temp.content, argv[i], strlen(argv[i]) + 1); - temp.content_len = strlen(argv[i]); - } else { - temp.content = new char[2]; - ZeroMemory(temp.content, 2); - temp.content_len = 0; - } - result.push_back(temp); - } - return 1; -} - -/// @brief sqlite3_exec -/// @param db -/// @param sql -/// @param callback -/// @param data -/// @return -int ExecuteSQL(DWORD db, const char *sql, DWORD callback, void *data) { - DWORD base = GetWeChatWinBase(); - DWORD sqlite3_exec_addr = base + SQLITE3_EXEC_OFFSET; - Sqlite3_exec fn_sqlite3_exec = (Sqlite3_exec)sqlite3_exec_addr; - int status = fn_sqlite3_exec(db, sql, (Sqlite3_callback)callback, data, 0); - return status; -} \ No newline at end of file diff --git a/src/db_operation.h b/src/db_operation.h deleted file mode 100644 index 16ce011..0000000 --- a/src/db_operation.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef DB_OPERATION_H_ -#define DB_OPERATION_H_ -#include -#include -struct SqlResult { - char *column_name; - DWORD column_name_len; - char *content; - DWORD content_len; - BOOL is_blob; -}; -/// @brief exec sql -/// @param db opened db -/// @param sql sql -/// @param callback callback func -/// @param data data -/// @return -int ExecuteSQL(DWORD db, const char *sql, DWORD callback, void *data); - -int Select(DWORD db_hanle, const char *sql,std::vector> &query_result); - -#endif \ No newline at end of file diff --git a/src/dllMain.cc b/src/dllMain.cc index 57880ea..6a54435 100644 --- a/src/dllMain.cc +++ b/src/dllMain.cc @@ -1,14 +1,20 @@ -#include "pch.h" -#include "api.h" -#include "common.h" -#include "wxhelper.h" +#include "pch.h" +#include "hide_module.h" +#include "global_context.h" + + +using namespace wxhelper; + + + + BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { - // http_start(19088); - wxhelper::WXHelper::GetInstance().http_start(19088); + DisableThreadLibraryCalls(hModule); + GlobalContext::GetInstance().initialize(hModule); break; } case DLL_THREAD_ATTACH: { @@ -18,9 +24,7 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, break; } case DLL_PROCESS_DETACH: { - CloseConsole(); - // http_close(); - wxhelper::WXHelper::GetInstance().http_close(); + GlobalContext::GetInstance().finally(); break; } } diff --git a/src/download.cc b/src/download.cc deleted file mode 100644 index 7544d8c..0000000 --- a/src/download.cc +++ /dev/null @@ -1,213 +0,0 @@ -#include "pch.h" -#include "download.h" - -#include "common.h" -#include "get_db_handle.h" - -#include "wechat_data.h" -#include "base64.h" - -#define WX_NEW_CHAT_MSG_OFFSET 0x76f010 -#define WX_GET_PRE_DOWNLOAD_MGR_OFFSET 0x80f110 -#define WX_PUSH_ATTACH_TASK_OFFSET 0x82bb40 -#define WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET 0x756e30 -#define WX_FREE_CHAT_MSG_OFFSET 0x756960 -#define WX_CHAT_MGR_OFFSET 0x792700 -#define WX_GET_MGR_BY_PREFIX_LOCAL_ID_OFFSET 0xbc0370 -#define WX_GET_CURRENT_DATA_PATH_OFFSET 0xc872c0 -#define WX_APP_MSG_INFO_OFFSET 0x7b3d20 -#define WX_GET_APP_MSG_XML_OFFSET 0xe628a0 -#define WX_FREE_APP_MSG_INFO_OFFSET 0x79d900 -#define WX_PUSH_THUMB_TASK_OFFSET 0x82ba40 -#define WX_VIDEO_MGR_OFFSET 0x829820 -#define WX_DOWNLOAD_VIDEO_IMG_OFFSET 0xd46c30 - -using namespace std; - -int DoDownloadTask(ULONG64 msg_id) { - int success = -1; - int db_index = 0; - int local_id = GetLocalIdByMsgId(msg_id, db_index); - if (local_id < 1) { - return -2; - } - - char chat_msg[0x2D8] = {0}; - DWORD base = GetWeChatWinBase(); - DWORD new_chat_msg_addr = base + WX_NEW_CHAT_MSG_OFFSET; - DWORD get_chat_mgr_addr = base + WX_CHAT_MGR_OFFSET; - DWORD pre_download_mgr_addr = base + WX_GET_PRE_DOWNLOAD_MGR_OFFSET; - DWORD push_attach_task_addr = base + WX_PUSH_ATTACH_TASK_OFFSET; - DWORD free_addr = base + WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET; - DWORD get_by_local_Id_addr = base + WX_GET_MGR_BY_PREFIX_LOCAL_ID_OFFSET; - DWORD get_current_data_path_addr = base + WX_GET_CURRENT_DATA_PATH_OFFSET; - DWORD free_app_msg_info_addr = base + WX_FREE_APP_MSG_INFO_OFFSET; - DWORD push_thumb_task_addr = base + WX_PUSH_THUMB_TASK_OFFSET; - DWORD video_mgr_addr = base + WX_VIDEO_MGR_OFFSET; - DWORD download_video_image_addr = base + WX_VIDEO_MGR_OFFSET; - - WeChatString current_data_path; - - __asm { - PUSHAD - PUSHFD - LEA ECX,current_data_path - CALL get_current_data_path_addr - - LEA ECX,chat_msg - CALL new_chat_msg_addr - - CALL get_chat_mgr_addr - PUSH dword ptr [db_index] - LEA ECX,chat_msg - PUSH dword ptr [local_id] - CALL get_by_local_Id_addr - ADD ESP,0x8 - POPFD - POPAD - } - wstring save_path = L""; - wstring thumb_path = L""; - if (current_data_path.length > 0) { - save_path += current_data_path.ptr; - save_path += L"wxhelper"; - } else { - return -1; - } - - if (!FindOrCreateDirectoryW(save_path.c_str())) { - return -3; - } - DWORD type = *(DWORD *)(chat_msg + 0x38); - wchar_t *content = *(wchar_t **)(chat_msg + 0x70); - - switch (type) { - case 0x3: { - save_path += L"\\image"; - if (!FindOrCreateDirectoryW(save_path.c_str())) { - return -3; - } - save_path = save_path +L"\\"+ to_wstring(msg_id) + L".png"; - break; - } - case 0x3E: - case 0x2B: { - save_path += L"\\video"; - if (!FindOrCreateDirectoryW(save_path.c_str())) { - return -3; - } - thumb_path = save_path + L"\\"+ to_wstring(msg_id) + L".jpg"; - save_path = save_path + L"\\"+ to_wstring(msg_id) + L".mp4"; - - break; - } - case 0x31: { - save_path += L"\\file"; - wcout << save_path << endl; - if (!FindOrCreateDirectoryW(save_path.c_str())) { - return -3; - } - char xml_app_msg[0xC80] = {0}; - DWORD new_app_msg_addr = base + WX_APP_MSG_INFO_OFFSET; - DWORD get_xml_addr = base + WX_GET_APP_MSG_XML_OFFSET; - WeChatString w_content(content); - - __asm { - PUSHAD - PUSHFD - LEA ECX,xml_app_msg - CALL new_app_msg_addr - PUSH 0x1 - LEA EAX,w_content - PUSH EAX - LEA ECX,xml_app_msg - CALL get_xml_addr - MOV success,EAX - LEA ECX,xml_app_msg - CALL free_app_msg_info_addr - POPFD - POPAD - } - if (success != 1) { - return -4; - } - WeChatString *file_name = (WeChatString *)((DWORD)xml_app_msg + 0x44); - save_path = save_path +L"\\" + to_wstring(msg_id) + L"_" + - wstring(file_name->ptr, file_name->length); - break; - } - default: - break; - } - WeChatString w_save_path(save_path); - WeChatString w_thumb_path(thumb_path); - int temp =1; - memcpy(&chat_msg[0x19C], &w_thumb_path, sizeof(w_thumb_path)); - memcpy(&chat_msg[0x1B0], &w_save_path, sizeof(w_save_path)); - memcpy(&chat_msg[0x29C], &temp, sizeof(temp)); - // note: the image has been downloaded and will not be downloaded again - // use low-level method - // this function does not work, need to modify chatmsg. - // if (type == 0x3E || type == 0x2B){ - // __asm{ - // PUSHAD - // PUSHFD - // CALL video_mgr_addr - // LEA ECX,chat_msg - // PUSH ECX - // MOV ECX,EAX - // CALL download_video_image_addr - // POPFD - // POPAD - // } - // } - - __asm { - PUSHAD - PUSHFD - CALL pre_download_mgr_addr - PUSH 0x1 - PUSH 0x0 - LEA ECX,chat_msg - PUSH ECX - MOV ECX,EAX - CALL push_attach_task_addr - MOV success,EAX - LEA ECX,chat_msg - PUSH 0x0 - CALL free_addr - POPFD - POPAD - } - - return success; -} - -int GetVoice(ULONG64 msg_id, wchar_t *dir) { - int success = -1; - string buff = GetVoiceBuffByMsgId(msg_id); - if (buff.size() == 0) { - success = 0; - return success; - } - wstring save_path = wstring(dir); - if (!FindOrCreateDirectoryW(save_path.c_str())) { - success = -2; - return success; - } - save_path = save_path + L"\\" + to_wstring(msg_id) + L".amr"; - HANDLE file_handle = CreateFileW(save_path.c_str(), GENERIC_ALL, 0, NULL, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (file_handle == INVALID_HANDLE_VALUE) { - #ifdef _DEBUG - wcout <<" save_path =" <& fn) { + base::type::EnumType lIndexMax = LevelHelper::kMaxValid; + do { + if (fn()) { + break; + } + *startIndex = static_cast(*startIndex << 1); + } while (*startIndex <= lIndexMax); +} + +// ConfigurationTypeHelper + +const char* ConfigurationTypeHelper::convertToString(ConfigurationType configurationType) { + // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet. + if (configurationType == ConfigurationType::Enabled) return "ENABLED"; + if (configurationType == ConfigurationType::Filename) return "FILENAME"; + if (configurationType == ConfigurationType::Format) return "FORMAT"; + if (configurationType == ConfigurationType::ToFile) return "TO_FILE"; + if (configurationType == ConfigurationType::ToStandardOutput) return "TO_STANDARD_OUTPUT"; + if (configurationType == ConfigurationType::SubsecondPrecision) return "SUBSECOND_PRECISION"; + if (configurationType == ConfigurationType::PerformanceTracking) return "PERFORMANCE_TRACKING"; + if (configurationType == ConfigurationType::MaxLogFileSize) return "MAX_LOG_FILE_SIZE"; + if (configurationType == ConfigurationType::LogFlushThreshold) return "LOG_FLUSH_THRESHOLD"; + return "UNKNOWN"; +} + +struct ConfigurationStringToTypeItem { + const char* configString; + ConfigurationType configType; +}; + +static struct ConfigurationStringToTypeItem configStringToTypeMap[] = { + { "enabled", ConfigurationType::Enabled }, + { "to_file", ConfigurationType::ToFile }, + { "to_standard_output", ConfigurationType::ToStandardOutput }, + { "format", ConfigurationType::Format }, + { "filename", ConfigurationType::Filename }, + { "subsecond_precision", ConfigurationType::SubsecondPrecision }, + { "milliseconds_width", ConfigurationType::MillisecondsWidth }, + { "performance_tracking", ConfigurationType::PerformanceTracking }, + { "max_log_file_size", ConfigurationType::MaxLogFileSize }, + { "log_flush_threshold", ConfigurationType::LogFlushThreshold }, +}; + +ConfigurationType ConfigurationTypeHelper::convertFromString(const char* configStr) { + for (auto& item : configStringToTypeMap) { + if (base::utils::Str::cStringCaseEq(configStr, item.configString)) { + return item.configType; + } + } + return ConfigurationType::Unknown; +} + +void ConfigurationTypeHelper::forEachConfigType(base::type::EnumType* startIndex, const std::function& fn) { + base::type::EnumType cIndexMax = ConfigurationTypeHelper::kMaxValid; + do { + if (fn()) { + break; + } + *startIndex = static_cast(*startIndex << 1); + } while (*startIndex <= cIndexMax); +} + +// Configuration + +Configuration::Configuration(const Configuration& c) : + m_level(c.m_level), + m_configurationType(c.m_configurationType), + m_value(c.m_value) { +} + +Configuration& Configuration::operator=(const Configuration& c) { + if (&c != this) { + m_level = c.m_level; + m_configurationType = c.m_configurationType; + m_value = c.m_value; + } + return *this; +} + +/// @brief Full constructor used to sets value of configuration +Configuration::Configuration(Level level, ConfigurationType configurationType, const std::string& value) : + m_level(level), + m_configurationType(configurationType), + m_value(value) { +} + +void Configuration::log(el::base::type::ostream_t& os) const { + os << LevelHelper::convertToString(m_level) + << ELPP_LITERAL(" ") << ConfigurationTypeHelper::convertToString(m_configurationType) + << ELPP_LITERAL(" = ") << m_value.c_str(); +} + +/// @brief Used to find configuration from configuration (pointers) repository. Avoid using it. +Configuration::Predicate::Predicate(Level level, ConfigurationType configurationType) : + m_level(level), + m_configurationType(configurationType) { +} + +bool Configuration::Predicate::operator()(const Configuration* conf) const { + return ((conf != nullptr) && (conf->level() == m_level) && (conf->configurationType() == m_configurationType)); +} + +// Configurations + +Configurations::Configurations(void) : + m_configurationFile(std::string()), + m_isFromFile(false) { +} + +Configurations::Configurations(const std::string& configurationFile, bool useDefaultsForRemaining, + Configurations* base) : + m_configurationFile(configurationFile), + m_isFromFile(false) { + parseFromFile(configurationFile, base); + if (useDefaultsForRemaining) { + setRemainingToDefault(); + } +} + +bool Configurations::parseFromFile(const std::string& configurationFile, Configurations* base) { + // We initial assertion with true because if we have assertion disabled, we want to pass this + // check and if assertion is enabled we will have values re-assigned any way. + bool assertionPassed = true; + ELPP_ASSERT((assertionPassed = base::utils::File::pathExists(configurationFile.c_str(), true)) == true, + "Configuration file [" << configurationFile << "] does not exist!"); + if (!assertionPassed) { + return false; + } + bool success = Parser::parseFromFile(configurationFile, this, base); + m_isFromFile = success; + return success; +} + +bool Configurations::parseFromText(const std::string& configurationsString, Configurations* base) { + bool success = Parser::parseFromText(configurationsString, this, base); + if (success) { + m_isFromFile = false; + } + return success; +} + +void Configurations::setFromBase(Configurations* base) { + if (base == nullptr || base == this) { + return; + } + base::threading::ScopedLock scopedLock(base->lock()); + for (Configuration*& conf : base->list()) { + set(conf); + } +} + +bool Configurations::hasConfiguration(ConfigurationType configurationType) { + base::type::EnumType lIndex = LevelHelper::kMinValid; + bool result = false; + LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { + if (hasConfiguration(LevelHelper::castFromInt(lIndex), configurationType)) { + result = true; + } + return result; + }); + return result; +} + +bool Configurations::hasConfiguration(Level level, ConfigurationType configurationType) { + base::threading::ScopedLock scopedLock(lock()); +#if ELPP_COMPILER_INTEL + // We cant specify template types here, Intel C++ throws compilation error + // "error: type name is not allowed" + return RegistryWithPred::get(level, configurationType) != nullptr; +#else + return RegistryWithPred::get(level, configurationType) != nullptr; +#endif // ELPP_COMPILER_INTEL +} + +void Configurations::set(Level level, ConfigurationType configurationType, const std::string& value) { + base::threading::ScopedLock scopedLock(lock()); + unsafeSet(level, configurationType, value); // This is not unsafe anymore as we have locked mutex + if (level == Level::Global) { + unsafeSetGlobally(configurationType, value, false); // Again this is not unsafe either + } +} + +void Configurations::set(Configuration* conf) { + if (conf == nullptr) { + return; + } + set(conf->level(), conf->configurationType(), conf->value()); +} + +void Configurations::setToDefault(void) { + setGlobally(ConfigurationType::Enabled, std::string("true"), true); + setGlobally(ConfigurationType::Filename, std::string(base::consts::kDefaultLogFile), true); +#if defined(ELPP_NO_LOG_TO_FILE) + setGlobally(ConfigurationType::ToFile, std::string("false"), true); +#else + setGlobally(ConfigurationType::ToFile, std::string("true"), true); +#endif // defined(ELPP_NO_LOG_TO_FILE) + setGlobally(ConfigurationType::ToStandardOutput, std::string("true"), true); + setGlobally(ConfigurationType::SubsecondPrecision, std::string("3"), true); + setGlobally(ConfigurationType::PerformanceTracking, std::string("true"), true); + setGlobally(ConfigurationType::MaxLogFileSize, std::string("0"), true); + setGlobally(ConfigurationType::LogFlushThreshold, std::string("0"), true); + + setGlobally(ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"), true); + set(Level::Debug, ConfigurationType::Format, + std::string("%datetime %level [%logger] [%user@%host] [%func] [%loc] %msg")); + // INFO and WARNING are set to default by Level::Global + set(Level::Error, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); + set(Level::Fatal, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); + set(Level::Verbose, ConfigurationType::Format, std::string("%datetime %level-%vlevel [%logger] %msg")); + set(Level::Trace, ConfigurationType::Format, std::string("%datetime %level [%logger] [%func] [%loc] %msg")); +} + +void Configurations::setRemainingToDefault(void) { + base::threading::ScopedLock scopedLock(lock()); +#if defined(ELPP_NO_LOG_TO_FILE) + unsafeSetIfNotExist(Level::Global, ConfigurationType::Enabled, std::string("false")); +#else + unsafeSetIfNotExist(Level::Global, ConfigurationType::Enabled, std::string("true")); +#endif // defined(ELPP_NO_LOG_TO_FILE) + unsafeSetIfNotExist(Level::Global, ConfigurationType::Filename, std::string(base::consts::kDefaultLogFile)); + unsafeSetIfNotExist(Level::Global, ConfigurationType::ToStandardOutput, std::string("true")); + unsafeSetIfNotExist(Level::Global, ConfigurationType::SubsecondPrecision, std::string("3")); + unsafeSetIfNotExist(Level::Global, ConfigurationType::PerformanceTracking, std::string("true")); + unsafeSetIfNotExist(Level::Global, ConfigurationType::MaxLogFileSize, std::string("0")); + unsafeSetIfNotExist(Level::Global, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); + unsafeSetIfNotExist(Level::Debug, ConfigurationType::Format, + std::string("%datetime %level [%logger] [%user@%host] [%func] [%loc] %msg")); + // INFO and WARNING are set to default by Level::Global + unsafeSetIfNotExist(Level::Error, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); + unsafeSetIfNotExist(Level::Fatal, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); + unsafeSetIfNotExist(Level::Verbose, ConfigurationType::Format, std::string("%datetime %level-%vlevel [%logger] %msg")); + unsafeSetIfNotExist(Level::Trace, ConfigurationType::Format, + std::string("%datetime %level [%logger] [%func] [%loc] %msg")); +} + +bool Configurations::Parser::parseFromFile(const std::string& configurationFile, Configurations* sender, + Configurations* base) { + sender->setFromBase(base); + std::ifstream fileStream_(configurationFile.c_str(), std::ifstream::in); + ELPP_ASSERT(fileStream_.is_open(), "Unable to open configuration file [" << configurationFile << "] for parsing."); + bool parsedSuccessfully = false; + std::string line = std::string(); + Level currLevel = Level::Unknown; + std::string currConfigStr = std::string(); + std::string currLevelStr = std::string(); + while (fileStream_.good()) { + std::getline(fileStream_, line); + parsedSuccessfully = parseLine(&line, &currConfigStr, &currLevelStr, &currLevel, sender); + ELPP_ASSERT(parsedSuccessfully, "Unable to parse configuration line: " << line); + } + return parsedSuccessfully; +} + +bool Configurations::Parser::parseFromText(const std::string& configurationsString, Configurations* sender, + Configurations* base) { + sender->setFromBase(base); + bool parsedSuccessfully = false; + std::stringstream ss(configurationsString); + std::string line = std::string(); + Level currLevel = Level::Unknown; + std::string currConfigStr = std::string(); + std::string currLevelStr = std::string(); + while (std::getline(ss, line)) { + parsedSuccessfully = parseLine(&line, &currConfigStr, &currLevelStr, &currLevel, sender); + ELPP_ASSERT(parsedSuccessfully, "Unable to parse configuration line: " << line); + } + return parsedSuccessfully; +} + +void Configurations::Parser::ignoreComments(std::string* line) { + std::size_t foundAt = 0; + std::size_t quotesStart = line->find("\""); + std::size_t quotesEnd = std::string::npos; + if (quotesStart != std::string::npos) { + quotesEnd = line->find("\"", quotesStart + 1); + while (quotesEnd != std::string::npos && line->at(quotesEnd - 1) == '\\') { + // Do not erase slash yet - we will erase it in parseLine(..) while loop + quotesEnd = line->find("\"", quotesEnd + 2); + } + } + if ((foundAt = line->find(base::consts::kConfigurationComment)) != std::string::npos) { + if (foundAt < quotesEnd) { + foundAt = line->find(base::consts::kConfigurationComment, quotesEnd + 1); + } + *line = line->substr(0, foundAt); + } +} + +bool Configurations::Parser::isLevel(const std::string& line) { + return base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationLevel)); +} + +bool Configurations::Parser::isComment(const std::string& line) { + return base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationComment)); +} + +bool Configurations::Parser::isConfig(const std::string& line) { + std::size_t assignment = line.find('='); + return line != "" && + ((line[0] >= 'A' && line[0] <= 'Z') || (line[0] >= 'a' && line[0] <= 'z')) && + (assignment != std::string::npos) && + (line.size() > assignment); +} + +bool Configurations::Parser::parseLine(std::string* line, std::string* currConfigStr, std::string* currLevelStr, + Level* currLevel, + Configurations* conf) { + ConfigurationType currConfig = ConfigurationType::Unknown; + std::string currValue = std::string(); + *line = base::utils::Str::trim(*line); + if (isComment(*line)) return true; + ignoreComments(line); + *line = base::utils::Str::trim(*line); + if (line->empty()) { + // Comment ignored + return true; + } + if (isLevel(*line)) { + if (line->size() <= 2) { + return true; + } + *currLevelStr = line->substr(1, line->size() - 2); + *currLevelStr = base::utils::Str::toUpper(*currLevelStr); + *currLevelStr = base::utils::Str::trim(*currLevelStr); + *currLevel = LevelHelper::convertFromString(currLevelStr->c_str()); + return true; + } + if (isConfig(*line)) { + std::size_t assignment = line->find('='); + *currConfigStr = line->substr(0, assignment); + *currConfigStr = base::utils::Str::toUpper(*currConfigStr); + *currConfigStr = base::utils::Str::trim(*currConfigStr); + currConfig = ConfigurationTypeHelper::convertFromString(currConfigStr->c_str()); + currValue = line->substr(assignment + 1); + currValue = base::utils::Str::trim(currValue); + std::size_t quotesStart = currValue.find("\"", 0); + std::size_t quotesEnd = std::string::npos; + if (quotesStart != std::string::npos) { + quotesEnd = currValue.find("\"", quotesStart + 1); + while (quotesEnd != std::string::npos && currValue.at(quotesEnd - 1) == '\\') { + currValue = currValue.erase(quotesEnd - 1, 1); + quotesEnd = currValue.find("\"", quotesEnd + 2); + } + } + if (quotesStart != std::string::npos && quotesEnd != std::string::npos) { + // Quote provided - check and strip if valid + ELPP_ASSERT((quotesStart < quotesEnd), "Configuration error - No ending quote found in [" + << currConfigStr << "]"); + ELPP_ASSERT((quotesStart + 1 != quotesEnd), "Empty configuration value for [" << currConfigStr << "]"); + if ((quotesStart != quotesEnd) && (quotesStart + 1 != quotesEnd)) { + // Explicit check in case if assertion is disabled + currValue = currValue.substr(quotesStart + 1, quotesEnd - 1); + } + } + } + ELPP_ASSERT(*currLevel != Level::Unknown, "Unrecognized severity level [" << *currLevelStr << "]"); + ELPP_ASSERT(currConfig != ConfigurationType::Unknown, "Unrecognized configuration [" << *currConfigStr << "]"); + if (*currLevel == Level::Unknown || currConfig == ConfigurationType::Unknown) { + return false; // unrecognizable level or config + } + conf->set(*currLevel, currConfig, currValue); + return true; +} + +void Configurations::unsafeSetIfNotExist(Level level, ConfigurationType configurationType, const std::string& value) { + Configuration* conf = RegistryWithPred::get(level, configurationType); + if (conf == nullptr) { + unsafeSet(level, configurationType, value); + } +} + +void Configurations::unsafeSet(Level level, ConfigurationType configurationType, const std::string& value) { + Configuration* conf = RegistryWithPred::get(level, configurationType); + if (conf == nullptr) { + registerNew(new Configuration(level, configurationType, value)); + } else { + conf->setValue(value); + } + if (level == Level::Global) { + unsafeSetGlobally(configurationType, value, false); + } +} + +void Configurations::setGlobally(ConfigurationType configurationType, const std::string& value, + bool includeGlobalLevel) { + if (includeGlobalLevel) { + set(Level::Global, configurationType, value); + } + base::type::EnumType lIndex = LevelHelper::kMinValid; + LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { + set(LevelHelper::castFromInt(lIndex), configurationType, value); + return false; // Do not break lambda function yet as we need to set all levels regardless + }); +} + +void Configurations::unsafeSetGlobally(ConfigurationType configurationType, const std::string& value, + bool includeGlobalLevel) { + if (includeGlobalLevel) { + unsafeSet(Level::Global, configurationType, value); + } + base::type::EnumType lIndex = LevelHelper::kMinValid; + LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { + unsafeSet(LevelHelper::castFromInt(lIndex), configurationType, value); + return false; // Do not break lambda function yet as we need to set all levels regardless + }); +} + +// LogBuilder + +void LogBuilder::convertToColoredOutput(base::type::string_t* logLine, Level level) { + if (!m_termSupportsColor) return; + const base::type::char_t* resetColor = ELPP_LITERAL("\x1b[0m"); + if (level == Level::Error || level == Level::Fatal) + *logLine = ELPP_LITERAL("\x1b[31m") + *logLine + resetColor; + else if (level == Level::Warning) + *logLine = ELPP_LITERAL("\x1b[33m") + *logLine + resetColor; + else if (level == Level::Debug) + *logLine = ELPP_LITERAL("\x1b[32m") + *logLine + resetColor; + else if (level == Level::Info) + *logLine = ELPP_LITERAL("\x1b[36m") + *logLine + resetColor; + else if (level == Level::Trace) + *logLine = ELPP_LITERAL("\x1b[35m") + *logLine + resetColor; +} + +// Logger + +Logger::Logger(const std::string& id, base::LogStreamsReferenceMapPtr logStreamsReference) : + m_id(id), + m_typedConfigurations(nullptr), + m_parentApplicationName(std::string()), + m_isConfigured(false), + m_logStreamsReference(logStreamsReference) { + initUnflushedCount(); +} + +Logger::Logger(const std::string& id, const Configurations& configurations, + base::LogStreamsReferenceMapPtr logStreamsReference) : + m_id(id), + m_typedConfigurations(nullptr), + m_parentApplicationName(std::string()), + m_isConfigured(false), + m_logStreamsReference(logStreamsReference) { + initUnflushedCount(); + configure(configurations); +} + +Logger::Logger(const Logger& logger) { + base::utils::safeDelete(m_typedConfigurations); + m_id = logger.m_id; + m_typedConfigurations = logger.m_typedConfigurations; + m_parentApplicationName = logger.m_parentApplicationName; + m_isConfigured = logger.m_isConfigured; + m_configurations = logger.m_configurations; + m_unflushedCount = logger.m_unflushedCount; + m_logStreamsReference = logger.m_logStreamsReference; +} + +Logger& Logger::operator=(const Logger& logger) { + if (&logger != this) { + base::utils::safeDelete(m_typedConfigurations); + m_id = logger.m_id; + m_typedConfigurations = logger.m_typedConfigurations; + m_parentApplicationName = logger.m_parentApplicationName; + m_isConfigured = logger.m_isConfigured; + m_configurations = logger.m_configurations; + m_unflushedCount = logger.m_unflushedCount; + m_logStreamsReference = logger.m_logStreamsReference; + } + return *this; +} + +void Logger::configure(const Configurations& configurations) { + m_isConfigured = false; // we set it to false in case if we fail + initUnflushedCount(); + if (m_typedConfigurations != nullptr) { + Configurations* c = const_cast(m_typedConfigurations->configurations()); + if (c->hasConfiguration(Level::Global, ConfigurationType::Filename)) { + flush(); + } + } + base::threading::ScopedLock scopedLock(lock()); + if (m_configurations != configurations) { + m_configurations.setFromBase(const_cast(&configurations)); + } + base::utils::safeDelete(m_typedConfigurations); + m_typedConfigurations = new base::TypedConfigurations(&m_configurations, m_logStreamsReference); + resolveLoggerFormatSpec(); + m_isConfigured = true; +} + +void Logger::reconfigure(void) { + ELPP_INTERNAL_INFO(1, "Reconfiguring logger [" << m_id << "]"); + configure(m_configurations); +} + +bool Logger::isValidId(const std::string& id) { + for (std::string::const_iterator it = id.begin(); it != id.end(); ++it) { + if (!base::utils::Str::contains(base::consts::kValidLoggerIdSymbols, *it)) { + return false; + } + } + return true; +} + +void Logger::flush(void) { + ELPP_INTERNAL_INFO(3, "Flushing logger [" << m_id << "] all levels"); + base::threading::ScopedLock scopedLock(lock()); + base::type::EnumType lIndex = LevelHelper::kMinValid; + LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { + flush(LevelHelper::castFromInt(lIndex), nullptr); + return false; + }); +} + +void Logger::flush(Level level, base::type::fstream_t* fs) { + if (fs == nullptr && m_typedConfigurations->toFile(level)) { + fs = m_typedConfigurations->fileStream(level); + } + if (fs != nullptr) { + fs->flush(); + std::unordered_map::iterator iter = m_unflushedCount.find(level); + if (iter != m_unflushedCount.end()) { + iter->second = 0; + } + Helpers::validateFileRolling(this, level); + } +} + +void Logger::initUnflushedCount(void) { + m_unflushedCount.clear(); + base::type::EnumType lIndex = LevelHelper::kMinValid; + LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { + m_unflushedCount.insert(std::make_pair(LevelHelper::castFromInt(lIndex), 0)); + return false; + }); +} + +void Logger::resolveLoggerFormatSpec(void) const { + base::type::EnumType lIndex = LevelHelper::kMinValid; + LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { + base::LogFormat* logFormat = + const_cast(&m_typedConfigurations->logFormat(LevelHelper::castFromInt(lIndex))); + base::utils::Str::replaceFirstWithEscape(logFormat->m_format, base::consts::kLoggerIdFormatSpecifier, m_id); + return false; + }); +} + +// el::base +namespace base { + +// el::base::utils +namespace utils { + +// File + +base::type::fstream_t* File::newFileStream(const std::string& filename) { + base::type::fstream_t *fs = new base::type::fstream_t(filename.c_str(), + base::type::fstream_t::out +#if !defined(ELPP_FRESH_LOG_FILE) + | base::type::fstream_t::app +#endif + ); +#if defined(ELPP_UNICODE) + std::locale elppUnicodeLocale(""); +# if ELPP_OS_WINDOWS + std::locale elppUnicodeLocaleWindows(elppUnicodeLocale, new std::codecvt_utf8_utf16); + elppUnicodeLocale = elppUnicodeLocaleWindows; +# endif // ELPP_OS_WINDOWS + fs->imbue(elppUnicodeLocale); +#endif // defined(ELPP_UNICODE) + if (fs->is_open()) { + fs->flush(); + } else { + base::utils::safeDelete(fs); + ELPP_INTERNAL_ERROR("Bad file [" << filename << "]", true); + } + return fs; +} + +std::size_t File::getSizeOfFile(base::type::fstream_t* fs) { + if (fs == nullptr) { + return 0; + } + // Since the file stream is appended to or truncated, the current + // offset is the file size. + std::size_t size = static_cast(fs->tellg()); + return size; +} + +bool File::pathExists(const char* path, bool considerFile) { + if (path == nullptr) { + return false; + } +#if ELPP_OS_UNIX + ELPP_UNUSED(considerFile); + struct stat st; + return (stat(path, &st) == 0); +#elif ELPP_OS_WINDOWS + DWORD fileType = GetFileAttributesA(path); + if (fileType == INVALID_FILE_ATTRIBUTES) { + return false; + } + return considerFile ? true : ((fileType & FILE_ATTRIBUTE_DIRECTORY) == 0 ? false : true); +#endif // ELPP_OS_UNIX +} + +bool File::createPath(const std::string& path) { + if (path.empty()) { + return false; + } + if (base::utils::File::pathExists(path.c_str())) { + return true; + } + int status = -1; + + char* currPath = const_cast(path.c_str()); + std::string builtPath = std::string(); +#if ELPP_OS_UNIX + if (path[0] == '/') { + builtPath = "/"; + } + currPath = STRTOK(currPath, base::consts::kFilePathSeparator, 0); +#elif ELPP_OS_WINDOWS + // Use secure functions API + char* nextTok_ = nullptr; + currPath = STRTOK(currPath, base::consts::kFilePathSeparator, &nextTok_); + ELPP_UNUSED(nextTok_); +#endif // ELPP_OS_UNIX + while (currPath != nullptr) { + builtPath.append(currPath); + builtPath.append(base::consts::kFilePathSeparator); +#if ELPP_OS_UNIX + status = mkdir(builtPath.c_str(), ELPP_LOG_PERMS); + currPath = STRTOK(nullptr, base::consts::kFilePathSeparator, 0); +#elif ELPP_OS_WINDOWS + status = _mkdir(builtPath.c_str()); + currPath = STRTOK(nullptr, base::consts::kFilePathSeparator, &nextTok_); +#endif // ELPP_OS_UNIX + } + if (status == -1) { + ELPP_INTERNAL_ERROR("Error while creating path [" << path << "]", true); + return false; + } + return true; +} + +std::string File::extractPathFromFilename(const std::string& fullPath, const char* separator) { + if ((fullPath == "") || (fullPath.find(separator) == std::string::npos)) { + return fullPath; + } + std::size_t lastSlashAt = fullPath.find_last_of(separator); + if (lastSlashAt == 0) { + return std::string(separator); + } + return fullPath.substr(0, lastSlashAt + 1); +} + +void File::buildStrippedFilename(const char* filename, char buff[], std::size_t limit) { + std::size_t sizeOfFilename = strlen(filename); + if (sizeOfFilename >= limit) { + filename += (sizeOfFilename - limit); + if (filename[0] != '.' && filename[1] != '.') { // prepend if not already + filename += 3; // 3 = '..' + STRCAT(buff, "..", limit); + } + } + STRCAT(buff, filename, limit); +} + +void File::buildBaseFilename(const std::string& fullPath, char buff[], std::size_t limit, const char* separator) { + const char *filename = fullPath.c_str(); + std::size_t lastSlashAt = fullPath.find_last_of(separator); + filename += lastSlashAt ? lastSlashAt+1 : 0; + std::size_t sizeOfFilename = strlen(filename); + if (sizeOfFilename >= limit) { + filename += (sizeOfFilename - limit); + if (filename[0] != '.' && filename[1] != '.') { // prepend if not already + filename += 3; // 3 = '..' + STRCAT(buff, "..", limit); + } + } + STRCAT(buff, filename, limit); +} + +// Str + +bool Str::wildCardMatch(const char* str, const char* pattern) { + while (*pattern) { + switch (*pattern) { + case '?': + if (!*str) + return false; + ++str; + ++pattern; + break; + case '*': + if (wildCardMatch(str, pattern + 1)) + return true; + if (*str && wildCardMatch(str + 1, pattern)) + return true; + return false; + default: + if (*str++ != *pattern++) + return false; + break; + } + } + return !*str && !*pattern; +} + +std::string& Str::ltrim(std::string& str) { + str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](char c) { + return !std::isspace(c); + } )); + return str; +} + +std::string& Str::rtrim(std::string& str) { + str.erase(std::find_if(str.rbegin(), str.rend(), [](char c) { + return !std::isspace(c); + }).base(), str.end()); + return str; +} + +std::string& Str::trim(std::string& str) { + return ltrim(rtrim(str)); +} + +bool Str::startsWith(const std::string& str, const std::string& start) { + return (str.length() >= start.length()) && (str.compare(0, start.length(), start) == 0); +} + +bool Str::endsWith(const std::string& str, const std::string& end) { + return (str.length() >= end.length()) && (str.compare(str.length() - end.length(), end.length(), end) == 0); +} + +std::string& Str::replaceAll(std::string& str, char replaceWhat, char replaceWith) { + std::replace(str.begin(), str.end(), replaceWhat, replaceWith); + return str; +} + +std::string& Str::replaceAll(std::string& str, const std::string& replaceWhat, + const std::string& replaceWith) { + if (replaceWhat == replaceWith) + return str; + std::size_t foundAt = std::string::npos; + while ((foundAt = str.find(replaceWhat, foundAt + 1)) != std::string::npos) { + str.replace(foundAt, replaceWhat.length(), replaceWith); + } + return str; +} + +void Str::replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat, + const base::type::string_t& replaceWith) { + std::size_t foundAt = base::type::string_t::npos; + while ((foundAt = str.find(replaceWhat, foundAt + 1)) != base::type::string_t::npos) { + if (foundAt > 0 && str[foundAt - 1] == base::consts::kFormatSpecifierChar) { + str.erase(foundAt - 1, 1); + ++foundAt; + } else { + str.replace(foundAt, replaceWhat.length(), replaceWith); + return; + } + } +} +#if defined(ELPP_UNICODE) +void Str::replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat, + const std::string& replaceWith) { + replaceFirstWithEscape(str, replaceWhat, base::type::string_t(replaceWith.begin(), replaceWith.end())); +} +#endif // defined(ELPP_UNICODE) + +std::string& Str::toUpper(std::string& str) { + std::transform(str.begin(), str.end(), str.begin(), + [](char c) { + return static_cast(::toupper(c)); + }); + return str; +} + +bool Str::cStringEq(const char* s1, const char* s2) { + if (s1 == nullptr && s2 == nullptr) return true; + if (s1 == nullptr || s2 == nullptr) return false; + return strcmp(s1, s2) == 0; +} + +bool Str::cStringCaseEq(const char* s1, const char* s2) { + if (s1 == nullptr && s2 == nullptr) return true; + if (s1 == nullptr || s2 == nullptr) return false; + + // With thanks to cygwin for this code + int d = 0; + + while (true) { + const int c1 = toupper(*s1++); + const int c2 = toupper(*s2++); + + if (((d = c1 - c2) != 0) || (c2 == '\0')) { + break; + } + } + + return d == 0; +} + +bool Str::contains(const char* str, char c) { + for (; *str; ++str) { + if (*str == c) + return true; + } + return false; +} + +char* Str::convertAndAddToBuff(std::size_t n, int len, char* buf, const char* bufLim, bool zeroPadded) { + char localBuff[10] = ""; + char* p = localBuff + sizeof(localBuff) - 2; + if (n > 0) { + for (; n > 0 && p > localBuff && len > 0; n /= 10, --len) + *--p = static_cast(n % 10 + '0'); + } else { + *--p = '0'; + --len; + } + if (zeroPadded) + while (p > localBuff && len-- > 0) *--p = static_cast('0'); + return addToBuff(p, buf, bufLim); +} + +char* Str::addToBuff(const char* str, char* buf, const char* bufLim) { + while ((buf < bufLim) && ((*buf = *str++) != '\0')) + ++buf; + return buf; +} + +char* Str::clearBuff(char buff[], std::size_t lim) { + STRCPY(buff, "", lim); + ELPP_UNUSED(lim); // For *nix we dont have anything using lim in above STRCPY macro + return buff; +} + +/// @brief Converts wchar* to char* +/// NOTE: Need to free return value after use! +char* Str::wcharPtrToCharPtr(const wchar_t* line) { + std::size_t len_ = wcslen(line) + 1; + char* buff_ = static_cast(malloc(len_ + 1)); +# if ELPP_OS_UNIX || (ELPP_OS_WINDOWS && !ELPP_CRT_DBG_WARNINGS) + std::wcstombs(buff_, line, len_); +# elif ELPP_OS_WINDOWS + std::size_t convCount_ = 0; + mbstate_t mbState_; + ::memset(static_cast(&mbState_), 0, sizeof(mbState_)); + wcsrtombs_s(&convCount_, buff_, len_, &line, len_, &mbState_); +# endif // ELPP_OS_UNIX || (ELPP_OS_WINDOWS && !ELPP_CRT_DBG_WARNINGS) + return buff_; +} + +// OS + +#if ELPP_OS_WINDOWS +/// @brief Gets environment variables for Windows based OS. +/// We are not using getenv(const char*) because of CRT deprecation +/// @param varname Variable name to get environment variable value for +/// @return If variable exist the value of it otherwise nullptr +const char* OS::getWindowsEnvironmentVariable(const char* varname) { + const DWORD bufferLen = 50; + static char buffer[bufferLen]; + if (GetEnvironmentVariableA(varname, buffer, bufferLen)) { + return buffer; + } + return nullptr; +} +#endif // ELPP_OS_WINDOWS +#if ELPP_OS_ANDROID +std::string OS::getProperty(const char* prop) { + char propVal[PROP_VALUE_MAX + 1]; + int ret = __system_property_get(prop, propVal); + return ret == 0 ? std::string() : std::string(propVal); +} + +std::string OS::getDeviceName(void) { + std::stringstream ss; + std::string manufacturer = getProperty("ro.product.manufacturer"); + std::string model = getProperty("ro.product.model"); + if (manufacturer.empty() || model.empty()) { + return std::string(); + } + ss << manufacturer << "-" << model; + return ss.str(); +} +#endif // ELPP_OS_ANDROID + +const std::string OS::getBashOutput(const char* command) { +#if (ELPP_OS_UNIX && !ELPP_OS_ANDROID && !ELPP_CYGWIN) + if (command == nullptr) { + return std::string(); + } + FILE* proc = nullptr; + if ((proc = popen(command, "r")) == nullptr) { + ELPP_INTERNAL_ERROR("\nUnable to run command [" << command << "]", true); + return std::string(); + } + char hBuff[4096]; + if (fgets(hBuff, sizeof(hBuff), proc) != nullptr) { + pclose(proc); + const std::size_t buffLen = strlen(hBuff); + if (buffLen > 0 && hBuff[buffLen - 1] == '\n') { + hBuff[buffLen - 1] = '\0'; + } + return std::string(hBuff); + } else { + pclose(proc); + } + return std::string(); +#else + ELPP_UNUSED(command); + return std::string(); +#endif // (ELPP_OS_UNIX && !ELPP_OS_ANDROID && !ELPP_CYGWIN) +} + +std::string OS::getEnvironmentVariable(const char* variableName, const char* defaultVal, + const char* alternativeBashCommand) { +#if ELPP_OS_UNIX + const char* val = getenv(variableName); +#elif ELPP_OS_WINDOWS + const char* val = getWindowsEnvironmentVariable(variableName); +#endif // ELPP_OS_UNIX + if ((val == nullptr) || ((strcmp(val, "") == 0))) { +#if ELPP_OS_UNIX && defined(ELPP_FORCE_ENV_VAR_FROM_BASH) + // Try harder on unix-based systems + std::string valBash = base::utils::OS::getBashOutput(alternativeBashCommand); + if (valBash.empty()) { + return std::string(defaultVal); + } else { + return valBash; + } +#elif ELPP_OS_WINDOWS || ELPP_OS_UNIX + ELPP_UNUSED(alternativeBashCommand); + return std::string(defaultVal); +#endif // ELPP_OS_UNIX && defined(ELPP_FORCE_ENV_VAR_FROM_BASH) + } + return std::string(val); +} + +std::string OS::currentUser(void) { +#if ELPP_OS_UNIX && !ELPP_OS_ANDROID + return getEnvironmentVariable("USER", base::consts::kUnknownUser, "whoami"); +#elif ELPP_OS_WINDOWS + return getEnvironmentVariable("USERNAME", base::consts::kUnknownUser); +#elif ELPP_OS_ANDROID + ELPP_UNUSED(base::consts::kUnknownUser); + return std::string("android"); +#else + return std::string(); +#endif // ELPP_OS_UNIX && !ELPP_OS_ANDROID +} + +std::string OS::currentHost(void) { +#if ELPP_OS_UNIX && !ELPP_OS_ANDROID + return getEnvironmentVariable("HOSTNAME", base::consts::kUnknownHost, "hostname"); +#elif ELPP_OS_WINDOWS + return getEnvironmentVariable("COMPUTERNAME", base::consts::kUnknownHost); +#elif ELPP_OS_ANDROID + ELPP_UNUSED(base::consts::kUnknownHost); + return getDeviceName(); +#else + return std::string(); +#endif // ELPP_OS_UNIX && !ELPP_OS_ANDROID +} + +bool OS::termSupportsColor(void) { + std::string term = getEnvironmentVariable("TERM", ""); + return term == "xterm" || term == "xterm-color" || term == "xterm-256color" + || term == "screen" || term == "linux" || term == "cygwin" + || term == "screen-256color"; +} + +// DateTime + +void DateTime::gettimeofday(struct timeval* tv) { +#if ELPP_OS_WINDOWS + if (tv != nullptr) { +# if ELPP_COMPILER_MSVC || defined(_MSC_EXTENSIONS) + const unsigned __int64 delta_ = 11644473600000000Ui64; +# else + const unsigned __int64 delta_ = 11644473600000000ULL; +# endif // ELPP_COMPILER_MSVC || defined(_MSC_EXTENSIONS) + const double secOffSet = 0.000001; + const unsigned long usecOffSet = 1000000; + FILETIME fileTime; + GetSystemTimeAsFileTime(&fileTime); + unsigned __int64 present = 0; + present |= fileTime.dwHighDateTime; + present = present << 32; + present |= fileTime.dwLowDateTime; + present /= 10; // mic-sec + // Subtract the difference + present -= delta_; + tv->tv_sec = static_cast(present * secOffSet); + tv->tv_usec = static_cast(present % usecOffSet); + } +#else + ::gettimeofday(tv, nullptr); +#endif // ELPP_OS_WINDOWS +} + +std::string DateTime::getDateTime(const char* format, const base::SubsecondPrecision* ssPrec) { + struct timeval currTime; + gettimeofday(&currTime); + return timevalToString(currTime, format, ssPrec); +} + +std::string DateTime::timevalToString(struct timeval tval, const char* format, + const el::base::SubsecondPrecision* ssPrec) { + struct ::tm timeInfo; + buildTimeInfo(&tval, &timeInfo); + const int kBuffSize = 30; + char buff_[kBuffSize] = ""; + parseFormat(buff_, kBuffSize, format, &timeInfo, static_cast(tval.tv_usec / ssPrec->m_offset), + ssPrec); + return std::string(buff_); +} + +base::type::string_t DateTime::formatTime(unsigned long long time, base::TimestampUnit timestampUnit) { + base::type::EnumType start = static_cast(timestampUnit); + const base::type::char_t* unit = base::consts::kTimeFormats[start].unit; + for (base::type::EnumType i = start; i < base::consts::kTimeFormatsCount - 1; ++i) { + if (time <= base::consts::kTimeFormats[i].value) { + break; + } + if (base::consts::kTimeFormats[i].value == 1000.0f && time / 1000.0f < 1.9f) { + break; + } + time /= static_cast(base::consts::kTimeFormats[i].value); + unit = base::consts::kTimeFormats[i + 1].unit; + } + base::type::stringstream_t ss; + ss << time << " " << unit; + return ss.str(); +} + +unsigned long long DateTime::getTimeDifference(const struct timeval& endTime, const struct timeval& startTime, + base::TimestampUnit timestampUnit) { + if (timestampUnit == base::TimestampUnit::Microsecond) { + return static_cast(static_cast(1000000 * endTime.tv_sec + endTime.tv_usec) - + static_cast(1000000 * startTime.tv_sec + startTime.tv_usec)); + } + // milliseconds + auto conv = [](const struct timeval& tim) { + return static_cast((tim.tv_sec * 1000) + (tim.tv_usec / 1000)); + }; + return static_cast(conv(endTime) - conv(startTime)); +} + +struct ::tm* DateTime::buildTimeInfo(struct timeval* currTime, struct ::tm* timeInfo) { +#if ELPP_OS_UNIX + time_t rawTime = currTime->tv_sec; + ::elpptime_r(&rawTime, timeInfo); + return timeInfo; +#else +# if ELPP_COMPILER_MSVC + ELPP_UNUSED(currTime); + time_t t; +# if defined(_USE_32BIT_TIME_T) + _time32(&t); +# else + _time64(&t); +# endif + elpptime_s(timeInfo, &t); + return timeInfo; +# else + // For any other compilers that don't have CRT warnings issue e.g, MinGW or TDM GCC- we use different method + time_t rawTime = currTime->tv_sec; + struct tm* tmInf = elpptime(&rawTime); + *timeInfo = *tmInf; + return timeInfo; +# endif // ELPP_COMPILER_MSVC +#endif // ELPP_OS_UNIX +} + +char* DateTime::parseFormat(char* buf, std::size_t bufSz, const char* format, const struct tm* tInfo, + std::size_t msec, const base::SubsecondPrecision* ssPrec) { + const char* bufLim = buf + bufSz; + for (; *format; ++format) { + if (*format == base::consts::kFormatSpecifierChar) { + switch (*++format) { + case base::consts::kFormatSpecifierChar: // Escape + break; + case '\0': // End + --format; + break; + case 'd': // Day + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_mday, 2, buf, bufLim); + continue; + case 'a': // Day of week (short) + buf = base::utils::Str::addToBuff(base::consts::kDaysAbbrev[tInfo->tm_wday], buf, bufLim); + continue; + case 'A': // Day of week (long) + buf = base::utils::Str::addToBuff(base::consts::kDays[tInfo->tm_wday], buf, bufLim); + continue; + case 'M': // month + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_mon + 1, 2, buf, bufLim); + continue; + case 'b': // month (short) + buf = base::utils::Str::addToBuff(base::consts::kMonthsAbbrev[tInfo->tm_mon], buf, bufLim); + continue; + case 'B': // month (long) + buf = base::utils::Str::addToBuff(base::consts::kMonths[tInfo->tm_mon], buf, bufLim); + continue; + case 'y': // year (two digits) + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_year + base::consts::kYearBase, 2, buf, bufLim); + continue; + case 'Y': // year (four digits) + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_year + base::consts::kYearBase, 4, buf, bufLim); + continue; + case 'h': // hour (12-hour) + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_hour % 12, 2, buf, bufLim); + continue; + case 'H': // hour (24-hour) + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_hour, 2, buf, bufLim); + continue; + case 'm': // minute + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_min, 2, buf, bufLim); + continue; + case 's': // second + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_sec, 2, buf, bufLim); + continue; + case 'z': // subsecond part + case 'g': + buf = base::utils::Str::convertAndAddToBuff(msec, ssPrec->m_width, buf, bufLim); + continue; + case 'F': // AM/PM + buf = base::utils::Str::addToBuff((tInfo->tm_hour >= 12) ? base::consts::kPm : base::consts::kAm, buf, bufLim); + continue; + default: + continue; + } + } + if (buf == bufLim) break; + *buf++ = *format; + } + return buf; +} + +// CommandLineArgs + +void CommandLineArgs::setArgs(int argc, char** argv) { + m_params.clear(); + m_paramsWithValue.clear(); + if (argc == 0 || argv == nullptr) { + return; + } + m_argc = argc; + m_argv = argv; + for (int i = 1; i < m_argc; ++i) { + const char* v = (strstr(m_argv[i], "=")); + if (v != nullptr && strlen(v) > 0) { + std::string key = std::string(m_argv[i]); + key = key.substr(0, key.find_first_of('=')); + if (hasParamWithValue(key.c_str())) { + ELPP_INTERNAL_INFO(1, "Skipping [" << key << "] arg since it already has value [" + << getParamValue(key.c_str()) << "]"); + } else { + m_paramsWithValue.insert(std::make_pair(key, std::string(v + 1))); + } + } + if (v == nullptr) { + if (hasParam(m_argv[i])) { + ELPP_INTERNAL_INFO(1, "Skipping [" << m_argv[i] << "] arg since it already exists"); + } else { + m_params.push_back(std::string(m_argv[i])); + } + } + } +} + +bool CommandLineArgs::hasParamWithValue(const char* paramKey) const { + return m_paramsWithValue.find(std::string(paramKey)) != m_paramsWithValue.end(); +} + +const char* CommandLineArgs::getParamValue(const char* paramKey) const { + std::unordered_map::const_iterator iter = m_paramsWithValue.find(std::string(paramKey)); + return iter != m_paramsWithValue.end() ? iter->second.c_str() : ""; +} + +bool CommandLineArgs::hasParam(const char* paramKey) const { + return std::find(m_params.begin(), m_params.end(), std::string(paramKey)) != m_params.end(); +} + +bool CommandLineArgs::empty(void) const { + return m_params.empty() && m_paramsWithValue.empty(); +} + +std::size_t CommandLineArgs::size(void) const { + return m_params.size() + m_paramsWithValue.size(); +} + +base::type::ostream_t& operator<<(base::type::ostream_t& os, const CommandLineArgs& c) { + for (int i = 1; i < c.m_argc; ++i) { + os << ELPP_LITERAL("[") << c.m_argv[i] << ELPP_LITERAL("]"); + if (i < c.m_argc - 1) { + os << ELPP_LITERAL(" "); + } + } + return os; +} + +} // namespace utils + +// el::base::threading +namespace threading { + +#if ELPP_THREADING_ENABLED +# if ELPP_USE_STD_THREADING +# if ELPP_ASYNC_LOGGING +static void msleep(int ms) { + // Only when async logging enabled - this is because async is strict on compiler +# if defined(ELPP_NO_SLEEP_FOR) + usleep(ms * 1000); +# else + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); +# endif // defined(ELPP_NO_SLEEP_FOR) +} +# endif // ELPP_ASYNC_LOGGING +# endif // !ELPP_USE_STD_THREADING +#endif // ELPP_THREADING_ENABLED + +} // namespace threading + +// el::base + +// SubsecondPrecision + +void SubsecondPrecision::init(int width) { + if (width < 1 || width > 6) { + width = base::consts::kDefaultSubsecondPrecision; + } + m_width = width; + switch (m_width) { + case 3: + m_offset = 1000; + break; + case 4: + m_offset = 100; + break; + case 5: + m_offset = 10; + break; + case 6: + m_offset = 1; + break; + default: + m_offset = 1000; + break; + } +} + +// LogFormat + +LogFormat::LogFormat(void) : + m_level(Level::Unknown), + m_userFormat(base::type::string_t()), + m_format(base::type::string_t()), + m_dateTimeFormat(std::string()), + m_flags(0x0), + m_currentUser(base::utils::OS::currentUser()), + m_currentHost(base::utils::OS::currentHost()) { +} + +LogFormat::LogFormat(Level level, const base::type::string_t& format) + : m_level(level), m_userFormat(format), m_currentUser(base::utils::OS::currentUser()), + m_currentHost(base::utils::OS::currentHost()) { + parseFromFormat(m_userFormat); +} + +LogFormat::LogFormat(const LogFormat& logFormat): + m_level(logFormat.m_level), + m_userFormat(logFormat.m_userFormat), + m_format(logFormat.m_format), + m_dateTimeFormat(logFormat.m_dateTimeFormat), + m_flags(logFormat.m_flags), + m_currentUser(logFormat.m_currentUser), + m_currentHost(logFormat.m_currentHost) { +} + +LogFormat::LogFormat(LogFormat&& logFormat) { + m_level = std::move(logFormat.m_level); + m_userFormat = std::move(logFormat.m_userFormat); + m_format = std::move(logFormat.m_format); + m_dateTimeFormat = std::move(logFormat.m_dateTimeFormat); + m_flags = std::move(logFormat.m_flags); + m_currentUser = std::move(logFormat.m_currentUser); + m_currentHost = std::move(logFormat.m_currentHost); +} + +LogFormat& LogFormat::operator=(const LogFormat& logFormat) { + if (&logFormat != this) { + m_level = logFormat.m_level; + m_userFormat = logFormat.m_userFormat; + m_dateTimeFormat = logFormat.m_dateTimeFormat; + m_flags = logFormat.m_flags; + m_currentUser = logFormat.m_currentUser; + m_currentHost = logFormat.m_currentHost; + } + return *this; +} + +bool LogFormat::operator==(const LogFormat& other) { + return m_level == other.m_level && m_userFormat == other.m_userFormat && m_format == other.m_format && + m_dateTimeFormat == other.m_dateTimeFormat && m_flags == other.m_flags; +} + +/// @brief Updates format to be used while logging. +/// @param userFormat User provided format +void LogFormat::parseFromFormat(const base::type::string_t& userFormat) { + // We make copy because we will be changing the format + // i.e, removing user provided date format from original format + // and then storing it. + base::type::string_t formatCopy = userFormat; + m_flags = 0x0; + auto conditionalAddFlag = [&](const base::type::char_t* specifier, base::FormatFlags flag) { + std::size_t foundAt = base::type::string_t::npos; + while ((foundAt = formatCopy.find(specifier, foundAt + 1)) != base::type::string_t::npos) { + if (foundAt > 0 && formatCopy[foundAt - 1] == base::consts::kFormatSpecifierChar) { + if (hasFlag(flag)) { + // If we already have flag we remove the escape chars so that '%%' is turned to '%' + // even after specifier resolution - this is because we only replaceFirst specifier + formatCopy.erase(foundAt - 1, 1); + ++foundAt; + } + } else { + if (!hasFlag(flag)) addFlag(flag); + } + } + }; + conditionalAddFlag(base::consts::kAppNameFormatSpecifier, base::FormatFlags::AppName); + conditionalAddFlag(base::consts::kSeverityLevelFormatSpecifier, base::FormatFlags::Level); + conditionalAddFlag(base::consts::kSeverityLevelShortFormatSpecifier, base::FormatFlags::LevelShort); + conditionalAddFlag(base::consts::kLoggerIdFormatSpecifier, base::FormatFlags::LoggerId); + conditionalAddFlag(base::consts::kThreadIdFormatSpecifier, base::FormatFlags::ThreadId); + conditionalAddFlag(base::consts::kLogFileFormatSpecifier, base::FormatFlags::File); + conditionalAddFlag(base::consts::kLogFileBaseFormatSpecifier, base::FormatFlags::FileBase); + conditionalAddFlag(base::consts::kLogLineFormatSpecifier, base::FormatFlags::Line); + conditionalAddFlag(base::consts::kLogLocationFormatSpecifier, base::FormatFlags::Location); + conditionalAddFlag(base::consts::kLogFunctionFormatSpecifier, base::FormatFlags::Function); + conditionalAddFlag(base::consts::kCurrentUserFormatSpecifier, base::FormatFlags::User); + conditionalAddFlag(base::consts::kCurrentHostFormatSpecifier, base::FormatFlags::Host); + conditionalAddFlag(base::consts::kMessageFormatSpecifier, base::FormatFlags::LogMessage); + conditionalAddFlag(base::consts::kVerboseLevelFormatSpecifier, base::FormatFlags::VerboseLevel); + // For date/time we need to extract user's date format first + std::size_t dateIndex = std::string::npos; + if ((dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier)) != std::string::npos) { + while (dateIndex != std::string::npos && dateIndex > 0 && formatCopy[dateIndex - 1] == base::consts::kFormatSpecifierChar) { + dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier, dateIndex + 1); + } + if (dateIndex != std::string::npos) { + addFlag(base::FormatFlags::DateTime); + updateDateFormat(dateIndex, formatCopy); + } + } + m_format = formatCopy; + updateFormatSpec(); +} + +void LogFormat::updateDateFormat(std::size_t index, base::type::string_t& currFormat) { + if (hasFlag(base::FormatFlags::DateTime)) { + index += ELPP_STRLEN(base::consts::kDateTimeFormatSpecifier); + } + const base::type::char_t* ptr = currFormat.c_str() + index; + if ((currFormat.size() > index) && (ptr[0] == '{')) { + // User has provided format for date/time + ++ptr; + int count = 1; // Start by 1 in order to remove starting brace + std::stringstream ss; + for (; *ptr; ++ptr, ++count) { + if (*ptr == '}') { + ++count; // In order to remove ending brace + break; + } + ss << static_cast(*ptr); + } + currFormat.erase(index, count); + m_dateTimeFormat = ss.str(); + } else { + // No format provided, use default + if (hasFlag(base::FormatFlags::DateTime)) { + m_dateTimeFormat = std::string(base::consts::kDefaultDateTimeFormat); + } + } +} + +void LogFormat::updateFormatSpec(void) { + // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet. + if (m_level == Level::Debug) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kDebugLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kDebugLevelShortLogValue); + } else if (m_level == Level::Info) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kInfoLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kInfoLevelShortLogValue); + } else if (m_level == Level::Warning) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kWarningLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kWarningLevelShortLogValue); + } else if (m_level == Level::Error) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kErrorLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kErrorLevelShortLogValue); + } else if (m_level == Level::Fatal) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kFatalLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kFatalLevelShortLogValue); + } else if (m_level == Level::Verbose) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kVerboseLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kVerboseLevelShortLogValue); + } else if (m_level == Level::Trace) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kTraceLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kTraceLevelShortLogValue); + } + if (hasFlag(base::FormatFlags::User)) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kCurrentUserFormatSpecifier, + m_currentUser); + } + if (hasFlag(base::FormatFlags::Host)) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kCurrentHostFormatSpecifier, + m_currentHost); + } + // Ignore Level::Global and Level::Unknown +} + +// TypedConfigurations + +TypedConfigurations::TypedConfigurations(Configurations* configurations, + LogStreamsReferenceMapPtr logStreamsReference) { + m_configurations = configurations; + m_logStreamsReference = logStreamsReference; + build(m_configurations); +} + +TypedConfigurations::TypedConfigurations(const TypedConfigurations& other) { + this->m_configurations = other.m_configurations; + this->m_logStreamsReference = other.m_logStreamsReference; + build(m_configurations); +} + +bool TypedConfigurations::enabled(Level level) { + return getConfigByVal(level, &m_enabledMap, "enabled"); +} + +bool TypedConfigurations::toFile(Level level) { + return getConfigByVal(level, &m_toFileMap, "toFile"); +} + +const std::string& TypedConfigurations::filename(Level level) { + return getConfigByRef(level, &m_filenameMap, "filename"); +} + +bool TypedConfigurations::toStandardOutput(Level level) { + return getConfigByVal(level, &m_toStandardOutputMap, "toStandardOutput"); +} + +const base::LogFormat& TypedConfigurations::logFormat(Level level) { + return getConfigByRef(level, &m_logFormatMap, "logFormat"); +} + +const base::SubsecondPrecision& TypedConfigurations::subsecondPrecision(Level level) { + return getConfigByRef(level, &m_subsecondPrecisionMap, "subsecondPrecision"); +} + +const base::MillisecondsWidth& TypedConfigurations::millisecondsWidth(Level level) { + return getConfigByRef(level, &m_subsecondPrecisionMap, "millisecondsWidth"); +} + +bool TypedConfigurations::performanceTracking(Level level) { + return getConfigByVal(level, &m_performanceTrackingMap, "performanceTracking"); +} + +base::type::fstream_t* TypedConfigurations::fileStream(Level level) { + return getConfigByRef(level, &m_fileStreamMap, "fileStream").get(); +} + +std::size_t TypedConfigurations::maxLogFileSize(Level level) { + return getConfigByVal(level, &m_maxLogFileSizeMap, "maxLogFileSize"); +} + +std::size_t TypedConfigurations::logFlushThreshold(Level level) { + return getConfigByVal(level, &m_logFlushThresholdMap, "logFlushThreshold"); +} + +void TypedConfigurations::build(Configurations* configurations) { + base::threading::ScopedLock scopedLock(lock()); + auto getBool = [] (std::string boolStr) -> bool { // Pass by value for trimming + base::utils::Str::trim(boolStr); + return (boolStr == "TRUE" || boolStr == "true" || boolStr == "1"); + }; + std::vector withFileSizeLimit; + for (Configurations::const_iterator it = configurations->begin(); it != configurations->end(); ++it) { + Configuration* conf = *it; + // We cannot use switch on strong enums because Intel C++ dont support them yet + if (conf->configurationType() == ConfigurationType::Enabled) { + setValue(conf->level(), getBool(conf->value()), &m_enabledMap); + } else if (conf->configurationType() == ConfigurationType::ToFile) { + setValue(conf->level(), getBool(conf->value()), &m_toFileMap); + } else if (conf->configurationType() == ConfigurationType::ToStandardOutput) { + setValue(conf->level(), getBool(conf->value()), &m_toStandardOutputMap); + } else if (conf->configurationType() == ConfigurationType::Filename) { + // We do not yet configure filename but we will configure in another + // loop. This is because if file cannot be created, we will force ToFile + // to be false. Because configuring logger is not necessarily performance + // sensitive operation, we can live with another loop; (by the way this loop + // is not very heavy either) + } else if (conf->configurationType() == ConfigurationType::Format) { + setValue(conf->level(), base::LogFormat(conf->level(), + base::type::string_t(conf->value().begin(), conf->value().end())), &m_logFormatMap); + } else if (conf->configurationType() == ConfigurationType::SubsecondPrecision) { + setValue(Level::Global, + base::SubsecondPrecision(static_cast(getULong(conf->value()))), &m_subsecondPrecisionMap); + } else if (conf->configurationType() == ConfigurationType::PerformanceTracking) { + setValue(Level::Global, getBool(conf->value()), &m_performanceTrackingMap); + } else if (conf->configurationType() == ConfigurationType::MaxLogFileSize) { + auto v = getULong(conf->value()); + setValue(conf->level(), static_cast(v), &m_maxLogFileSizeMap); + if (v != 0) { + withFileSizeLimit.push_back(conf); + } + } else if (conf->configurationType() == ConfigurationType::LogFlushThreshold) { + setValue(conf->level(), static_cast(getULong(conf->value())), &m_logFlushThresholdMap); + } + } + // As mentioned earlier, we will now set filename configuration in separate loop to deal with non-existent files + for (Configurations::const_iterator it = configurations->begin(); it != configurations->end(); ++it) { + Configuration* conf = *it; + if (conf->configurationType() == ConfigurationType::Filename) { + insertFile(conf->level(), conf->value()); + } + } + for (std::vector::iterator conf = withFileSizeLimit.begin(); + conf != withFileSizeLimit.end(); ++conf) { + // This is not unsafe as mutex is locked in currect scope + unsafeValidateFileRolling((*conf)->level(), base::defaultPreRollOutCallback); + } +} + +unsigned long TypedConfigurations::getULong(std::string confVal) { + bool valid = true; + base::utils::Str::trim(confVal); + valid = !confVal.empty() && std::find_if(confVal.begin(), confVal.end(), + [](char c) { + return !base::utils::Str::isDigit(c); + }) == confVal.end(); + if (!valid) { + valid = false; + ELPP_ASSERT(valid, "Configuration value not a valid integer [" << confVal << "]"); + return 0; + } + return atol(confVal.c_str()); +} + +std::string TypedConfigurations::resolveFilename(const std::string& filename) { + std::string resultingFilename = filename; + std::size_t dateIndex = std::string::npos; + std::string dateTimeFormatSpecifierStr = std::string(base::consts::kDateTimeFormatSpecifierForFilename); + if ((dateIndex = resultingFilename.find(dateTimeFormatSpecifierStr.c_str())) != std::string::npos) { + while (dateIndex > 0 && resultingFilename[dateIndex - 1] == base::consts::kFormatSpecifierChar) { + dateIndex = resultingFilename.find(dateTimeFormatSpecifierStr.c_str(), dateIndex + 1); + } + if (dateIndex != std::string::npos) { + const char* ptr = resultingFilename.c_str() + dateIndex; + // Goto end of specifier + ptr += dateTimeFormatSpecifierStr.size(); + std::string fmt; + if ((resultingFilename.size() > dateIndex) && (ptr[0] == '{')) { + // User has provided format for date/time + ++ptr; + int count = 1; // Start by 1 in order to remove starting brace + std::stringstream ss; + for (; *ptr; ++ptr, ++count) { + if (*ptr == '}') { + ++count; // In order to remove ending brace + break; + } + ss << *ptr; + } + resultingFilename.erase(dateIndex + dateTimeFormatSpecifierStr.size(), count); + fmt = ss.str(); + } else { + fmt = std::string(base::consts::kDefaultDateTimeFormatInFilename); + } + base::SubsecondPrecision ssPrec(3); + std::string now = base::utils::DateTime::getDateTime(fmt.c_str(), &ssPrec); + base::utils::Str::replaceAll(now, '/', '-'); // Replace path element since we are dealing with filename + base::utils::Str::replaceAll(resultingFilename, dateTimeFormatSpecifierStr, now); + } + } + return resultingFilename; +} + +void TypedConfigurations::insertFile(Level level, const std::string& fullFilename) { + std::string resolvedFilename = resolveFilename(fullFilename); + if (resolvedFilename.empty()) { + std::cerr << "Could not load empty file for logging, please re-check your configurations for level [" + << LevelHelper::convertToString(level) << "]"; + } + std::string filePath = base::utils::File::extractPathFromFilename(resolvedFilename, base::consts::kFilePathSeparator); + if (filePath.size() < resolvedFilename.size()) { + base::utils::File::createPath(filePath); + } + auto create = [&](Level level) { + base::LogStreamsReferenceMap::iterator filestreamIter = m_logStreamsReference->find(resolvedFilename); + base::type::fstream_t* fs = nullptr; + if (filestreamIter == m_logStreamsReference->end()) { + // We need a completely new stream, nothing to share with + fs = base::utils::File::newFileStream(resolvedFilename); + m_filenameMap.insert(std::make_pair(level, resolvedFilename)); + m_fileStreamMap.insert(std::make_pair(level, base::FileStreamPtr(fs))); + m_logStreamsReference->insert(std::make_pair(resolvedFilename, base::FileStreamPtr(m_fileStreamMap.at(level)))); + } else { + // Woops! we have an existing one, share it! + m_filenameMap.insert(std::make_pair(level, filestreamIter->first)); + m_fileStreamMap.insert(std::make_pair(level, base::FileStreamPtr(filestreamIter->second))); + fs = filestreamIter->second.get(); + } + if (fs == nullptr) { + // We display bad file error from newFileStream() + ELPP_INTERNAL_ERROR("Setting [TO_FILE] of [" + << LevelHelper::convertToString(level) << "] to FALSE", false); + setValue(level, false, &m_toFileMap); + } + }; + // If we dont have file conf for any level, create it for Level::Global first + // otherwise create for specified level + create(m_filenameMap.empty() && m_fileStreamMap.empty() ? Level::Global : level); +} + +bool TypedConfigurations::unsafeValidateFileRolling(Level level, const PreRollOutCallback& preRollOutCallback) { + base::type::fstream_t* fs = unsafeGetConfigByRef(level, &m_fileStreamMap, "fileStream").get(); + if (fs == nullptr) { + return true; + } + std::size_t maxLogFileSize = unsafeGetConfigByVal(level, &m_maxLogFileSizeMap, "maxLogFileSize"); + std::size_t currFileSize = base::utils::File::getSizeOfFile(fs); + if (maxLogFileSize != 0 && currFileSize >= maxLogFileSize) { + std::string fname = unsafeGetConfigByRef(level, &m_filenameMap, "filename"); + ELPP_INTERNAL_INFO(1, "Truncating log file [" << fname << "] as a result of configurations for level [" + << LevelHelper::convertToString(level) << "]"); + fs->close(); + preRollOutCallback(fname.c_str(), currFileSize); + fs->open(fname, std::fstream::out | std::fstream::trunc); + return true; + } + return false; +} + +// RegisteredHitCounters + +bool RegisteredHitCounters::validateEveryN(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { + base::threading::ScopedLock scopedLock(lock()); + base::HitCounter* counter = get(filename, lineNumber); + if (counter == nullptr) { + registerNew(counter = new base::HitCounter(filename, lineNumber)); + } + counter->validateHitCounts(n); + bool result = (n >= 1 && counter->hitCounts() != 0 && counter->hitCounts() % n == 0); + return result; +} + +/// @brief Validates counter for hits >= N, i.e, registers new if does not exist otherwise updates original one +/// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned +bool RegisteredHitCounters::validateAfterN(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { + base::threading::ScopedLock scopedLock(lock()); + base::HitCounter* counter = get(filename, lineNumber); + if (counter == nullptr) { + registerNew(counter = new base::HitCounter(filename, lineNumber)); + } + // Do not use validateHitCounts here since we do not want to reset counter here + // Note the >= instead of > because we are incrementing + // after this check + if (counter->hitCounts() >= n) + return true; + counter->increment(); + return false; +} + +/// @brief Validates counter for hits are <= n, i.e, registers new if does not exist otherwise updates original one +/// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned +bool RegisteredHitCounters::validateNTimes(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { + base::threading::ScopedLock scopedLock(lock()); + base::HitCounter* counter = get(filename, lineNumber); + if (counter == nullptr) { + registerNew(counter = new base::HitCounter(filename, lineNumber)); + } + counter->increment(); + // Do not use validateHitCounts here since we do not want to reset counter here + if (counter->hitCounts() <= n) + return true; + return false; +} + +// RegisteredLoggers + +RegisteredLoggers::RegisteredLoggers(const LogBuilderPtr& defaultLogBuilder) : + m_defaultLogBuilder(defaultLogBuilder) { + m_defaultConfigurations.setToDefault(); + m_logStreamsReference = std::make_shared(); +} + +Logger* RegisteredLoggers::get(const std::string& id, bool forceCreation) { + base::threading::ScopedLock scopedLock(lock()); + Logger* logger_ = base::utils::Registry::get(id); + if (logger_ == nullptr && forceCreation) { + bool validId = Logger::isValidId(id); + if (!validId) { + ELPP_ASSERT(validId, "Invalid logger ID [" << id << "]. Not registering this logger."); + return nullptr; + } + logger_ = new Logger(id, m_defaultConfigurations, m_logStreamsReference); + logger_->m_logBuilder = m_defaultLogBuilder; + registerNew(id, logger_); + LoggerRegistrationCallback* callback = nullptr; + for (const std::pair& h + : m_loggerRegistrationCallbacks) { + callback = h.second.get(); + if (callback != nullptr && callback->enabled()) { + callback->handle(logger_); + } + } + } + return logger_; +} + +bool RegisteredLoggers::remove(const std::string& id) { + if (id == base::consts::kDefaultLoggerId) { + return false; + } + // get has internal lock + Logger* logger = base::utils::Registry::get(id); + if (logger != nullptr) { + // unregister has internal lock + unregister(logger); + } + return true; +} + +void RegisteredLoggers::unsafeFlushAll(void) { + ELPP_INTERNAL_INFO(1, "Flushing all log files"); + for (base::LogStreamsReferenceMap::iterator it = m_logStreamsReference->begin(); + it != m_logStreamsReference->end(); ++it) { + if (it->second.get() == nullptr) continue; + it->second->flush(); + } +} + +// VRegistry + +VRegistry::VRegistry(base::type::VerboseLevel level, base::type::EnumType* pFlags) : m_level(level), m_pFlags(pFlags) { +} + +/// @brief Sets verbose level. Accepted range is 0-9 +void VRegistry::setLevel(base::type::VerboseLevel level) { + base::threading::ScopedLock scopedLock(lock()); + if (level > 9) + m_level = base::consts::kMaxVerboseLevel; + else + m_level = level; +} + +void VRegistry::setModules(const char* modules) { + base::threading::ScopedLock scopedLock(lock()); + auto addSuffix = [](std::stringstream& ss, const char* sfx, const char* prev) { + if (prev != nullptr && base::utils::Str::endsWith(ss.str(), std::string(prev))) { + std::string chr(ss.str().substr(0, ss.str().size() - strlen(prev))); + ss.str(std::string("")); + ss << chr; + } + if (base::utils::Str::endsWith(ss.str(), std::string(sfx))) { + std::string chr(ss.str().substr(0, ss.str().size() - strlen(sfx))); + ss.str(std::string("")); + ss << chr; + } + ss << sfx; + }; + auto insert = [&](std::stringstream& ss, base::type::VerboseLevel level) { + if (!base::utils::hasFlag(LoggingFlag::DisableVModulesExtensions, *m_pFlags)) { + addSuffix(ss, ".h", nullptr); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".c", ".h"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".cpp", ".c"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".cc", ".cpp"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".cxx", ".cc"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".-inl.h", ".cxx"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".hxx", ".-inl.h"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".hpp", ".hxx"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".hh", ".hpp"); + } + m_modules.insert(std::make_pair(ss.str(), level)); + }; + bool isMod = true; + bool isLevel = false; + std::stringstream ss; + int level = -1; + for (; *modules; ++modules) { + switch (*modules) { + case '=': + isLevel = true; + isMod = false; + break; + case ',': + isLevel = false; + isMod = true; + if (!ss.str().empty() && level != -1) { + insert(ss, static_cast(level)); + ss.str(std::string("")); + level = -1; + } + break; + default: + if (isMod) { + ss << *modules; + } else if (isLevel) { + if (isdigit(*modules)) { + level = static_cast(*modules) - 48; + } + } + break; + } + } + if (!ss.str().empty() && level != -1) { + insert(ss, static_cast(level)); + } +} + +bool VRegistry::allowed(base::type::VerboseLevel vlevel, const char* file) { + base::threading::ScopedLock scopedLock(lock()); + if (m_modules.empty() || file == nullptr) { + return vlevel <= m_level; + } else { + char baseFilename[base::consts::kSourceFilenameMaxLength] = ""; + base::utils::File::buildBaseFilename(file, baseFilename); + std::unordered_map::iterator it = m_modules.begin(); + for (; it != m_modules.end(); ++it) { + if (base::utils::Str::wildCardMatch(baseFilename, it->first.c_str())) { + return vlevel <= it->second; + } + } + if (base::utils::hasFlag(LoggingFlag::AllowVerboseIfModuleNotSpecified, *m_pFlags)) { + return true; + } + return false; + } +} + +void VRegistry::setFromArgs(const base::utils::CommandLineArgs* commandLineArgs) { + if (commandLineArgs->hasParam("-v") || commandLineArgs->hasParam("--verbose") || + commandLineArgs->hasParam("-V") || commandLineArgs->hasParam("--VERBOSE")) { + setLevel(base::consts::kMaxVerboseLevel); + } else if (commandLineArgs->hasParamWithValue("--v")) { + setLevel(static_cast(atoi(commandLineArgs->getParamValue("--v")))); + } else if (commandLineArgs->hasParamWithValue("--V")) { + setLevel(static_cast(atoi(commandLineArgs->getParamValue("--V")))); + } else if ((commandLineArgs->hasParamWithValue("-vmodule")) && vModulesEnabled()) { + setModules(commandLineArgs->getParamValue("-vmodule")); + } else if (commandLineArgs->hasParamWithValue("-VMODULE") && vModulesEnabled()) { + setModules(commandLineArgs->getParamValue("-VMODULE")); + } +} + +#if !defined(ELPP_DEFAULT_LOGGING_FLAGS) +# define ELPP_DEFAULT_LOGGING_FLAGS 0x0 +#endif // !defined(ELPP_DEFAULT_LOGGING_FLAGS) +// Storage +#if ELPP_ASYNC_LOGGING +Storage::Storage(const LogBuilderPtr& defaultLogBuilder, base::IWorker* asyncDispatchWorker) : +#else +Storage::Storage(const LogBuilderPtr& defaultLogBuilder) : +#endif // ELPP_ASYNC_LOGGING + m_registeredHitCounters(new base::RegisteredHitCounters()), + m_registeredLoggers(new base::RegisteredLoggers(defaultLogBuilder)), + m_flags(ELPP_DEFAULT_LOGGING_FLAGS), + m_vRegistry(new base::VRegistry(0, &m_flags)), + +#if ELPP_ASYNC_LOGGING + m_asyncLogQueue(new base::AsyncLogQueue()), + m_asyncDispatchWorker(asyncDispatchWorker), +#endif // ELPP_ASYNC_LOGGING + + m_preRollOutCallback(base::defaultPreRollOutCallback) { + // Register default logger + m_registeredLoggers->get(std::string(base::consts::kDefaultLoggerId)); + // We register default logger anyway (worse case it's not going to register) just in case + m_registeredLoggers->get("default"); + +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + // Register performance logger and reconfigure format + Logger* performanceLogger = m_registeredLoggers->get(std::string(base::consts::kPerformanceLoggerId)); + m_registeredLoggers->get("performance"); + performanceLogger->configurations()->setGlobally(ConfigurationType::Format, std::string("%datetime %level %msg")); + performanceLogger->reconfigure(); +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + +#if defined(ELPP_SYSLOG) + // Register syslog logger and reconfigure format + Logger* sysLogLogger = m_registeredLoggers->get(std::string(base::consts::kSysLogLoggerId)); + sysLogLogger->configurations()->setGlobally(ConfigurationType::Format, std::string("%level: %msg")); + sysLogLogger->reconfigure(); +#endif // defined(ELPP_SYSLOG) + addFlag(LoggingFlag::AllowVerboseIfModuleNotSpecified); +#if ELPP_ASYNC_LOGGING + installLogDispatchCallback(std::string("AsyncLogDispatchCallback")); +#else + installLogDispatchCallback(std::string("DefaultLogDispatchCallback")); +#endif // ELPP_ASYNC_LOGGING +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + installPerformanceTrackingCallback + (std::string("DefaultPerformanceTrackingCallback")); +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + ELPP_INTERNAL_INFO(1, "Easylogging++ has been initialized"); +#if ELPP_ASYNC_LOGGING + m_asyncDispatchWorker->start(); +#endif // ELPP_ASYNC_LOGGING +} + +Storage::~Storage(void) { + ELPP_INTERNAL_INFO(4, "Destroying storage"); +#if ELPP_ASYNC_LOGGING + ELPP_INTERNAL_INFO(5, "Replacing log dispatch callback to synchronous"); + uninstallLogDispatchCallback(std::string("AsyncLogDispatchCallback")); + installLogDispatchCallback(std::string("DefaultLogDispatchCallback")); + ELPP_INTERNAL_INFO(5, "Destroying asyncDispatchWorker"); + base::utils::safeDelete(m_asyncDispatchWorker); + ELPP_INTERNAL_INFO(5, "Destroying asyncLogQueue"); + base::utils::safeDelete(m_asyncLogQueue); +#endif // ELPP_ASYNC_LOGGING + ELPP_INTERNAL_INFO(5, "Destroying registeredHitCounters"); + base::utils::safeDelete(m_registeredHitCounters); + ELPP_INTERNAL_INFO(5, "Destroying registeredLoggers"); + base::utils::safeDelete(m_registeredLoggers); + ELPP_INTERNAL_INFO(5, "Destroying vRegistry"); + base::utils::safeDelete(m_vRegistry); +} + +bool Storage::hasCustomFormatSpecifier(const char* formatSpecifier) { + base::threading::ScopedLock scopedLock(customFormatSpecifiersLock()); + return std::find(m_customFormatSpecifiers.begin(), m_customFormatSpecifiers.end(), + formatSpecifier) != m_customFormatSpecifiers.end(); +} + +void Storage::installCustomFormatSpecifier(const CustomFormatSpecifier& customFormatSpecifier) { + if (hasCustomFormatSpecifier(customFormatSpecifier.formatSpecifier())) { + return; + } + base::threading::ScopedLock scopedLock(customFormatSpecifiersLock()); + m_customFormatSpecifiers.push_back(customFormatSpecifier); +} + +bool Storage::uninstallCustomFormatSpecifier(const char* formatSpecifier) { + base::threading::ScopedLock scopedLock(customFormatSpecifiersLock()); + std::vector::iterator it = std::find(m_customFormatSpecifiers.begin(), + m_customFormatSpecifiers.end(), formatSpecifier); + if (it != m_customFormatSpecifiers.end() && strcmp(formatSpecifier, it->formatSpecifier()) == 0) { + m_customFormatSpecifiers.erase(it); + return true; + } + return false; +} + +void Storage::setApplicationArguments(int argc, char** argv) { + m_commandLineArgs.setArgs(argc, argv); + m_vRegistry->setFromArgs(commandLineArgs()); + // default log file +#if !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG) + if (m_commandLineArgs.hasParamWithValue(base::consts::kDefaultLogFileParam)) { + Configurations c; + c.setGlobally(ConfigurationType::Filename, + std::string(m_commandLineArgs.getParamValue(base::consts::kDefaultLogFileParam))); + registeredLoggers()->setDefaultConfigurations(c); + for (base::RegisteredLoggers::iterator it = registeredLoggers()->begin(); + it != registeredLoggers()->end(); ++it) { + it->second->configure(c); + } + } +#endif // !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG) +#if defined(ELPP_LOGGING_FLAGS_FROM_ARG) + if (m_commandLineArgs.hasParamWithValue(base::consts::kLoggingFlagsParam)) { + int userInput = atoi(m_commandLineArgs.getParamValue(base::consts::kLoggingFlagsParam)); + if (ELPP_DEFAULT_LOGGING_FLAGS == 0x0) { + m_flags = userInput; + } else { + base::utils::addFlag(userInput, &m_flags); + } + } +#endif // defined(ELPP_LOGGING_FLAGS_FROM_ARG) +} + +} // namespace base + +// LogDispatchCallback +#if defined(ELPP_THREAD_SAFE) +void LogDispatchCallback::handle(const LogDispatchData* data) { + base::threading::ScopedLock scopedLock(m_fileLocksMapLock); + std::string filename = data->logMessage()->logger()->typedConfigurations()->filename(data->logMessage()->level()); + auto lock = m_fileLocks.find(filename); + if (lock == m_fileLocks.end()) { + m_fileLocks.emplace(std::make_pair(filename, std::unique_ptr(new base::threading::Mutex))); + } +} +#else +void LogDispatchCallback::handle(const LogDispatchData* /*data*/) {} +#endif + +base::threading::Mutex& LogDispatchCallback::fileHandle(const LogDispatchData* data) { + auto it = m_fileLocks.find(data->logMessage()->logger()->typedConfigurations()->filename(data->logMessage()->level())); + return *(it->second.get()); +} + +namespace base { +// DefaultLogDispatchCallback + +void DefaultLogDispatchCallback::handle(const LogDispatchData* data) { +#if defined(ELPP_THREAD_SAFE) + LogDispatchCallback::handle(data); + base::threading::ScopedLock scopedLock(fileHandle(data)); +#endif + m_data = data; + dispatch(m_data->logMessage()->logger()->logBuilder()->build(m_data->logMessage(), + m_data->dispatchAction() == base::DispatchAction::NormalLog)); +} + +void DefaultLogDispatchCallback::dispatch(base::type::string_t&& logLine) { + if (m_data->dispatchAction() == base::DispatchAction::NormalLog) { + if (m_data->logMessage()->logger()->m_typedConfigurations->toFile(m_data->logMessage()->level())) { + base::type::fstream_t* fs = m_data->logMessage()->logger()->m_typedConfigurations->fileStream( + m_data->logMessage()->level()); + if (fs != nullptr) { + fs->write(logLine.c_str(), logLine.size()); + if (fs->fail()) { + ELPP_INTERNAL_ERROR("Unable to write log to file [" + << m_data->logMessage()->logger()->m_typedConfigurations->filename(m_data->logMessage()->level()) << "].\n" + << "Few possible reasons (could be something else):\n" << " * Permission denied\n" + << " * Disk full\n" << " * Disk is not writable", true); + } else { + if (ELPP->hasFlag(LoggingFlag::ImmediateFlush) + || (m_data->logMessage()->logger()->isFlushNeeded(m_data->logMessage()->level()))) { + m_data->logMessage()->logger()->flush(m_data->logMessage()->level(), fs); + } + } + } else { + ELPP_INTERNAL_ERROR("Log file for [" << LevelHelper::convertToString(m_data->logMessage()->level()) << "] " + << "has not been configured but [TO_FILE] is configured to TRUE. [Logger ID: " + << m_data->logMessage()->logger()->id() << "]", false); + } + } + if (m_data->logMessage()->logger()->m_typedConfigurations->toStandardOutput(m_data->logMessage()->level())) { + if (ELPP->hasFlag(LoggingFlag::ColoredTerminalOutput)) + m_data->logMessage()->logger()->logBuilder()->convertToColoredOutput(&logLine, m_data->logMessage()->level()); + ELPP_COUT << ELPP_COUT_LINE(logLine); + } + } +#if defined(ELPP_SYSLOG) + else if (m_data->dispatchAction() == base::DispatchAction::SysLog) { + // Determine syslog priority + int sysLogPriority = 0; + if (m_data->logMessage()->level() == Level::Fatal) + sysLogPriority = LOG_EMERG; + else if (m_data->logMessage()->level() == Level::Error) + sysLogPriority = LOG_ERR; + else if (m_data->logMessage()->level() == Level::Warning) + sysLogPriority = LOG_WARNING; + else if (m_data->logMessage()->level() == Level::Info) + sysLogPriority = LOG_INFO; + else if (m_data->logMessage()->level() == Level::Debug) + sysLogPriority = LOG_DEBUG; + else + sysLogPriority = LOG_NOTICE; +# if defined(ELPP_UNICODE) + char* line = base::utils::Str::wcharPtrToCharPtr(logLine.c_str()); + syslog(sysLogPriority, "%s", line); + free(line); +# else + syslog(sysLogPriority, "%s", logLine.c_str()); +# endif + } +#endif // defined(ELPP_SYSLOG) +} + +#if ELPP_ASYNC_LOGGING + +// AsyncLogDispatchCallback + +void AsyncLogDispatchCallback::handle(const LogDispatchData* data) { + base::type::string_t logLine = data->logMessage()->logger()->logBuilder()->build(data->logMessage(), + data->dispatchAction() == base::DispatchAction::NormalLog); + if (data->dispatchAction() == base::DispatchAction::NormalLog + && data->logMessage()->logger()->typedConfigurations()->toStandardOutput(data->logMessage()->level())) { + if (ELPP->hasFlag(LoggingFlag::ColoredTerminalOutput)) + data->logMessage()->logger()->logBuilder()->convertToColoredOutput(&logLine, data->logMessage()->level()); + ELPP_COUT << ELPP_COUT_LINE(logLine); + } + // Save resources and only queue if we want to write to file otherwise just ignore handler + if (data->logMessage()->logger()->typedConfigurations()->toFile(data->logMessage()->level())) { + ELPP->asyncLogQueue()->push(AsyncLogItem(*(data->logMessage()), *data, logLine)); + } +} + +// AsyncDispatchWorker +AsyncDispatchWorker::AsyncDispatchWorker() { + setContinueRunning(false); +} + +AsyncDispatchWorker::~AsyncDispatchWorker() { + setContinueRunning(false); + ELPP_INTERNAL_INFO(6, "Stopping dispatch worker - Cleaning log queue"); + clean(); + ELPP_INTERNAL_INFO(6, "Log queue cleaned"); +} + +bool AsyncDispatchWorker::clean(void) { + std::mutex m; + std::unique_lock lk(m); + cv.wait(lk, [] { return !ELPP->asyncLogQueue()->empty(); }); + emptyQueue(); + lk.unlock(); + cv.notify_one(); + return ELPP->asyncLogQueue()->empty(); +} + +void AsyncDispatchWorker::emptyQueue(void) { + while (!ELPP->asyncLogQueue()->empty()) { + AsyncLogItem data = ELPP->asyncLogQueue()->next(); + handle(&data); + base::threading::msleep(100); + } +} + +void AsyncDispatchWorker::start(void) { + base::threading::msleep(5000); // 5s (why?) + setContinueRunning(true); + std::thread t1(&AsyncDispatchWorker::run, this); + t1.join(); +} + +void AsyncDispatchWorker::handle(AsyncLogItem* logItem) { + LogDispatchData* data = logItem->data(); + LogMessage* logMessage = logItem->logMessage(); + Logger* logger = logMessage->logger(); + base::TypedConfigurations* conf = logger->typedConfigurations(); + base::type::string_t logLine = logItem->logLine(); + if (data->dispatchAction() == base::DispatchAction::NormalLog) { + if (conf->toFile(logMessage->level())) { + base::type::fstream_t* fs = conf->fileStream(logMessage->level()); + if (fs != nullptr) { + fs->write(logLine.c_str(), logLine.size()); + if (fs->fail()) { + ELPP_INTERNAL_ERROR("Unable to write log to file [" + << conf->filename(logMessage->level()) << "].\n" + << "Few possible reasons (could be something else):\n" << " * Permission denied\n" + << " * Disk full\n" << " * Disk is not writable", true); + } else { + if (ELPP->hasFlag(LoggingFlag::ImmediateFlush) || (logger->isFlushNeeded(logMessage->level()))) { + logger->flush(logMessage->level(), fs); + } + } + } else { + ELPP_INTERNAL_ERROR("Log file for [" << LevelHelper::convertToString(logMessage->level()) << "] " + << "has not been configured but [TO_FILE] is configured to TRUE. [Logger ID: " << logger->id() << "]", false); + } + } + } +# if defined(ELPP_SYSLOG) + else if (data->dispatchAction() == base::DispatchAction::SysLog) { + // Determine syslog priority + int sysLogPriority = 0; + if (logMessage->level() == Level::Fatal) + sysLogPriority = LOG_EMERG; + else if (logMessage->level() == Level::Error) + sysLogPriority = LOG_ERR; + else if (logMessage->level() == Level::Warning) + sysLogPriority = LOG_WARNING; + else if (logMessage->level() == Level::Info) + sysLogPriority = LOG_INFO; + else if (logMessage->level() == Level::Debug) + sysLogPriority = LOG_DEBUG; + else + sysLogPriority = LOG_NOTICE; +# if defined(ELPP_UNICODE) + char* line = base::utils::Str::wcharPtrToCharPtr(logLine.c_str()); + syslog(sysLogPriority, "%s", line); + free(line); +# else + syslog(sysLogPriority, "%s", logLine.c_str()); +# endif + } +# endif // defined(ELPP_SYSLOG) +} + +void AsyncDispatchWorker::run(void) { + while (continueRunning()) { + emptyQueue(); + base::threading::msleep(10); // 10ms + } +} +#endif // ELPP_ASYNC_LOGGING + +// DefaultLogBuilder + +base::type::string_t DefaultLogBuilder::build(const LogMessage* logMessage, bool appendNewLine) const { + base::TypedConfigurations* tc = logMessage->logger()->typedConfigurations(); + const base::LogFormat* logFormat = &tc->logFormat(logMessage->level()); + base::type::string_t logLine = logFormat->format(); + char buff[base::consts::kSourceFilenameMaxLength + base::consts::kSourceLineMaxLength] = ""; + const char* bufLim = buff + sizeof(buff); + if (logFormat->hasFlag(base::FormatFlags::AppName)) { + // App name + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kAppNameFormatSpecifier, + logMessage->logger()->parentApplicationName()); + } + if (logFormat->hasFlag(base::FormatFlags::ThreadId)) { + // Thread ID + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kThreadIdFormatSpecifier, + ELPP->getThreadName(base::threading::getCurrentThreadId())); + } + if (logFormat->hasFlag(base::FormatFlags::DateTime)) { + // DateTime + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kDateTimeFormatSpecifier, + base::utils::DateTime::getDateTime(logFormat->dateTimeFormat().c_str(), + &tc->subsecondPrecision(logMessage->level()))); + } + if (logFormat->hasFlag(base::FormatFlags::Function)) { + // Function + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFunctionFormatSpecifier, logMessage->func()); + } + if (logFormat->hasFlag(base::FormatFlags::File)) { + // File + base::utils::Str::clearBuff(buff, base::consts::kSourceFilenameMaxLength); + base::utils::File::buildStrippedFilename(logMessage->file().c_str(), buff); + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFileFormatSpecifier, std::string(buff)); + } + if (logFormat->hasFlag(base::FormatFlags::FileBase)) { + // FileBase + base::utils::Str::clearBuff(buff, base::consts::kSourceFilenameMaxLength); + base::utils::File::buildBaseFilename(logMessage->file(), buff); + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFileBaseFormatSpecifier, std::string(buff)); + } + if (logFormat->hasFlag(base::FormatFlags::Line)) { + // Line + char* buf = base::utils::Str::clearBuff(buff, base::consts::kSourceLineMaxLength); + buf = base::utils::Str::convertAndAddToBuff(logMessage->line(), base::consts::kSourceLineMaxLength, buf, bufLim, false); + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogLineFormatSpecifier, std::string(buff)); + } + if (logFormat->hasFlag(base::FormatFlags::Location)) { + // Location + char* buf = base::utils::Str::clearBuff(buff, + base::consts::kSourceFilenameMaxLength + base::consts::kSourceLineMaxLength); + base::utils::File::buildStrippedFilename(logMessage->file().c_str(), buff); + buf = base::utils::Str::addToBuff(buff, buf, bufLim); + buf = base::utils::Str::addToBuff(":", buf, bufLim); + buf = base::utils::Str::convertAndAddToBuff(logMessage->line(), base::consts::kSourceLineMaxLength, buf, bufLim, + false); + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogLocationFormatSpecifier, std::string(buff)); + } + if (logMessage->level() == Level::Verbose && logFormat->hasFlag(base::FormatFlags::VerboseLevel)) { + // Verbose level + char* buf = base::utils::Str::clearBuff(buff, 1); + buf = base::utils::Str::convertAndAddToBuff(logMessage->verboseLevel(), 1, buf, bufLim, false); + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kVerboseLevelFormatSpecifier, std::string(buff)); + } + if (logFormat->hasFlag(base::FormatFlags::LogMessage)) { + // Log message + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kMessageFormatSpecifier, logMessage->message()); + } +#if !defined(ELPP_DISABLE_CUSTOM_FORMAT_SPECIFIERS) + el::base::threading::ScopedLock lock_(ELPP->customFormatSpecifiersLock()); + ELPP_UNUSED(lock_); + for (std::vector::const_iterator it = ELPP->customFormatSpecifiers()->begin(); + it != ELPP->customFormatSpecifiers()->end(); ++it) { + std::string fs(it->formatSpecifier()); + base::type::string_t wcsFormatSpecifier(fs.begin(), fs.end()); + base::utils::Str::replaceFirstWithEscape(logLine, wcsFormatSpecifier, it->resolver()(logMessage)); + } +#endif // !defined(ELPP_DISABLE_CUSTOM_FORMAT_SPECIFIERS) + if (appendNewLine) logLine += ELPP_LITERAL("\n"); + return logLine; +} + +// LogDispatcher + +void LogDispatcher::dispatch(void) { + if (m_proceed && m_dispatchAction == base::DispatchAction::None) { + m_proceed = false; + } + if (!m_proceed) { + return; + } +#ifndef ELPP_NO_GLOBAL_LOCK + // see https://github.com/muflihun/easyloggingpp/issues/580 + // global lock is turned on by default unless + // ELPP_NO_GLOBAL_LOCK is defined + base::threading::ScopedLock scopedLock(ELPP->lock()); +#endif + base::TypedConfigurations* tc = m_logMessage->logger()->m_typedConfigurations; + if (ELPP->hasFlag(LoggingFlag::StrictLogFileSizeCheck)) { + tc->validateFileRolling(m_logMessage->level(), ELPP->preRollOutCallback()); + } + LogDispatchCallback* callback = nullptr; + LogDispatchData data; + for (const std::pair& h + : ELPP->m_logDispatchCallbacks) { + callback = h.second.get(); + if (callback != nullptr && callback->enabled()) { + data.setLogMessage(m_logMessage); + data.setDispatchAction(m_dispatchAction); + callback->handle(&data); + } + } +} + +// MessageBuilder + +void MessageBuilder::initialize(Logger* logger) { + m_logger = logger; + m_containerLogSeparator = ELPP->hasFlag(LoggingFlag::NewLineForContainer) ? + ELPP_LITERAL("\n ") : ELPP_LITERAL(", "); +} + +MessageBuilder& MessageBuilder::operator<<(const wchar_t* msg) { + if (msg == nullptr) { + m_logger->stream() << base::consts::kNullPointer; + return *this; + } +# if defined(ELPP_UNICODE) + m_logger->stream() << msg; +# else + char* buff_ = base::utils::Str::wcharPtrToCharPtr(msg); + m_logger->stream() << buff_; + free(buff_); +# endif + if (ELPP->hasFlag(LoggingFlag::AutoSpacing)) { + m_logger->stream() << " "; + } + return *this; +} + +// Writer + +Writer& Writer::construct(Logger* logger, bool needLock) { + m_logger = logger; + initializeLogger(logger->id(), false, needLock); + m_messageBuilder.initialize(m_logger); + return *this; +} + +Writer& Writer::construct(int count, const char* loggerIds, ...) { + if (ELPP->hasFlag(LoggingFlag::MultiLoggerSupport)) { + va_list loggersList; + va_start(loggersList, loggerIds); + const char* id = loggerIds; + m_loggerIds.reserve(count); + for (int i = 0; i < count; ++i) { + m_loggerIds.push_back(std::string(id)); + id = va_arg(loggersList, const char*); + } + va_end(loggersList); + initializeLogger(m_loggerIds.at(0)); + } else { + initializeLogger(std::string(loggerIds)); + } + m_messageBuilder.initialize(m_logger); + return *this; +} + +void Writer::initializeLogger(const std::string& loggerId, bool lookup, bool needLock) { + if (lookup) { + m_logger = ELPP->registeredLoggers()->get(loggerId, ELPP->hasFlag(LoggingFlag::CreateLoggerAutomatically)); + } + if (m_logger == nullptr) { + { + if (!ELPP->registeredLoggers()->has(std::string(base::consts::kDefaultLoggerId))) { + // Somehow default logger has been unregistered. Not good! Register again + ELPP->registeredLoggers()->get(std::string(base::consts::kDefaultLoggerId)); + } + } + Writer(Level::Debug, m_file, m_line, m_func).construct(1, base::consts::kDefaultLoggerId) + << "Logger [" << loggerId << "] is not registered yet!"; + m_proceed = false; + } else { + if (needLock) { + m_logger->acquireLock(); // This should not be unlocked by checking m_proceed because + // m_proceed can be changed by lines below + } + if (ELPP->hasFlag(LoggingFlag::HierarchicalLogging)) { + m_proceed = m_level == Level::Verbose ? m_logger->enabled(m_level) : + LevelHelper::castToInt(m_level) >= LevelHelper::castToInt(ELPP->m_loggingLevel); + } else { + m_proceed = m_logger->enabled(m_level); + } + } +} + +void Writer::processDispatch() { +#if ELPP_LOGGING_ENABLED + if (ELPP->hasFlag(LoggingFlag::MultiLoggerSupport)) { + bool firstDispatched = false; + base::type::string_t logMessage; + std::size_t i = 0; + do { + if (m_proceed) { + if (firstDispatched) { + m_logger->stream() << logMessage; + } else { + firstDispatched = true; + if (m_loggerIds.size() > 1) { + logMessage = m_logger->stream().str(); + } + } + triggerDispatch(); + } else if (m_logger != nullptr) { + m_logger->stream().str(ELPP_LITERAL("")); + m_logger->releaseLock(); + } + if (i + 1 < m_loggerIds.size()) { + initializeLogger(m_loggerIds.at(i + 1)); + } + } while (++i < m_loggerIds.size()); + } else { + if (m_proceed) { + triggerDispatch(); + } else if (m_logger != nullptr) { + m_logger->stream().str(ELPP_LITERAL("")); + m_logger->releaseLock(); + } + } +#else + if (m_logger != nullptr) { + m_logger->stream().str(ELPP_LITERAL("")); + m_logger->releaseLock(); + } +#endif // ELPP_LOGGING_ENABLED +} + +void Writer::triggerDispatch(void) { + try { + if (m_proceed) { + if (m_msg == nullptr) { + LogMessage msg(m_level, m_file, m_line, m_func, m_verboseLevel, + m_logger); + base::LogDispatcher(m_proceed, &msg, m_dispatchAction).dispatch(); + } else { + base::LogDispatcher(m_proceed, m_msg, m_dispatchAction).dispatch(); + } + } + if (m_logger != nullptr) { + m_logger->stream().str(ELPP_LITERAL("")); + m_logger->releaseLock(); + } + if (m_proceed && m_level == Level::Fatal + && !ELPP->hasFlag(LoggingFlag::DisableApplicationAbortOnFatalLog)) { + base::Writer(Level::Warning, m_file, m_line, m_func).construct(1, base::consts::kDefaultLoggerId) + << "Aborting application. Reason: Fatal log at [" << m_file << ":" << m_line << "]"; + std::stringstream reasonStream; + reasonStream << "Fatal log at [" << m_file << ":" << m_line << "]" + << " If you wish to disable 'abort on fatal log' please use " + << "el::Loggers::addFlag(el::LoggingFlag::DisableApplicationAbortOnFatalLog)"; + base::utils::abort(1, reasonStream.str()); + } + m_proceed = false; + } + catch(std::exception & ex){ + // Extremely low memory situation; don't let exception be unhandled. + } +} + +// PErrorWriter + +PErrorWriter::~PErrorWriter(void) { + if (m_proceed) { +#if ELPP_COMPILER_MSVC + char buff[256]; + strerror_s(buff, 256, errno); + m_logger->stream() << ": " << buff << " [" << errno << "]"; +#else + m_logger->stream() << ": " << strerror(errno) << " [" << errno << "]"; +#endif + } +} + +// PerformanceTracker + +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + +PerformanceTracker::PerformanceTracker(const std::string& blockName, + base::TimestampUnit timestampUnit, + const std::string& loggerId, + bool scopedLog, Level level) : + m_blockName(blockName), m_timestampUnit(timestampUnit), m_loggerId(loggerId), m_scopedLog(scopedLog), + m_level(level), m_hasChecked(false), m_lastCheckpointId(std::string()), m_enabled(false) { +#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED + // We store it locally so that if user happen to change configuration by the end of scope + // or before calling checkpoint, we still depend on state of configuration at time of construction + el::Logger* loggerPtr = ELPP->registeredLoggers()->get(loggerId, false); + m_enabled = loggerPtr != nullptr && loggerPtr->m_typedConfigurations->performanceTracking(m_level); + if (m_enabled) { + base::utils::DateTime::gettimeofday(&m_startTime); + } +#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED +} + +PerformanceTracker::~PerformanceTracker(void) { +#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED + if (m_enabled) { + base::threading::ScopedLock scopedLock(lock()); + if (m_scopedLog) { + base::utils::DateTime::gettimeofday(&m_endTime); + base::type::string_t formattedTime = getFormattedTimeTaken(); + PerformanceTrackingData data(PerformanceTrackingData::DataType::Complete); + data.init(this); + data.m_formattedTimeTaken = formattedTime; + PerformanceTrackingCallback* callback = nullptr; + for (const std::pair& h + : ELPP->m_performanceTrackingCallbacks) { + callback = h.second.get(); + if (callback != nullptr && callback->enabled()) { + callback->handle(&data); + } + } + } + } +#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) +} + +void PerformanceTracker::checkpoint(const std::string& id, const char* file, base::type::LineNumber line, + const char* func) { +#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED + if (m_enabled) { + base::threading::ScopedLock scopedLock(lock()); + base::utils::DateTime::gettimeofday(&m_endTime); + base::type::string_t formattedTime = m_hasChecked ? getFormattedTimeTaken(m_lastCheckpointTime) : ELPP_LITERAL(""); + PerformanceTrackingData data(PerformanceTrackingData::DataType::Checkpoint); + data.init(this); + data.m_checkpointId = id; + data.m_file = file; + data.m_line = line; + data.m_func = func; + data.m_formattedTimeTaken = formattedTime; + PerformanceTrackingCallback* callback = nullptr; + for (const std::pair& h + : ELPP->m_performanceTrackingCallbacks) { + callback = h.second.get(); + if (callback != nullptr && callback->enabled()) { + callback->handle(&data); + } + } + base::utils::DateTime::gettimeofday(&m_lastCheckpointTime); + m_hasChecked = true; + m_lastCheckpointId = id; + } +#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED + ELPP_UNUSED(id); + ELPP_UNUSED(file); + ELPP_UNUSED(line); + ELPP_UNUSED(func); +} + +const base::type::string_t PerformanceTracker::getFormattedTimeTaken(struct timeval startTime) const { + if (ELPP->hasFlag(LoggingFlag::FixedTimeFormat)) { + base::type::stringstream_t ss; + ss << base::utils::DateTime::getTimeDifference(m_endTime, + startTime, m_timestampUnit) << " " << base::consts::kTimeFormats[static_cast + (m_timestampUnit)].unit; + return ss.str(); + } + return base::utils::DateTime::formatTime(base::utils::DateTime::getTimeDifference(m_endTime, + startTime, m_timestampUnit), m_timestampUnit); +} + +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + +namespace debug { +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) + +// StackTrace + +StackTrace::StackTraceEntry::StackTraceEntry(std::size_t index, const std::string& loc, const std::string& demang, + const std::string& hex, + const std::string& addr) : + m_index(index), + m_location(loc), + m_demangled(demang), + m_hex(hex), + m_addr(addr) { +} + +std::ostream& operator<<(std::ostream& ss, const StackTrace::StackTraceEntry& si) { + ss << "[" << si.m_index << "] " << si.m_location << (si.m_hex.empty() ? "" : "+") << si.m_hex << " " << si.m_addr << + (si.m_demangled.empty() ? "" : ":") << si.m_demangled; + return ss; +} + +std::ostream& operator<<(std::ostream& os, const StackTrace& st) { + std::vector::const_iterator it = st.m_stack.begin(); + while (it != st.m_stack.end()) { + os << " " << *it++ << "\n"; + } + return os; +} + +void StackTrace::generateNew(void) { +#ifdef HAVE_EXECINFO + m_stack.clear(); + void* stack[kMaxStack]; + unsigned int size = backtrace(stack, kMaxStack); + char** strings = backtrace_symbols(stack, size); + if (size > kStackStart) { // Skip StackTrace c'tor and generateNew + for (std::size_t i = kStackStart; i < size; ++i) { + std::string mangName; + std::string location; + std::string hex; + std::string addr; + + // entry: 2 crash.cpp.bin 0x0000000101552be5 _ZN2el4base5debug10StackTraceC1Ev + 21 + const std::string line(strings[i]); + auto p = line.find("_"); + if (p != std::string::npos) { + mangName = line.substr(p); + mangName = mangName.substr(0, mangName.find(" +")); + } + p = line.find("0x"); + if (p != std::string::npos) { + addr = line.substr(p); + addr = addr.substr(0, addr.find("_")); + } + // Perform demangling if parsed properly + if (!mangName.empty()) { + int status = 0; + char* demangName = abi::__cxa_demangle(mangName.data(), 0, 0, &status); + // if demangling is successful, output the demangled function name + if (status == 0) { + // Success (see http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html) + StackTraceEntry entry(i - 1, location, demangName, hex, addr); + m_stack.push_back(entry); + } else { + // Not successful - we will use mangled name + StackTraceEntry entry(i - 1, location, mangName, hex, addr); + m_stack.push_back(entry); + } + free(demangName); + } else { + StackTraceEntry entry(i - 1, line); + m_stack.push_back(entry); + } + } + } + free(strings); +#else + ELPP_INTERNAL_INFO(1, "Stacktrace generation not supported for selected compiler"); +#endif // ELPP_STACKTRACE +} + +// Static helper functions + +static std::string crashReason(int sig) { + std::stringstream ss; + bool foundReason = false; + for (int i = 0; i < base::consts::kCrashSignalsCount; ++i) { + if (base::consts::kCrashSignals[i].numb == sig) { + ss << "Application has crashed due to [" << base::consts::kCrashSignals[i].name << "] signal"; + if (ELPP->hasFlag(el::LoggingFlag::LogDetailedCrashReason)) { + ss << std::endl << + " " << base::consts::kCrashSignals[i].brief << std::endl << + " " << base::consts::kCrashSignals[i].detail; + } + foundReason = true; + } + } + if (!foundReason) { + ss << "Application has crashed due to unknown signal [" << sig << "]"; + } + return ss.str(); +} +/// @brief Logs reason of crash from sig +static void logCrashReason(int sig, bool stackTraceIfAvailable, Level level, const char* logger) { + if (sig == SIGINT && ELPP->hasFlag(el::LoggingFlag::IgnoreSigInt)) { + return; + } + std::stringstream ss; + ss << "CRASH HANDLED; "; + ss << crashReason(sig); +#if ELPP_STACKTRACE + if (stackTraceIfAvailable) { + ss << std::endl << " ======= Backtrace: =========" << std::endl << base::debug::StackTrace(); + } +#else + ELPP_UNUSED(stackTraceIfAvailable); +#endif // ELPP_STACKTRACE + ELPP_WRITE_LOG(el::base::Writer, level, base::DispatchAction::NormalLog, logger) << ss.str(); +} + +static inline void crashAbort(int sig) { + base::utils::abort(sig, std::string()); +} + +/// @brief Default application crash handler +/// +/// @detail This function writes log using 'default' logger, prints stack trace for GCC based compilers and aborts program. +static inline void defaultCrashHandler(int sig) { + base::debug::logCrashReason(sig, true, Level::Fatal, base::consts::kDefaultLoggerId); + base::debug::crashAbort(sig); +} + +// CrashHandler + +CrashHandler::CrashHandler(bool useDefault) { + if (useDefault) { + setHandler(defaultCrashHandler); + } +} + +void CrashHandler::setHandler(const Handler& cHandler) { + m_handler = cHandler; +#if defined(ELPP_HANDLE_SIGABRT) + int i = 0; // SIGABRT is at base::consts::kCrashSignals[0] +#else + int i = 1; +#endif // defined(ELPP_HANDLE_SIGABRT) + for (; i < base::consts::kCrashSignalsCount; ++i) { + m_handler = signal(base::consts::kCrashSignals[i].numb, cHandler); + } +} + +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) +} // namespace debug +} // namespace base + +// el + +// Helpers + +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) + +void Helpers::crashAbort(int sig, const char* sourceFile, unsigned int long line) { + std::stringstream ss; + ss << base::debug::crashReason(sig).c_str(); + ss << " - [Called el::Helpers::crashAbort(" << sig << ")]"; + if (sourceFile != nullptr && strlen(sourceFile) > 0) { + ss << " - Source: " << sourceFile; + if (line > 0) + ss << ":" << line; + else + ss << " (line number not specified)"; + } + base::utils::abort(sig, ss.str()); +} + +void Helpers::logCrashReason(int sig, bool stackTraceIfAvailable, Level level, const char* logger) { + el::base::debug::logCrashReason(sig, stackTraceIfAvailable, level, logger); +} + +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) + +// Loggers + +Logger* Loggers::getLogger(const std::string& identity, bool registerIfNotAvailable) { + return ELPP->registeredLoggers()->get(identity, registerIfNotAvailable); +} + +void Loggers::setDefaultLogBuilder(el::LogBuilderPtr& logBuilderPtr) { + ELPP->registeredLoggers()->setDefaultLogBuilder(logBuilderPtr); +} + +bool Loggers::unregisterLogger(const std::string& identity) { + return ELPP->registeredLoggers()->remove(identity); +} + +bool Loggers::hasLogger(const std::string& identity) { + return ELPP->registeredLoggers()->has(identity); +} + +Logger* Loggers::reconfigureLogger(Logger* logger, const Configurations& configurations) { + if (!logger) return nullptr; + logger->configure(configurations); + return logger; +} + +Logger* Loggers::reconfigureLogger(const std::string& identity, const Configurations& configurations) { + return Loggers::reconfigureLogger(Loggers::getLogger(identity), configurations); +} + +Logger* Loggers::reconfigureLogger(const std::string& identity, ConfigurationType configurationType, + const std::string& value) { + Logger* logger = Loggers::getLogger(identity); + if (logger == nullptr) { + return nullptr; + } + logger->configurations()->set(Level::Global, configurationType, value); + logger->reconfigure(); + return logger; +} + +void Loggers::reconfigureAllLoggers(const Configurations& configurations) { + for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->begin(); + it != ELPP->registeredLoggers()->end(); ++it) { + Loggers::reconfigureLogger(it->second, configurations); + } +} + +void Loggers::reconfigureAllLoggers(Level level, ConfigurationType configurationType, + const std::string& value) { + for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->begin(); + it != ELPP->registeredLoggers()->end(); ++it) { + Logger* logger = it->second; + logger->configurations()->set(level, configurationType, value); + logger->reconfigure(); + } +} + +void Loggers::setDefaultConfigurations(const Configurations& configurations, bool reconfigureExistingLoggers) { + ELPP->registeredLoggers()->setDefaultConfigurations(configurations); + if (reconfigureExistingLoggers) { + Loggers::reconfigureAllLoggers(configurations); + } +} + +const Configurations* Loggers::defaultConfigurations(void) { + return ELPP->registeredLoggers()->defaultConfigurations(); +} + +const base::LogStreamsReferenceMapPtr Loggers::logStreamsReference(void) { + return ELPP->registeredLoggers()->logStreamsReference(); +} + +base::TypedConfigurations Loggers::defaultTypedConfigurations(void) { + return base::TypedConfigurations( + ELPP->registeredLoggers()->defaultConfigurations(), + ELPP->registeredLoggers()->logStreamsReference()); +} + +std::vector* Loggers::populateAllLoggerIds(std::vector* targetList) { + targetList->clear(); + for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->list().begin(); + it != ELPP->registeredLoggers()->list().end(); ++it) { + targetList->push_back(it->first); + } + return targetList; +} + +void Loggers::configureFromGlobal(const char* globalConfigurationFilePath) { + std::ifstream gcfStream(globalConfigurationFilePath, std::ifstream::in); + ELPP_ASSERT(gcfStream.is_open(), "Unable to open global configuration file [" << globalConfigurationFilePath + << "] for parsing."); + std::string line = std::string(); + std::stringstream ss; + Logger* logger = nullptr; + auto configure = [&](void) { + ELPP_INTERNAL_INFO(8, "Configuring logger: '" << logger->id() << "' with configurations \n" << ss.str() + << "\n--------------"); + Configurations c; + c.parseFromText(ss.str()); + logger->configure(c); + }; + while (gcfStream.good()) { + std::getline(gcfStream, line); + ELPP_INTERNAL_INFO(1, "Parsing line: " << line); + base::utils::Str::trim(line); + if (Configurations::Parser::isComment(line)) continue; + Configurations::Parser::ignoreComments(&line); + base::utils::Str::trim(line); + if (line.size() > 2 && base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationLoggerId))) { + if (!ss.str().empty() && logger != nullptr) { + configure(); + } + ss.str(std::string("")); + line = line.substr(2); + base::utils::Str::trim(line); + if (line.size() > 1) { + ELPP_INTERNAL_INFO(1, "Getting logger: '" << line << "'"); + logger = getLogger(line); + } + } else { + ss << line << "\n"; + } + } + if (!ss.str().empty() && logger != nullptr) { + configure(); + } +} + +bool Loggers::configureFromArg(const char* argKey) { +#if defined(ELPP_DISABLE_CONFIGURATION_FROM_PROGRAM_ARGS) + ELPP_UNUSED(argKey); +#else + if (!Helpers::commandLineArgs()->hasParamWithValue(argKey)) { + return false; + } + configureFromGlobal(Helpers::commandLineArgs()->getParamValue(argKey)); +#endif // defined(ELPP_DISABLE_CONFIGURATION_FROM_PROGRAM_ARGS) + return true; +} + +void Loggers::flushAll(void) { + ELPP->registeredLoggers()->flushAll(); +} + +void Loggers::setVerboseLevel(base::type::VerboseLevel level) { + ELPP->vRegistry()->setLevel(level); +} + +base::type::VerboseLevel Loggers::verboseLevel(void) { + return ELPP->vRegistry()->level(); +} + +void Loggers::setVModules(const char* modules) { + if (ELPP->vRegistry()->vModulesEnabled()) { + ELPP->vRegistry()->setModules(modules); + } +} + +void Loggers::clearVModules(void) { + ELPP->vRegistry()->clearModules(); +} + +// VersionInfo + +const std::string VersionInfo::version(void) { + return std::string("9.96.7"); +} +/// @brief Release date of current version +const std::string VersionInfo::releaseDate(void) { + return std::string("24-11-2018 0728hrs"); +} + +} // namespace el diff --git a/src/easylogging++.h b/src/easylogging++.h new file mode 100644 index 0000000..6e81edf --- /dev/null +++ b/src/easylogging++.h @@ -0,0 +1,4576 @@ +// +// Bismillah ar-Rahmaan ar-Raheem +// +// Easylogging++ v9.96.7 +// Single-header only, cross-platform logging library for C++ applications +// +// Copyright (c) 2012-2018 Amrayn Web Services +// Copyright (c) 2012-2018 @abumusamq +// +// This library is released under the MIT Licence. +// https://github.com/amrayn/easyloggingpp/blob/master/LICENSE +// +// https://amrayn.com +// http://muflihun.com +// + +#ifndef EASYLOGGINGPP_H +#define EASYLOGGINGPP_H +// Compilers and C++0x/C++11 Evaluation +#if __cplusplus >= 201103L +# define ELPP_CXX11 1 +#endif // __cplusplus >= 201103L +#if (defined(__GNUC__)) +# define ELPP_COMPILER_GCC 1 +#else +# define ELPP_COMPILER_GCC 0 +#endif +#if ELPP_COMPILER_GCC +# define ELPP_GCC_VERSION (__GNUC__ * 10000 \ ++ __GNUC_MINOR__ * 100 \ ++ __GNUC_PATCHLEVEL__) +# if defined(__GXX_EXPERIMENTAL_CXX0X__) +# define ELPP_CXX0X 1 +# endif +#endif +// Visual C++ +#if defined(_MSC_VER) +# define ELPP_COMPILER_MSVC 1 +#else +# define ELPP_COMPILER_MSVC 0 +#endif +#define ELPP_CRT_DBG_WARNINGS ELPP_COMPILER_MSVC +#if ELPP_COMPILER_MSVC +# if (_MSC_VER == 1600) +# define ELPP_CXX0X 1 +# elif(_MSC_VER >= 1700) +# define ELPP_CXX11 1 +# endif +#endif +// Clang++ +#if (defined(__clang__) && (__clang__ == 1)) +# define ELPP_COMPILER_CLANG 1 +#else +# define ELPP_COMPILER_CLANG 0 +#endif +#if ELPP_COMPILER_CLANG +# if __has_include() +# include // Make __GLIBCXX__ defined when using libstdc++ +# if !defined(__GLIBCXX__) || __GLIBCXX__ >= 20150426 +# define ELPP_CLANG_SUPPORTS_THREAD +# endif // !defined(__GLIBCXX__) || __GLIBCXX__ >= 20150426 +# endif // __has_include() +#endif +#if (defined(__MINGW32__) || defined(__MINGW64__)) +# define ELPP_MINGW 1 +#else +# define ELPP_MINGW 0 +#endif +#if (defined(__CYGWIN__) && (__CYGWIN__ == 1)) +# define ELPP_CYGWIN 1 +#else +# define ELPP_CYGWIN 0 +#endif +#if (defined(__INTEL_COMPILER)) +# define ELPP_COMPILER_INTEL 1 +#else +# define ELPP_COMPILER_INTEL 0 +#endif +// Operating System Evaluation +// Windows +#if (defined(_WIN32) || defined(_WIN64)) +# define ELPP_OS_WINDOWS 1 +#else +# define ELPP_OS_WINDOWS 0 +#endif +// Linux +#if (defined(__linux) || defined(__linux__)) +# define ELPP_OS_LINUX 1 +#else +# define ELPP_OS_LINUX 0 +#endif +#if (defined(__APPLE__)) +# define ELPP_OS_MAC 1 +#else +# define ELPP_OS_MAC 0 +#endif +#if (defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) +# define ELPP_OS_FREEBSD 1 +#else +# define ELPP_OS_FREEBSD 0 +#endif +#if (defined(__sun)) +# define ELPP_OS_SOLARIS 1 +#else +# define ELPP_OS_SOLARIS 0 +#endif +#if (defined(_AIX)) +# define ELPP_OS_AIX 1 +#else +# define ELPP_OS_AIX 0 +#endif +#if (defined(__NetBSD__)) +# define ELPP_OS_NETBSD 1 +#else +# define ELPP_OS_NETBSD 0 +#endif +#if defined(__EMSCRIPTEN__) +# define ELPP_OS_EMSCRIPTEN 1 +#else +# define ELPP_OS_EMSCRIPTEN 0 +#endif +#if (defined(__QNX__) || defined(__QNXNTO__)) +# define ELPP_OS_QNX 1 +#else +# define ELPP_OS_QNX 0 +#endif +// Unix +#if ((ELPP_OS_LINUX || ELPP_OS_MAC || ELPP_OS_FREEBSD || ELPP_OS_NETBSD || ELPP_OS_SOLARIS || ELPP_OS_AIX || ELPP_OS_EMSCRIPTEN || ELPP_OS_QNX) && (!ELPP_OS_WINDOWS)) +# define ELPP_OS_UNIX 1 +#else +# define ELPP_OS_UNIX 0 +#endif +#if (defined(__ANDROID__)) +# define ELPP_OS_ANDROID 1 +#else +# define ELPP_OS_ANDROID 0 +#endif +// Evaluating Cygwin as *nix OS +#if !ELPP_OS_UNIX && !ELPP_OS_WINDOWS && ELPP_CYGWIN +# undef ELPP_OS_UNIX +# undef ELPP_OS_LINUX +# define ELPP_OS_UNIX 1 +# define ELPP_OS_LINUX 1 +#endif // !ELPP_OS_UNIX && !ELPP_OS_WINDOWS && ELPP_CYGWIN +#if !defined(ELPP_INTERNAL_DEBUGGING_OUT_INFO) +# define ELPP_INTERNAL_DEBUGGING_OUT_INFO std::cout +#endif // !defined(ELPP_INTERNAL_DEBUGGING_OUT) +#if !defined(ELPP_INTERNAL_DEBUGGING_OUT_ERROR) +# define ELPP_INTERNAL_DEBUGGING_OUT_ERROR std::cerr +#endif // !defined(ELPP_INTERNAL_DEBUGGING_OUT) +#if !defined(ELPP_INTERNAL_DEBUGGING_ENDL) +# define ELPP_INTERNAL_DEBUGGING_ENDL std::endl +#endif // !defined(ELPP_INTERNAL_DEBUGGING_OUT) +#if !defined(ELPP_INTERNAL_DEBUGGING_MSG) +# define ELPP_INTERNAL_DEBUGGING_MSG(msg) msg +#endif // !defined(ELPP_INTERNAL_DEBUGGING_OUT) +// Internal Assertions and errors +#if !defined(ELPP_DISABLE_ASSERT) +# if (defined(ELPP_DEBUG_ASSERT_FAILURE)) +# define ELPP_ASSERT(expr, msg) if (!(expr)) { \ +std::stringstream internalInfoStream; internalInfoStream << msg; \ +ELPP_INTERNAL_DEBUGGING_OUT_ERROR \ +<< "EASYLOGGING++ ASSERTION FAILED (LINE: " << __LINE__ << ") [" #expr << "] WITH MESSAGE \"" \ +<< ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStream.str()) << "\"" << ELPP_INTERNAL_DEBUGGING_ENDL; base::utils::abort(1, \ +"ELPP Assertion failure, please define ELPP_DEBUG_ASSERT_FAILURE"); } +# else +# define ELPP_ASSERT(expr, msg) if (!(expr)) { \ +std::stringstream internalInfoStream; internalInfoStream << msg; \ +ELPP_INTERNAL_DEBUGGING_OUT_ERROR\ +<< "ASSERTION FAILURE FROM EASYLOGGING++ (LINE: " \ +<< __LINE__ << ") [" #expr << "] WITH MESSAGE \"" << ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStream.str()) << "\"" \ +<< ELPP_INTERNAL_DEBUGGING_ENDL; } +# endif // (defined(ELPP_DEBUG_ASSERT_FAILURE)) +#else +# define ELPP_ASSERT(x, y) +#endif //(!defined(ELPP_DISABLE_ASSERT) +#if ELPP_COMPILER_MSVC +# define ELPP_INTERNAL_DEBUGGING_WRITE_PERROR \ +{ char buff[256]; strerror_s(buff, 256, errno); \ +ELPP_INTERNAL_DEBUGGING_OUT_ERROR << ": " << buff << " [" << errno << "]";} (void)0 +#else +# define ELPP_INTERNAL_DEBUGGING_WRITE_PERROR \ +ELPP_INTERNAL_DEBUGGING_OUT_ERROR << ": " << strerror(errno) << " [" << errno << "]"; (void)0 +#endif // ELPP_COMPILER_MSVC +#if defined(ELPP_DEBUG_ERRORS) +# if !defined(ELPP_INTERNAL_ERROR) +# define ELPP_INTERNAL_ERROR(msg, pe) { \ +std::stringstream internalInfoStream; internalInfoStream << " " << msg; \ +ELPP_INTERNAL_DEBUGGING_OUT_ERROR \ +<< "ERROR FROM EASYLOGGING++ (LINE: " << __LINE__ << ") " \ +<< ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStream.str()) << ELPP_INTERNAL_DEBUGGING_ENDL; \ +if (pe) { ELPP_INTERNAL_DEBUGGING_OUT_ERROR << " "; ELPP_INTERNAL_DEBUGGING_WRITE_PERROR; }} (void)0 +# endif +#else +# undef ELPP_INTERNAL_INFO +# define ELPP_INTERNAL_ERROR(msg, pe) +#endif // defined(ELPP_DEBUG_ERRORS) +#if (defined(ELPP_DEBUG_INFO)) +# if !(defined(ELPP_INTERNAL_INFO_LEVEL)) +# define ELPP_INTERNAL_INFO_LEVEL 9 +# endif // !(defined(ELPP_INTERNAL_INFO_LEVEL)) +# if !defined(ELPP_INTERNAL_INFO) +# define ELPP_INTERNAL_INFO(lvl, msg) { if (lvl <= ELPP_INTERNAL_INFO_LEVEL) { \ +std::stringstream internalInfoStream; internalInfoStream << " " << msg; \ +ELPP_INTERNAL_DEBUGGING_OUT_INFO << ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStream.str()) \ +<< ELPP_INTERNAL_DEBUGGING_ENDL; }} +# endif +#else +# undef ELPP_INTERNAL_INFO +# define ELPP_INTERNAL_INFO(lvl, msg) +#endif // (defined(ELPP_DEBUG_INFO)) +#if (defined(ELPP_FEATURE_ALL)) || (defined(ELPP_FEATURE_CRASH_LOG)) +# if (ELPP_COMPILER_GCC && !ELPP_MINGW && !ELPP_CYGWIN && !ELPP_OS_ANDROID && !ELPP_OS_EMSCRIPTEN && !ELPP_OS_QNX) +# define ELPP_STACKTRACE 1 +# else +# if ELPP_COMPILER_MSVC +# pragma message("Stack trace not available for this compiler") +# else +# warning "Stack trace not available for this compiler"; +# endif // ELPP_COMPILER_MSVC +# define ELPP_STACKTRACE 0 +# endif // ELPP_COMPILER_GCC +#else +# define ELPP_STACKTRACE 0 +#endif // (defined(ELPP_FEATURE_ALL)) || (defined(ELPP_FEATURE_CRASH_LOG)) +// Miscellaneous macros +#define ELPP_UNUSED(x) (void)x +#if ELPP_OS_UNIX +// Log file permissions for unix-based systems +# define ELPP_LOG_PERMS S_IRUSR | S_IWUSR | S_IXUSR | S_IWGRP | S_IRGRP | S_IXGRP | S_IWOTH | S_IXOTH +#endif // ELPP_OS_UNIX +#if defined(ELPP_AS_DLL) && ELPP_COMPILER_MSVC +# if defined(ELPP_EXPORT_SYMBOLS) +# define ELPP_EXPORT __declspec(dllexport) +# else +# define ELPP_EXPORT __declspec(dllimport) +# endif // defined(ELPP_EXPORT_SYMBOLS) +#else +# define ELPP_EXPORT +#endif // defined(ELPP_AS_DLL) && ELPP_COMPILER_MSVC +// Some special functions that are VC++ specific +#undef STRTOK +#undef STRERROR +#undef STRCAT +#undef STRCPY +#if ELPP_CRT_DBG_WARNINGS +# define STRTOK(a, b, c) strtok_s(a, b, c) +# define STRERROR(a, b, c) strerror_s(a, b, c) +# define STRCAT(a, b, len) strcat_s(a, len, b) +# define STRCPY(a, b, len) strcpy_s(a, len, b) +#else +# define STRTOK(a, b, c) strtok(a, b) +# define STRERROR(a, b, c) strerror(c) +# define STRCAT(a, b, len) strcat(a, b) +# define STRCPY(a, b, len) strcpy(a, b) +#endif +// Compiler specific support evaluations +#if (ELPP_MINGW && !defined(ELPP_FORCE_USE_STD_THREAD)) +# define ELPP_USE_STD_THREADING 0 +#else +# if ((ELPP_COMPILER_CLANG && defined(ELPP_CLANG_SUPPORTS_THREAD)) || \ + (!ELPP_COMPILER_CLANG && defined(ELPP_CXX11)) || \ + defined(ELPP_FORCE_USE_STD_THREAD)) +# define ELPP_USE_STD_THREADING 1 +# else +# define ELPP_USE_STD_THREADING 0 +# endif +#endif +#undef ELPP_FINAL +#if ELPP_COMPILER_INTEL || (ELPP_GCC_VERSION < 40702) +# define ELPP_FINAL +#else +# define ELPP_FINAL final +#endif // ELPP_COMPILER_INTEL || (ELPP_GCC_VERSION < 40702) +#if defined(ELPP_EXPERIMENTAL_ASYNC) +# define ELPP_ASYNC_LOGGING 1 +#else +# define ELPP_ASYNC_LOGGING 0 +#endif // defined(ELPP_EXPERIMENTAL_ASYNC) +#if defined(ELPP_THREAD_SAFE) || ELPP_ASYNC_LOGGING +# define ELPP_THREADING_ENABLED 1 +#else +# define ELPP_THREADING_ENABLED 0 +#endif // defined(ELPP_THREAD_SAFE) || ELPP_ASYNC_LOGGING +// Function macro ELPP_FUNC +#undef ELPP_FUNC +#if ELPP_COMPILER_MSVC // Visual C++ +# define ELPP_FUNC __FUNCSIG__ +#elif ELPP_COMPILER_GCC // GCC +# define ELPP_FUNC __PRETTY_FUNCTION__ +#elif ELPP_COMPILER_INTEL // Intel C++ +# define ELPP_FUNC __PRETTY_FUNCTION__ +#elif ELPP_COMPILER_CLANG // Clang++ +# define ELPP_FUNC __PRETTY_FUNCTION__ +#else +# if defined(__func__) +# define ELPP_FUNC __func__ +# else +# define ELPP_FUNC "" +# endif // defined(__func__) +#endif // defined(_MSC_VER) +#undef ELPP_VARIADIC_TEMPLATES_SUPPORTED +// Keep following line commented until features are fixed +#define ELPP_VARIADIC_TEMPLATES_SUPPORTED \ +(ELPP_COMPILER_GCC || ELPP_COMPILER_CLANG || ELPP_COMPILER_INTEL || (ELPP_COMPILER_MSVC && _MSC_VER >= 1800)) +// Logging Enable/Disable macros +#if defined(ELPP_DISABLE_LOGS) +#define ELPP_LOGGING_ENABLED 0 +#else +#define ELPP_LOGGING_ENABLED 1 +#endif +#if (!defined(ELPP_DISABLE_DEBUG_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_DEBUG_LOG 1 +#else +# define ELPP_DEBUG_LOG 0 +#endif // (!defined(ELPP_DISABLE_DEBUG_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!defined(ELPP_DISABLE_INFO_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_INFO_LOG 1 +#else +# define ELPP_INFO_LOG 0 +#endif // (!defined(ELPP_DISABLE_INFO_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!defined(ELPP_DISABLE_WARNING_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_WARNING_LOG 1 +#else +# define ELPP_WARNING_LOG 0 +#endif // (!defined(ELPP_DISABLE_WARNING_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!defined(ELPP_DISABLE_ERROR_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_ERROR_LOG 1 +#else +# define ELPP_ERROR_LOG 0 +#endif // (!defined(ELPP_DISABLE_ERROR_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!defined(ELPP_DISABLE_FATAL_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_FATAL_LOG 1 +#else +# define ELPP_FATAL_LOG 0 +#endif // (!defined(ELPP_DISABLE_FATAL_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!defined(ELPP_DISABLE_TRACE_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_TRACE_LOG 1 +#else +# define ELPP_TRACE_LOG 0 +#endif // (!defined(ELPP_DISABLE_TRACE_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!defined(ELPP_DISABLE_VERBOSE_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_VERBOSE_LOG 1 +#else +# define ELPP_VERBOSE_LOG 0 +#endif // (!defined(ELPP_DISABLE_VERBOSE_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!(ELPP_CXX0X || ELPP_CXX11)) +# error "C++0x (or higher) support not detected! (Is `-std=c++11' missing?)" +#endif // (!(ELPP_CXX0X || ELPP_CXX11)) +// Headers +#if defined(ELPP_SYSLOG) +# include +#endif // defined(ELPP_SYSLOG) +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(ELPP_UNICODE) +# include +# if ELPP_OS_WINDOWS +# include +# endif // ELPP_OS_WINDOWS +#endif // defined(ELPP_UNICODE) +#ifdef HAVE_EXECINFO +# include +# include +#endif // ENABLE_EXECINFO +#if ELPP_OS_ANDROID +# include +#endif // ELPP_OS_ANDROID +#if ELPP_OS_UNIX +# include +# include +#elif ELPP_OS_WINDOWS +# include +# include +# if defined(WIN32_LEAN_AND_MEAN) +# if defined(ELPP_WINSOCK2) +# include +# else +# include +# endif // defined(ELPP_WINSOCK2) +# endif // defined(WIN32_LEAN_AND_MEAN) +#endif // ELPP_OS_UNIX +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if ELPP_THREADING_ENABLED +# if ELPP_USE_STD_THREADING +# include +# include +# else +# if ELPP_OS_UNIX +# include +# endif // ELPP_OS_UNIX +# endif // ELPP_USE_STD_THREADING +#endif // ELPP_THREADING_ENABLED +#if ELPP_ASYNC_LOGGING +# if defined(ELPP_NO_SLEEP_FOR) +# include +# endif // defined(ELPP_NO_SLEEP_FOR) +# include +# include +# include +#endif // ELPP_ASYNC_LOGGING +#if defined(ELPP_STL_LOGGING) +// For logging STL based templates +# include +# include +# include +# include +# include +# include +# if defined(ELPP_LOG_STD_ARRAY) +# include +# endif // defined(ELPP_LOG_STD_ARRAY) +# if defined(ELPP_LOG_UNORDERED_SET) +# include +# endif // defined(ELPP_UNORDERED_SET) +#endif // defined(ELPP_STL_LOGGING) +#if defined(ELPP_QT_LOGGING) +// For logging Qt based classes & templates +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif // defined(ELPP_QT_LOGGING) +#if defined(ELPP_BOOST_LOGGING) +// For logging boost based classes & templates +# include +# include +# include +# include +# include +# include +# include +# include +#endif // defined(ELPP_BOOST_LOGGING) +#if defined(ELPP_WXWIDGETS_LOGGING) +// For logging wxWidgets based classes & templates +# include +#endif // defined(ELPP_WXWIDGETS_LOGGING) +#if defined(ELPP_UTC_DATETIME) +# define elpptime_r gmtime_r +# define elpptime_s gmtime_s +# define elpptime gmtime +#else +# define elpptime_r localtime_r +# define elpptime_s localtime_s +# define elpptime localtime +#endif // defined(ELPP_UTC_DATETIME) +// Forward declarations +namespace el { +class Logger; +class LogMessage; +class PerformanceTrackingData; +class Loggers; +class Helpers; +template class Callback; +class LogDispatchCallback; +class PerformanceTrackingCallback; +class LoggerRegistrationCallback; +class LogDispatchData; +namespace base { +class Storage; +class RegisteredLoggers; +class PerformanceTracker; +class MessageBuilder; +class Writer; +class PErrorWriter; +class LogDispatcher; +class DefaultLogBuilder; +class DefaultLogDispatchCallback; +#if ELPP_ASYNC_LOGGING +class AsyncLogDispatchCallback; +class AsyncDispatchWorker; +#endif // ELPP_ASYNC_LOGGING +class DefaultPerformanceTrackingCallback; +} // namespace base +} // namespace el +/// @brief Easylogging++ entry namespace +namespace el { +/// @brief Namespace containing base/internal functionality used by Easylogging++ +namespace base { +/// @brief Data types used by Easylogging++ +namespace type { +#undef ELPP_LITERAL +#undef ELPP_STRLEN +#undef ELPP_COUT +#if defined(ELPP_UNICODE) +# define ELPP_LITERAL(txt) L##txt +# define ELPP_STRLEN wcslen +# if defined ELPP_CUSTOM_COUT +# define ELPP_COUT ELPP_CUSTOM_COUT +# else +# define ELPP_COUT std::wcout +# endif // defined ELPP_CUSTOM_COUT +typedef wchar_t char_t; +typedef std::wstring string_t; +typedef std::wstringstream stringstream_t; +typedef std::wfstream fstream_t; +typedef std::wostream ostream_t; +#else +# define ELPP_LITERAL(txt) txt +# define ELPP_STRLEN strlen +# if defined ELPP_CUSTOM_COUT +# define ELPP_COUT ELPP_CUSTOM_COUT +# else +# define ELPP_COUT std::cout +# endif // defined ELPP_CUSTOM_COUT +typedef char char_t; +typedef std::string string_t; +typedef std::stringstream stringstream_t; +typedef std::fstream fstream_t; +typedef std::ostream ostream_t; +#endif // defined(ELPP_UNICODE) +#if defined(ELPP_CUSTOM_COUT_LINE) +# define ELPP_COUT_LINE(logLine) ELPP_CUSTOM_COUT_LINE(logLine) +#else +# define ELPP_COUT_LINE(logLine) logLine << std::flush +#endif // defined(ELPP_CUSTOM_COUT_LINE) +typedef unsigned int EnumType; +typedef unsigned short VerboseLevel; +typedef unsigned long int LineNumber; +typedef std::shared_ptr StoragePointer; +typedef std::shared_ptr LogDispatchCallbackPtr; +typedef std::shared_ptr PerformanceTrackingCallbackPtr; +typedef std::shared_ptr LoggerRegistrationCallbackPtr; +typedef std::unique_ptr PerformanceTrackerPtr; +} // namespace type +/// @brief Internal helper class that prevent copy constructor for class +/// +/// @detail When using this class simply inherit it privately +class NoCopy { + protected: + NoCopy(void) {} + private: + NoCopy(const NoCopy&); + NoCopy& operator=(const NoCopy&); +}; +/// @brief Internal helper class that makes all default constructors private. +/// +/// @detail This prevents initializing class making it static unless an explicit constructor is declared. +/// When using this class simply inherit it privately +class StaticClass { + private: + StaticClass(void); + StaticClass(const StaticClass&); + StaticClass& operator=(const StaticClass&); +}; +} // namespace base +/// @brief Represents enumeration for severity level used to determine level of logging +/// +/// @detail With Easylogging++, developers may disable or enable any level regardless of +/// what the severity is. Or they can choose to log using hierarchical logging flag +enum class Level : base::type::EnumType { + /// @brief Generic level that represents all the levels. Useful when setting global configuration for all levels + Global = 1, + /// @brief Information that can be useful to back-trace certain events - mostly useful than debug logs. + Trace = 2, + /// @brief Informational events most useful for developers to debug application + Debug = 4, + /// @brief Severe error information that will presumably abort application + Fatal = 8, + /// @brief Information representing errors in application but application will keep running + Error = 16, + /// @brief Useful when application has potentially harmful situations + Warning = 32, + /// @brief Information that can be highly useful and vary with verbose logging level. + Verbose = 64, + /// @brief Mainly useful to represent current progress of application + Info = 128, + /// @brief Represents unknown level + Unknown = 1010 +}; +} // namespace el +namespace std { +template<> struct hash { + public: + std::size_t operator()(const el::Level& l) const { + return hash {}(static_cast(l)); + } +}; +} +namespace el { +/// @brief Static class that contains helper functions for el::Level +class LevelHelper : base::StaticClass { + public: + /// @brief Represents minimum valid level. Useful when iterating through enum. + static const base::type::EnumType kMinValid = static_cast(Level::Trace); + /// @brief Represents maximum valid level. This is used internally and you should not need it. + static const base::type::EnumType kMaxValid = static_cast(Level::Info); + /// @brief Casts level to int, useful for iterating through enum. + static base::type::EnumType castToInt(Level level) { + return static_cast(level); + } + /// @brief Casts int(ushort) to level, useful for iterating through enum. + static Level castFromInt(base::type::EnumType l) { + return static_cast(l); + } + /// @brief Converts level to associated const char* + /// @return Upper case string based level. + static const char* convertToString(Level level); + /// @brief Converts from levelStr to Level + /// @param levelStr Upper case string based level. + /// Lower case is also valid but providing upper case is recommended. + static Level convertFromString(const char* levelStr); + /// @brief Applies specified function to each level starting from startIndex + /// @param startIndex initial value to start the iteration from. This is passed as pointer and + /// is left-shifted so this can be used inside function (fn) to represent current level. + /// @param fn function to apply with each level. This bool represent whether or not to stop iterating through levels. + static void forEachLevel(base::type::EnumType* startIndex, const std::function& fn); +}; +/// @brief Represents enumeration of ConfigurationType used to configure or access certain aspect +/// of logging +enum class ConfigurationType : base::type::EnumType { + /// @brief Determines whether or not corresponding level and logger of logging is enabled + /// You may disable all logs by using el::Level::Global + Enabled = 1, + /// @brief Whether or not to write corresponding log to log file + ToFile = 2, + /// @brief Whether or not to write corresponding level and logger log to standard output. + /// By standard output meaning termnal, command prompt etc + ToStandardOutput = 4, + /// @brief Determines format of logging corresponding level and logger. + Format = 8, + /// @brief Determines log file (full path) to write logs to for corresponding level and logger + Filename = 16, + /// @brief Specifies precision of the subsecond part. It should be within range (1-6). + SubsecondPrecision = 32, + /// @brief Alias of SubsecondPrecision (for backward compatibility) + MillisecondsWidth = SubsecondPrecision, + /// @brief Determines whether or not performance tracking is enabled. + /// + /// @detail This does not depend on logger or level. Performance tracking always uses 'performance' logger + PerformanceTracking = 64, + /// @brief Specifies log file max size. + /// + /// @detail If file size of corresponding log file (for corresponding level) is >= specified size, log file will + /// be truncated and re-initiated. + MaxLogFileSize = 128, + /// @brief Specifies number of log entries to hold until we flush pending log data + LogFlushThreshold = 256, + /// @brief Represents unknown configuration + Unknown = 1010 +}; +/// @brief Static class that contains helper functions for el::ConfigurationType +class ConfigurationTypeHelper : base::StaticClass { + public: + /// @brief Represents minimum valid configuration type. Useful when iterating through enum. + static const base::type::EnumType kMinValid = static_cast(ConfigurationType::Enabled); + /// @brief Represents maximum valid configuration type. This is used internally and you should not need it. + static const base::type::EnumType kMaxValid = static_cast(ConfigurationType::MaxLogFileSize); + /// @brief Casts configuration type to int, useful for iterating through enum. + static base::type::EnumType castToInt(ConfigurationType configurationType) { + return static_cast(configurationType); + } + /// @brief Casts int(ushort) to configuration type, useful for iterating through enum. + static ConfigurationType castFromInt(base::type::EnumType c) { + return static_cast(c); + } + /// @brief Converts configuration type to associated const char* + /// @returns Upper case string based configuration type. + static const char* convertToString(ConfigurationType configurationType); + /// @brief Converts from configStr to ConfigurationType + /// @param configStr Upper case string based configuration type. + /// Lower case is also valid but providing upper case is recommended. + static ConfigurationType convertFromString(const char* configStr); + /// @brief Applies specified function to each configuration type starting from startIndex + /// @param startIndex initial value to start the iteration from. This is passed by pointer and is left-shifted + /// so this can be used inside function (fn) to represent current configuration type. + /// @param fn function to apply with each configuration type. + /// This bool represent whether or not to stop iterating through configurations. + static inline void forEachConfigType(base::type::EnumType* startIndex, const std::function& fn); +}; +/// @brief Flags used while writing logs. This flags are set by user +enum class LoggingFlag : base::type::EnumType { + /// @brief Makes sure we have new line for each container log entry + NewLineForContainer = 1, + /// @brief Makes sure if -vmodule is used and does not specifies a module, then verbose + /// logging is allowed via that module. + AllowVerboseIfModuleNotSpecified = 2, + /// @brief When handling crashes by default, detailed crash reason will be logged as well + LogDetailedCrashReason = 4, + /// @brief Allows to disable application abortion when logged using FATAL level + DisableApplicationAbortOnFatalLog = 8, + /// @brief Flushes log with every log-entry (performance sensitive) - Disabled by default + ImmediateFlush = 16, + /// @brief Enables strict file rolling + StrictLogFileSizeCheck = 32, + /// @brief Make terminal output colorful for supported terminals + ColoredTerminalOutput = 64, + /// @brief Supports use of multiple logging in same macro, e.g, CLOG(INFO, "default", "network") + MultiLoggerSupport = 128, + /// @brief Disables comparing performance tracker's checkpoints + DisablePerformanceTrackingCheckpointComparison = 256, + /// @brief Disable VModules + DisableVModules = 512, + /// @brief Disable VModules extensions + DisableVModulesExtensions = 1024, + /// @brief Enables hierarchical logging + HierarchicalLogging = 2048, + /// @brief Creates logger automatically when not available + CreateLoggerAutomatically = 4096, + /// @brief Adds spaces b/w logs that separated by left-shift operator + AutoSpacing = 8192, + /// @brief Preserves time format and does not convert it to sec, hour etc (performance tracking only) + FixedTimeFormat = 16384, + // @brief Ignore SIGINT or crash + IgnoreSigInt = 32768, +}; +namespace base { +/// @brief Namespace containing constants used internally. +namespace consts { +static const char kFormatSpecifierCharValue = 'v'; +static const char kFormatSpecifierChar = '%'; +static const unsigned int kMaxLogPerCounter = 100000; +static const unsigned int kMaxLogPerContainer = 100; +static const unsigned int kDefaultSubsecondPrecision = 3; + +#ifdef ELPP_DEFAULT_LOGGER +static const char* kDefaultLoggerId = ELPP_DEFAULT_LOGGER; +#else +static const char* kDefaultLoggerId = "default"; +#endif + +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) +#ifdef ELPP_DEFAULT_PERFORMANCE_LOGGER +static const char* kPerformanceLoggerId = ELPP_DEFAULT_PERFORMANCE_LOGGER; +#else +static const char* kPerformanceLoggerId = "performance"; +#endif // ELPP_DEFAULT_PERFORMANCE_LOGGER +#endif + +#if defined(ELPP_SYSLOG) +static const char* kSysLogLoggerId = "syslog"; +#endif // defined(ELPP_SYSLOG) + +#if ELPP_OS_WINDOWS +static const char* kFilePathSeparator = "\\"; +#else +static const char* kFilePathSeparator = "/"; +#endif // ELPP_OS_WINDOWS + +static const std::size_t kSourceFilenameMaxLength = 100; +static const std::size_t kSourceLineMaxLength = 10; +static const Level kPerformanceTrackerDefaultLevel = Level::Info; +const struct { + double value; + const base::type::char_t* unit; +} kTimeFormats[] = { + { 1000.0f, ELPP_LITERAL("us") }, + { 1000.0f, ELPP_LITERAL("ms") }, + { 60.0f, ELPP_LITERAL("seconds") }, + { 60.0f, ELPP_LITERAL("minutes") }, + { 24.0f, ELPP_LITERAL("hours") }, + { 7.0f, ELPP_LITERAL("days") } +}; +static const int kTimeFormatsCount = sizeof(kTimeFormats) / sizeof(kTimeFormats[0]); +const struct { + int numb; + const char* name; + const char* brief; + const char* detail; +} kCrashSignals[] = { + // NOTE: Do not re-order, if you do please check CrashHandler(bool) constructor and CrashHandler::setHandler(..) + { + SIGABRT, "SIGABRT", "Abnormal termination", + "Program was abnormally terminated." + }, + { + SIGFPE, "SIGFPE", "Erroneous arithmetic operation", + "Arithmetic operation issue such as division by zero or operation resulting in overflow." + }, + { + SIGILL, "SIGILL", "Illegal instruction", + "Generally due to a corruption in the code or to an attempt to execute data." + }, + { + SIGSEGV, "SIGSEGV", "Invalid access to memory", + "Program is trying to read an invalid (unallocated, deleted or corrupted) or inaccessible memory." + }, + { + SIGINT, "SIGINT", "Interactive attention signal", + "Interruption generated (generally) by user or operating system." + }, +}; +static const int kCrashSignalsCount = sizeof(kCrashSignals) / sizeof(kCrashSignals[0]); +} // namespace consts +} // namespace base +typedef std::function PreRollOutCallback; +namespace base { +static inline void defaultPreRollOutCallback(const char*, std::size_t) {} +/// @brief Enum to represent timestamp unit +enum class TimestampUnit : base::type::EnumType { + Microsecond = 0, Millisecond = 1, Second = 2, Minute = 3, Hour = 4, Day = 5 +}; +/// @brief Format flags used to determine specifiers that are active for performance improvements. +enum class FormatFlags : base::type::EnumType { + DateTime = 1 << 1, + LoggerId = 1 << 2, + File = 1 << 3, + Line = 1 << 4, + Location = 1 << 5, + Function = 1 << 6, + User = 1 << 7, + Host = 1 << 8, + LogMessage = 1 << 9, + VerboseLevel = 1 << 10, + AppName = 1 << 11, + ThreadId = 1 << 12, + Level = 1 << 13, + FileBase = 1 << 14, + LevelShort = 1 << 15 +}; +/// @brief A subsecond precision class containing actual width and offset of the subsecond part +class SubsecondPrecision { + public: + SubsecondPrecision(void) { + init(base::consts::kDefaultSubsecondPrecision); + } + explicit SubsecondPrecision(int width) { + init(width); + } + bool operator==(const SubsecondPrecision& ssPrec) { + return m_width == ssPrec.m_width && m_offset == ssPrec.m_offset; + } + int m_width; + unsigned int m_offset; + private: + void init(int width); +}; +/// @brief Type alias of SubsecondPrecision +typedef SubsecondPrecision MillisecondsWidth; +/// @brief Namespace containing utility functions/static classes used internally +namespace utils { +/// @brief Deletes memory safely and points to null +template +static +typename std::enable_if::value, void>::type +safeDelete(T*& pointer) { + if (pointer == nullptr) + return; + delete pointer; + pointer = nullptr; +} +/// @brief Bitwise operations for C++11 strong enum class. This casts e into Flag_T and returns value after bitwise operation +/// Use these function as

flag = bitwise::Or(MyEnum::val1, flag);
+namespace bitwise { +template +static inline base::type::EnumType And(Enum e, base::type::EnumType flag) { + return static_cast(flag) & static_cast(e); +} +template +static inline base::type::EnumType Not(Enum e, base::type::EnumType flag) { + return static_cast(flag) & ~(static_cast(e)); +} +template +static inline base::type::EnumType Or(Enum e, base::type::EnumType flag) { + return static_cast(flag) | static_cast(e); +} +} // namespace bitwise +template +static inline void addFlag(Enum e, base::type::EnumType* flag) { + *flag = base::utils::bitwise::Or(e, *flag); +} +template +static inline void removeFlag(Enum e, base::type::EnumType* flag) { + *flag = base::utils::bitwise::Not(e, *flag); +} +template +static inline bool hasFlag(Enum e, base::type::EnumType flag) { + return base::utils::bitwise::And(e, flag) > 0x0; +} +} // namespace utils +namespace threading { +#if ELPP_THREADING_ENABLED +# if !ELPP_USE_STD_THREADING +namespace internal { +/// @brief A mutex wrapper for compiler that dont yet support std::recursive_mutex +class Mutex : base::NoCopy { + public: + Mutex(void) { +# if ELPP_OS_UNIX + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&m_underlyingMutex, &attr); + pthread_mutexattr_destroy(&attr); +# elif ELPP_OS_WINDOWS + InitializeCriticalSection(&m_underlyingMutex); +# endif // ELPP_OS_UNIX + } + + virtual ~Mutex(void) { +# if ELPP_OS_UNIX + pthread_mutex_destroy(&m_underlyingMutex); +# elif ELPP_OS_WINDOWS + DeleteCriticalSection(&m_underlyingMutex); +# endif // ELPP_OS_UNIX + } + + inline void lock(void) { +# if ELPP_OS_UNIX + pthread_mutex_lock(&m_underlyingMutex); +# elif ELPP_OS_WINDOWS + EnterCriticalSection(&m_underlyingMutex); +# endif // ELPP_OS_UNIX + } + + inline bool try_lock(void) { +# if ELPP_OS_UNIX + return (pthread_mutex_trylock(&m_underlyingMutex) == 0); +# elif ELPP_OS_WINDOWS + return TryEnterCriticalSection(&m_underlyingMutex); +# endif // ELPP_OS_UNIX + } + + inline void unlock(void) { +# if ELPP_OS_UNIX + pthread_mutex_unlock(&m_underlyingMutex); +# elif ELPP_OS_WINDOWS + LeaveCriticalSection(&m_underlyingMutex); +# endif // ELPP_OS_UNIX + } + + private: +# if ELPP_OS_UNIX + pthread_mutex_t m_underlyingMutex; +# elif ELPP_OS_WINDOWS + CRITICAL_SECTION m_underlyingMutex; +# endif // ELPP_OS_UNIX +}; +/// @brief Scoped lock for compiler that dont yet support std::lock_guard +template +class ScopedLock : base::NoCopy { + public: + explicit ScopedLock(M& mutex) { + m_mutex = &mutex; + m_mutex->lock(); + } + + virtual ~ScopedLock(void) { + m_mutex->unlock(); + } + private: + M* m_mutex; + ScopedLock(void); +}; +} // namespace internal +typedef base::threading::internal::Mutex Mutex; +typedef base::threading::internal::ScopedLock ScopedLock; +# else +typedef std::recursive_mutex Mutex; +typedef std::lock_guard ScopedLock; +# endif // !ELPP_USE_STD_THREADING +#else +namespace internal { +/// @brief Mutex wrapper used when multi-threading is disabled. +class NoMutex : base::NoCopy { + public: + NoMutex(void) {} + inline void lock(void) {} + inline bool try_lock(void) { + return true; + } + inline void unlock(void) {} +}; +/// @brief Lock guard wrapper used when multi-threading is disabled. +template +class NoScopedLock : base::NoCopy { + public: + explicit NoScopedLock(Mutex&) { + } + virtual ~NoScopedLock(void) { + } + private: + NoScopedLock(void); +}; +} // namespace internal +typedef base::threading::internal::NoMutex Mutex; +typedef base::threading::internal::NoScopedLock ScopedLock; +#endif // ELPP_THREADING_ENABLED +/// @brief Base of thread safe class, this class is inheritable-only +class ThreadSafe { + public: + virtual inline void acquireLock(void) ELPP_FINAL { m_mutex.lock(); } + virtual inline void releaseLock(void) ELPP_FINAL { m_mutex.unlock(); } + virtual inline base::threading::Mutex& lock(void) ELPP_FINAL { return m_mutex; } + protected: + ThreadSafe(void) {} + virtual ~ThreadSafe(void) {} + private: + base::threading::Mutex m_mutex; +}; + +#if ELPP_THREADING_ENABLED +# if !ELPP_USE_STD_THREADING +/// @brief Gets ID of currently running threading in windows systems. On unix, nothing is returned. +static std::string getCurrentThreadId(void) { + std::stringstream ss; +# if (ELPP_OS_WINDOWS) + ss << GetCurrentThreadId(); +# endif // (ELPP_OS_WINDOWS) + return ss.str(); +} +# else +/// @brief Gets ID of currently running threading using std::this_thread::get_id() +static std::string getCurrentThreadId(void) { + std::stringstream ss; + ss << std::this_thread::get_id(); + return ss.str(); +} +# endif // !ELPP_USE_STD_THREADING +#else +static inline std::string getCurrentThreadId(void) { + return std::string(); +} +#endif // ELPP_THREADING_ENABLED +} // namespace threading +namespace utils { +class File : base::StaticClass { + public: + /// @brief Creates new out file stream for specified filename. + /// @return Pointer to newly created fstream or nullptr + static base::type::fstream_t* newFileStream(const std::string& filename); + + /// @brief Gets size of file provided in stream + static std::size_t getSizeOfFile(base::type::fstream_t* fs); + + /// @brief Determines whether or not provided path exist in current file system + static bool pathExists(const char* path, bool considerFile = false); + + /// @brief Creates specified path on file system + /// @param path Path to create. + static bool createPath(const std::string& path); + /// @brief Extracts path of filename with leading slash + static std::string extractPathFromFilename(const std::string& fullPath, + const char* separator = base::consts::kFilePathSeparator); + /// @brief builds stripped filename and puts it in buff + static void buildStrippedFilename(const char* filename, char buff[], + std::size_t limit = base::consts::kSourceFilenameMaxLength); + /// @brief builds base filename and puts it in buff + static void buildBaseFilename(const std::string& fullPath, char buff[], + std::size_t limit = base::consts::kSourceFilenameMaxLength, + const char* separator = base::consts::kFilePathSeparator); +}; +/// @brief String utilities helper class used internally. You should not use it. +class Str : base::StaticClass { + public: + /// @brief Checks if character is digit. Dont use libc implementation of it to prevent locale issues. + static inline bool isDigit(char c) { + return c >= '0' && c <= '9'; + } + + /// @brief Matches wildcards, '*' and '?' only supported. + static bool wildCardMatch(const char* str, const char* pattern); + + static std::string& ltrim(std::string& str); + static std::string& rtrim(std::string& str); + static std::string& trim(std::string& str); + + /// @brief Determines whether or not str starts with specified string + /// @param str String to check + /// @param start String to check against + /// @return Returns true if starts with specified string, false otherwise + static bool startsWith(const std::string& str, const std::string& start); + + /// @brief Determines whether or not str ends with specified string + /// @param str String to check + /// @param end String to check against + /// @return Returns true if ends with specified string, false otherwise + static bool endsWith(const std::string& str, const std::string& end); + + /// @brief Replaces all instances of replaceWhat with 'replaceWith'. Original variable is changed for performance. + /// @param [in,out] str String to replace from + /// @param replaceWhat Character to replace + /// @param replaceWith Character to replace with + /// @return Modified version of str + static std::string& replaceAll(std::string& str, char replaceWhat, char replaceWith); + + /// @brief Replaces all instances of 'replaceWhat' with 'replaceWith'. (String version) Replaces in place + /// @param str String to replace from + /// @param replaceWhat Character to replace + /// @param replaceWith Character to replace with + /// @return Modified (original) str + static std::string& replaceAll(std::string& str, const std::string& replaceWhat, + const std::string& replaceWith); + + static void replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat, + const base::type::string_t& replaceWith); +#if defined(ELPP_UNICODE) + static void replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat, + const std::string& replaceWith); +#endif // defined(ELPP_UNICODE) + /// @brief Converts string to uppercase + /// @param str String to convert + /// @return Uppercase string + static std::string& toUpper(std::string& str); + + /// @brief Compares cstring equality - uses strcmp + static bool cStringEq(const char* s1, const char* s2); + + /// @brief Compares cstring equality (case-insensitive) - uses toupper(char) + /// Dont use strcasecmp because of CRT (VC++) + static bool cStringCaseEq(const char* s1, const char* s2); + + /// @brief Returns true if c exist in str + static bool contains(const char* str, char c); + + static char* convertAndAddToBuff(std::size_t n, int len, char* buf, const char* bufLim, bool zeroPadded = true); + static char* addToBuff(const char* str, char* buf, const char* bufLim); + static char* clearBuff(char buff[], std::size_t lim); + + /// @brief Converts wchar* to char* + /// NOTE: Need to free return value after use! + static char* wcharPtrToCharPtr(const wchar_t* line); +}; +/// @brief Operating System helper static class used internally. You should not use it. +class OS : base::StaticClass { + public: +#if ELPP_OS_WINDOWS + /// @brief Gets environment variables for Windows based OS. + /// We are not using getenv(const char*) because of CRT deprecation + /// @param varname Variable name to get environment variable value for + /// @return If variable exist the value of it otherwise nullptr + static const char* getWindowsEnvironmentVariable(const char* varname); +#endif // ELPP_OS_WINDOWS +#if ELPP_OS_ANDROID + /// @brief Reads android property value + static std::string getProperty(const char* prop); + + /// @brief Reads android device name + static std::string getDeviceName(void); +#endif // ELPP_OS_ANDROID + + /// @brief Runs command on terminal and returns the output. + /// + /// @detail This is applicable only on unix based systems, for all other OS, an empty string is returned. + /// @param command Bash command + /// @return Result of bash output or empty string if no result found. + static const std::string getBashOutput(const char* command); + + /// @brief Gets environment variable. This is cross-platform and CRT safe (for VC++) + /// @param variableName Environment variable name + /// @param defaultVal If no environment variable or value found the value to return by default + /// @param alternativeBashCommand If environment variable not found what would be alternative bash command + /// in order to look for value user is looking for. E.g, for 'user' alternative command will 'whoami' + static std::string getEnvironmentVariable(const char* variableName, const char* defaultVal, + const char* alternativeBashCommand = nullptr); + /// @brief Gets current username. + static std::string currentUser(void); + + /// @brief Gets current host name or computer name. + /// + /// @detail For android systems this is device name with its manufacturer and model separated by hyphen + static std::string currentHost(void); + /// @brief Whether or not terminal supports colors + static bool termSupportsColor(void); +}; +/// @brief Contains utilities for cross-platform date/time. This class make use of el::base::utils::Str +class DateTime : base::StaticClass { + public: + /// @brief Cross platform gettimeofday for Windows and unix platform. This can be used to determine current microsecond. + /// + /// @detail For unix system it uses gettimeofday(timeval*, timezone*) and for Windows, a separate implementation is provided + /// @param [in,out] tv Pointer that gets updated + static void gettimeofday(struct timeval* tv); + + /// @brief Gets current date and time with a subsecond part. + /// @param format User provided date/time format + /// @param ssPrec A pointer to base::SubsecondPrecision from configuration (non-null) + /// @returns string based date time in specified format. + static std::string getDateTime(const char* format, const base::SubsecondPrecision* ssPrec); + + /// @brief Converts timeval (struct from ctime) to string using specified format and subsecond precision + static std::string timevalToString(struct timeval tval, const char* format, + const el::base::SubsecondPrecision* ssPrec); + + /// @brief Formats time to get unit accordingly, units like second if > 1000 or minutes if > 60000 etc + static base::type::string_t formatTime(unsigned long long time, base::TimestampUnit timestampUnit); + + /// @brief Gets time difference in milli/micro second depending on timestampUnit + static unsigned long long getTimeDifference(const struct timeval& endTime, const struct timeval& startTime, + base::TimestampUnit timestampUnit); + + + static struct ::tm* buildTimeInfo(struct timeval* currTime, struct ::tm* timeInfo); + private: + static char* parseFormat(char* buf, std::size_t bufSz, const char* format, const struct tm* tInfo, + std::size_t msec, const base::SubsecondPrecision* ssPrec); +}; +/// @brief Command line arguments for application if specified using el::Helpers::setArgs(..) or START_EASYLOGGINGPP(..) +class CommandLineArgs { + public: + CommandLineArgs(void) { + setArgs(0, static_cast(nullptr)); + } + CommandLineArgs(int argc, const char** argv) { + setArgs(argc, argv); + } + CommandLineArgs(int argc, char** argv) { + setArgs(argc, argv); + } + virtual ~CommandLineArgs(void) {} + /// @brief Sets arguments and parses them + inline void setArgs(int argc, const char** argv) { + setArgs(argc, const_cast(argv)); + } + /// @brief Sets arguments and parses them + void setArgs(int argc, char** argv); + /// @brief Returns true if arguments contain paramKey with a value (separated by '=') + bool hasParamWithValue(const char* paramKey) const; + /// @brief Returns value of arguments + /// @see hasParamWithValue(const char*) + const char* getParamValue(const char* paramKey) const; + /// @brief Return true if arguments has a param (not having a value) i,e without '=' + bool hasParam(const char* paramKey) const; + /// @brief Returns true if no params available. This exclude argv[0] + bool empty(void) const; + /// @brief Returns total number of arguments. This exclude argv[0] + std::size_t size(void) const; + friend base::type::ostream_t& operator<<(base::type::ostream_t& os, const CommandLineArgs& c); + + private: + int m_argc; + char** m_argv; + std::unordered_map m_paramsWithValue; + std::vector m_params; +}; +/// @brief Abstract registry (aka repository) that provides basic interface for pointer repository specified by T_Ptr type. +/// +/// @detail Most of the functions are virtual final methods but anything implementing this abstract class should implement +/// unregisterAll() and deepCopy(const AbstractRegistry&) and write registerNew() method according to container +/// and few more methods; get() to find element, unregister() to unregister single entry. +/// Please note that this is thread-unsafe and should also implement thread-safety mechanisms in implementation. +template +class AbstractRegistry : public base::threading::ThreadSafe { + public: + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + + /// @brief Default constructor + AbstractRegistry(void) {} + + /// @brief Move constructor that is useful for base classes + AbstractRegistry(AbstractRegistry&& sr) { + if (this == &sr) { + return; + } + unregisterAll(); + m_list = std::move(sr.m_list); + } + + bool operator==(const AbstractRegistry& other) { + if (size() != other.size()) { + return false; + } + for (std::size_t i = 0; i < m_list.size(); ++i) { + if (m_list.at(i) != other.m_list.at(i)) { + return false; + } + } + return true; + } + + bool operator!=(const AbstractRegistry& other) { + if (size() != other.size()) { + return true; + } + for (std::size_t i = 0; i < m_list.size(); ++i) { + if (m_list.at(i) != other.m_list.at(i)) { + return true; + } + } + return false; + } + + /// @brief Assignment move operator + AbstractRegistry& operator=(AbstractRegistry&& sr) { + if (this == &sr) { + return *this; + } + unregisterAll(); + m_list = std::move(sr.m_list); + return *this; + } + + virtual ~AbstractRegistry(void) { + } + + /// @return Iterator pointer from start of repository + virtual inline iterator begin(void) ELPP_FINAL { + return m_list.begin(); + } + + /// @return Iterator pointer from end of repository + virtual inline iterator end(void) ELPP_FINAL { + return m_list.end(); + } + + + /// @return Constant iterator pointer from start of repository + virtual inline const_iterator cbegin(void) const ELPP_FINAL { + return m_list.cbegin(); + } + + /// @return End of repository + virtual inline const_iterator cend(void) const ELPP_FINAL { + return m_list.cend(); + } + + /// @return Whether or not repository is empty + virtual inline bool empty(void) const ELPP_FINAL { + return m_list.empty(); + } + + /// @return Size of repository + virtual inline std::size_t size(void) const ELPP_FINAL { + return m_list.size(); + } + + /// @brief Returns underlying container by reference + virtual inline Container& list(void) ELPP_FINAL { + return m_list; + } + + /// @brief Returns underlying container by constant reference. + virtual inline const Container& list(void) const ELPP_FINAL { + return m_list; + } + + /// @brief Unregisters all the pointers from current repository. + virtual void unregisterAll(void) = 0; + + protected: + virtual void deepCopy(const AbstractRegistry&) = 0; + void reinitDeepCopy(const AbstractRegistry& sr) { + unregisterAll(); + deepCopy(sr); + } + + private: + Container m_list; +}; + +/// @brief A pointer registry mechanism to manage memory and provide search functionalities. (non-predicate version) +/// +/// @detail NOTE: This is thread-unsafe implementation (although it contains lock function, it does not use these functions) +/// of AbstractRegistry. Any implementation of this class should be +/// explicitly (by using lock functions) +template +class Registry : public AbstractRegistry> { + public: + typedef typename Registry::iterator iterator; + typedef typename Registry::const_iterator const_iterator; + + Registry(void) {} + + /// @brief Copy constructor that is useful for base classes. Try to avoid this constructor, use move constructor. + Registry(const Registry& sr) : AbstractRegistry>() { + if (this == &sr) { + return; + } + this->reinitDeepCopy(sr); + } + + /// @brief Assignment operator that unregisters all the existing registries and deeply copies each of repo element + /// @see unregisterAll() + /// @see deepCopy(const AbstractRegistry&) + Registry& operator=(const Registry& sr) { + if (this == &sr) { + return *this; + } + this->reinitDeepCopy(sr); + return *this; + } + + virtual ~Registry(void) { + unregisterAll(); + } + + protected: + virtual void unregisterAll(void) ELPP_FINAL { + if (!this->empty()) { + for (auto&& curr : this->list()) { + base::utils::safeDelete(curr.second); + } + this->list().clear(); + } + } + +/// @brief Registers new registry to repository. + virtual void registerNew(const T_Key& uniqKey, T_Ptr* ptr) ELPP_FINAL { + unregister(uniqKey); + this->list().insert(std::make_pair(uniqKey, ptr)); + } + +/// @brief Unregisters single entry mapped to specified unique key + void unregister(const T_Key& uniqKey) { + T_Ptr* existing = get(uniqKey); + if (existing != nullptr) { + this->list().erase(uniqKey); + base::utils::safeDelete(existing); + } + } + +/// @brief Gets pointer from repository. If none found, nullptr is returned. + T_Ptr* get(const T_Key& uniqKey) { + iterator it = this->list().find(uniqKey); + return it == this->list().end() + ? nullptr + : it->second; + } + + private: + virtual void deepCopy(const AbstractRegistry>& sr) ELPP_FINAL { + for (const_iterator it = sr.cbegin(); it != sr.cend(); ++it) { + registerNew(it->first, new T_Ptr(*it->second)); + } + } +}; + +/// @brief A pointer registry mechanism to manage memory and provide search functionalities. (predicate version) +/// +/// @detail NOTE: This is thread-unsafe implementation of AbstractRegistry. Any implementation of this class +/// should be made thread-safe explicitly +template +class RegistryWithPred : public AbstractRegistry> { + public: + typedef typename RegistryWithPred::iterator iterator; + typedef typename RegistryWithPred::const_iterator const_iterator; + + RegistryWithPred(void) { + } + + virtual ~RegistryWithPred(void) { + unregisterAll(); + } + + /// @brief Copy constructor that is useful for base classes. Try to avoid this constructor, use move constructor. + RegistryWithPred(const RegistryWithPred& sr) : AbstractRegistry>() { + if (this == &sr) { + return; + } + this->reinitDeepCopy(sr); + } + + /// @brief Assignment operator that unregisters all the existing registries and deeply copies each of repo element + /// @see unregisterAll() + /// @see deepCopy(const AbstractRegistry&) + RegistryWithPred& operator=(const RegistryWithPred& sr) { + if (this == &sr) { + return *this; + } + this->reinitDeepCopy(sr); + return *this; + } + + friend base::type::ostream_t& operator<<(base::type::ostream_t& os, const RegistryWithPred& sr) { + for (const_iterator it = sr.list().begin(); it != sr.list().end(); ++it) { + os << ELPP_LITERAL(" ") << **it << ELPP_LITERAL("\n"); + } + return os; + } + + protected: + virtual void unregisterAll(void) ELPP_FINAL { + if (!this->empty()) { + for (auto&& curr : this->list()) { + base::utils::safeDelete(curr); + } + this->list().clear(); + } + } + + virtual void unregister(T_Ptr*& ptr) ELPP_FINAL { + if (ptr) { + iterator iter = this->begin(); + for (; iter != this->end(); ++iter) { + if (ptr == *iter) { + break; + } + } + if (iter != this->end() && *iter != nullptr) { + this->list().erase(iter); + base::utils::safeDelete(*iter); + } + } + } + + virtual inline void registerNew(T_Ptr* ptr) ELPP_FINAL { + this->list().push_back(ptr); + } + +/// @brief Gets pointer from repository with specified arguments. Arguments are passed to predicate +/// in order to validate pointer. + template + T_Ptr* get(const T& arg1, const T2 arg2) { + iterator iter = std::find_if(this->list().begin(), this->list().end(), Pred(arg1, arg2)); + if (iter != this->list().end() && *iter != nullptr) { + return *iter; + } + return nullptr; + } + + private: + virtual void deepCopy(const AbstractRegistry>& sr) { + for (const_iterator it = sr.list().begin(); it != sr.list().end(); ++it) { + registerNew(new T_Ptr(**it)); + } + } +}; +class Utils { + public: + template + static bool installCallback(const std::string& id, std::unordered_map* mapT) { + if (mapT->find(id) == mapT->end()) { + mapT->insert(std::make_pair(id, TPtr(new T()))); + return true; + } + return false; + } + + template + static void uninstallCallback(const std::string& id, std::unordered_map* mapT) { + if (mapT->find(id) != mapT->end()) { + mapT->erase(id); + } + } + + template + static T* callback(const std::string& id, std::unordered_map* mapT) { + typename std::unordered_map::iterator iter = mapT->find(id); + if (iter != mapT->end()) { + return static_cast(iter->second.get()); + } + return nullptr; + } +}; +} // namespace utils +} // namespace base +/// @brief Base of Easylogging++ friendly class +/// +/// @detail After inheriting this class publicly, implement pure-virtual function `void log(std::ostream&) const` +class Loggable { + public: + virtual ~Loggable(void) {} + virtual void log(el::base::type::ostream_t&) const = 0; + private: + friend inline el::base::type::ostream_t& operator<<(el::base::type::ostream_t& os, const Loggable& loggable) { + loggable.log(os); + return os; + } +}; +namespace base { +/// @brief Represents log format containing flags and date format. This is used internally to start initial log +class LogFormat : public Loggable { + public: + LogFormat(void); + LogFormat(Level level, const base::type::string_t& format); + LogFormat(const LogFormat& logFormat); + LogFormat(LogFormat&& logFormat); + LogFormat& operator=(const LogFormat& logFormat); + virtual ~LogFormat(void) {} + bool operator==(const LogFormat& other); + + /// @brief Updates format to be used while logging. + /// @param userFormat User provided format + void parseFromFormat(const base::type::string_t& userFormat); + + inline Level level(void) const { + return m_level; + } + + inline const base::type::string_t& userFormat(void) const { + return m_userFormat; + } + + inline const base::type::string_t& format(void) const { + return m_format; + } + + inline const std::string& dateTimeFormat(void) const { + return m_dateTimeFormat; + } + + inline base::type::EnumType flags(void) const { + return m_flags; + } + + inline bool hasFlag(base::FormatFlags flag) const { + return base::utils::hasFlag(flag, m_flags); + } + + virtual void log(el::base::type::ostream_t& os) const { + os << m_format; + } + + protected: + /// @brief Updates date time format if available in currFormat. + /// @param index Index where %datetime, %date or %time was found + /// @param [in,out] currFormat current format that is being used to format + virtual void updateDateFormat(std::size_t index, base::type::string_t& currFormat) ELPP_FINAL; + + /// @brief Updates %level from format. This is so that we dont have to do it at log-writing-time. It uses m_format and m_level + virtual void updateFormatSpec(void) ELPP_FINAL; + + inline void addFlag(base::FormatFlags flag) { + base::utils::addFlag(flag, &m_flags); + } + + private: + Level m_level; + base::type::string_t m_userFormat; + base::type::string_t m_format; + std::string m_dateTimeFormat; + base::type::EnumType m_flags; + std::string m_currentUser; + std::string m_currentHost; + friend class el::Logger; // To resolve loggerId format specifier easily +}; +} // namespace base +/// @brief Resolving function for format specifier +typedef std::function FormatSpecifierValueResolver; +/// @brief User-provided custom format specifier +/// @see el::Helpers::installCustomFormatSpecifier +/// @see FormatSpecifierValueResolver +class CustomFormatSpecifier { + public: + CustomFormatSpecifier(const char* formatSpecifier, const FormatSpecifierValueResolver& resolver) : + m_formatSpecifier(formatSpecifier), m_resolver(resolver) {} + inline const char* formatSpecifier(void) const { + return m_formatSpecifier; + } + inline const FormatSpecifierValueResolver& resolver(void) const { + return m_resolver; + } + inline bool operator==(const char* formatSpecifier) { + return strcmp(m_formatSpecifier, formatSpecifier) == 0; + } + + private: + const char* m_formatSpecifier; + FormatSpecifierValueResolver m_resolver; +}; +/// @brief Represents single configuration that has representing level, configuration type and a string based value. +/// +/// @detail String based value means any value either its boolean, integer or string itself, it will be embedded inside quotes +/// and will be parsed later. +/// +/// Consider some examples below: +/// * el::Configuration confEnabledInfo(el::Level::Info, el::ConfigurationType::Enabled, "true"); +/// * el::Configuration confMaxLogFileSizeInfo(el::Level::Info, el::ConfigurationType::MaxLogFileSize, "2048"); +/// * el::Configuration confFilenameInfo(el::Level::Info, el::ConfigurationType::Filename, "/var/log/my.log"); +class Configuration : public Loggable { + public: + Configuration(const Configuration& c); + Configuration& operator=(const Configuration& c); + + virtual ~Configuration(void) { + } + + /// @brief Full constructor used to sets value of configuration + Configuration(Level level, ConfigurationType configurationType, const std::string& value); + + /// @brief Gets level of current configuration + inline Level level(void) const { + return m_level; + } + + /// @brief Gets configuration type of current configuration + inline ConfigurationType configurationType(void) const { + return m_configurationType; + } + + /// @brief Gets string based configuration value + inline const std::string& value(void) const { + return m_value; + } + + /// @brief Set string based configuration value + /// @param value Value to set. Values have to be std::string; For boolean values use "true", "false", for any integral values + /// use them in quotes. They will be parsed when configuring + inline void setValue(const std::string& value) { + m_value = value; + } + + virtual void log(el::base::type::ostream_t& os) const; + + /// @brief Used to find configuration from configuration (pointers) repository. Avoid using it. + class Predicate { + public: + Predicate(Level level, ConfigurationType configurationType); + + bool operator()(const Configuration* conf) const; + + private: + Level m_level; + ConfigurationType m_configurationType; + }; + + private: + Level m_level; + ConfigurationType m_configurationType; + std::string m_value; +}; + +/// @brief Thread-safe Configuration repository +/// +/// @detail This repository represents configurations for all the levels and configuration type mapped to a value. +class Configurations : public base::utils::RegistryWithPred { + public: + /// @brief Default constructor with empty repository + Configurations(void); + + /// @brief Constructor used to set configurations using configuration file. + /// @param configurationFile Full path to configuration file + /// @param useDefaultsForRemaining Lets you set the remaining configurations to default. + /// @param base If provided, this configuration will be based off existing repository that this argument is pointing to. + /// @see parseFromFile(const std::string&, Configurations* base) + /// @see setRemainingToDefault() + Configurations(const std::string& configurationFile, bool useDefaultsForRemaining = true, + Configurations* base = nullptr); + + virtual ~Configurations(void) { + } + + /// @brief Parses configuration from file. + /// @param configurationFile Full path to configuration file + /// @param base Configurations to base new configuration repository off. This value is used when you want to use + /// existing Configurations to base all the values and then set rest of configuration via configuration file. + /// @return True if successfully parsed, false otherwise. You may define 'ELPP_DEBUG_ASSERT_FAILURE' to make sure you + /// do not proceed without successful parse. + bool parseFromFile(const std::string& configurationFile, Configurations* base = nullptr); + + /// @brief Parse configurations from configuration string. + /// + /// @detail This configuration string has same syntax as configuration file contents. Make sure all the necessary + /// new line characters are provided. + /// @param base Configurations to base new configuration repository off. This value is used when you want to use + /// existing Configurations to base all the values and then set rest of configuration via configuration text. + /// @return True if successfully parsed, false otherwise. You may define 'ELPP_DEBUG_ASSERT_FAILURE' to make sure you + /// do not proceed without successful parse. + bool parseFromText(const std::string& configurationsString, Configurations* base = nullptr); + + /// @brief Sets configuration based-off an existing configurations. + /// @param base Pointer to existing configurations. + void setFromBase(Configurations* base); + + /// @brief Determines whether or not specified configuration type exists in the repository. + /// + /// @detail Returns as soon as first level is found. + /// @param configurationType Type of configuration to check existence for. + bool hasConfiguration(ConfigurationType configurationType); + + /// @brief Determines whether or not specified configuration type exists for specified level + /// @param level Level to check + /// @param configurationType Type of configuration to check existence for. + bool hasConfiguration(Level level, ConfigurationType configurationType); + + /// @brief Sets value of configuration for specified level. + /// + /// @detail Any existing configuration for specified level will be replaced. Also note that configuration types + /// ConfigurationType::SubsecondPrecision and ConfigurationType::PerformanceTracking will be ignored if not set for + /// Level::Global because these configurations are not dependant on level. + /// @param level Level to set configuration for (el::Level). + /// @param configurationType Type of configuration (el::ConfigurationType) + /// @param value A string based value. Regardless of what the data type of configuration is, it will always be string + /// from users' point of view. This is then parsed later to be used internally. + /// @see Configuration::setValue(const std::string& value) + /// @see el::Level + /// @see el::ConfigurationType + void set(Level level, ConfigurationType configurationType, const std::string& value); + + /// @brief Sets single configuration based on other single configuration. + /// @see set(Level level, ConfigurationType configurationType, const std::string& value) + void set(Configuration* conf); + + inline Configuration* get(Level level, ConfigurationType configurationType) { + base::threading::ScopedLock scopedLock(lock()); + return RegistryWithPred::get(level, configurationType); + } + + /// @brief Sets configuration for all levels. + /// @param configurationType Type of configuration + /// @param value String based value + /// @see Configurations::set(Level level, ConfigurationType configurationType, const std::string& value) + inline void setGlobally(ConfigurationType configurationType, const std::string& value) { + setGlobally(configurationType, value, false); + } + + /// @brief Clears repository so that all the configurations are unset + inline void clear(void) { + base::threading::ScopedLock scopedLock(lock()); + unregisterAll(); + } + + /// @brief Gets configuration file used in parsing this configurations. + /// + /// @detail If this repository was set manually or by text this returns empty string. + inline const std::string& configurationFile(void) const { + return m_configurationFile; + } + + /// @brief Sets configurations to "factory based" configurations. + void setToDefault(void); + + /// @brief Lets you set the remaining configurations to default. + /// + /// @detail By remaining, it means that the level/type a configuration does not exist for. + /// This function is useful when you want to minimize chances of failures, e.g, if you have a configuration file that sets + /// configuration for all the configurations except for Enabled or not, we use this so that ENABLED is set to default i.e, + /// true. If you dont do this explicitly (either by calling this function or by using second param in Constructor + /// and try to access a value, an error is thrown + void setRemainingToDefault(void); + + /// @brief Parser used internally to parse configurations from file or text. + /// + /// @detail This class makes use of base::utils::Str. + /// You should not need this unless you are working on some tool for Easylogging++ + class Parser : base::StaticClass { + public: + /// @brief Parses configuration from file. + /// @param configurationFile Full path to configuration file + /// @param sender Sender configurations pointer. Usually 'this' is used from calling class + /// @param base Configurations to base new configuration repository off. This value is used when you want to use + /// existing Configurations to base all the values and then set rest of configuration via configuration file. + /// @return True if successfully parsed, false otherwise. You may define '_STOP_ON_FIRSTELPP_ASSERTION' to make sure you + /// do not proceed without successful parse. + static bool parseFromFile(const std::string& configurationFile, Configurations* sender, + Configurations* base = nullptr); + + /// @brief Parse configurations from configuration string. + /// + /// @detail This configuration string has same syntax as configuration file contents. Make sure all the necessary + /// new line characters are provided. You may define '_STOP_ON_FIRSTELPP_ASSERTION' to make sure you + /// do not proceed without successful parse (This is recommended) + /// @param configurationsString the configuration in plain text format + /// @param sender Sender configurations pointer. Usually 'this' is used from calling class + /// @param base Configurations to base new configuration repository off. This value is used when you want to use + /// existing Configurations to base all the values and then set rest of configuration via configuration text. + /// @return True if successfully parsed, false otherwise. + static bool parseFromText(const std::string& configurationsString, Configurations* sender, + Configurations* base = nullptr); + + private: + friend class el::Loggers; + static void ignoreComments(std::string* line); + static bool isLevel(const std::string& line); + static bool isComment(const std::string& line); + static inline bool isConfig(const std::string& line); + static bool parseLine(std::string* line, std::string* currConfigStr, std::string* currLevelStr, Level* currLevel, + Configurations* conf); + }; + + private: + std::string m_configurationFile; + bool m_isFromFile; + friend class el::Loggers; + + /// @brief Unsafely sets configuration if does not already exist + void unsafeSetIfNotExist(Level level, ConfigurationType configurationType, const std::string& value); + + /// @brief Thread unsafe set + void unsafeSet(Level level, ConfigurationType configurationType, const std::string& value); + + /// @brief Sets configurations for all levels including Level::Global if includeGlobalLevel is true + /// @see Configurations::setGlobally(ConfigurationType configurationType, const std::string& value) + void setGlobally(ConfigurationType configurationType, const std::string& value, bool includeGlobalLevel); + + /// @brief Sets configurations (Unsafely) for all levels including Level::Global if includeGlobalLevel is true + /// @see Configurations::setGlobally(ConfigurationType configurationType, const std::string& value) + void unsafeSetGlobally(ConfigurationType configurationType, const std::string& value, bool includeGlobalLevel); +}; + +namespace base { +typedef std::shared_ptr FileStreamPtr; +typedef std::unordered_map LogStreamsReferenceMap; +typedef std::shared_ptr LogStreamsReferenceMapPtr; +/// @brief Configurations with data types. +/// +/// @detail el::Configurations have string based values. This is whats used internally in order to read correct configurations. +/// This is to perform faster while writing logs using correct configurations. +/// +/// This is thread safe and final class containing non-virtual destructor (means nothing should inherit this class) +class TypedConfigurations : public base::threading::ThreadSafe { + public: + /// @brief Constructor to initialize (construct) the object off el::Configurations + /// @param configurations Configurations pointer/reference to base this typed configurations off. + /// @param logStreamsReference Use ELPP->registeredLoggers()->logStreamsReference() + TypedConfigurations(Configurations* configurations, LogStreamsReferenceMapPtr logStreamsReference); + + TypedConfigurations(const TypedConfigurations& other); + + virtual ~TypedConfigurations(void) { + } + + const Configurations* configurations(void) const { + return m_configurations; + } + + bool enabled(Level level); + bool toFile(Level level); + const std::string& filename(Level level); + bool toStandardOutput(Level level); + const base::LogFormat& logFormat(Level level); + const base::SubsecondPrecision& subsecondPrecision(Level level = Level::Global); + const base::MillisecondsWidth& millisecondsWidth(Level level = Level::Global); + bool performanceTracking(Level level = Level::Global); + base::type::fstream_t* fileStream(Level level); + std::size_t maxLogFileSize(Level level); + std::size_t logFlushThreshold(Level level); + + private: + Configurations* m_configurations; + std::unordered_map m_enabledMap; + std::unordered_map m_toFileMap; + std::unordered_map m_filenameMap; + std::unordered_map m_toStandardOutputMap; + std::unordered_map m_logFormatMap; + std::unordered_map m_subsecondPrecisionMap; + std::unordered_map m_performanceTrackingMap; + std::unordered_map m_fileStreamMap; + std::unordered_map m_maxLogFileSizeMap; + std::unordered_map m_logFlushThresholdMap; + LogStreamsReferenceMapPtr m_logStreamsReference = nullptr; + + friend class el::Helpers; + friend class el::base::MessageBuilder; + friend class el::base::Writer; + friend class el::base::DefaultLogDispatchCallback; + friend class el::base::LogDispatcher; + + template + inline Conf_T getConfigByVal(Level level, const std::unordered_map* confMap, const char* confName) { + base::threading::ScopedLock scopedLock(lock()); + return unsafeGetConfigByVal(level, confMap, confName); // This is not unsafe anymore - mutex locked in scope + } + + template + inline Conf_T& getConfigByRef(Level level, std::unordered_map* confMap, const char* confName) { + base::threading::ScopedLock scopedLock(lock()); + return unsafeGetConfigByRef(level, confMap, confName); // This is not unsafe anymore - mutex locked in scope + } + + template + Conf_T unsafeGetConfigByVal(Level level, const std::unordered_map* confMap, const char* confName) { + ELPP_UNUSED(confName); + typename std::unordered_map::const_iterator it = confMap->find(level); + if (it == confMap->end()) { + try { + return confMap->at(Level::Global); + } catch (...) { + ELPP_INTERNAL_ERROR("Unable to get configuration [" << confName << "] for level [" + << LevelHelper::convertToString(level) << "]" + << std::endl << "Please ensure you have properly configured logger.", false); + return Conf_T(); + } + } + return it->second; + } + + template + Conf_T& unsafeGetConfigByRef(Level level, std::unordered_map* confMap, const char* confName) { + ELPP_UNUSED(confName); + typename std::unordered_map::iterator it = confMap->find(level); + if (it == confMap->end()) { + try { + return confMap->at(Level::Global); + } catch (...) { + ELPP_INTERNAL_ERROR("Unable to get configuration [" << confName << "] for level [" + << LevelHelper::convertToString(level) << "]" + << std::endl << "Please ensure you have properly configured logger.", false); + } + } + return it->second; + } + + template + void setValue(Level level, const Conf_T& value, std::unordered_map* confMap, + bool includeGlobalLevel = true) { + // If map is empty and we are allowed to add into generic level (Level::Global), do it! + if (confMap->empty() && includeGlobalLevel) { + confMap->insert(std::make_pair(Level::Global, value)); + return; + } + // If same value exist in generic level already, dont add it to explicit level + typename std::unordered_map::iterator it = confMap->find(Level::Global); + if (it != confMap->end() && it->second == value) { + return; + } + // Now make sure we dont double up values if we really need to add it to explicit level + it = confMap->find(level); + if (it == confMap->end()) { + // Value not found for level, add new + confMap->insert(std::make_pair(level, value)); + } else { + // Value found, just update value + confMap->at(level) = value; + } + } + + void build(Configurations* configurations); + unsigned long getULong(std::string confVal); + std::string resolveFilename(const std::string& filename); + void insertFile(Level level, const std::string& fullFilename); + bool unsafeValidateFileRolling(Level level, const PreRollOutCallback& preRollOutCallback); + + inline bool validateFileRolling(Level level, const PreRollOutCallback& preRollOutCallback) { + base::threading::ScopedLock scopedLock(lock()); + return unsafeValidateFileRolling(level, preRollOutCallback); + } +}; +/// @brief Class that keeps record of current line hit for occasional logging +class HitCounter { + public: + HitCounter(void) : + m_filename(""), + m_lineNumber(0), + m_hitCounts(0) { + } + + HitCounter(const char* filename, base::type::LineNumber lineNumber) : + m_filename(filename), + m_lineNumber(lineNumber), + m_hitCounts(0) { + } + + HitCounter(const HitCounter& hitCounter) : + m_filename(hitCounter.m_filename), + m_lineNumber(hitCounter.m_lineNumber), + m_hitCounts(hitCounter.m_hitCounts) { + } + + HitCounter& operator=(const HitCounter& hitCounter) { + if (&hitCounter != this) { + m_filename = hitCounter.m_filename; + m_lineNumber = hitCounter.m_lineNumber; + m_hitCounts = hitCounter.m_hitCounts; + } + return *this; + } + + virtual ~HitCounter(void) { + } + + /// @brief Resets location of current hit counter + inline void resetLocation(const char* filename, base::type::LineNumber lineNumber) { + m_filename = filename; + m_lineNumber = lineNumber; + } + + /// @brief Validates hit counts and resets it if necessary + inline void validateHitCounts(std::size_t n) { + if (m_hitCounts >= base::consts::kMaxLogPerCounter) { + m_hitCounts = (n >= 1 ? base::consts::kMaxLogPerCounter % n : 0); + } + ++m_hitCounts; + } + + inline const char* filename(void) const { + return m_filename; + } + + inline base::type::LineNumber lineNumber(void) const { + return m_lineNumber; + } + + inline std::size_t hitCounts(void) const { + return m_hitCounts; + } + + inline void increment(void) { + ++m_hitCounts; + } + + class Predicate { + public: + Predicate(const char* filename, base::type::LineNumber lineNumber) + : m_filename(filename), + m_lineNumber(lineNumber) { + } + inline bool operator()(const HitCounter* counter) { + return ((counter != nullptr) && + (strcmp(counter->m_filename, m_filename) == 0) && + (counter->m_lineNumber == m_lineNumber)); + } + + private: + const char* m_filename; + base::type::LineNumber m_lineNumber; + }; + + private: + const char* m_filename; + base::type::LineNumber m_lineNumber; + std::size_t m_hitCounts; +}; +/// @brief Repository for hit counters used across the application +class RegisteredHitCounters : public base::utils::RegistryWithPred { + public: + /// @brief Validates counter for every N, i.e, registers new if does not exist otherwise updates original one + /// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned + bool validateEveryN(const char* filename, base::type::LineNumber lineNumber, std::size_t n); + + /// @brief Validates counter for hits >= N, i.e, registers new if does not exist otherwise updates original one + /// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned + bool validateAfterN(const char* filename, base::type::LineNumber lineNumber, std::size_t n); + + /// @brief Validates counter for hits are <= n, i.e, registers new if does not exist otherwise updates original one + /// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned + bool validateNTimes(const char* filename, base::type::LineNumber lineNumber, std::size_t n); + + /// @brief Gets hit counter registered at specified position + inline const base::HitCounter* getCounter(const char* filename, base::type::LineNumber lineNumber) { + base::threading::ScopedLock scopedLock(lock()); + return get(filename, lineNumber); + } +}; +/// @brief Action to be taken for dispatching +enum class DispatchAction : base::type::EnumType { + None = 1, NormalLog = 2, SysLog = 4 +}; +} // namespace base +template +class Callback : protected base::threading::ThreadSafe { + public: + Callback(void) : m_enabled(true) {} + inline bool enabled(void) const { + return m_enabled; + } + inline void setEnabled(bool enabled) { + base::threading::ScopedLock scopedLock(lock()); + m_enabled = enabled; + } + protected: + virtual void handle(const T* handlePtr) = 0; + private: + bool m_enabled; +}; +class LogDispatchData { + public: + LogDispatchData() : m_logMessage(nullptr), m_dispatchAction(base::DispatchAction::None) {} + inline const LogMessage* logMessage(void) const { + return m_logMessage; + } + inline base::DispatchAction dispatchAction(void) const { + return m_dispatchAction; + } + inline void setLogMessage(LogMessage* logMessage) { + m_logMessage = logMessage; + } + inline void setDispatchAction(base::DispatchAction dispatchAction) { + m_dispatchAction = dispatchAction; + } + private: + LogMessage* m_logMessage; + base::DispatchAction m_dispatchAction; + friend class base::LogDispatcher; + +}; +class LogDispatchCallback : public Callback { + protected: + virtual void handle(const LogDispatchData* data); + base::threading::Mutex& fileHandle(const LogDispatchData* data); + private: + friend class base::LogDispatcher; + std::unordered_map> m_fileLocks; + base::threading::Mutex m_fileLocksMapLock; +}; +class PerformanceTrackingCallback : public Callback { + private: + friend class base::PerformanceTracker; +}; +class LoggerRegistrationCallback : public Callback { + private: + friend class base::RegisteredLoggers; +}; +class LogBuilder : base::NoCopy { + public: + LogBuilder() : m_termSupportsColor(base::utils::OS::termSupportsColor()) {} + virtual ~LogBuilder(void) { + ELPP_INTERNAL_INFO(3, "Destroying log builder...") + } + virtual base::type::string_t build(const LogMessage* logMessage, bool appendNewLine) const = 0; + void convertToColoredOutput(base::type::string_t* logLine, Level level); + private: + bool m_termSupportsColor; + friend class el::base::DefaultLogDispatchCallback; +}; +typedef std::shared_ptr LogBuilderPtr; +/// @brief Represents a logger holding ID and configurations we need to write logs +/// +/// @detail This class does not write logs itself instead its used by writer to read configurations from. +class Logger : public base::threading::ThreadSafe, public Loggable { + public: + Logger(const std::string& id, base::LogStreamsReferenceMapPtr logStreamsReference); + Logger(const std::string& id, const Configurations& configurations, base::LogStreamsReferenceMapPtr logStreamsReference); + Logger(const Logger& logger); + Logger& operator=(const Logger& logger); + + virtual ~Logger(void) { + base::utils::safeDelete(m_typedConfigurations); + } + + virtual inline void log(el::base::type::ostream_t& os) const { + os << m_id.c_str(); + } + + /// @brief Configures the logger using specified configurations. + void configure(const Configurations& configurations); + + /// @brief Reconfigures logger using existing configurations + void reconfigure(void); + + inline const std::string& id(void) const { + return m_id; + } + + inline const std::string& parentApplicationName(void) const { + return m_parentApplicationName; + } + + inline void setParentApplicationName(const std::string& parentApplicationName) { + m_parentApplicationName = parentApplicationName; + } + + inline Configurations* configurations(void) { + return &m_configurations; + } + + inline base::TypedConfigurations* typedConfigurations(void) { + return m_typedConfigurations; + } + + static bool isValidId(const std::string& id); + + /// @brief Flushes logger to sync all log files for all levels + void flush(void); + + void flush(Level level, base::type::fstream_t* fs); + + inline bool isFlushNeeded(Level level) { + return ++m_unflushedCount.find(level)->second >= m_typedConfigurations->logFlushThreshold(level); + } + + inline LogBuilder* logBuilder(void) const { + return m_logBuilder.get(); + } + + inline void setLogBuilder(const LogBuilderPtr& logBuilder) { + m_logBuilder = logBuilder; + } + + inline bool enabled(Level level) const { + return m_typedConfigurations->enabled(level); + } + +#if ELPP_VARIADIC_TEMPLATES_SUPPORTED +# define LOGGER_LEVEL_WRITERS_SIGNATURES(FUNCTION_NAME)\ +template \ +inline void FUNCTION_NAME(const char*, const T&, const Args&...);\ +template \ +inline void FUNCTION_NAME(const T&); + + template + inline void verbose(int, const char*, const T&, const Args&...); + + template + inline void verbose(int, const T&); + + LOGGER_LEVEL_WRITERS_SIGNATURES(info) + LOGGER_LEVEL_WRITERS_SIGNATURES(debug) + LOGGER_LEVEL_WRITERS_SIGNATURES(warn) + LOGGER_LEVEL_WRITERS_SIGNATURES(error) + LOGGER_LEVEL_WRITERS_SIGNATURES(fatal) + LOGGER_LEVEL_WRITERS_SIGNATURES(trace) +# undef LOGGER_LEVEL_WRITERS_SIGNATURES +#endif // ELPP_VARIADIC_TEMPLATES_SUPPORTED + private: + std::string m_id; + base::TypedConfigurations* m_typedConfigurations; + base::type::stringstream_t m_stream; + std::string m_parentApplicationName; + bool m_isConfigured; + Configurations m_configurations; + std::unordered_map m_unflushedCount; + base::LogStreamsReferenceMapPtr m_logStreamsReference = nullptr; + LogBuilderPtr m_logBuilder; + + friend class el::LogMessage; + friend class el::Loggers; + friend class el::Helpers; + friend class el::base::RegisteredLoggers; + friend class el::base::DefaultLogDispatchCallback; + friend class el::base::MessageBuilder; + friend class el::base::Writer; + friend class el::base::PErrorWriter; + friend class el::base::Storage; + friend class el::base::PerformanceTracker; + friend class el::base::LogDispatcher; + + Logger(void); + +#if ELPP_VARIADIC_TEMPLATES_SUPPORTED + template + void log_(Level, int, const char*, const T&, const Args&...); + + template + inline void log_(Level, int, const T&); + + template + void log(Level, const char*, const T&, const Args&...); + + template + inline void log(Level, const T&); +#endif // ELPP_VARIADIC_TEMPLATES_SUPPORTED + + void initUnflushedCount(void); + + inline base::type::stringstream_t& stream(void) { + return m_stream; + } + + void resolveLoggerFormatSpec(void) const; +}; +namespace base { +/// @brief Loggers repository +class RegisteredLoggers : public base::utils::Registry { + public: + explicit RegisteredLoggers(const LogBuilderPtr& defaultLogBuilder); + + virtual ~RegisteredLoggers(void) { + unsafeFlushAll(); + } + + inline void setDefaultConfigurations(const Configurations& configurations) { + base::threading::ScopedLock scopedLock(lock()); + m_defaultConfigurations.setFromBase(const_cast(&configurations)); + } + + inline Configurations* defaultConfigurations(void) { + return &m_defaultConfigurations; + } + + Logger* get(const std::string& id, bool forceCreation = true); + + template + inline bool installLoggerRegistrationCallback(const std::string& id) { + return base::utils::Utils::installCallback(id, + &m_loggerRegistrationCallbacks); + } + + template + inline void uninstallLoggerRegistrationCallback(const std::string& id) { + base::utils::Utils::uninstallCallback(id, &m_loggerRegistrationCallbacks); + } + + template + inline T* loggerRegistrationCallback(const std::string& id) { + return base::utils::Utils::callback(id, &m_loggerRegistrationCallbacks); + } + + bool remove(const std::string& id); + + inline bool has(const std::string& id) { + return get(id, false) != nullptr; + } + + inline void unregister(Logger*& logger) { + base::threading::ScopedLock scopedLock(lock()); + base::utils::Registry::unregister(logger->id()); + } + + inline LogStreamsReferenceMapPtr logStreamsReference(void) { + return m_logStreamsReference; + } + + inline void flushAll(void) { + base::threading::ScopedLock scopedLock(lock()); + unsafeFlushAll(); + } + + inline void setDefaultLogBuilder(LogBuilderPtr& logBuilderPtr) { + base::threading::ScopedLock scopedLock(lock()); + m_defaultLogBuilder = logBuilderPtr; + } + + private: + LogBuilderPtr m_defaultLogBuilder; + Configurations m_defaultConfigurations; + base::LogStreamsReferenceMapPtr m_logStreamsReference = nullptr; + std::unordered_map m_loggerRegistrationCallbacks; + friend class el::base::Storage; + + void unsafeFlushAll(void); +}; +/// @brief Represents registries for verbose logging +class VRegistry : base::NoCopy, public base::threading::ThreadSafe { + public: + explicit VRegistry(base::type::VerboseLevel level, base::type::EnumType* pFlags); + + /// @brief Sets verbose level. Accepted range is 0-9 + void setLevel(base::type::VerboseLevel level); + + inline base::type::VerboseLevel level(void) const { + return m_level; + } + + inline void clearModules(void) { + base::threading::ScopedLock scopedLock(lock()); + m_modules.clear(); + } + + void setModules(const char* modules); + + bool allowed(base::type::VerboseLevel vlevel, const char* file); + + inline const std::unordered_map& modules(void) const { + return m_modules; + } + + void setFromArgs(const base::utils::CommandLineArgs* commandLineArgs); + + /// @brief Whether or not vModules enabled + inline bool vModulesEnabled(void) { + return !base::utils::hasFlag(LoggingFlag::DisableVModules, *m_pFlags); + } + + private: + base::type::VerboseLevel m_level; + base::type::EnumType* m_pFlags; + std::unordered_map m_modules; +}; +} // namespace base +class LogMessage { + public: + LogMessage(Level level, const std::string& file, base::type::LineNumber line, const std::string& func, + base::type::VerboseLevel verboseLevel, Logger* logger) : + m_level(level), m_file(file), m_line(line), m_func(func), + m_verboseLevel(verboseLevel), m_logger(logger), m_message(logger->stream().str()) { + } + inline Level level(void) const { + return m_level; + } + inline const std::string& file(void) const { + return m_file; + } + inline base::type::LineNumber line(void) const { + return m_line; + } + inline const std::string& func(void) const { + return m_func; + } + inline base::type::VerboseLevel verboseLevel(void) const { + return m_verboseLevel; + } + inline Logger* logger(void) const { + return m_logger; + } + inline const base::type::string_t& message(void) const { + return m_message; + } + private: + Level m_level; + std::string m_file; + base::type::LineNumber m_line; + std::string m_func; + base::type::VerboseLevel m_verboseLevel; + Logger* m_logger; + base::type::string_t m_message; +}; +namespace base { +#if ELPP_ASYNC_LOGGING +class AsyncLogItem { + public: + explicit AsyncLogItem(const LogMessage& logMessage, const LogDispatchData& data, const base::type::string_t& logLine) + : m_logMessage(logMessage), m_dispatchData(data), m_logLine(logLine) {} + virtual ~AsyncLogItem() {} + inline LogMessage* logMessage(void) { + return &m_logMessage; + } + inline LogDispatchData* data(void) { + return &m_dispatchData; + } + inline base::type::string_t logLine(void) { + return m_logLine; + } + private: + LogMessage m_logMessage; + LogDispatchData m_dispatchData; + base::type::string_t m_logLine; +}; +class AsyncLogQueue : public base::threading::ThreadSafe { + public: + virtual ~AsyncLogQueue() { + ELPP_INTERNAL_INFO(6, "~AsyncLogQueue"); + } + + inline AsyncLogItem next(void) { + base::threading::ScopedLock scopedLock(lock()); + AsyncLogItem result = m_queue.front(); + m_queue.pop(); + return result; + } + + inline void push(const AsyncLogItem& item) { + base::threading::ScopedLock scopedLock(lock()); + m_queue.push(item); + } + inline void pop(void) { + base::threading::ScopedLock scopedLock(lock()); + m_queue.pop(); + } + inline AsyncLogItem front(void) { + base::threading::ScopedLock scopedLock(lock()); + return m_queue.front(); + } + inline bool empty(void) { + base::threading::ScopedLock scopedLock(lock()); + return m_queue.empty(); + } + private: + std::queue m_queue; +}; +class IWorker { + public: + virtual ~IWorker() {} + virtual void start() = 0; +}; +#endif // ELPP_ASYNC_LOGGING +/// @brief Easylogging++ management storage +class Storage : base::NoCopy, public base::threading::ThreadSafe { + public: +#if ELPP_ASYNC_LOGGING + Storage(const LogBuilderPtr& defaultLogBuilder, base::IWorker* asyncDispatchWorker); +#else + explicit Storage(const LogBuilderPtr& defaultLogBuilder); +#endif // ELPP_ASYNC_LOGGING + + virtual ~Storage(void); + + inline bool validateEveryNCounter(const char* filename, base::type::LineNumber lineNumber, std::size_t occasion) { + return hitCounters()->validateEveryN(filename, lineNumber, occasion); + } + + inline bool validateAfterNCounter(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { + return hitCounters()->validateAfterN(filename, lineNumber, n); + } + + inline bool validateNTimesCounter(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { + return hitCounters()->validateNTimes(filename, lineNumber, n); + } + + inline base::RegisteredHitCounters* hitCounters(void) const { + return m_registeredHitCounters; + } + + inline base::RegisteredLoggers* registeredLoggers(void) const { + return m_registeredLoggers; + } + + inline base::VRegistry* vRegistry(void) const { + return m_vRegistry; + } + +#if ELPP_ASYNC_LOGGING + inline base::AsyncLogQueue* asyncLogQueue(void) const { + return m_asyncLogQueue; + } +#endif // ELPP_ASYNC_LOGGING + + inline const base::utils::CommandLineArgs* commandLineArgs(void) const { + return &m_commandLineArgs; + } + + inline void addFlag(LoggingFlag flag) { + base::utils::addFlag(flag, &m_flags); + } + + inline void removeFlag(LoggingFlag flag) { + base::utils::removeFlag(flag, &m_flags); + } + + inline bool hasFlag(LoggingFlag flag) const { + return base::utils::hasFlag(flag, m_flags); + } + + inline base::type::EnumType flags(void) const { + return m_flags; + } + + inline void setFlags(base::type::EnumType flags) { + m_flags = flags; + } + + inline void setPreRollOutCallback(const PreRollOutCallback& callback) { + m_preRollOutCallback = callback; + } + + inline void unsetPreRollOutCallback(void) { + m_preRollOutCallback = base::defaultPreRollOutCallback; + } + + inline PreRollOutCallback& preRollOutCallback(void) { + return m_preRollOutCallback; + } + + bool hasCustomFormatSpecifier(const char* formatSpecifier); + void installCustomFormatSpecifier(const CustomFormatSpecifier& customFormatSpecifier); + bool uninstallCustomFormatSpecifier(const char* formatSpecifier); + + const std::vector* customFormatSpecifiers(void) const { + return &m_customFormatSpecifiers; + } + + base::threading::Mutex& customFormatSpecifiersLock() { + return m_customFormatSpecifiersLock; + } + + inline void setLoggingLevel(Level level) { + m_loggingLevel = level; + } + + template + inline bool installLogDispatchCallback(const std::string& id) { + return base::utils::Utils::installCallback(id, &m_logDispatchCallbacks); + } + + template + inline void uninstallLogDispatchCallback(const std::string& id) { + base::utils::Utils::uninstallCallback(id, &m_logDispatchCallbacks); + } + template + inline T* logDispatchCallback(const std::string& id) { + return base::utils::Utils::callback(id, &m_logDispatchCallbacks); + } + +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + template + inline bool installPerformanceTrackingCallback(const std::string& id) { + return base::utils::Utils::installCallback(id, + &m_performanceTrackingCallbacks); + } + + template + inline void uninstallPerformanceTrackingCallback(const std::string& id) { + base::utils::Utils::uninstallCallback(id, + &m_performanceTrackingCallbacks); + } + + template + inline T* performanceTrackingCallback(const std::string& id) { + return base::utils::Utils::callback(id, &m_performanceTrackingCallbacks); + } +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + + /// @brief Sets thread name for current thread. Requires std::thread + inline void setThreadName(const std::string& name) { + if (name.empty()) return; + base::threading::ScopedLock scopedLock(m_threadNamesLock); + m_threadNames[base::threading::getCurrentThreadId()] = name; + } + + inline std::string getThreadName(const std::string& threadId) { + base::threading::ScopedLock scopedLock(m_threadNamesLock); + std::unordered_map::const_iterator it = m_threadNames.find(threadId); + if (it == m_threadNames.end()) { + return threadId; + } + return it->second; + } + private: + base::RegisteredHitCounters* m_registeredHitCounters; + base::RegisteredLoggers* m_registeredLoggers; + base::type::EnumType m_flags; + base::VRegistry* m_vRegistry; +#if ELPP_ASYNC_LOGGING + base::AsyncLogQueue* m_asyncLogQueue; + base::IWorker* m_asyncDispatchWorker; +#endif // ELPP_ASYNC_LOGGING + base::utils::CommandLineArgs m_commandLineArgs; + PreRollOutCallback m_preRollOutCallback; + std::unordered_map m_logDispatchCallbacks; + std::unordered_map m_performanceTrackingCallbacks; + std::unordered_map m_threadNames; + std::vector m_customFormatSpecifiers; + base::threading::Mutex m_customFormatSpecifiersLock; + base::threading::Mutex m_threadNamesLock; + Level m_loggingLevel; + + friend class el::Helpers; + friend class el::base::DefaultLogDispatchCallback; + friend class el::LogBuilder; + friend class el::base::MessageBuilder; + friend class el::base::Writer; + friend class el::base::PerformanceTracker; + friend class el::base::LogDispatcher; + + void setApplicationArguments(int argc, char** argv); + + inline void setApplicationArguments(int argc, const char** argv) { + setApplicationArguments(argc, const_cast(argv)); + } +}; +extern ELPP_EXPORT base::type::StoragePointer elStorage; +#define ELPP el::base::elStorage +class DefaultLogDispatchCallback : public LogDispatchCallback { + protected: + void handle(const LogDispatchData* data); + private: + const LogDispatchData* m_data; + void dispatch(base::type::string_t&& logLine); +}; +#if ELPP_ASYNC_LOGGING +class AsyncLogDispatchCallback : public LogDispatchCallback { + protected: + void handle(const LogDispatchData* data); +}; +class AsyncDispatchWorker : public base::IWorker, public base::threading::ThreadSafe { + public: + AsyncDispatchWorker(); + virtual ~AsyncDispatchWorker(); + + bool clean(void); + void emptyQueue(void); + virtual void start(void); + void handle(AsyncLogItem* logItem); + void run(void); + + void setContinueRunning(bool value) { + base::threading::ScopedLock scopedLock(m_continueRunningLock); + m_continueRunning = value; + } + + bool continueRunning(void) const { + return m_continueRunning; + } + private: + std::condition_variable cv; + bool m_continueRunning; + base::threading::Mutex m_continueRunningLock; +}; +#endif // ELPP_ASYNC_LOGGING +} // namespace base +namespace base { +class DefaultLogBuilder : public LogBuilder { + public: + base::type::string_t build(const LogMessage* logMessage, bool appendNewLine) const; +}; +/// @brief Dispatches log messages +class LogDispatcher : base::NoCopy { + public: + LogDispatcher(bool proceed, LogMessage* logMessage, base::DispatchAction dispatchAction) : + m_proceed(proceed), + m_logMessage(logMessage), + m_dispatchAction(std::move(dispatchAction)) { + } + + void dispatch(void); + + private: + bool m_proceed; + LogMessage* m_logMessage; + base::DispatchAction m_dispatchAction; +}; +#if defined(ELPP_STL_LOGGING) +/// @brief Workarounds to write some STL logs +/// +/// @detail There is workaround needed to loop through some stl containers. In order to do that, we need iterable containers +/// of same type and provide iterator interface and pass it on to writeIterator(). +/// Remember, this is passed by value in constructor so that we dont change original containers. +/// This operation is as expensive as Big-O(std::min(class_.size(), base::consts::kMaxLogPerContainer)) +namespace workarounds { +/// @brief Abstract IterableContainer template that provides interface for iterable classes of type T +template +class IterableContainer { + public: + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + IterableContainer(void) {} + virtual ~IterableContainer(void) {} + iterator begin(void) { + return getContainer().begin(); + } + iterator end(void) { + return getContainer().end(); + } + private: + virtual Container& getContainer(void) = 0; +}; +/// @brief Implements IterableContainer and provides iterable std::priority_queue class +template, typename Comparator = std::less> +class IterablePriorityQueue : public IterableContainer, + public std::priority_queue { + public: + IterablePriorityQueue(std::priority_queue queue_) { + std::size_t count_ = 0; + while (++count_ < base::consts::kMaxLogPerContainer && !queue_.empty()) { + this->push(queue_.top()); + queue_.pop(); + } + } + private: + inline Container& getContainer(void) { + return this->c; + } +}; +/// @brief Implements IterableContainer and provides iterable std::queue class +template> +class IterableQueue : public IterableContainer, public std::queue { + public: + IterableQueue(std::queue queue_) { + std::size_t count_ = 0; + while (++count_ < base::consts::kMaxLogPerContainer && !queue_.empty()) { + this->push(queue_.front()); + queue_.pop(); + } + } + private: + inline Container& getContainer(void) { + return this->c; + } +}; +/// @brief Implements IterableContainer and provides iterable std::stack class +template> +class IterableStack : public IterableContainer, public std::stack { + public: + IterableStack(std::stack stack_) { + std::size_t count_ = 0; + while (++count_ < base::consts::kMaxLogPerContainer && !stack_.empty()) { + this->push(stack_.top()); + stack_.pop(); + } + } + private: + inline Container& getContainer(void) { + return this->c; + } +}; +} // namespace workarounds +#endif // defined(ELPP_STL_LOGGING) +// Log message builder +class MessageBuilder { + public: + MessageBuilder(void) : m_logger(nullptr), m_containerLogSeparator(ELPP_LITERAL("")) {} + void initialize(Logger* logger); + +# define ELPP_SIMPLE_LOG(LOG_TYPE)\ +MessageBuilder& operator<<(LOG_TYPE msg) {\ +m_logger->stream() << msg;\ +if (ELPP->hasFlag(LoggingFlag::AutoSpacing)) {\ +m_logger->stream() << " ";\ +}\ +return *this;\ +} + + inline MessageBuilder& operator<<(const std::string& msg) { + return operator<<(msg.c_str()); + } + ELPP_SIMPLE_LOG(char) + ELPP_SIMPLE_LOG(bool) + ELPP_SIMPLE_LOG(signed short) + ELPP_SIMPLE_LOG(unsigned short) + ELPP_SIMPLE_LOG(signed int) + ELPP_SIMPLE_LOG(unsigned int) + ELPP_SIMPLE_LOG(signed long) + ELPP_SIMPLE_LOG(unsigned long) + ELPP_SIMPLE_LOG(float) + ELPP_SIMPLE_LOG(double) + ELPP_SIMPLE_LOG(char*) + ELPP_SIMPLE_LOG(const char*) + ELPP_SIMPLE_LOG(const void*) + ELPP_SIMPLE_LOG(long double) + inline MessageBuilder& operator<<(const std::wstring& msg) { + return operator<<(msg.c_str()); + } + MessageBuilder& operator<<(const wchar_t* msg); + // ostream manipulators + inline MessageBuilder& operator<<(std::ostream& (*OStreamMani)(std::ostream&)) { + m_logger->stream() << OStreamMani; + return *this; + } +#define ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(temp) \ +template \ +inline MessageBuilder& operator<<(const temp& template_inst) { \ +return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ +} +#define ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(temp) \ +template \ +inline MessageBuilder& operator<<(const temp& template_inst) { \ +return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ +} +#define ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(temp) \ +template \ +inline MessageBuilder& operator<<(const temp& template_inst) { \ +return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ +} +#define ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(temp) \ +template \ +inline MessageBuilder& operator<<(const temp& template_inst) { \ +return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ +} +#define ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG(temp) \ +template \ +inline MessageBuilder& operator<<(const temp& template_inst) { \ +return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ +} + +#if defined(ELPP_STL_LOGGING) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(std::vector) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(std::list) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(std::deque) + ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(std::set) + ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(std::multiset) + ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::map) + ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::multimap) + template + inline MessageBuilder& operator<<(const std::queue& queue_) { + base::workarounds::IterableQueue iterableQueue_ = + static_cast >(queue_); + return writeIterator(iterableQueue_.begin(), iterableQueue_.end(), iterableQueue_.size()); + } + template + inline MessageBuilder& operator<<(const std::stack& stack_) { + base::workarounds::IterableStack iterableStack_ = + static_cast >(stack_); + return writeIterator(iterableStack_.begin(), iterableStack_.end(), iterableStack_.size()); + } + template + inline MessageBuilder& operator<<(const std::priority_queue& priorityQueue_) { + base::workarounds::IterablePriorityQueue iterablePriorityQueue_ = + static_cast >(priorityQueue_); + return writeIterator(iterablePriorityQueue_.begin(), iterablePriorityQueue_.end(), iterablePriorityQueue_.size()); + } + template + MessageBuilder& operator<<(const std::pair& pair_) { + m_logger->stream() << ELPP_LITERAL("("); + operator << (static_cast(pair_.first)); + m_logger->stream() << ELPP_LITERAL(", "); + operator << (static_cast(pair_.second)); + m_logger->stream() << ELPP_LITERAL(")"); + return *this; + } + template + MessageBuilder& operator<<(const std::bitset& bitset_) { + m_logger->stream() << ELPP_LITERAL("["); + operator << (bitset_.to_string()); + m_logger->stream() << ELPP_LITERAL("]"); + return *this; + } +# if defined(ELPP_LOG_STD_ARRAY) + template + inline MessageBuilder& operator<<(const std::array& array) { + return writeIterator(array.begin(), array.end(), array.size()); + } +# endif // defined(ELPP_LOG_STD_ARRAY) +# if defined(ELPP_LOG_UNORDERED_MAP) + ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG(std::unordered_map) + ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG(std::unordered_multimap) +# endif // defined(ELPP_LOG_UNORDERED_MAP) +# if defined(ELPP_LOG_UNORDERED_SET) + ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::unordered_set) + ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::unordered_multiset) +# endif // defined(ELPP_LOG_UNORDERED_SET) +#endif // defined(ELPP_STL_LOGGING) +#if defined(ELPP_QT_LOGGING) + inline MessageBuilder& operator<<(const QString& msg) { +# if defined(ELPP_UNICODE) + m_logger->stream() << msg.toStdWString(); +# else + m_logger->stream() << msg.toStdString(); +# endif // defined(ELPP_UNICODE) + return *this; + } + inline MessageBuilder& operator<<(const QByteArray& msg) { + return operator << (QString(msg)); + } + inline MessageBuilder& operator<<(const QStringRef& msg) { + return operator<<(msg.toString()); + } + inline MessageBuilder& operator<<(qint64 msg) { +# if defined(ELPP_UNICODE) + m_logger->stream() << QString::number(msg).toStdWString(); +# else + m_logger->stream() << QString::number(msg).toStdString(); +# endif // defined(ELPP_UNICODE) + return *this; + } + inline MessageBuilder& operator<<(quint64 msg) { +# if defined(ELPP_UNICODE) + m_logger->stream() << QString::number(msg).toStdWString(); +# else + m_logger->stream() << QString::number(msg).toStdString(); +# endif // defined(ELPP_UNICODE) + return *this; + } + inline MessageBuilder& operator<<(QChar msg) { + m_logger->stream() << msg.toLatin1(); + return *this; + } + inline MessageBuilder& operator<<(const QLatin1String& msg) { + m_logger->stream() << msg.latin1(); + return *this; + } + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QList) + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QVector) + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QQueue) + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QSet) + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QLinkedList) + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QStack) + template + MessageBuilder& operator<<(const QPair& pair_) { + m_logger->stream() << ELPP_LITERAL("("); + operator << (static_cast(pair_.first)); + m_logger->stream() << ELPP_LITERAL(", "); + operator << (static_cast(pair_.second)); + m_logger->stream() << ELPP_LITERAL(")"); + return *this; + } + template + MessageBuilder& operator<<(const QMap& map_) { + m_logger->stream() << ELPP_LITERAL("["); + QList keys = map_.keys(); + typename QList::const_iterator begin = keys.begin(); + typename QList::const_iterator end = keys.end(); + int max_ = static_cast(base::consts::kMaxLogPerContainer); // to prevent warning + for (int index_ = 0; begin != end && index_ < max_; ++index_, ++begin) { + m_logger->stream() << ELPP_LITERAL("("); + operator << (static_cast(*begin)); + m_logger->stream() << ELPP_LITERAL(", "); + operator << (static_cast(map_.value(*begin))); + m_logger->stream() << ELPP_LITERAL(")"); + m_logger->stream() << ((index_ < keys.size() -1) ? m_containerLogSeparator : ELPP_LITERAL("")); + } + if (begin != end) { + m_logger->stream() << ELPP_LITERAL("..."); + } + m_logger->stream() << ELPP_LITERAL("]"); + return *this; + } + template + inline MessageBuilder& operator<<(const QMultiMap& map_) { + operator << (static_cast>(map_)); + return *this; + } + template + MessageBuilder& operator<<(const QHash& hash_) { + m_logger->stream() << ELPP_LITERAL("["); + QList keys = hash_.keys(); + typename QList::const_iterator begin = keys.begin(); + typename QList::const_iterator end = keys.end(); + int max_ = static_cast(base::consts::kMaxLogPerContainer); // prevent type warning + for (int index_ = 0; begin != end && index_ < max_; ++index_, ++begin) { + m_logger->stream() << ELPP_LITERAL("("); + operator << (static_cast(*begin)); + m_logger->stream() << ELPP_LITERAL(", "); + operator << (static_cast(hash_.value(*begin))); + m_logger->stream() << ELPP_LITERAL(")"); + m_logger->stream() << ((index_ < keys.size() -1) ? m_containerLogSeparator : ELPP_LITERAL("")); + } + if (begin != end) { + m_logger->stream() << ELPP_LITERAL("..."); + } + m_logger->stream() << ELPP_LITERAL("]"); + return *this; + } + template + inline MessageBuilder& operator<<(const QMultiHash& multiHash_) { + operator << (static_cast>(multiHash_)); + return *this; + } +#endif // defined(ELPP_QT_LOGGING) +#if defined(ELPP_BOOST_LOGGING) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::vector) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::stable_vector) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::list) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::deque) + ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(boost::container::map) + ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(boost::container::flat_map) + ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(boost::container::set) + ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(boost::container::flat_set) +#endif // defined(ELPP_BOOST_LOGGING) + + /// @brief Macro used internally that can be used externally to make containers easylogging++ friendly + /// + /// @detail This macro expands to write an ostream& operator<< for container. This container is expected to + /// have begin() and end() methods that return respective iterators + /// @param ContainerType Type of container e.g, MyList from WX_DECLARE_LIST(int, MyList); in wxwidgets + /// @param SizeMethod Method used to get size of container. + /// @param ElementInstance Instance of element to be fed out. Instance name is "elem". See WXELPP_ENABLED macro + /// for an example usage +#define MAKE_CONTAINERELPP_FRIENDLY(ContainerType, SizeMethod, ElementInstance) \ +el::base::type::ostream_t& operator<<(el::base::type::ostream_t& ss, const ContainerType& container) {\ +const el::base::type::char_t* sep = ELPP->hasFlag(el::LoggingFlag::NewLineForContainer) ? \ +ELPP_LITERAL("\n ") : ELPP_LITERAL(", ");\ +ContainerType::const_iterator elem = container.begin();\ +ContainerType::const_iterator endElem = container.end();\ +std::size_t size_ = container.SizeMethod; \ +ss << ELPP_LITERAL("[");\ +for (std::size_t i = 0; elem != endElem && i < el::base::consts::kMaxLogPerContainer; ++i, ++elem) { \ +ss << ElementInstance;\ +ss << ((i < size_ - 1) ? sep : ELPP_LITERAL(""));\ +}\ +if (elem != endElem) {\ +ss << ELPP_LITERAL("...");\ +}\ +ss << ELPP_LITERAL("]");\ +return ss;\ +} +#if defined(ELPP_WXWIDGETS_LOGGING) + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(wxVector) +# define ELPP_WX_PTR_ENABLED(ContainerType) MAKE_CONTAINERELPP_FRIENDLY(ContainerType, size(), *(*elem)) +# define ELPP_WX_ENABLED(ContainerType) MAKE_CONTAINERELPP_FRIENDLY(ContainerType, size(), (*elem)) +# define ELPP_WX_HASH_MAP_ENABLED(ContainerType) MAKE_CONTAINERELPP_FRIENDLY(ContainerType, size(), \ +ELPP_LITERAL("(") << elem->first << ELPP_LITERAL(", ") << elem->second << ELPP_LITERAL(")") +#else +# define ELPP_WX_PTR_ENABLED(ContainerType) +# define ELPP_WX_ENABLED(ContainerType) +# define ELPP_WX_HASH_MAP_ENABLED(ContainerType) +#endif // defined(ELPP_WXWIDGETS_LOGGING) + // Other classes + template + ELPP_SIMPLE_LOG(const Class&) +#undef ELPP_SIMPLE_LOG +#undef ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG +#undef ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG +#undef ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG +#undef ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG +#undef ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG + private: + Logger* m_logger; + const base::type::char_t* m_containerLogSeparator; + + template + MessageBuilder& writeIterator(Iterator begin_, Iterator end_, std::size_t size_) { + m_logger->stream() << ELPP_LITERAL("["); + for (std::size_t i = 0; begin_ != end_ && i < base::consts::kMaxLogPerContainer; ++i, ++begin_) { + operator << (*begin_); + m_logger->stream() << ((i < size_ - 1) ? m_containerLogSeparator : ELPP_LITERAL("")); + } + if (begin_ != end_) { + m_logger->stream() << ELPP_LITERAL("..."); + } + m_logger->stream() << ELPP_LITERAL("]"); + if (ELPP->hasFlag(LoggingFlag::AutoSpacing)) { + m_logger->stream() << " "; + } + return *this; + } +}; +/// @brief Writes nothing - Used when certain log is disabled +class NullWriter : base::NoCopy { + public: + NullWriter(void) {} + + // Null manipulator + inline NullWriter& operator<<(std::ostream& (*)(std::ostream&)) { + return *this; + } + + template + inline NullWriter& operator<<(const T&) { + return *this; + } + + inline operator bool() { + return true; + } +}; +/// @brief Main entry point of each logging +class Writer : base::NoCopy { + public: + Writer(Level level, const char* file, base::type::LineNumber line, + const char* func, base::DispatchAction dispatchAction = base::DispatchAction::NormalLog, + base::type::VerboseLevel verboseLevel = 0) : + m_msg(nullptr), m_level(level), m_file(file), m_line(line), m_func(func), m_verboseLevel(verboseLevel), + m_logger(nullptr), m_proceed(false), m_dispatchAction(dispatchAction) { + } + + Writer(LogMessage* msg, base::DispatchAction dispatchAction = base::DispatchAction::NormalLog) : + m_msg(msg), m_level(msg != nullptr ? msg->level() : Level::Unknown), + m_line(0), m_logger(nullptr), m_proceed(false), m_dispatchAction(dispatchAction) { + } + + virtual ~Writer(void) { + processDispatch(); + } + + template + inline Writer& operator<<(const T& log) { +#if ELPP_LOGGING_ENABLED + if (m_proceed) { + m_messageBuilder << log; + } +#endif // ELPP_LOGGING_ENABLED + return *this; + } + + inline Writer& operator<<(std::ostream& (*log)(std::ostream&)) { +#if ELPP_LOGGING_ENABLED + if (m_proceed) { + m_messageBuilder << log; + } +#endif // ELPP_LOGGING_ENABLED + return *this; + } + + inline operator bool() { + return true; + } + + Writer& construct(Logger* logger, bool needLock = true); + Writer& construct(int count, const char* loggerIds, ...); + protected: + LogMessage* m_msg; + Level m_level; + const char* m_file; + const base::type::LineNumber m_line; + const char* m_func; + base::type::VerboseLevel m_verboseLevel; + Logger* m_logger; + bool m_proceed; + base::MessageBuilder m_messageBuilder; + base::DispatchAction m_dispatchAction; + std::vector m_loggerIds; + friend class el::Helpers; + + void initializeLogger(const std::string& loggerId, bool lookup = true, bool needLock = true); + void processDispatch(); + void triggerDispatch(void); +}; +class PErrorWriter : public base::Writer { + public: + PErrorWriter(Level level, const char* file, base::type::LineNumber line, + const char* func, base::DispatchAction dispatchAction = base::DispatchAction::NormalLog, + base::type::VerboseLevel verboseLevel = 0) : + base::Writer(level, file, line, func, dispatchAction, verboseLevel) { + } + + virtual ~PErrorWriter(void); +}; +} // namespace base +// Logging from Logger class. Why this is here? Because we have Storage and Writer class available +#if ELPP_VARIADIC_TEMPLATES_SUPPORTED +template +void Logger::log_(Level level, int vlevel, const char* s, const T& value, const Args&... args) { + base::MessageBuilder b; + b.initialize(this); + while (*s) { + if (*s == base::consts::kFormatSpecifierChar) { + if (*(s + 1) == base::consts::kFormatSpecifierChar) { + ++s; + } else { + if (*(s + 1) == base::consts::kFormatSpecifierCharValue) { + ++s; + b << value; + log_(level, vlevel, ++s, args...); + return; + } + } + } + b << *s++; + } + ELPP_INTERNAL_ERROR("Too many arguments provided. Unable to handle. Please provide more format specifiers", false); +} +template +void Logger::log_(Level level, int vlevel, const T& log) { + if (level == Level::Verbose) { + if (ELPP->vRegistry()->allowed(vlevel, __FILE__)) { + base::Writer(Level::Verbose, "FILE", 0, "FUNCTION", + base::DispatchAction::NormalLog, vlevel).construct(this, false) << log; + } else { + stream().str(ELPP_LITERAL("")); + releaseLock(); + } + } else { + base::Writer(level, "FILE", 0, "FUNCTION").construct(this, false) << log; + } +} +template +inline void Logger::log(Level level, const char* s, const T& value, const Args&... args) { + acquireLock(); // released in Writer! + log_(level, 0, s, value, args...); +} +template +inline void Logger::log(Level level, const T& log) { + acquireLock(); // released in Writer! + log_(level, 0, log); +} +# if ELPP_VERBOSE_LOG +template +inline void Logger::verbose(int vlevel, const char* s, const T& value, const Args&... args) { + acquireLock(); // released in Writer! + log_(el::Level::Verbose, vlevel, s, value, args...); +} +template +inline void Logger::verbose(int vlevel, const T& log) { + acquireLock(); // released in Writer! + log_(el::Level::Verbose, vlevel, log); +} +# else +template +inline void Logger::verbose(int, const char*, const T&, const Args&...) { + return; +} +template +inline void Logger::verbose(int, const T&) { + return; +} +# endif // ELPP_VERBOSE_LOG +# define LOGGER_LEVEL_WRITERS(FUNCTION_NAME, LOG_LEVEL)\ +template \ +inline void Logger::FUNCTION_NAME(const char* s, const T& value, const Args&... args) {\ +log(LOG_LEVEL, s, value, args...);\ +}\ +template \ +inline void Logger::FUNCTION_NAME(const T& value) {\ +log(LOG_LEVEL, value);\ +} +# define LOGGER_LEVEL_WRITERS_DISABLED(FUNCTION_NAME, LOG_LEVEL)\ +template \ +inline void Logger::FUNCTION_NAME(const char*, const T&, const Args&...) {\ +return;\ +}\ +template \ +inline void Logger::FUNCTION_NAME(const T&) {\ +return;\ +} + +# if ELPP_INFO_LOG +LOGGER_LEVEL_WRITERS(info, Level::Info) +# else +LOGGER_LEVEL_WRITERS_DISABLED(info, Level::Info) +# endif // ELPP_INFO_LOG +# if ELPP_DEBUG_LOG +LOGGER_LEVEL_WRITERS(debug, Level::Debug) +# else +LOGGER_LEVEL_WRITERS_DISABLED(debug, Level::Debug) +# endif // ELPP_DEBUG_LOG +# if ELPP_WARNING_LOG +LOGGER_LEVEL_WRITERS(warn, Level::Warning) +# else +LOGGER_LEVEL_WRITERS_DISABLED(warn, Level::Warning) +# endif // ELPP_WARNING_LOG +# if ELPP_ERROR_LOG +LOGGER_LEVEL_WRITERS(error, Level::Error) +# else +LOGGER_LEVEL_WRITERS_DISABLED(error, Level::Error) +# endif // ELPP_ERROR_LOG +# if ELPP_FATAL_LOG +LOGGER_LEVEL_WRITERS(fatal, Level::Fatal) +# else +LOGGER_LEVEL_WRITERS_DISABLED(fatal, Level::Fatal) +# endif // ELPP_FATAL_LOG +# if ELPP_TRACE_LOG +LOGGER_LEVEL_WRITERS(trace, Level::Trace) +# else +LOGGER_LEVEL_WRITERS_DISABLED(trace, Level::Trace) +# endif // ELPP_TRACE_LOG +# undef LOGGER_LEVEL_WRITERS +# undef LOGGER_LEVEL_WRITERS_DISABLED +#endif // ELPP_VARIADIC_TEMPLATES_SUPPORTED +#if ELPP_COMPILER_MSVC +# define ELPP_VARIADIC_FUNC_MSVC(variadicFunction, variadicArgs) variadicFunction variadicArgs +# define ELPP_VARIADIC_FUNC_MSVC_RUN(variadicFunction, ...) ELPP_VARIADIC_FUNC_MSVC(variadicFunction, (__VA_ARGS__)) +# define el_getVALength(...) ELPP_VARIADIC_FUNC_MSVC_RUN(el_resolveVALength, 0, ## __VA_ARGS__,\ +10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#else +# if ELPP_COMPILER_CLANG +# define el_getVALength(...) el_resolveVALength(0, __VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +# else +# define el_getVALength(...) el_resolveVALength(0, ## __VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +# endif // ELPP_COMPILER_CLANG +#endif // ELPP_COMPILER_MSVC +#define el_resolveVALength(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N +#define ELPP_WRITE_LOG(writer, level, dispatchAction, ...) \ +writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#define ELPP_WRITE_LOG_IF(writer, condition, level, dispatchAction, ...) if (condition) \ +writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#define ELPP_WRITE_LOG_EVERY_N(writer, occasion, level, dispatchAction, ...) \ +ELPP->validateEveryNCounter(__FILE__, __LINE__, occasion) && \ +writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#define ELPP_WRITE_LOG_AFTER_N(writer, n, level, dispatchAction, ...) \ +ELPP->validateAfterNCounter(__FILE__, __LINE__, n) && \ +writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#define ELPP_WRITE_LOG_N_TIMES(writer, n, level, dispatchAction, ...) \ +ELPP->validateNTimesCounter(__FILE__, __LINE__, n) && \ +writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) +class PerformanceTrackingData { + public: + enum class DataType : base::type::EnumType { + Checkpoint = 1, Complete = 2 + }; + // Do not use constructor, will run into multiple definition error, use init(PerformanceTracker*) + explicit PerformanceTrackingData(DataType dataType) : m_performanceTracker(nullptr), + m_dataType(dataType), m_firstCheckpoint(false), m_file(""), m_line(0), m_func("") {} + inline const std::string* blockName(void) const; + inline const struct timeval* startTime(void) const; + inline const struct timeval* endTime(void) const; + inline const struct timeval* lastCheckpointTime(void) const; + inline const base::PerformanceTracker* performanceTracker(void) const { + return m_performanceTracker; + } + inline PerformanceTrackingData::DataType dataType(void) const { + return m_dataType; + } + inline bool firstCheckpoint(void) const { + return m_firstCheckpoint; + } + inline std::string checkpointId(void) const { + return m_checkpointId; + } + inline const char* file(void) const { + return m_file; + } + inline base::type::LineNumber line(void) const { + return m_line; + } + inline const char* func(void) const { + return m_func; + } + inline const base::type::string_t* formattedTimeTaken() const { + return &m_formattedTimeTaken; + } + inline const std::string& loggerId(void) const; + private: + base::PerformanceTracker* m_performanceTracker; + base::type::string_t m_formattedTimeTaken; + PerformanceTrackingData::DataType m_dataType; + bool m_firstCheckpoint; + std::string m_checkpointId; + const char* m_file; + base::type::LineNumber m_line; + const char* m_func; + inline void init(base::PerformanceTracker* performanceTracker, bool firstCheckpoint = false) { + m_performanceTracker = performanceTracker; + m_firstCheckpoint = firstCheckpoint; + } + + friend class el::base::PerformanceTracker; +}; +namespace base { +/// @brief Represents performanceTracker block of code that conditionally adds performance status to log +/// either when goes outside the scope of when checkpoint() is called +class PerformanceTracker : public base::threading::ThreadSafe, public Loggable { + public: + PerformanceTracker(const std::string& blockName, + base::TimestampUnit timestampUnit = base::TimestampUnit::Millisecond, + const std::string& loggerId = std::string(el::base::consts::kPerformanceLoggerId), + bool scopedLog = true, Level level = base::consts::kPerformanceTrackerDefaultLevel); + /// @brief Copy constructor + PerformanceTracker(const PerformanceTracker& t) : + m_blockName(t.m_blockName), m_timestampUnit(t.m_timestampUnit), m_loggerId(t.m_loggerId), m_scopedLog(t.m_scopedLog), + m_level(t.m_level), m_hasChecked(t.m_hasChecked), m_lastCheckpointId(t.m_lastCheckpointId), m_enabled(t.m_enabled), + m_startTime(t.m_startTime), m_endTime(t.m_endTime), m_lastCheckpointTime(t.m_lastCheckpointTime) { + } + virtual ~PerformanceTracker(void); + /// @brief A checkpoint for current performanceTracker block. + void checkpoint(const std::string& id = std::string(), const char* file = __FILE__, + base::type::LineNumber line = __LINE__, + const char* func = ""); + inline Level level(void) const { + return m_level; + } + private: + std::string m_blockName; + base::TimestampUnit m_timestampUnit; + std::string m_loggerId; + bool m_scopedLog; + Level m_level; + bool m_hasChecked; + std::string m_lastCheckpointId; + bool m_enabled; + struct timeval m_startTime, m_endTime, m_lastCheckpointTime; + + PerformanceTracker(void); + + friend class el::PerformanceTrackingData; + friend class base::DefaultPerformanceTrackingCallback; + + const inline base::type::string_t getFormattedTimeTaken() const { + return getFormattedTimeTaken(m_startTime); + } + + const base::type::string_t getFormattedTimeTaken(struct timeval startTime) const; + + virtual inline void log(el::base::type::ostream_t& os) const { + os << getFormattedTimeTaken(); + } +}; +class DefaultPerformanceTrackingCallback : public PerformanceTrackingCallback { + protected: + void handle(const PerformanceTrackingData* data) { + m_data = data; + base::type::stringstream_t ss; + if (m_data->dataType() == PerformanceTrackingData::DataType::Complete) { + ss << ELPP_LITERAL("Executed [") << m_data->blockName()->c_str() << ELPP_LITERAL("] in [") << + *m_data->formattedTimeTaken() << ELPP_LITERAL("]"); + } else { + ss << ELPP_LITERAL("Performance checkpoint"); + if (!m_data->checkpointId().empty()) { + ss << ELPP_LITERAL(" [") << m_data->checkpointId().c_str() << ELPP_LITERAL("]"); + } + ss << ELPP_LITERAL(" for block [") << m_data->blockName()->c_str() << ELPP_LITERAL("] : [") << + *m_data->performanceTracker(); + if (!ELPP->hasFlag(LoggingFlag::DisablePerformanceTrackingCheckpointComparison) + && m_data->performanceTracker()->m_hasChecked) { + ss << ELPP_LITERAL(" ([") << *m_data->formattedTimeTaken() << ELPP_LITERAL("] from "); + if (m_data->performanceTracker()->m_lastCheckpointId.empty()) { + ss << ELPP_LITERAL("last checkpoint"); + } else { + ss << ELPP_LITERAL("checkpoint '") << m_data->performanceTracker()->m_lastCheckpointId.c_str() << ELPP_LITERAL("'"); + } + ss << ELPP_LITERAL(")]"); + } else { + ss << ELPP_LITERAL("]"); + } + } + el::base::Writer(m_data->performanceTracker()->level(), m_data->file(), m_data->line(), m_data->func()).construct(1, + m_data->loggerId().c_str()) << ss.str(); + } + private: + const PerformanceTrackingData* m_data; +}; +} // namespace base +inline const std::string* PerformanceTrackingData::blockName() const { + return const_cast(&m_performanceTracker->m_blockName); +} +inline const struct timeval* PerformanceTrackingData::startTime() const { + return const_cast(&m_performanceTracker->m_startTime); +} +inline const struct timeval* PerformanceTrackingData::endTime() const { + return const_cast(&m_performanceTracker->m_endTime); +} +inline const struct timeval* PerformanceTrackingData::lastCheckpointTime() const { + return const_cast(&m_performanceTracker->m_lastCheckpointTime); +} +inline const std::string& PerformanceTrackingData::loggerId(void) const { + return m_performanceTracker->m_loggerId; +} +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) +namespace base { +/// @brief Contains some internal debugging tools like crash handler and stack tracer +namespace debug { +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) +class StackTrace : base::NoCopy { + public: + static const unsigned int kMaxStack = 64; + static const unsigned int kStackStart = 2; // We want to skip c'tor and StackTrace::generateNew() + class StackTraceEntry { + public: + StackTraceEntry(std::size_t index, const std::string& loc, const std::string& demang, const std::string& hex, + const std::string& addr); + StackTraceEntry(std::size_t index, const std::string& loc) : + m_index(index), + m_location(loc) { + } + std::size_t m_index; + std::string m_location; + std::string m_demangled; + std::string m_hex; + std::string m_addr; + friend std::ostream& operator<<(std::ostream& ss, const StackTraceEntry& si); + + private: + StackTraceEntry(void); + }; + + StackTrace(void) { + generateNew(); + } + + virtual ~StackTrace(void) { + } + + inline std::vector& getLatestStack(void) { + return m_stack; + } + + friend std::ostream& operator<<(std::ostream& os, const StackTrace& st); + + private: + std::vector m_stack; + + void generateNew(void); +}; +/// @brief Handles unexpected crashes +class CrashHandler : base::NoCopy { + public: + typedef void (*Handler)(int); + + explicit CrashHandler(bool useDefault); + explicit CrashHandler(const Handler& cHandler) { + setHandler(cHandler); + } + void setHandler(const Handler& cHandler); + + private: + Handler m_handler; +}; +#else +class CrashHandler { + public: + explicit CrashHandler(bool) {} +}; +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) +} // namespace debug +} // namespace base +extern base::debug::CrashHandler elCrashHandler; +#define MAKE_LOGGABLE(ClassType, ClassInstance, OutputStreamInstance) \ +el::base::type::ostream_t& operator<<(el::base::type::ostream_t& OutputStreamInstance, const ClassType& ClassInstance) +/// @brief Initializes syslog with process ID, options and facility. calls closelog() on d'tor +class SysLogInitializer { + public: + SysLogInitializer(const char* processIdent, int options = 0, int facility = 0) { +#if defined(ELPP_SYSLOG) + (void)base::consts::kSysLogLoggerId; + openlog(processIdent, options, facility); +#else + ELPP_UNUSED(processIdent); + ELPP_UNUSED(options); + ELPP_UNUSED(facility); +#endif // defined(ELPP_SYSLOG) + } + virtual ~SysLogInitializer(void) { +#if defined(ELPP_SYSLOG) + closelog(); +#endif // defined(ELPP_SYSLOG) + } +}; +#define ELPP_INITIALIZE_SYSLOG(id, opt, fac) el::SysLogInitializer elSyslogInit(id, opt, fac) +/// @brief Static helpers for developers +class Helpers : base::StaticClass { + public: + /// @brief Shares logging repository (base::Storage) + static inline void setStorage(base::type::StoragePointer storage) { + ELPP = storage; + } + /// @return Main storage repository + static inline base::type::StoragePointer storage() { + return ELPP; + } + /// @brief Sets application arguments and figures out whats active for logging and whats not. + static inline void setArgs(int argc, char** argv) { + ELPP->setApplicationArguments(argc, argv); + } + /// @copydoc setArgs(int argc, char** argv) + static inline void setArgs(int argc, const char** argv) { + ELPP->setApplicationArguments(argc, const_cast(argv)); + } + /// @brief Sets thread name for current thread. Requires std::thread + static inline void setThreadName(const std::string& name) { + ELPP->setThreadName(name); + } + static inline std::string getThreadName() { + return ELPP->getThreadName(base::threading::getCurrentThreadId()); + } +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) + /// @brief Overrides default crash handler and installs custom handler. + /// @param crashHandler A functor with no return type that takes single int argument. + /// Handler is a typedef with specification: void (*Handler)(int) + static inline void setCrashHandler(const el::base::debug::CrashHandler::Handler& crashHandler) { + el::elCrashHandler.setHandler(crashHandler); + } + /// @brief Abort due to crash with signal in parameter + /// @param sig Crash signal + static void crashAbort(int sig, const char* sourceFile = "", unsigned int long line = 0); + /// @brief Logs reason of crash as per sig + /// @param sig Crash signal + /// @param stackTraceIfAvailable Includes stack trace if available + /// @param level Logging level + /// @param logger Logger to use for logging + static void logCrashReason(int sig, bool stackTraceIfAvailable = false, + Level level = Level::Fatal, const char* logger = base::consts::kDefaultLoggerId); +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) + /// @brief Installs pre rollout callback, this callback is triggered when log file is about to be rolled out + /// (can be useful for backing up) + static inline void installPreRollOutCallback(const PreRollOutCallback& callback) { + ELPP->setPreRollOutCallback(callback); + } + /// @brief Uninstalls pre rollout callback + static inline void uninstallPreRollOutCallback(void) { + ELPP->unsetPreRollOutCallback(); + } + /// @brief Installs post log dispatch callback, this callback is triggered when log is dispatched + template + static inline bool installLogDispatchCallback(const std::string& id) { + return ELPP->installLogDispatchCallback(id); + } + /// @brief Uninstalls log dispatch callback + template + static inline void uninstallLogDispatchCallback(const std::string& id) { + ELPP->uninstallLogDispatchCallback(id); + } + template + static inline T* logDispatchCallback(const std::string& id) { + return ELPP->logDispatchCallback(id); + } +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + /// @brief Installs post performance tracking callback, this callback is triggered when performance tracking is finished + template + static inline bool installPerformanceTrackingCallback(const std::string& id) { + return ELPP->installPerformanceTrackingCallback(id); + } + /// @brief Uninstalls post performance tracking handler + template + static inline void uninstallPerformanceTrackingCallback(const std::string& id) { + ELPP->uninstallPerformanceTrackingCallback(id); + } + template + static inline T* performanceTrackingCallback(const std::string& id) { + return ELPP->performanceTrackingCallback(id); + } +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + /// @brief Converts template to std::string - useful for loggable classes to log containers within log(std::ostream&) const + template + static std::string convertTemplateToStdString(const T& templ) { + el::Logger* logger = + ELPP->registeredLoggers()->get(el::base::consts::kDefaultLoggerId); + if (logger == nullptr) { + return std::string(); + } + base::MessageBuilder b; + b.initialize(logger); + logger->acquireLock(); + b << templ; +#if defined(ELPP_UNICODE) + std::string s = std::string(logger->stream().str().begin(), logger->stream().str().end()); +#else + std::string s = logger->stream().str(); +#endif // defined(ELPP_UNICODE) + logger->stream().str(ELPP_LITERAL("")); + logger->releaseLock(); + return s; + } + /// @brief Returns command line arguments (pointer) provided to easylogging++ + static inline const el::base::utils::CommandLineArgs* commandLineArgs(void) { + return ELPP->commandLineArgs(); + } + /// @brief Reserve space for custom format specifiers for performance + /// @see std::vector::reserve + static inline void reserveCustomFormatSpecifiers(std::size_t size) { + ELPP->m_customFormatSpecifiers.reserve(size); + } + /// @brief Installs user defined format specifier and handler + static inline void installCustomFormatSpecifier(const CustomFormatSpecifier& customFormatSpecifier) { + ELPP->installCustomFormatSpecifier(customFormatSpecifier); + } + /// @brief Uninstalls user defined format specifier and handler + static inline bool uninstallCustomFormatSpecifier(const char* formatSpecifier) { + return ELPP->uninstallCustomFormatSpecifier(formatSpecifier); + } + /// @brief Returns true if custom format specifier is installed + static inline bool hasCustomFormatSpecifier(const char* formatSpecifier) { + return ELPP->hasCustomFormatSpecifier(formatSpecifier); + } + static inline void validateFileRolling(Logger* logger, Level level) { + if (ELPP == nullptr || logger == nullptr) return; + logger->m_typedConfigurations->validateFileRolling(level, ELPP->preRollOutCallback()); + } +}; +/// @brief Static helpers to deal with loggers and their configurations +class Loggers : base::StaticClass { + public: + /// @brief Gets existing or registers new logger + static Logger* getLogger(const std::string& identity, bool registerIfNotAvailable = true); + /// @brief Changes default log builder for future loggers + static void setDefaultLogBuilder(el::LogBuilderPtr& logBuilderPtr); + /// @brief Installs logger registration callback, this callback is triggered when new logger is registered + template + static inline bool installLoggerRegistrationCallback(const std::string& id) { + return ELPP->registeredLoggers()->installLoggerRegistrationCallback(id); + } + /// @brief Uninstalls log dispatch callback + template + static inline void uninstallLoggerRegistrationCallback(const std::string& id) { + ELPP->registeredLoggers()->uninstallLoggerRegistrationCallback(id); + } + template + static inline T* loggerRegistrationCallback(const std::string& id) { + return ELPP->registeredLoggers()->loggerRegistrationCallback(id); + } + /// @brief Unregisters logger - use it only when you know what you are doing, you may unregister + /// loggers initialized / used by third-party libs. + static bool unregisterLogger(const std::string& identity); + /// @brief Whether or not logger with id is registered + static bool hasLogger(const std::string& identity); + /// @brief Reconfigures specified logger with new configurations + static Logger* reconfigureLogger(Logger* logger, const Configurations& configurations); + /// @brief Reconfigures logger with new configurations after looking it up using identity + static Logger* reconfigureLogger(const std::string& identity, const Configurations& configurations); + /// @brief Reconfigures logger's single configuration + static Logger* reconfigureLogger(const std::string& identity, ConfigurationType configurationType, + const std::string& value); + /// @brief Reconfigures all the existing loggers with new configurations + static void reconfigureAllLoggers(const Configurations& configurations); + /// @brief Reconfigures single configuration for all the loggers + static inline void reconfigureAllLoggers(ConfigurationType configurationType, const std::string& value) { + reconfigureAllLoggers(Level::Global, configurationType, value); + } + /// @brief Reconfigures single configuration for all the loggers for specified level + static void reconfigureAllLoggers(Level level, ConfigurationType configurationType, + const std::string& value); + /// @brief Sets default configurations. This configuration is used for future (and conditionally for existing) loggers + static void setDefaultConfigurations(const Configurations& configurations, + bool reconfigureExistingLoggers = false); + /// @brief Returns current default + static const Configurations* defaultConfigurations(void); + /// @brief Returns log stream reference pointer if needed by user + static const base::LogStreamsReferenceMapPtr logStreamsReference(void); + /// @brief Default typed configuration based on existing defaultConf + static base::TypedConfigurations defaultTypedConfigurations(void); + /// @brief Populates all logger IDs in current repository. + /// @param [out] targetList List of fill up. + static std::vector* populateAllLoggerIds(std::vector* targetList); + /// @brief Sets configurations from global configuration file. + static void configureFromGlobal(const char* globalConfigurationFilePath); + /// @brief Configures loggers using command line arg. Ensure you have already set command line args, + /// @return False if invalid argument or argument with no value provided, true if attempted to configure logger. + /// If true is returned that does not mean it has been configured successfully, it only means that it + /// has attempted to configure logger using configuration file provided in argument + static bool configureFromArg(const char* argKey); + /// @brief Flushes all loggers for all levels - Be careful if you dont know how many loggers are registered + static void flushAll(void); + /// @brief Adds logging flag used internally. + static inline void addFlag(LoggingFlag flag) { + ELPP->addFlag(flag); + } + /// @brief Removes logging flag used internally. + static inline void removeFlag(LoggingFlag flag) { + ELPP->removeFlag(flag); + } + /// @brief Determines whether or not certain flag is active + static inline bool hasFlag(LoggingFlag flag) { + return ELPP->hasFlag(flag); + } + /// @brief Adds flag and removes it when scope goes out + class ScopedAddFlag { + public: + ScopedAddFlag(LoggingFlag flag) : m_flag(flag) { + Loggers::addFlag(m_flag); + } + ~ScopedAddFlag(void) { + Loggers::removeFlag(m_flag); + } + private: + LoggingFlag m_flag; + }; + /// @brief Removes flag and add it when scope goes out + class ScopedRemoveFlag { + public: + ScopedRemoveFlag(LoggingFlag flag) : m_flag(flag) { + Loggers::removeFlag(m_flag); + } + ~ScopedRemoveFlag(void) { + Loggers::addFlag(m_flag); + } + private: + LoggingFlag m_flag; + }; + /// @brief Sets hierarchy for logging. Needs to enable logging flag (HierarchicalLogging) + static void setLoggingLevel(Level level) { + ELPP->setLoggingLevel(level); + } + /// @brief Sets verbose level on the fly + static void setVerboseLevel(base::type::VerboseLevel level); + /// @brief Gets current verbose level + static base::type::VerboseLevel verboseLevel(void); + /// @brief Sets vmodules as specified (on the fly) + static void setVModules(const char* modules); + /// @brief Clears vmodules + static void clearVModules(void); +}; +class VersionInfo : base::StaticClass { + public: + /// @brief Current version number + static const std::string version(void); + + /// @brief Release date of current version + static const std::string releaseDate(void); +}; +} // namespace el +#undef VLOG_IS_ON +/// @brief Determines whether verbose logging is on for specified level current file. +#define VLOG_IS_ON(verboseLevel) (ELPP->vRegistry()->allowed(verboseLevel, __FILE__)) +#undef TIMED_BLOCK +#undef TIMED_SCOPE +#undef TIMED_SCOPE_IF +#undef TIMED_FUNC +#undef TIMED_FUNC_IF +#undef ELPP_MIN_UNIT +#if defined(ELPP_PERFORMANCE_MICROSECONDS) +# define ELPP_MIN_UNIT el::base::TimestampUnit::Microsecond +#else +# define ELPP_MIN_UNIT el::base::TimestampUnit::Millisecond +#endif // (defined(ELPP_PERFORMANCE_MICROSECONDS)) +/// @brief Performance tracked scope. Performance gets written when goes out of scope using +/// 'performance' logger. +/// +/// @detail Please note in order to check the performance at a certain time you can use obj->checkpoint(); +/// @see el::base::PerformanceTracker +/// @see el::base::PerformanceTracker::checkpoint +// Note: Do not surround this definition with null macro because of obj instance +#define TIMED_SCOPE_IF(obj, blockname, condition) el::base::type::PerformanceTrackerPtr obj( condition ? \ + new el::base::PerformanceTracker(blockname, ELPP_MIN_UNIT) : nullptr ) +#define TIMED_SCOPE(obj, blockname) TIMED_SCOPE_IF(obj, blockname, true) +#define TIMED_BLOCK(obj, blockName) for (struct { int i; el::base::type::PerformanceTrackerPtr timer; } obj = { 0, \ + el::base::type::PerformanceTrackerPtr(new el::base::PerformanceTracker(blockName, ELPP_MIN_UNIT)) }; obj.i < 1; ++obj.i) +/// @brief Performance tracked function. Performance gets written when goes out of scope using +/// 'performance' logger. +/// +/// @detail Please note in order to check the performance at a certain time you can use obj->checkpoint(); +/// @see el::base::PerformanceTracker +/// @see el::base::PerformanceTracker::checkpoint +#define TIMED_FUNC_IF(obj,condition) TIMED_SCOPE_IF(obj, ELPP_FUNC, condition) +#define TIMED_FUNC(obj) TIMED_SCOPE(obj, ELPP_FUNC) +#undef PERFORMANCE_CHECKPOINT +#undef PERFORMANCE_CHECKPOINT_WITH_ID +#define PERFORMANCE_CHECKPOINT(obj) obj->checkpoint(std::string(), __FILE__, __LINE__, ELPP_FUNC) +#define PERFORMANCE_CHECKPOINT_WITH_ID(obj, id) obj->checkpoint(id, __FILE__, __LINE__, ELPP_FUNC) +#undef ELPP_COUNTER +#undef ELPP_COUNTER_POS +/// @brief Gets hit counter for file/line +#define ELPP_COUNTER (ELPP->hitCounters()->getCounter(__FILE__, __LINE__)) +/// @brief Gets hit counter position for file/line, -1 if not registered yet +#define ELPP_COUNTER_POS (ELPP_COUNTER == nullptr ? -1 : ELPP_COUNTER->hitCounts()) +// Undef levels to support LOG(LEVEL) +#undef INFO +#undef WARNING +#undef DEBUG +#undef ERROR +#undef FATAL +#undef TRACE +#undef VERBOSE +// Undef existing +#undef CINFO +#undef CWARNING +#undef CDEBUG +#undef CFATAL +#undef CERROR +#undef CTRACE +#undef CVERBOSE +#undef CINFO_IF +#undef CWARNING_IF +#undef CDEBUG_IF +#undef CERROR_IF +#undef CFATAL_IF +#undef CTRACE_IF +#undef CVERBOSE_IF +#undef CINFO_EVERY_N +#undef CWARNING_EVERY_N +#undef CDEBUG_EVERY_N +#undef CERROR_EVERY_N +#undef CFATAL_EVERY_N +#undef CTRACE_EVERY_N +#undef CVERBOSE_EVERY_N +#undef CINFO_AFTER_N +#undef CWARNING_AFTER_N +#undef CDEBUG_AFTER_N +#undef CERROR_AFTER_N +#undef CFATAL_AFTER_N +#undef CTRACE_AFTER_N +#undef CVERBOSE_AFTER_N +#undef CINFO_N_TIMES +#undef CWARNING_N_TIMES +#undef CDEBUG_N_TIMES +#undef CERROR_N_TIMES +#undef CFATAL_N_TIMES +#undef CTRACE_N_TIMES +#undef CVERBOSE_N_TIMES +// Normal logs +#if ELPP_INFO_LOG +# define CINFO(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Info, dispatchAction, __VA_ARGS__) +#else +# define CINFO(writer, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_INFO_LOG +#if ELPP_WARNING_LOG +# define CWARNING(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Warning, dispatchAction, __VA_ARGS__) +#else +# define CWARNING(writer, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_WARNING_LOG +#if ELPP_DEBUG_LOG +# define CDEBUG(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Debug, dispatchAction, __VA_ARGS__) +#else +# define CDEBUG(writer, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_DEBUG_LOG +#if ELPP_ERROR_LOG +# define CERROR(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Error, dispatchAction, __VA_ARGS__) +#else +# define CERROR(writer, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_ERROR_LOG +#if ELPP_FATAL_LOG +# define CFATAL(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Fatal, dispatchAction, __VA_ARGS__) +#else +# define CFATAL(writer, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_FATAL_LOG +#if ELPP_TRACE_LOG +# define CTRACE(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Trace, dispatchAction, __VA_ARGS__) +#else +# define CTRACE(writer, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_TRACE_LOG +#if ELPP_VERBOSE_LOG +# define CVERBOSE(writer, vlevel, dispatchAction, ...) if (VLOG_IS_ON(vlevel)) writer(\ +el::Level::Verbose, __FILE__, __LINE__, ELPP_FUNC, dispatchAction, vlevel).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#else +# define CVERBOSE(writer, vlevel, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_VERBOSE_LOG +// Conditional logs +#if ELPP_INFO_LOG +# define CINFO_IF(writer, condition_, dispatchAction, ...) \ +ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Info, dispatchAction, __VA_ARGS__) +#else +# define CINFO_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_INFO_LOG +#if ELPP_WARNING_LOG +# define CWARNING_IF(writer, condition_, dispatchAction, ...)\ +ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Warning, dispatchAction, __VA_ARGS__) +#else +# define CWARNING_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_WARNING_LOG +#if ELPP_DEBUG_LOG +# define CDEBUG_IF(writer, condition_, dispatchAction, ...)\ +ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Debug, dispatchAction, __VA_ARGS__) +#else +# define CDEBUG_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_DEBUG_LOG +#if ELPP_ERROR_LOG +# define CERROR_IF(writer, condition_, dispatchAction, ...)\ +ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Error, dispatchAction, __VA_ARGS__) +#else +# define CERROR_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_ERROR_LOG +#if ELPP_FATAL_LOG +# define CFATAL_IF(writer, condition_, dispatchAction, ...)\ +ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Fatal, dispatchAction, __VA_ARGS__) +#else +# define CFATAL_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_FATAL_LOG +#if ELPP_TRACE_LOG +# define CTRACE_IF(writer, condition_, dispatchAction, ...)\ +ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Trace, dispatchAction, __VA_ARGS__) +#else +# define CTRACE_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_TRACE_LOG +#if ELPP_VERBOSE_LOG +# define CVERBOSE_IF(writer, condition_, vlevel, dispatchAction, ...) if (VLOG_IS_ON(vlevel) && (condition_)) writer( \ +el::Level::Verbose, __FILE__, __LINE__, ELPP_FUNC, dispatchAction, vlevel).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#else +# define CVERBOSE_IF(writer, condition_, vlevel, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_VERBOSE_LOG +// Occasional logs +#if ELPP_INFO_LOG +# define CINFO_EVERY_N(writer, occasion, dispatchAction, ...)\ +ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Info, dispatchAction, __VA_ARGS__) +#else +# define CINFO_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_INFO_LOG +#if ELPP_WARNING_LOG +# define CWARNING_EVERY_N(writer, occasion, dispatchAction, ...)\ +ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Warning, dispatchAction, __VA_ARGS__) +#else +# define CWARNING_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_WARNING_LOG +#if ELPP_DEBUG_LOG +# define CDEBUG_EVERY_N(writer, occasion, dispatchAction, ...)\ +ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Debug, dispatchAction, __VA_ARGS__) +#else +# define CDEBUG_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_DEBUG_LOG +#if ELPP_ERROR_LOG +# define CERROR_EVERY_N(writer, occasion, dispatchAction, ...)\ +ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Error, dispatchAction, __VA_ARGS__) +#else +# define CERROR_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_ERROR_LOG +#if ELPP_FATAL_LOG +# define CFATAL_EVERY_N(writer, occasion, dispatchAction, ...)\ +ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Fatal, dispatchAction, __VA_ARGS__) +#else +# define CFATAL_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_FATAL_LOG +#if ELPP_TRACE_LOG +# define CTRACE_EVERY_N(writer, occasion, dispatchAction, ...)\ +ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Trace, dispatchAction, __VA_ARGS__) +#else +# define CTRACE_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_TRACE_LOG +#if ELPP_VERBOSE_LOG +# define CVERBOSE_EVERY_N(writer, occasion, vlevel, dispatchAction, ...)\ +CVERBOSE_IF(writer, ELPP->validateEveryNCounter(__FILE__, __LINE__, occasion), vlevel, dispatchAction, __VA_ARGS__) +#else +# define CVERBOSE_EVERY_N(writer, occasion, vlevel, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_VERBOSE_LOG +// After N logs +#if ELPP_INFO_LOG +# define CINFO_AFTER_N(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Info, dispatchAction, __VA_ARGS__) +#else +# define CINFO_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_INFO_LOG +#if ELPP_WARNING_LOG +# define CWARNING_AFTER_N(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Warning, dispatchAction, __VA_ARGS__) +#else +# define CWARNING_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_WARNING_LOG +#if ELPP_DEBUG_LOG +# define CDEBUG_AFTER_N(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Debug, dispatchAction, __VA_ARGS__) +#else +# define CDEBUG_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_DEBUG_LOG +#if ELPP_ERROR_LOG +# define CERROR_AFTER_N(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Error, dispatchAction, __VA_ARGS__) +#else +# define CERROR_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_ERROR_LOG +#if ELPP_FATAL_LOG +# define CFATAL_AFTER_N(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Fatal, dispatchAction, __VA_ARGS__) +#else +# define CFATAL_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_FATAL_LOG +#if ELPP_TRACE_LOG +# define CTRACE_AFTER_N(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Trace, dispatchAction, __VA_ARGS__) +#else +# define CTRACE_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_TRACE_LOG +#if ELPP_VERBOSE_LOG +# define CVERBOSE_AFTER_N(writer, n, vlevel, dispatchAction, ...)\ +CVERBOSE_IF(writer, ELPP->validateAfterNCounter(__FILE__, __LINE__, n), vlevel, dispatchAction, __VA_ARGS__) +#else +# define CVERBOSE_AFTER_N(writer, n, vlevel, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_VERBOSE_LOG +// N Times logs +#if ELPP_INFO_LOG +# define CINFO_N_TIMES(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Info, dispatchAction, __VA_ARGS__) +#else +# define CINFO_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_INFO_LOG +#if ELPP_WARNING_LOG +# define CWARNING_N_TIMES(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Warning, dispatchAction, __VA_ARGS__) +#else +# define CWARNING_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_WARNING_LOG +#if ELPP_DEBUG_LOG +# define CDEBUG_N_TIMES(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Debug, dispatchAction, __VA_ARGS__) +#else +# define CDEBUG_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_DEBUG_LOG +#if ELPP_ERROR_LOG +# define CERROR_N_TIMES(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Error, dispatchAction, __VA_ARGS__) +#else +# define CERROR_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_ERROR_LOG +#if ELPP_FATAL_LOG +# define CFATAL_N_TIMES(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Fatal, dispatchAction, __VA_ARGS__) +#else +# define CFATAL_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_FATAL_LOG +#if ELPP_TRACE_LOG +# define CTRACE_N_TIMES(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Trace, dispatchAction, __VA_ARGS__) +#else +# define CTRACE_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_TRACE_LOG +#if ELPP_VERBOSE_LOG +# define CVERBOSE_N_TIMES(writer, n, vlevel, dispatchAction, ...)\ +CVERBOSE_IF(writer, ELPP->validateNTimesCounter(__FILE__, __LINE__, n), vlevel, dispatchAction, __VA_ARGS__) +#else +# define CVERBOSE_N_TIMES(writer, n, vlevel, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_VERBOSE_LOG +// +// Custom Loggers - Requires (level, dispatchAction, loggerId/s) +// +// undef existing +#undef CLOG +#undef CLOG_VERBOSE +#undef CVLOG +#undef CLOG_IF +#undef CLOG_VERBOSE_IF +#undef CVLOG_IF +#undef CLOG_EVERY_N +#undef CVLOG_EVERY_N +#undef CLOG_AFTER_N +#undef CVLOG_AFTER_N +#undef CLOG_N_TIMES +#undef CVLOG_N_TIMES +// Normal logs +#define CLOG(LEVEL, ...)\ +C##LEVEL(el::base::Writer, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CVLOG(vlevel, ...) CVERBOSE(el::base::Writer, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) +// Conditional logs +#define CLOG_IF(condition, LEVEL, ...)\ +C##LEVEL##_IF(el::base::Writer, condition, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CVLOG_IF(condition, vlevel, ...)\ +CVERBOSE_IF(el::base::Writer, condition, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) +// Hit counts based logs +#define CLOG_EVERY_N(n, LEVEL, ...)\ +C##LEVEL##_EVERY_N(el::base::Writer, n, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CVLOG_EVERY_N(n, vlevel, ...)\ +CVERBOSE_EVERY_N(el::base::Writer, n, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CLOG_AFTER_N(n, LEVEL, ...)\ +C##LEVEL##_AFTER_N(el::base::Writer, n, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CVLOG_AFTER_N(n, vlevel, ...)\ +CVERBOSE_AFTER_N(el::base::Writer, n, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CLOG_N_TIMES(n, LEVEL, ...)\ +C##LEVEL##_N_TIMES(el::base::Writer, n, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CVLOG_N_TIMES(n, vlevel, ...)\ +CVERBOSE_N_TIMES(el::base::Writer, n, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) +// +// Default Loggers macro using CLOG(), CLOG_VERBOSE() and CVLOG() macros +// +// undef existing +#undef LOG +#undef VLOG +#undef LOG_IF +#undef VLOG_IF +#undef LOG_EVERY_N +#undef VLOG_EVERY_N +#undef LOG_AFTER_N +#undef VLOG_AFTER_N +#undef LOG_N_TIMES +#undef VLOG_N_TIMES +#undef ELPP_CURR_FILE_LOGGER_ID +#if defined(ELPP_DEFAULT_LOGGER) +# define ELPP_CURR_FILE_LOGGER_ID ELPP_DEFAULT_LOGGER +#else +# define ELPP_CURR_FILE_LOGGER_ID el::base::consts::kDefaultLoggerId +#endif +#undef ELPP_TRACE +#define ELPP_TRACE CLOG(TRACE, ELPP_CURR_FILE_LOGGER_ID) +// Normal logs +#define LOG(LEVEL) CLOG(LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define VLOG(vlevel) CVLOG(vlevel, ELPP_CURR_FILE_LOGGER_ID) +// Conditional logs +#define LOG_IF(condition, LEVEL) CLOG_IF(condition, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define VLOG_IF(condition, vlevel) CVLOG_IF(condition, vlevel, ELPP_CURR_FILE_LOGGER_ID) +// Hit counts based logs +#define LOG_EVERY_N(n, LEVEL) CLOG_EVERY_N(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define VLOG_EVERY_N(n, vlevel) CVLOG_EVERY_N(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) +#define LOG_AFTER_N(n, LEVEL) CLOG_AFTER_N(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define VLOG_AFTER_N(n, vlevel) CVLOG_AFTER_N(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) +#define LOG_N_TIMES(n, LEVEL) CLOG_N_TIMES(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define VLOG_N_TIMES(n, vlevel) CVLOG_N_TIMES(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) +// Generic PLOG() +#undef CPLOG +#undef CPLOG_IF +#undef PLOG +#undef PLOG_IF +#undef DCPLOG +#undef DCPLOG_IF +#undef DPLOG +#undef DPLOG_IF +#define CPLOG(LEVEL, ...)\ +C##LEVEL(el::base::PErrorWriter, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CPLOG_IF(condition, LEVEL, ...)\ +C##LEVEL##_IF(el::base::PErrorWriter, condition, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define DCPLOG(LEVEL, ...)\ +if (ELPP_DEBUG_LOG) C##LEVEL(el::base::PErrorWriter, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define DCPLOG_IF(condition, LEVEL, ...)\ +C##LEVEL##_IF(el::base::PErrorWriter, (ELPP_DEBUG_LOG) && (condition), el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define PLOG(LEVEL) CPLOG(LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define PLOG_IF(condition, LEVEL) CPLOG_IF(condition, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DPLOG(LEVEL) DCPLOG(LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DPLOG_IF(condition, LEVEL) DCPLOG_IF(condition, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +// Generic SYSLOG() +#undef CSYSLOG +#undef CSYSLOG_IF +#undef CSYSLOG_EVERY_N +#undef CSYSLOG_AFTER_N +#undef CSYSLOG_N_TIMES +#undef SYSLOG +#undef SYSLOG_IF +#undef SYSLOG_EVERY_N +#undef SYSLOG_AFTER_N +#undef SYSLOG_N_TIMES +#undef DCSYSLOG +#undef DCSYSLOG_IF +#undef DCSYSLOG_EVERY_N +#undef DCSYSLOG_AFTER_N +#undef DCSYSLOG_N_TIMES +#undef DSYSLOG +#undef DSYSLOG_IF +#undef DSYSLOG_EVERY_N +#undef DSYSLOG_AFTER_N +#undef DSYSLOG_N_TIMES +#if defined(ELPP_SYSLOG) +# define CSYSLOG(LEVEL, ...)\ +C##LEVEL(el::base::Writer, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define CSYSLOG_IF(condition, LEVEL, ...)\ +C##LEVEL##_IF(el::base::Writer, condition, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define CSYSLOG_EVERY_N(n, LEVEL, ...) C##LEVEL##_EVERY_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define CSYSLOG_AFTER_N(n, LEVEL, ...) C##LEVEL##_AFTER_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define CSYSLOG_N_TIMES(n, LEVEL, ...) C##LEVEL##_N_TIMES(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define SYSLOG(LEVEL) CSYSLOG(LEVEL, el::base::consts::kSysLogLoggerId) +# define SYSLOG_IF(condition, LEVEL) CSYSLOG_IF(condition, LEVEL, el::base::consts::kSysLogLoggerId) +# define SYSLOG_EVERY_N(n, LEVEL) CSYSLOG_EVERY_N(n, LEVEL, el::base::consts::kSysLogLoggerId) +# define SYSLOG_AFTER_N(n, LEVEL) CSYSLOG_AFTER_N(n, LEVEL, el::base::consts::kSysLogLoggerId) +# define SYSLOG_N_TIMES(n, LEVEL) CSYSLOG_N_TIMES(n, LEVEL, el::base::consts::kSysLogLoggerId) +# define DCSYSLOG(LEVEL, ...) if (ELPP_DEBUG_LOG) C##LEVEL(el::base::Writer, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define DCSYSLOG_IF(condition, LEVEL, ...)\ +C##LEVEL##_IF(el::base::Writer, (ELPP_DEBUG_LOG) && (condition), el::base::DispatchAction::SysLog, __VA_ARGS__) +# define DCSYSLOG_EVERY_N(n, LEVEL, ...)\ +if (ELPP_DEBUG_LOG) C##LEVEL##_EVERY_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define DCSYSLOG_AFTER_N(n, LEVEL, ...)\ +if (ELPP_DEBUG_LOG) C##LEVEL##_AFTER_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define DCSYSLOG_N_TIMES(n, LEVEL, ...)\ +if (ELPP_DEBUG_LOG) C##LEVEL##_EVERY_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define DSYSLOG(LEVEL) DCSYSLOG(LEVEL, el::base::consts::kSysLogLoggerId) +# define DSYSLOG_IF(condition, LEVEL) DCSYSLOG_IF(condition, LEVEL, el::base::consts::kSysLogLoggerId) +# define DSYSLOG_EVERY_N(n, LEVEL) DCSYSLOG_EVERY_N(n, LEVEL, el::base::consts::kSysLogLoggerId) +# define DSYSLOG_AFTER_N(n, LEVEL) DCSYSLOG_AFTER_N(n, LEVEL, el::base::consts::kSysLogLoggerId) +# define DSYSLOG_N_TIMES(n, LEVEL) DCSYSLOG_N_TIMES(n, LEVEL, el::base::consts::kSysLogLoggerId) +#else +# define CSYSLOG(LEVEL, ...) el::base::NullWriter() +# define CSYSLOG_IF(condition, LEVEL, ...) el::base::NullWriter() +# define CSYSLOG_EVERY_N(n, LEVEL, ...) el::base::NullWriter() +# define CSYSLOG_AFTER_N(n, LEVEL, ...) el::base::NullWriter() +# define CSYSLOG_N_TIMES(n, LEVEL, ...) el::base::NullWriter() +# define SYSLOG(LEVEL) el::base::NullWriter() +# define SYSLOG_IF(condition, LEVEL) el::base::NullWriter() +# define SYSLOG_EVERY_N(n, LEVEL) el::base::NullWriter() +# define SYSLOG_AFTER_N(n, LEVEL) el::base::NullWriter() +# define SYSLOG_N_TIMES(n, LEVEL) el::base::NullWriter() +# define DCSYSLOG(LEVEL, ...) el::base::NullWriter() +# define DCSYSLOG_IF(condition, LEVEL, ...) el::base::NullWriter() +# define DCSYSLOG_EVERY_N(n, LEVEL, ...) el::base::NullWriter() +# define DCSYSLOG_AFTER_N(n, LEVEL, ...) el::base::NullWriter() +# define DCSYSLOG_N_TIMES(n, LEVEL, ...) el::base::NullWriter() +# define DSYSLOG(LEVEL) el::base::NullWriter() +# define DSYSLOG_IF(condition, LEVEL) el::base::NullWriter() +# define DSYSLOG_EVERY_N(n, LEVEL) el::base::NullWriter() +# define DSYSLOG_AFTER_N(n, LEVEL) el::base::NullWriter() +# define DSYSLOG_N_TIMES(n, LEVEL) el::base::NullWriter() +#endif // defined(ELPP_SYSLOG) +// +// Custom Debug Only Loggers - Requires (level, loggerId/s) +// +// undef existing +#undef DCLOG +#undef DCVLOG +#undef DCLOG_IF +#undef DCVLOG_IF +#undef DCLOG_EVERY_N +#undef DCVLOG_EVERY_N +#undef DCLOG_AFTER_N +#undef DCVLOG_AFTER_N +#undef DCLOG_N_TIMES +#undef DCVLOG_N_TIMES +// Normal logs +#define DCLOG(LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG(LEVEL, __VA_ARGS__) +#define DCLOG_VERBOSE(vlevel, ...) if (ELPP_DEBUG_LOG) CLOG_VERBOSE(vlevel, __VA_ARGS__) +#define DCVLOG(vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG(vlevel, __VA_ARGS__) +// Conditional logs +#define DCLOG_IF(condition, LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG_IF(condition, LEVEL, __VA_ARGS__) +#define DCVLOG_IF(condition, vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG_IF(condition, vlevel, __VA_ARGS__) +// Hit counts based logs +#define DCLOG_EVERY_N(n, LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG_EVERY_N(n, LEVEL, __VA_ARGS__) +#define DCVLOG_EVERY_N(n, vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG_EVERY_N(n, vlevel, __VA_ARGS__) +#define DCLOG_AFTER_N(n, LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG_AFTER_N(n, LEVEL, __VA_ARGS__) +#define DCVLOG_AFTER_N(n, vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG_AFTER_N(n, vlevel, __VA_ARGS__) +#define DCLOG_N_TIMES(n, LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG_N_TIMES(n, LEVEL, __VA_ARGS__) +#define DCVLOG_N_TIMES(n, vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG_N_TIMES(n, vlevel, __VA_ARGS__) +// +// Default Debug Only Loggers macro using CLOG(), CLOG_VERBOSE() and CVLOG() macros +// +#if !defined(ELPP_NO_DEBUG_MACROS) +// undef existing +#undef DLOG +#undef DVLOG +#undef DLOG_IF +#undef DVLOG_IF +#undef DLOG_EVERY_N +#undef DVLOG_EVERY_N +#undef DLOG_AFTER_N +#undef DVLOG_AFTER_N +#undef DLOG_N_TIMES +#undef DVLOG_N_TIMES +// Normal logs +#define DLOG(LEVEL) DCLOG(LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DVLOG(vlevel) DCVLOG(vlevel, ELPP_CURR_FILE_LOGGER_ID) +// Conditional logs +#define DLOG_IF(condition, LEVEL) DCLOG_IF(condition, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DVLOG_IF(condition, vlevel) DCVLOG_IF(condition, vlevel, ELPP_CURR_FILE_LOGGER_ID) +// Hit counts based logs +#define DLOG_EVERY_N(n, LEVEL) DCLOG_EVERY_N(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DVLOG_EVERY_N(n, vlevel) DCVLOG_EVERY_N(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) +#define DLOG_AFTER_N(n, LEVEL) DCLOG_AFTER_N(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DVLOG_AFTER_N(n, vlevel) DCVLOG_AFTER_N(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) +#define DLOG_N_TIMES(n, LEVEL) DCLOG_N_TIMES(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DVLOG_N_TIMES(n, vlevel) DCVLOG_N_TIMES(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) +#endif // defined(ELPP_NO_DEBUG_MACROS) +#if !defined(ELPP_NO_CHECK_MACROS) +// Check macros +#undef CCHECK +#undef CPCHECK +#undef CCHECK_EQ +#undef CCHECK_NE +#undef CCHECK_LT +#undef CCHECK_GT +#undef CCHECK_LE +#undef CCHECK_GE +#undef CCHECK_BOUNDS +#undef CCHECK_NOTNULL +#undef CCHECK_STRCASEEQ +#undef CCHECK_STRCASENE +#undef CHECK +#undef PCHECK +#undef CHECK_EQ +#undef CHECK_NE +#undef CHECK_LT +#undef CHECK_GT +#undef CHECK_LE +#undef CHECK_GE +#undef CHECK_BOUNDS +#undef CHECK_NOTNULL +#undef CHECK_STRCASEEQ +#undef CHECK_STRCASENE +#define CCHECK(condition, ...) CLOG_IF(!(condition), FATAL, __VA_ARGS__) << "Check failed: [" << #condition << "] " +#define CPCHECK(condition, ...) CPLOG_IF(!(condition), FATAL, __VA_ARGS__) << "Check failed: [" << #condition << "] " +#define CHECK(condition) CCHECK(condition, ELPP_CURR_FILE_LOGGER_ID) +#define PCHECK(condition) CPCHECK(condition, ELPP_CURR_FILE_LOGGER_ID) +#define CCHECK_EQ(a, b, ...) CCHECK(a == b, __VA_ARGS__) +#define CCHECK_NE(a, b, ...) CCHECK(a != b, __VA_ARGS__) +#define CCHECK_LT(a, b, ...) CCHECK(a < b, __VA_ARGS__) +#define CCHECK_GT(a, b, ...) CCHECK(a > b, __VA_ARGS__) +#define CCHECK_LE(a, b, ...) CCHECK(a <= b, __VA_ARGS__) +#define CCHECK_GE(a, b, ...) CCHECK(a >= b, __VA_ARGS__) +#define CCHECK_BOUNDS(val, min, max, ...) CCHECK(val >= min && val <= max, __VA_ARGS__) +#define CHECK_EQ(a, b) CCHECK_EQ(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_NE(a, b) CCHECK_NE(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_LT(a, b) CCHECK_LT(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_GT(a, b) CCHECK_GT(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_LE(a, b) CCHECK_LE(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_GE(a, b) CCHECK_GE(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_BOUNDS(val, min, max) CCHECK_BOUNDS(val, min, max, ELPP_CURR_FILE_LOGGER_ID) +#define CCHECK_NOTNULL(ptr, ...) CCHECK((ptr) != nullptr, __VA_ARGS__) +#define CCHECK_STREQ(str1, str2, ...) CLOG_IF(!el::base::utils::Str::cStringEq(str1, str2), FATAL, __VA_ARGS__) \ +<< "Check failed: [" << #str1 << " == " << #str2 << "] " +#define CCHECK_STRNE(str1, str2, ...) CLOG_IF(el::base::utils::Str::cStringEq(str1, str2), FATAL, __VA_ARGS__) \ +<< "Check failed: [" << #str1 << " != " << #str2 << "] " +#define CCHECK_STRCASEEQ(str1, str2, ...) CLOG_IF(!el::base::utils::Str::cStringCaseEq(str1, str2), FATAL, __VA_ARGS__) \ +<< "Check failed: [" << #str1 << " == " << #str2 << "] " +#define CCHECK_STRCASENE(str1, str2, ...) CLOG_IF(el::base::utils::Str::cStringCaseEq(str1, str2), FATAL, __VA_ARGS__) \ +<< "Check failed: [" << #str1 << " != " << #str2 << "] " +#define CHECK_NOTNULL(ptr) CCHECK_NOTNULL((ptr), ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_STREQ(str1, str2) CCHECK_STREQ(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_STRNE(str1, str2) CCHECK_STRNE(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_STRCASEEQ(str1, str2) CCHECK_STRCASEEQ(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_STRCASENE(str1, str2) CCHECK_STRCASENE(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#undef DCCHECK +#undef DCCHECK_EQ +#undef DCCHECK_NE +#undef DCCHECK_LT +#undef DCCHECK_GT +#undef DCCHECK_LE +#undef DCCHECK_GE +#undef DCCHECK_BOUNDS +#undef DCCHECK_NOTNULL +#undef DCCHECK_STRCASEEQ +#undef DCCHECK_STRCASENE +#undef DCPCHECK +#undef DCHECK +#undef DCHECK_EQ +#undef DCHECK_NE +#undef DCHECK_LT +#undef DCHECK_GT +#undef DCHECK_LE +#undef DCHECK_GE +#undef DCHECK_BOUNDS_ +#undef DCHECK_NOTNULL +#undef DCHECK_STRCASEEQ +#undef DCHECK_STRCASENE +#undef DPCHECK +#define DCCHECK(condition, ...) if (ELPP_DEBUG_LOG) CCHECK(condition, __VA_ARGS__) +#define DCCHECK_EQ(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_EQ(a, b, __VA_ARGS__) +#define DCCHECK_NE(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_NE(a, b, __VA_ARGS__) +#define DCCHECK_LT(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_LT(a, b, __VA_ARGS__) +#define DCCHECK_GT(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_GT(a, b, __VA_ARGS__) +#define DCCHECK_LE(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_LE(a, b, __VA_ARGS__) +#define DCCHECK_GE(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_GE(a, b, __VA_ARGS__) +#define DCCHECK_BOUNDS(val, min, max, ...) if (ELPP_DEBUG_LOG) CCHECK_BOUNDS(val, min, max, __VA_ARGS__) +#define DCCHECK_NOTNULL(ptr, ...) if (ELPP_DEBUG_LOG) CCHECK_NOTNULL((ptr), __VA_ARGS__) +#define DCCHECK_STREQ(str1, str2, ...) if (ELPP_DEBUG_LOG) CCHECK_STREQ(str1, str2, __VA_ARGS__) +#define DCCHECK_STRNE(str1, str2, ...) if (ELPP_DEBUG_LOG) CCHECK_STRNE(str1, str2, __VA_ARGS__) +#define DCCHECK_STRCASEEQ(str1, str2, ...) if (ELPP_DEBUG_LOG) CCHECK_STRCASEEQ(str1, str2, __VA_ARGS__) +#define DCCHECK_STRCASENE(str1, str2, ...) if (ELPP_DEBUG_LOG) CCHECK_STRCASENE(str1, str2, __VA_ARGS__) +#define DCPCHECK(condition, ...) if (ELPP_DEBUG_LOG) CPCHECK(condition, __VA_ARGS__) +#define DCHECK(condition) DCCHECK(condition, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_EQ(a, b) DCCHECK_EQ(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_NE(a, b) DCCHECK_NE(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_LT(a, b) DCCHECK_LT(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_GT(a, b) DCCHECK_GT(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_LE(a, b) DCCHECK_LE(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_GE(a, b) DCCHECK_GE(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_BOUNDS(val, min, max) DCCHECK_BOUNDS(val, min, max, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_NOTNULL(ptr) DCCHECK_NOTNULL((ptr), ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_STREQ(str1, str2) DCCHECK_STREQ(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_STRNE(str1, str2) DCCHECK_STRNE(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_STRCASEEQ(str1, str2) DCCHECK_STRCASEEQ(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_STRCASENE(str1, str2) DCCHECK_STRCASENE(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define DPCHECK(condition) DCPCHECK(condition, ELPP_CURR_FILE_LOGGER_ID) +#endif // defined(ELPP_NO_CHECK_MACROS) +#if defined(ELPP_DISABLE_DEFAULT_CRASH_HANDLING) +# define ELPP_USE_DEF_CRASH_HANDLER false +#else +# define ELPP_USE_DEF_CRASH_HANDLER true +#endif // defined(ELPP_DISABLE_DEFAULT_CRASH_HANDLING) +#define ELPP_CRASH_HANDLER_INIT +#define ELPP_INIT_EASYLOGGINGPP(val) \ +namespace el { \ +namespace base { \ +el::base::type::StoragePointer elStorage(val); \ +} \ +el::base::debug::CrashHandler elCrashHandler(ELPP_USE_DEF_CRASH_HANDLER); \ +} + +#if ELPP_ASYNC_LOGGING +# define INITIALIZE_EASYLOGGINGPP ELPP_INIT_EASYLOGGINGPP(new el::base::Storage(el::LogBuilderPtr(new el::base::DefaultLogBuilder()),\ +new el::base::AsyncDispatchWorker())) +#else +# define INITIALIZE_EASYLOGGINGPP ELPP_INIT_EASYLOGGINGPP(new el::base::Storage(el::LogBuilderPtr(new el::base::DefaultLogBuilder()))) +#endif // ELPP_ASYNC_LOGGING +#define INITIALIZE_NULL_EASYLOGGINGPP \ +namespace el {\ +namespace base {\ +el::base::type::StoragePointer elStorage;\ +}\ +el::base::debug::CrashHandler elCrashHandler(ELPP_USE_DEF_CRASH_HANDLER);\ +} +#define SHARE_EASYLOGGINGPP(initializedStorage)\ +namespace el {\ +namespace base {\ +el::base::type::StoragePointer elStorage(initializedStorage);\ +}\ +el::base::debug::CrashHandler elCrashHandler(ELPP_USE_DEF_CRASH_HANDLER);\ +} + +#if defined(ELPP_UNICODE) +# define START_EASYLOGGINGPP(argc, argv) el::Helpers::setArgs(argc, argv); std::locale::global(std::locale("")) +#else +# define START_EASYLOGGINGPP(argc, argv) el::Helpers::setArgs(argc, argv) +#endif // defined(ELPP_UNICODE) +#endif // EASYLOGGINGPP_H diff --git a/src/forward.cc b/src/forward.cc deleted file mode 100644 index b014607..0000000 --- a/src/forward.cc +++ /dev/null @@ -1,42 +0,0 @@ -#include "pch.h" -#include "forward.h" - -#include "common.h" -#include "get_db_handle.h" -#include "wechat_data.h" -#define WX_FORWARD_MSG_OFFSET 0xce6730 -#define WX_INIT_CHAT_MSG_OFFSET 0xf59e40 - -int ForwardMsg(wchar_t *wxid, unsigned long long msgid) { - int success = 0; - - int db_index = 0; - int localid = GetLocalIdByMsgId(msgid, db_index); - - if (localid == 0) return 0; - WeChatString to_user(wxid); - DWORD base = GetWeChatWinBase(); - DWORD forward_msg_addr = base + WX_FORWARD_MSG_OFFSET; - DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; - __asm { - PUSHAD - PUSHFD - MOV EDX, DWORD PTR [db_index] - PUSH EDX - MOV EAX, DWORD PTR [localid] - PUSH EAX - SUB ESP,0x14 - MOV ECX,ESP - LEA ESI,to_user - PUSH ESI - CALL init_chat_msg_addr - XOR ECX,ECX - CALL forward_msg_addr - MOVZX EAX,AL - MOV success,EAX - ADD ESP,0x1c - POPFD - POPAD - } - return success; -} \ No newline at end of file diff --git a/src/forward.h b/src/forward.h deleted file mode 100644 index cce23c2..0000000 --- a/src/forward.h +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef FORWARD_H_ -#define FORWARD_H_ - -int ForwardMsg(wchar_t *wxid, unsigned long long msgid); -#endif \ No newline at end of file diff --git a/src/get_db_handle.h b/src/get_db_handle.h deleted file mode 100644 index f48a539..0000000 --- a/src/get_db_handle.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef GET_DB_HANDLE_H_ -#define GET_DB_HANDLE_H_ -#include "windows.h" -#include -#include - -std::vector GetDbHandles(); -DWORD GetDbHandleByDbName(wchar_t *dbname); -unsigned int GetLocalIdByMsgId(ULONG64 msgid, int &dbIndex); -std::vector GetChatMsgByMsgId(ULONG64 msgid); - -std::string GetVoiceBuffByMsgId(ULONG64 msgid); -#endif \ No newline at end of file diff --git a/src/global_context.cc b/src/global_context.cc new file mode 100644 index 0000000..909973d --- /dev/null +++ b/src/global_context.cc @@ -0,0 +1,38 @@ +#include "pch.h" +#include "global_context.h" +#include "http_server.h" +#include "easylogging++.h" +#include "hooks.h" + + +namespace wxhelper { + +void GlobalContext::initialize(HMODULE module) { + module_ = module; + DWORD base = Utils::GetWeChatWinBase(); + config.emplace(); + log.emplace(); + log->initialize(); + hide_module.emplace(); + #ifndef _DEBUG + hide_module->Hide(module_); + #endif + + HttpServer::GetInstance().Init(19088); + HttpServer::GetInstance().HttpStart(); + DB::GetInstance().init(base); + contact_mgr.emplace(ContactMgr{base}); + misc_mgr.emplace(MiscMgr{base}); + send_mgr.emplace(SendMessageMgr{base}); + account_mgr.emplace(AccountMgr{base}); + chat_room_mgr.emplace(ChatRoomMgr{base}); + sns_mgr.emplace(SNSMgr{base}); +} + +void GlobalContext::finally() { + HttpServer::GetInstance().HttpClose(); + hooks::UnHookLog(); + hooks::UnHookRecvMsg(); + hooks::UnHookSearchContact(); +} +} // namespace wxhelper \ No newline at end of file diff --git a/src/global_context.h b/src/global_context.h new file mode 100644 index 0000000..0a6a7fa --- /dev/null +++ b/src/global_context.h @@ -0,0 +1,43 @@ +#ifndef GLOBAL_CONTEXT_H_ +#define GLOBAL_CONTEXT_H_ +#include "account_mgr.h" +#include "config.h" +#include "contact_mgr.h" +#include "db.h" +#include "hide_module.h" +#include "http_server.h" +#include "log.h" +#include "misc_mgr.h" +#include "send_message_mgr.h" +#include "chat_room_mgr.h" +#include "sns_mgr.h" +#include "singleton.h" + +namespace wxhelper { + +enum class GlobalContextState { NOT_INITIALIZED, INITIALIZING, INITIALIZED }; + +class GlobalContext :public Singleton{ + public: + void initialize(HMODULE module); + void finally(); + + public: + std::optional config; + std::optional hide_module; + std::optional log; + std::optional contact_mgr; + std::optional misc_mgr; + std::optional send_mgr; + std::optional account_mgr; + std::optional chat_room_mgr; + std::optional sns_mgr; + + GlobalContextState state = GlobalContextState::INITIALIZED; + + private: + HMODULE module_; +}; + +} // namespace wxhelper +#endif \ No newline at end of file diff --git a/src/handler.h b/src/handler.h new file mode 100644 index 0000000..985843a --- /dev/null +++ b/src/handler.h @@ -0,0 +1,10 @@ +#ifndef WXHELPER_HANDLER_H_ +#define WXHELPER_HANDLER_H_ +#include +namespace wxhelper { +class Handler { + public: + virtual void HandlerRequest(struct mg_connection *c, void *ev_data) = 0; +}; +} // namespace wxhelper +#endif \ No newline at end of file diff --git a/src/hide_module.cc b/src/hide_module.cc new file mode 100644 index 0000000..81e1b0d --- /dev/null +++ b/src/hide_module.cc @@ -0,0 +1,64 @@ +#include "pch.h" +#include "hide_module.h" + + +namespace wxhelper { + +void HideModule::Hide(const char* module_name) { + HMODULE hMod = ::GetModuleHandleA(module_name); + PLIST_ENTRY Head, Cur; + PPEB_LDR_DATA ldr; + PLDR_MODULE ldm; + + __asm { + mov eax, fs: [0x30] + mov ecx, [eax + 0x0c] + mov ldr, ecx + } + Head = &(ldr->InLoadOrderModuleList); + Cur = Head->Flink; + do { + ldm = CONTAINING_RECORD(Cur, LDR_MODULE, InLoadOrderModuleList); + if (hMod == ldm->BaseAddress) { + ldm->InLoadOrderModuleList.Blink->Flink = + ldm->InLoadOrderModuleList.Flink; + ldm->InLoadOrderModuleList.Flink->Blink = + ldm->InLoadOrderModuleList.Blink; + ldm->InInitializationOrderModuleList.Blink->Flink = + ldm->InInitializationOrderModuleList.Flink; + ldm->InInitializationOrderModuleList.Flink->Blink = + ldm->InInitializationOrderModuleList.Blink; + ldm->InMemoryOrderModuleList.Blink->Flink = + ldm->InMemoryOrderModuleList.Flink; + ldm->InMemoryOrderModuleList.Flink->Blink = + ldm->InMemoryOrderModuleList.Blink; + break; + } + Cur = Cur->Flink; + } while (Head != Cur); +} + +void HideModule::Hide(HMODULE module) { + void* peb_ptr = nullptr; + _asm { + PUSH EAX + MOV EAX, FS:[0x30] + MOV peb_ptr, EAX + POP EAX + } + void* ldr_ptr = *((void**)((unsigned char*)peb_ptr + 0xc)); + void* cur_ptr = *((void**)((unsigned char*)ldr_ptr + 0x0c)); + void* next_ptr = cur_ptr; + do { + void* next = *((void**)((unsigned char*)next_ptr)); + void* last = *((void**)((unsigned char*)next_ptr + 0x4)); + void* base_addr = *((void**)((unsigned char*)next_ptr + 0x18)); + if (base_addr == module) { + *((void**)((unsigned char*)last)) = next; + *((void**)((unsigned char*)next + 0x4)) = last; + cur_ptr = next; + } + next_ptr = *((void**)next_ptr); + } while (cur_ptr != next_ptr); +} +} // namespace wxhelper diff --git a/src/hide_module.h b/src/hide_module.h new file mode 100644 index 0000000..1cc4c0f --- /dev/null +++ b/src/hide_module.h @@ -0,0 +1,48 @@ +#ifndef WXHELEPER_HIDE_MODULE_H_ +#define WXHELEPER_HIDE_MODULE_H_ +#include +namespace wxhelper { +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING, *PUNICODE_STRING; + +typedef struct _PEB_LDR_DATA { + ULONG Length; + BOOLEAN Initialized; + PVOID SsHandle; + LIST_ENTRY InLoadOrderModuleList; + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; +} PEB_LDR_DATA, *PPEB_LDR_DATA; + +typedef struct _LDR_DATA_TABLE_ENTRY { + LIST_ENTRY InLoadOrderModuleList; + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; + void* BaseAddress; + void* EntryPoint; + ULONG SizeOfImage; + UNICODE_STRING FullDllName; + UNICODE_STRING BaseDllName; + ULONG Flags; + SHORT LoadCount; + SHORT TlsIndex; + HANDLE SectionHandle; + ULONG CheckSum; + ULONG TimeDateStamp; +} LDR_MODULE, *PLDR_MODULE; + + +class HideModule { + private: + /* data */ + public: + static void Hide(const char* module_name); + static void Hide(HMODULE module); +}; + + +} // namespace wxhelper +#endif \ No newline at end of file diff --git a/src/hook_img.cc b/src/hook_img.cc deleted file mode 100644 index 4a1a6aa..0000000 --- a/src/hook_img.cc +++ /dev/null @@ -1,231 +0,0 @@ -#include "pch.h" -#include "hook_img.h" - -#include "common.h" -using namespace std; - -// #define WX_HOOK_IMG_OFFSET 0xd7eaa5 -// #define WX_HOOK_IMG_NEXT_OFFSET 0xda56e0 -#define WX_HOOK_IMG_OFFSET 0xd723dc -#define WX_HOOK_IMG_NEXT_OFFSET 0xe91d90 -#define WX_SELF_ID_OFFSET 0x2E2CD3C -#define BUFSIZE 1024 - -#define JPEG0 0xFF -#define JPEG1 0xD8 -#define JPEG2 0xFF -#define PNG0 0x89 -#define PNG1 0x50 -#define PNG2 0x4E -#define BMP0 0x42 -#define BMP1 0x4D -#define GIF0 0x47 -#define GIF1 0x49 -#define GIF2 0x46 - - - -static wstring kImgStorePath = L""; -static int kImgHooked = FALSE; -static DWORD kWeChatWinBase = GetWeChatWinBase(); -static char kOriginImgAsmCode[5] = {0}; - -static DWORD kHookImgNextAddress = kWeChatWinBase + WX_HOOK_IMG_NEXT_OFFSET; -static DWORD kHookImgJmpBackAddress = kWeChatWinBase + WX_HOOK_IMG_OFFSET + 0x5; - -void OnHookImg(DWORD obj_addr) { - DWORD wxid_addr = GetWeChatWinBase() + WX_SELF_ID_OFFSET; - string wxid = string(*(char **)wxid_addr, *(DWORD *)(wxid_addr + 0x10)); - wstring self_id = String2Wstring(wxid); - wstring save_path = kImgStorePath + self_id; - if (!FindOrCreateDirectoryW(save_path.c_str())) { - return; - } - wchar_t *origin_file_path = *(wchar_t **)obj_addr; - wstring img_path(origin_file_path); - if (img_path.find(L"_t.dat") != wstring::npos) { - return; - } - - int pos_begin = img_path.find_last_of(L"\\") + 1; - int pos_end = img_path.find_last_of(L"."); - wstring file_name = img_path.substr(pos_begin, pos_end - pos_begin); - char buffer[BUFSIZE] = {0}; - DWORD bytes_read = 0; - DWORD bytes_write = 0; - unsigned char magic_head[4] = {0}; - wchar_t suffix[5] = {0}; - - HANDLE h_origin_file = - CreateFileW(origin_file_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL); - if (h_origin_file == INVALID_HANDLE_VALUE) { - return; - } - - if (ReadFile(h_origin_file, buffer, BUFSIZE, &bytes_read, NULL)) { - memcpy(magic_head, buffer, 3); - } - - if (magic_head[0] == PNG0 && magic_head[1] == PNG1 && magic_head[2] == PNG2) { - lstrcpyW(suffix, L".png"); - } else if (magic_head[0] == GIF0 && magic_head[1] == GIF1 && - magic_head[2] == GIF2) { - lstrcpyW(suffix, L".gif"); - } else if (magic_head[0] == JPEG0 && magic_head[1] == JPEG1 && - magic_head[2] == JPEG2) { - lstrcpyW(suffix, L".jpg"); - } else { - lstrcpyW(suffix, L""); - } - - wstring save_img_path = save_path + L"\\" + file_name + suffix; - HANDLE save_file = CreateFileW(save_img_path.c_str(), GENERIC_ALL, 0, NULL, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (save_file == INVALID_HANDLE_VALUE) { - return; - } - if (!WriteFile(save_file, (LPCVOID)buffer, bytes_read, &bytes_write, 0)) { - CloseHandle(h_origin_file); - CloseHandle(save_file); - return; - } - do { - if (ReadFile(h_origin_file, buffer, BUFSIZE, &bytes_read, NULL)) { - if (!WriteFile(save_file, (LPCVOID)buffer, bytes_read, &bytes_write, 0)) { - CloseHandle(h_origin_file); - CloseHandle(save_file); - return; - } - } - } while (bytes_read == BUFSIZE); - CloseHandle(h_origin_file); - CloseHandle(save_file); -} - -/// @brief hook img implement -_declspec(naked) void handle_img() { - __asm { - PUSHAD - PUSHFD - PUSH ECX - CALL OnHookImg - ADD ESP, 0x4 - POPFD - POPAD - CALL kHookImgNextAddress - JMP kHookImgJmpBackAddress - } -} - -/// @brief hook image -/// @param save_path image save dir -/// @return -int HookImg(wstring save_path) { - kWeChatWinBase = GetWeChatWinBase(); - if (!kWeChatWinBase) { - return -1; - } - if (kImgHooked) { - return 2; - } - kImgStorePath = save_path; - if (kImgStorePath.back() != '\\') { - kImgStorePath += L"\\"; - } - wstring createpath = kImgStorePath.substr(0, kImgStorePath.length() - 1); - if (!FindOrCreateDirectoryW(createpath.c_str())) { - return -2; - } - DWORD hook_img_addr = kWeChatWinBase + WX_HOOK_IMG_OFFSET; - kHookImgNextAddress = kWeChatWinBase + WX_HOOK_IMG_NEXT_OFFSET; - static DWORD kHookImgJmpBackAddress = hook_img_addr + 0x5; - HookAnyAddress(hook_img_addr, (LPVOID)handle_img, kOriginImgAsmCode); - kImgHooked = TRUE; - return 1; -} - -int UnHookImg() { - if (!kImgHooked) return 1; - DWORD hook_img_addr = kWeChatWinBase + WX_HOOK_IMG_OFFSET; - UnHookAnyAddress(hook_img_addr, kOriginImgAsmCode); - kImgHooked = FALSE; - return 1; -} - -int GetImgByName(wchar_t* file_path,wchar_t* save_dir) { - wstring save_path(save_dir); - wstring orign_file_path(file_path); - if (!FindOrCreateDirectoryW(save_path.c_str())) { - return 0; - } - - int pos_begin = orign_file_path.find_last_of(L"\\") + 1; - int pos_end = orign_file_path.find_last_of(L"."); - wstring file_name = orign_file_path.substr(pos_begin, pos_end - pos_begin); - HANDLE h_origin_file = - CreateFileW(file_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL); - char buffer[BUFSIZE] = {0}; - DWORD bytes_read = 0; - DWORD bytes_write = 0; - unsigned char magic_head[4] = {0}; - wstring suffix; - short key = 0; - if (ReadFile(h_origin_file, buffer, BUFSIZE, &bytes_read, NULL)) { - memcpy(magic_head, buffer, 3); - } else { - CloseHandle(h_origin_file); - return 0; - } - if ((magic_head[0] ^ JPEG0) == (magic_head[1] ^ JPEG1)) { - key = magic_head[0] ^ JPEG0; - suffix = L".jpg"; - } else if ((magic_head[0] ^ PNG1) == (magic_head[1] ^ PNG2)) { - key = magic_head[0] ^ PNG1; - suffix = L".png"; - } else if ((magic_head[0] ^ GIF0) == (magic_head[1] ^ GIF1)) { - key = magic_head[0] ^ GIF0; - suffix = L".gif"; - } else if ((magic_head[0] ^ BMP0) == (magic_head[1] ^ BMP1)) { - key = magic_head[0] ^ BMP0; - suffix = L".bmp"; - } else { - key = -1; - suffix = L".dat"; - } - wstring save_img_path = save_path + L"\\" + file_name + suffix; - HANDLE save_img = CreateFileW(save_img_path.c_str(), GENERIC_ALL, 0, NULL, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (save_img == INVALID_HANDLE_VALUE) { - return 0; - } - if (key > 0) { - for (unsigned int i = 0; i < bytes_read; i++) { - buffer[i]^=key; - } - } - if (!WriteFile(save_img, (LPCVOID)buffer, bytes_read, &bytes_write, 0)) { - CloseHandle(h_origin_file); - CloseHandle(save_img); - return 0; - } - - do { - if (ReadFile(h_origin_file, buffer, BUFSIZE, &bytes_read, NULL)) { - if (key > 0) { - for (unsigned int i = 0; i < bytes_read; i++) { - buffer[i] ^= key; - } - } - if (!WriteFile(save_img, (LPCVOID)buffer, bytes_read, &bytes_write, 0)) { - CloseHandle(h_origin_file); - CloseHandle(save_img); - return 0; - } - } - } while (bytes_read == BUFSIZE); - CloseHandle(h_origin_file); - CloseHandle(save_img); - return 1; -} \ No newline at end of file diff --git a/src/hook_img.h b/src/hook_img.h deleted file mode 100644 index a0d6bb6..0000000 --- a/src/hook_img.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef HOOK_IMG_H_ -#define HOOK_IMG_H_ -#include "windows.h" - -int HookImg(std::wstring save_path); -int UnHookImg(); - -int GetImgByName(wchar_t* file_path,wchar_t* save_dir); -#endif \ No newline at end of file diff --git a/src/hook_log.cc b/src/hook_log.cc deleted file mode 100644 index bffe7b0..0000000 --- a/src/hook_log.cc +++ /dev/null @@ -1,78 +0,0 @@ -#include "pch.h" -#include "hook_log.h" - -#include "common.h" - -using namespace std; - -#define WX_HOOK_LOG_OFFSET 0xed1675 -#define WX_HOOK_LOG_NEXT_OFFSET 0x2344832 - -static int kLogHooked = FALSE; -static DWORD kWeChatWinBase = GetWeChatWinBase(); -static char kOriginLogAsmCode[5] = {0}; - -static DWORD kHookLogAddress = kWeChatWinBase + WX_HOOK_LOG_OFFSET; -static DWORD kHookLogNextAddress = kWeChatWinBase + WX_HOOK_LOG_NEXT_OFFSET; -static DWORD kHookLogJmpBackAddress = kWeChatWinBase + WX_HOOK_LOG_OFFSET + 0x5; - -void log_print(DWORD addr) { - if (!addr) { - return; - } - DWORD dwId = 0; - char *msg = (char *)addr; - int size = MultiByteToWideChar(CP_UTF8, 0, msg, -1, 0, 0); - wchar_t *w_msg = new wchar_t[size + 1]; - memset(w_msg, 0, (size + 1) * 2); - MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, size); - size = WideCharToMultiByte(CP_ACP, 0, w_msg, -1, 0, 0, 0, 0); - char *ansi_message = new char[size + 1]; - memset(ansi_message, 0, size + 1); - WideCharToMultiByte(CP_ACP, 0, w_msg, -1, ansi_message, size, 0, 0); - delete[] w_msg; - w_msg = NULL; - cout << ansi_message; - delete[] ansi_message; - ansi_message = NULL; -} - -_declspec(naked) void handle_log() { - __asm { - PUSHAD - PUSHFD - PUSH EAX - CALL log_print - ADD ESP, 0x4 - POPFD - POPAD - CALL kHookLogNextAddress - JMP kHookLogJmpBackAddress - } -} - -int HookLog() { - kWeChatWinBase = GetWeChatWinBase(); - if (!kWeChatWinBase) { - return -1; - } - if (kLogHooked) { - return 2; - } - kHookLogAddress = kWeChatWinBase + WX_HOOK_LOG_OFFSET; - kHookLogNextAddress = kWeChatWinBase + WX_HOOK_LOG_NEXT_OFFSET; - kHookLogJmpBackAddress = kHookLogAddress + 0x5; - HookAnyAddress(kHookLogAddress, (LPVOID)handle_log, kOriginLogAsmCode); - kLogHooked = TRUE; - return 1; -} - -int UnHookLog() { - if (!kLogHooked) { - return 1; - } - DWORD hook_img_addr = kWeChatWinBase + WX_HOOK_LOG_OFFSET; - UnHookAnyAddress(hook_img_addr, kOriginLogAsmCode); - kLogHooked = FALSE; - return 1; -} \ No newline at end of file diff --git a/src/hook_log.h b/src/hook_log.h deleted file mode 100644 index 5dcad93..0000000 --- a/src/hook_log.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef HOOK_LOG_H_ -#define HOOK_LOG_H_ -#include "windows.h" - -int HookLog(); -int UnHookLog(); - -#endif \ No newline at end of file diff --git a/src/hook_recv_msg.cc b/src/hook_recv_msg.cc deleted file mode 100644 index c57e58c..0000000 --- a/src/hook_recv_msg.cc +++ /dev/null @@ -1,301 +0,0 @@ -#include "hook_recv_msg.h" - -#include -#include - -#include - -#include "common.h" -#include "pch.h" -using namespace nlohmann; - -using namespace std; -#define WX_RECV_MSG_HOOK_OFFSET 0xd19a0b -#define WX_RECV_MSG_HOOK_NEXT_OFFSET 0x756960 -#define WX_SNS_HOOK_OFFSET 0x14f9e15 -#define WX_SNS_HOOK_NEXT_OFFSET 0x14fa0a0 - -// SyncMgr::addMsgListToDB -// #define WX_RECV_MSG_HOOK_OFFSET 0xB9C919 -// #define WX_RECV_MSG_HOOK_NEXT_OFFSET 0x722FF0 - -#define CLIENT_IP "127.0.0.1" -static int kServerPort = 0; -static int kMessageHooked = FALSE; -static char kServerIp[16]= "127.0.0.1"; -static DWORD kWeChatWinBase = GetWeChatWinBase(); - -static char kOriginReceMsgAsmCode[5] = {0}; -static DWORD kReceMsgJmpBackAddress = - kWeChatWinBase + WX_RECV_MSG_HOOK_OFFSET + 0x5; -static DWORD kReceMsgNextAddress = - kWeChatWinBase + WX_RECV_MSG_HOOK_NEXT_OFFSET; - - -static char kOriginSnsMsgAsmCode[5] = {0}; -static DWORD kSnsMsgJmpBackAddress = - kWeChatWinBase + WX_SNS_HOOK_OFFSET + 0x5; -static DWORD kSnsMsgNextAddress = - kWeChatWinBase + WX_SNS_HOOK_NEXT_OFFSET; - -struct InnerMessageStruct { - char *buffer; - int length; - ~InnerMessageStruct() { - if (this->buffer != NULL) { - delete[] this->buffer; - this->buffer = NULL; - } - } -}; -/// @brief send message by socket -/// @param buffer content -/// @param len len -/// @return true/false -BOOL SendBySocket(const char *buffer, size_t len) { - if (kServerPort == 0) { - return false; - } - SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (client_socket < 0) { -#ifdef _DEBUG - cout << "create socket error," - << " errno:" << errno << endl; -#endif - return false; - } - BOOL status = false; - sockaddr_in client_addr; - memset(&client_addr, 0, sizeof(client_addr)); - client_addr.sin_family = AF_INET; - client_addr.sin_port = htons((u_short)kServerPort); - InetPtonA(AF_INET, kServerIp, &client_addr.sin_addr.s_addr); - if (connect(client_socket, reinterpret_cast(&client_addr), - sizeof(sockaddr)) < 0) { -#ifdef _DEBUG - cout << "kServerIp:" << kServerIp < sms(msg); - json j_msg = - json::parse(msg->buffer, msg->buffer + msg->length, nullptr, false); - if (j_msg.is_discarded() == true) { - return; - } - string jstr = j_msg.dump() + "\n"; -#ifdef _DEBUG - cout << "json:" + jstr << endl; -#endif - SendBySocket(jstr.c_str(), jstr.size()); -} -/// @brief msg handle -/// @param msg_addr msg address in memory -void __cdecl OnRecvMsg(DWORD msg_addr) { - json j_msg; - unsigned long long msgid = *(unsigned long long *)(msg_addr + 0x30); - j_msg["msgId"] = msgid; - j_msg["pid"] = GetCurrentProcessId(); - j_msg["type"] = *(DWORD *)(msg_addr + 0x38); - j_msg["isSendMsg"] = *(BOOL *)(msg_addr + 0x3C); - if (j_msg["isSendMsg"].get()) { - j_msg["isSendByPhone"] = (int)(*(BYTE *)(msg_addr + 0xD8)); - } - j_msg["time"] = - unicode_to_utf8((wchar_t *)GetTimeW(*(DWORD *)(msg_addr + 0x44)).c_str()); - j_msg["timestamp"] = *(DWORD *)(msg_addr + 0x44); - j_msg["fromGroup"] = - unicode_to_utf8((wchar_t *)READ_WSTRING(msg_addr, 0x48).c_str()); - int length = *(DWORD *)(msg_addr + 0x178); - if (length == 0) { - j_msg["fromUser"] = j_msg["fromGroup"].get(); - } else { - j_msg["fromUser"] = - unicode_to_utf8((wchar_t *)READ_WSTRING(msg_addr, 0x174).c_str()); - } - int content_len = *(DWORD *)(msg_addr + 0x74); - if (content_len > 0) { - j_msg["content"] = - unicode_to_utf8((wchar_t *)READ_WSTRING(msg_addr, 0x70).c_str()); - } - int sign_len = *(DWORD *)(msg_addr + 0x18C); - if (sign_len > 0) { - j_msg["sign"] = - unicode_to_utf8((wchar_t *)READ_WSTRING(msg_addr, 0x188).c_str()); - } - int thumb_len = *(DWORD *)(msg_addr + 0x1A0); - if (thumb_len > 0) { - j_msg["thumbPath"] = - unicode_to_utf8((wchar_t *)READ_WSTRING(msg_addr, 0x19C).c_str()); - } - int path_len = *(DWORD *)(msg_addr + 0x1B4); - if (path_len > 0) { - j_msg["path"] = - unicode_to_utf8((wchar_t *)READ_WSTRING(msg_addr, 0x1B0).c_str()); - } - - int signature_len = *(DWORD *)(msg_addr + 0x1F4); - if (signature_len > 0) { - j_msg["signature"] = - unicode_to_utf8((wchar_t *)READ_WSTRING(msg_addr, 0x1F0).c_str()); - } - - string jstr = j_msg.dump() + '\n'; - InnerMessageStruct *inner_msg = new InnerMessageStruct; - inner_msg->buffer = new char[jstr.size() + 1]; - memcpy(inner_msg->buffer, jstr.c_str(), jstr.size() + 1); - inner_msg->length = jstr.size(); - HANDLE thread = CreateThread( - NULL, 0, (LPTHREAD_START_ROUTINE)SendSocketMessage, inner_msg, NULL, 0); - if (thread) { - CloseHandle(thread); - } -} - - - - void __cdecl OnSnsTimeLineMsg(DWORD msg_addr) { - json j_sns; - DWORD begin_addr = *(DWORD *)(msg_addr + 0x20); - DWORD end_addr = *(DWORD *)(msg_addr + 0x24); - #ifdef _DEBUG - cout << "begin" <buffer = new char[jstr.size() + 1]; - memcpy(inner_msg->buffer, jstr.c_str(), jstr.size() + 1); - inner_msg->length = jstr.size(); - HANDLE thread = CreateThread( - NULL, 0, (LPTHREAD_START_ROUTINE)SendSocketMessage, inner_msg, NULL, 0); - if (thread) { - CloseHandle(thread); - } -} - -/// @brief hook implement -_declspec(naked) void handle_sync_msg() { - __asm { - PUSHAD - PUSHFD - PUSH ECX - CALL OnRecvMsg - ADD ESP, 0x4 - POPFD - POPAD - CALL kReceMsgNextAddress - JMP kReceMsgJmpBackAddress - } -} - - -/// @brief hook sns msg implement -_declspec(naked) void handle_sns_msg() { - __asm { - PUSHAD - PUSHFD - PUSH [ESP + 0x24] - CALL OnSnsTimeLineMsg - ADD ESP, 0x4 - POPFD - POPAD - CALL kSnsMsgNextAddress - JMP kSnsMsgJmpBackAddress - } -} - - -/// @brief hook any address address+0x5 -/// @param port 端口 -/// @return 成功返回1,已经hook返回2,失败返回-1 -int HookRecvMsg(char* client_ip,int port) { - kServerPort = port; - strcpy_s(kServerIp,client_ip); - kWeChatWinBase = GetWeChatWinBase(); - if (!kWeChatWinBase) { - return -1; - } - - if (kMessageHooked) { - return 2; - } - kWeChatWinBase = GetWeChatWinBase(); - DWORD hook_recv_msg_addr = kWeChatWinBase + WX_RECV_MSG_HOOK_OFFSET; - kReceMsgNextAddress = kWeChatWinBase + WX_RECV_MSG_HOOK_NEXT_OFFSET; - kReceMsgJmpBackAddress = hook_recv_msg_addr + 0x5; - HookAnyAddress(hook_recv_msg_addr, (LPVOID)handle_sync_msg, - kOriginReceMsgAsmCode); - - DWORD hook_sns_msg_addr = kWeChatWinBase + WX_SNS_HOOK_OFFSET; - kSnsMsgNextAddress = kWeChatWinBase + WX_SNS_HOOK_NEXT_OFFSET; - kSnsMsgJmpBackAddress = hook_sns_msg_addr + 0x5; - HookAnyAddress(hook_sns_msg_addr, (LPVOID)handle_sns_msg, - kOriginSnsMsgAsmCode); - - - kMessageHooked = TRUE; - return 1; -} - -int UnHookRecvMsg() { - kServerPort = 0; - if (!kMessageHooked) return 2; - DWORD hook_recv_msg_addr = kWeChatWinBase + WX_RECV_MSG_HOOK_OFFSET; - DWORD hook_sns_addr = kWeChatWinBase + WX_SNS_HOOK_OFFSET; - UnHookAnyAddress(hook_recv_msg_addr, kOriginReceMsgAsmCode); - UnHookAnyAddress(hook_sns_addr, kOriginSnsMsgAsmCode); - kMessageHooked = FALSE; - return 1; -} \ No newline at end of file diff --git a/src/hook_recv_msg.h b/src/hook_recv_msg.h deleted file mode 100644 index ac2cf8d..0000000 --- a/src/hook_recv_msg.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef HOOK_RECV_MSG_H_ -#define HOOK_RECV_MSG_H_ - -/// @brief hook any address address+0x5 -/// @param port 端口 -/// @return 成功返回1,已经hook返回2 -int HookRecvMsg(char* client_ip,int port); - -int UnHookRecvMsg(); -#endif \ No newline at end of file diff --git a/src/hook_voice.cc b/src/hook_voice.cc deleted file mode 100644 index 1fa3205..0000000 --- a/src/hook_voice.cc +++ /dev/null @@ -1,88 +0,0 @@ -#include "pch.h" -#include "hook_voice.h" - -#include "common.h" - -using namespace std; - -#define WX_HOOK_VOICE_OFFSET 0xd4d8d8 -#define WX_HOOK_VOICE_NEXT_OFFSET 0x203d130 -#define WX_SELF_ID_OFFSET 0x2FFD484 - -static wstring kVoiceStorePath = L""; -static int kVoiceHooked = FALSE; -static DWORD kWeChatWinBase = GetWeChatWinBase(); -static char kOriginVoiceAsmCode[5] = {0}; - -static DWORD kHookVoiceNextAddress = kWeChatWinBase + WX_HOOK_VOICE_NEXT_OFFSET; -static DWORD kHookVoiceJmpBackAddress = - kWeChatWinBase + WX_HOOK_VOICE_OFFSET + 0x5; - -void OnHookVoice(DWORD buff,int len , DWORD msg_addr) { - DWORD wxid_addr = GetWeChatWinBase() + WX_SELF_ID_OFFSET; - string wxid = string(*(char **)wxid_addr, *(DWORD *)(wxid_addr + 0x10)); - wstring self_id = utf8_to_unicode(wxid.c_str()); - wstring save_path = kVoiceStorePath + self_id; - if (!FindOrCreateDirectoryW(save_path.c_str())) { - return; - } - unsigned long long msgid = *(unsigned long long *)(msg_addr + 0x30); - save_path = save_path + L"\\" + to_wstring(msgid) + L".amr"; - HANDLE file_handle = CreateFileW(save_path.c_str(), GENERIC_ALL, 0, NULL, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (file_handle == INVALID_HANDLE_VALUE) { - return; - } - DWORD bytes_write = 0; - WriteFile(file_handle, (LPCVOID)buff, len, &bytes_write, 0); - CloseHandle(file_handle); -} - -/// @brief hook voice implement -_declspec(naked) void handle_voice() { - __asm { - PUSHAD - PUSHFD - PUSH EDI - PUSH EDX - PUSH EAX - CALL OnHookVoice - ADD ESP, 0xC - POPFD - POPAD - CALL kHookVoiceNextAddress - JMP kHookVoiceJmpBackAddress - } -} - -int HookVoice(std::wstring save_path) { - kWeChatWinBase = GetWeChatWinBase(); - if (!kWeChatWinBase) { - return -1; - } - if (kVoiceHooked) { - return 2; - } - kVoiceStorePath = save_path; - if (kVoiceStorePath.back() != '\\') { - kVoiceStorePath += L"\\"; - } - wstring createpath = kVoiceStorePath.substr(0, kVoiceStorePath.length() - 1); - if (!FindOrCreateDirectoryW(createpath.c_str())) { - return -2; - } - DWORD hook_voice_addr = kWeChatWinBase + WX_HOOK_VOICE_OFFSET; - kHookVoiceNextAddress = kWeChatWinBase + WX_HOOK_VOICE_NEXT_OFFSET; - static DWORD kHookVoiceJmpBackAddress = hook_voice_addr + 0x5; - HookAnyAddress(hook_voice_addr, (LPVOID)handle_voice, kOriginVoiceAsmCode); - kVoiceHooked = TRUE; - return 1; -} - -int UnHookVoice() { - if (!kVoiceHooked) return 1; - DWORD hook_voice_addr = kWeChatWinBase + WX_HOOK_VOICE_OFFSET; - UnHookAnyAddress(hook_voice_addr, kOriginVoiceAsmCode); - kVoiceHooked = FALSE; - return 1; -} diff --git a/src/hook_voice.h b/src/hook_voice.h deleted file mode 100644 index 429221a..0000000 --- a/src/hook_voice.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef HOOK_VOICE_H_ -#define HOOK_VOICE_H_ -#include - -int HookVoice(std::wstring save_path); - -int UnHookVoice(); -#endif \ No newline at end of file diff --git a/src/hooks.cc b/src/hooks.cc new file mode 100644 index 0000000..c5147f9 --- /dev/null +++ b/src/hooks.cc @@ -0,0 +1,533 @@ +#include +#include + +#include + +#include "easylogging++.h" +#include "pch.h" +#include "wechat_function.h" +using namespace nlohmann; +using namespace std; +namespace wxhelper { +namespace hooks { +static int server_port_ = 0; +static bool msg_hook_flag_ = false; +static char server_ip_[16] = "127.0.0.1"; + +static char msg_asm_code_[5] = {0}; +static DWORD msg_back_addr_ = 0; +static DWORD msg_next_addr_ = 0; + +static char sns_asm_code_[5] = {0}; +static DWORD sns_back_addr_ = 0; +static DWORD sns_next_addr_ = 0; + +static bool log_hook_flag_ = false; +static char log_asm_code_[5] = {0}; +static DWORD log_back_addr_ = 0; +static DWORD log_next_addr_ = 0; + +static bool search_contact_flag_ = false; +static char search_contact_asm_code_[5] = {0}; +static DWORD search_contact_back_addr_ = 0; +static DWORD search_contact_next_addr_ = 0; + +static bool error_code_flag_ = false; +static char error_code_asm_code_[5] = {0}; +static DWORD error_code_back_addr_ = 0; +static DWORD error_code_next_addr_ = 0; + +bool user_info_flag_ = false; +static char user_info_asm_code_[5] = {0}; +static DWORD user_info_back_addr_ = 0; +static DWORD user_info_next_addr_ = 0; + +UserInfo userinfo = {}; + +void SendSocketMessage(InnerMessageStruct *msg) { + if (msg == NULL) { + return; + } + unique_ptr sms(msg); + json j_msg = + json::parse(msg->buffer, msg->buffer + msg->length, nullptr, false); + if (j_msg.is_discarded() == true) { + return; + } + string jstr = j_msg.dump() + "\n"; + + if (server_port_ == 0) { + LOG(INFO) << "http server port error :" << server_port_; + return; + } + SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (client_socket < 0) { + LOG(INFO) << "socket init fail"; + return; + } + BOOL status = false; + sockaddr_in client_addr; + memset(&client_addr, 0, sizeof(client_addr)); + client_addr.sin_family = AF_INET; + client_addr.sin_port = htons((u_short)server_port_); + InetPtonA(AF_INET, server_ip_, &client_addr.sin_addr.s_addr); + if (connect(client_socket, reinterpret_cast(&client_addr), + sizeof(sockaddr)) < 0) { + LOG(INFO) << "socket connect fail"; + return; + } + char recv_buf[1024] = {0}; + int ret = send(client_socket, jstr.c_str(), jstr.size(), 0); + if (ret == -1 || ret == 0) { + LOG(INFO) << "socket send fail ,ret:" << ret; + closesocket(client_socket); + return; + } + memset(recv_buf, 0, sizeof(recv_buf)); + ret = recv(client_socket, recv_buf, sizeof(recv_buf), 0); + closesocket(client_socket); + if (ret == -1 || ret == 0) { + LOG(INFO) << "socket recv fail ,ret:" << ret; + } +} + +void __cdecl OnRecvMsg(DWORD msg_addr) { + json j_msg; + unsigned long long msgid = *(unsigned long long *)(msg_addr + 0x30); + j_msg["msgId"] = msgid; + j_msg["pid"] = GetCurrentProcessId(); + j_msg["type"] = *(DWORD *)(msg_addr + 0x38); + j_msg["isSendMsg"] = *(BOOL *)(msg_addr + 0x3C); + if (j_msg["isSendMsg"].get()) { + j_msg["isSendByPhone"] = (int)(*(BYTE *)(msg_addr + 0xD8)); + } + j_msg["time"] = + Utils::WstringToUTF8(Utils::GetTimeW(*(DWORD *)(msg_addr + 0x44))); + j_msg["timestamp"] = *(DWORD *)(msg_addr + 0x44); + j_msg["fromGroup"] = Utils::WstringToUTF8(READ_WSTRING(msg_addr, 0x48)); + int length = *(DWORD *)(msg_addr + 0x178); + if (length == 0) { + j_msg["fromUser"] = j_msg["fromGroup"].get(); + } else { + j_msg["fromUser"] = Utils::WstringToUTF8(READ_WSTRING(msg_addr, 0x174)); + } + int content_len = *(DWORD *)(msg_addr + 0x74); + if (content_len > 0) { + j_msg["content"] = Utils::WstringToUTF8(READ_WSTRING(msg_addr, 0x70)); + } + int sign_len = *(DWORD *)(msg_addr + 0x18C); + if (sign_len > 0) { + j_msg["sign"] = Utils::WstringToUTF8(READ_WSTRING(msg_addr, 0x188)); + } + int thumb_len = *(DWORD *)(msg_addr + 0x1A0); + if (thumb_len > 0) { + j_msg["thumbPath"] = Utils::WstringToUTF8(READ_WSTRING(msg_addr, 0x19C)); + } + int path_len = *(DWORD *)(msg_addr + 0x1B4); + if (path_len > 0) { + j_msg["path"] = Utils::WstringToUTF8(READ_WSTRING(msg_addr, 0x1B0)); + } + + int signature_len = *(DWORD *)(msg_addr + 0x1F4); + if (signature_len > 0) { + j_msg["signature"] = Utils::WstringToUTF8(READ_WSTRING(msg_addr, 0x1F0)); + } + + string jstr = j_msg.dump() + '\n'; + InnerMessageStruct *inner_msg = new InnerMessageStruct; + inner_msg->buffer = new char[jstr.size() + 1]; + memcpy(inner_msg->buffer, jstr.c_str(), jstr.size() + 1); + inner_msg->length = jstr.size(); + HANDLE thread = CreateThread( + NULL, 0, (LPTHREAD_START_ROUTINE)SendSocketMessage, inner_msg, NULL, 0); + if (thread) { + CloseHandle(thread); + } +} + +/// @brief hook msg implement +_declspec(naked) void HandleSyncMsg() { + __asm { + PUSHAD + PUSHFD + PUSH ECX + CALL OnRecvMsg + ADD ESP, 0x4 + POPFD + POPAD + CALL msg_next_addr_ + JMP msg_back_addr_ + } +} + +void __cdecl OnSnsTimeLineMsg(DWORD msg_addr) { + json j_sns; + DWORD begin_addr = *(DWORD *)(msg_addr + 0x20); + DWORD end_addr = *(DWORD *)(msg_addr + 0x24); + if (begin_addr == 0) { + j_sns = {{"data", json::array()}}; + } else { + while (begin_addr < end_addr) { + json j_item; + j_item["snsId"] = *(unsigned long long *)(begin_addr); + j_item["createTime"] = *(DWORD *)(begin_addr + 0x2C); + + j_item["senderId"] = Utils::WstringToUTF8(READ_WSTRING(begin_addr, 0x18)); + + j_item["content"] = Utils::WstringToUTF8(READ_WSTRING(begin_addr, 0x3c)); + + j_item["xml"] = Utils::WstringToUTF8(READ_WSTRING(begin_addr, 0x384)); + + j_sns["data"].push_back(j_item); + begin_addr += 0xB48; + } + } + string jstr = j_sns.dump() + '\n'; + InnerMessageStruct *inner_msg = new InnerMessageStruct; + inner_msg->buffer = new char[jstr.size() + 1]; + memcpy(inner_msg->buffer, jstr.c_str(), jstr.size() + 1); + inner_msg->length = jstr.size(); + HANDLE thread = CreateThread( + NULL, 0, (LPTHREAD_START_ROUTINE)SendSocketMessage, inner_msg, NULL, 0); + if (thread) { + CloseHandle(thread); + } +} + +/// @brief hook sns msg implement +_declspec(naked) void HandleSNSMsg() { + __asm { + PUSHAD + PUSHFD + PUSH [ESP + 0x24] + CALL OnSnsTimeLineMsg + ADD ESP, 0x4 + POPFD + POPAD + CALL sns_next_addr_ + JMP sns_back_addr_ + } +} + +int HookRecvMsg(char *client_ip, int port) { + server_port_ = port; + strcpy_s(server_ip_, client_ip); + DWORD base = Utils::GetWeChatWinBase(); + if (!base) { + return -1; + } + + if (msg_hook_flag_) { + return 2; + } + + DWORD hook_recv_msg_addr = base + WX_RECV_MSG_HOOK_OFFSET; + msg_next_addr_ = base + WX_RECV_MSG_HOOK_NEXT_OFFSET; + msg_back_addr_ = hook_recv_msg_addr + 0x5; + LOG(INFO) << "base" << base; + LOG(INFO) << "msg_next_addr_" << msg_next_addr_; + LOG(INFO) << "msg_back_addr_" << msg_back_addr_; + Utils::HookAnyAddress(hook_recv_msg_addr, (LPVOID)HandleSyncMsg, + msg_asm_code_); + + DWORD hook_sns_msg_addr = base + WX_SNS_HOOK_OFFSET; + sns_next_addr_ = base + WX_SNS_HOOK_NEXT_OFFSET; + sns_back_addr_ = hook_sns_msg_addr + 0x5; + LOG(INFO) << "base" << base; + LOG(INFO) << "sns_next_addr_" << sns_next_addr_; + LOG(INFO) << "sns_back_addr_" << sns_back_addr_; + Utils::HookAnyAddress(hook_sns_msg_addr, (LPVOID)HandleSNSMsg, sns_asm_code_); + + msg_hook_flag_ = true; + return 1; +} + +int UnHookRecvMsg() { + server_port_ = 0; + if (!msg_hook_flag_) { + LOG(INFO) << "this port already hooked"; + return 2; + } + DWORD base = Utils::GetWeChatWinBase(); + DWORD hook_recv_msg_addr = base + WX_RECV_MSG_HOOK_OFFSET; + DWORD hook_sns_addr = base + WX_SNS_HOOK_OFFSET; + Utils::UnHookAnyAddress(hook_recv_msg_addr, msg_asm_code_); + Utils::UnHookAnyAddress(hook_sns_addr, sns_asm_code_); + msg_hook_flag_ = false; + return 1; +} + +void PrintLog(DWORD addr) { + if (!addr) { + return; + } + DWORD dwId = 0; + char *msg = (char *)addr; + int size = MultiByteToWideChar(CP_UTF8, 0, msg, -1, 0, 0); + wchar_t *w_msg = new wchar_t[size + 1]; + memset(w_msg, 0, (size + 1) * 2); + MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, size); + size = WideCharToMultiByte(CP_ACP, 0, w_msg, -1, 0, 0, 0, 0); + char *ansi_message = new char[size + 1]; + memset(ansi_message, 0, size + 1); + WideCharToMultiByte(CP_ACP, 0, w_msg, -1, ansi_message, size, 0, 0); + delete[] w_msg; + w_msg = NULL; + LOG(INFO) << ansi_message; + delete[] ansi_message; + ansi_message = NULL; +} + +_declspec(naked) void HandleLog() { + __asm { + PUSHAD + PUSHFD + PUSH EAX + CALL PrintLog + ADD ESP, 0x4 + POPFD + POPAD + CALL log_next_addr_ + JMP log_back_addr_ + } +} + +int HookLog() { + DWORD base = Utils::GetWeChatWinBase(); + if (!base) { + return -1; + } + if (log_hook_flag_) { + return 2; + } + DWORD hook_log_addr = base + WX_HOOK_LOG_OFFSET; + log_next_addr_ = base + WX_HOOK_LOG_NEXT_OFFSET; + log_back_addr_ = hook_log_addr + 0x5; + Utils::HookAnyAddress(hook_log_addr, (LPVOID)HandleLog, log_asm_code_); + log_hook_flag_ = true; + return 1; +} +int UnHookLog() { + if (!log_hook_flag_) { + return 1; + } + DWORD base = Utils::GetWeChatWinBase(); + DWORD hook_log_addr = base + WX_HOOK_LOG_OFFSET; + Utils::UnHookAnyAddress(hook_log_addr, log_asm_code_); + log_hook_flag_ = FALSE; + return 1; +} + +void SetErrorCode(int code) { userinfo.error_code = code; } + +void SetUserInfoDetail(DWORD address) { + LOG(INFO) << "hook userinfo addr" <<&userinfo; + DWORD length = *(DWORD *)(address + 0x8); + userinfo.keyword = new wchar_t[length + 1]; + userinfo.keyword_len = length; + if (length) { + memcpy(userinfo.keyword, (wchar_t *)(*(DWORD *)(address + 0x4)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.keyword, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x1C); + userinfo.v3 = new wchar_t[length + 1]; + userinfo.v3_len = length; + if (length) { + memcpy(userinfo.v3, (wchar_t *)(*(DWORD *)(address + 0x18)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.v3, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x30); + userinfo.big_image = new wchar_t[length + 1]; + userinfo.big_image_len = length; + if (length) { + memcpy(userinfo.big_image, (wchar_t *)(*(DWORD *)(address + 0x2C)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.big_image, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0xC8); + userinfo.nickname = new wchar_t[length + 1]; + userinfo.nickname_len = length; + if (length) { + memcpy(userinfo.nickname, (wchar_t *)(*(DWORD *)(address + 0xC4)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.nickname, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x108); + userinfo.v2 = new wchar_t[length + 1]; + userinfo.v2_len = length; + if (length) { + memcpy(userinfo.v2, (wchar_t *)(*(DWORD *)(address + 0x104)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.v2, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x16C); + userinfo.small_image = new wchar_t[length + 1]; + userinfo.small_image_len = length; + if (length) { + memcpy(userinfo.small_image, (wchar_t *)(*(DWORD *)(address + 0x168)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.small_image, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x1F8); + userinfo.signature = new wchar_t[length + 1]; + userinfo.signature_len = length; + if (length) { + memcpy(userinfo.signature, (wchar_t *)(*(DWORD *)(address + 0x1F4)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.signature, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x20C); + userinfo.nation = new wchar_t[length + 1]; + userinfo.nation_len = length; + if (length) { + memcpy(userinfo.nation, (wchar_t *)(*(DWORD *)(address + 0x208)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.nation, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x220); + userinfo.province = new wchar_t[length + 1]; + userinfo.province_len = length; + if (length) { + memcpy(userinfo.province, (wchar_t *)(*(DWORD *)(address + 0x21C)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.province, (length + 1) * sizeof(wchar_t)); + } + + length = *(DWORD *)(address + 0x234); + userinfo.city = new wchar_t[length + 1]; + userinfo.city_len = length; + if (length) { + memcpy(userinfo.city, (wchar_t *)(*(DWORD *)(address + 0x230)), + (length + 1) * sizeof(wchar_t)); + } else { + ZeroMemory(userinfo.city, (length + 1) * sizeof(wchar_t)); + } + + userinfo.sex = *(DWORD *)(address + 0x1BC); + userinfo.over = true; +} + +void DeleteUserInfoCache() { + if (userinfo.keyword) { + delete userinfo.keyword; + } + if (userinfo.v2) { + delete userinfo.v2; + } + if (userinfo.v3) { + delete userinfo.v3; + } + if (userinfo.nickname) { + delete userinfo.nickname; + } + if (userinfo.nation) { + delete userinfo.nation; + } + if (userinfo.province) { + delete userinfo.province; + } + if (userinfo.city) { + delete userinfo.city; + } + if (userinfo.signature) { + delete userinfo.signature; + } + if (userinfo.small_image) { + delete userinfo.small_image; + } + if (userinfo.big_image) { + delete userinfo.big_image; + } + ZeroMemory(&userinfo, sizeof(UserInfo)); + userinfo.error_code = 1; +} + +__declspec(naked) void HandleErrorCode() { + __asm { + PUSHAD; + PUSHFD; + PUSH ESI; + CALL SetErrorCode; + ADD ESP, 0x4; + POPFD; + POPAD; + CALL error_code_next_addr_; + JMP error_code_back_addr_; + } +} + +__declspec(naked) void HandleUserInfoDetail() { + __asm { + PUSHAD; + PUSHFD; + PUSH dword ptr [EBP + 0x14]; + CALL SetUserInfoDetail; + ADD ESP, 0x4; + POPFD; + POPAD; + CALL user_info_next_addr_; + JMP user_info_back_addr_; + } +} + +int HookSearchContact() { + DWORD base = Utils::GetWeChatWinBase(); + if (!base) { + return -1; + } + if (search_contact_flag_) { + return 2; + } + DWORD hook_error_code_addr = base + WX_SEARCH_CONTACT_ERROR_CODE_HOOK_OFFSET; + error_code_next_addr_ = base + WX_SEARCH_CONTACT_ERROR_CODE_HOOK_NEXT_OFFSET; + error_code_back_addr_ = hook_error_code_addr + 0x5; + Utils::HookAnyAddress(hook_error_code_addr, (LPVOID)HandleErrorCode, + error_code_asm_code_); + + DWORD hook_user_info_addr = base + WX_SEARCH_CONTACT_DETAIL_HOOK_OFFSET; + user_info_next_addr_ = base + WX_SEARCH_CONTACT_DETAIL_HOOK_NEXT_OFFSET; + user_info_back_addr_ = hook_user_info_addr + 0x5; + + Utils::HookAnyAddress(hook_user_info_addr, (LPVOID)HandleUserInfoDetail, + user_info_asm_code_); + error_code_flag_ = true; + user_info_flag_ = true; + return 1; +} + +int UnHookSearchContact() { + DWORD base = Utils::GetWeChatWinBase(); + DWORD hook_user_info_addr = base + WX_SEARCH_CONTACT_DETAIL_HOOK_OFFSET; + DWORD hook_error_code_addr = base + WX_SEARCH_CONTACT_ERROR_CODE_HOOK_OFFSET; + + if (!user_info_flag_) return 2; + Utils::UnHookAnyAddress(hook_user_info_addr, user_info_asm_code_); + user_info_flag_ = false; + + if (!error_code_flag_) return 2; + Utils::UnHookAnyAddress(hook_error_code_addr, error_code_asm_code_); + error_code_flag_ = false; + + return 1; +} +} // namespace hooks +} // namespace wxhelper \ No newline at end of file diff --git a/src/hooks.h b/src/hooks.h new file mode 100644 index 0000000..960712e --- /dev/null +++ b/src/hooks.h @@ -0,0 +1,23 @@ +#ifndef WXHELPER_HOOKS_H_ +#define WXHELPER_HOOKS_H_ +#include "Windows.h" +#include "wechat_function.h" +namespace wxhelper { +namespace hooks { +extern UserInfo userinfo; +extern bool user_info_flag_ ; + +int HookRecvMsg(char* client_ip, int port); + +int UnHookRecvMsg(); + +void SendSocketMessage(InnerMessageStruct* msg); + +int HookLog(); +int UnHookLog(); +int HookSearchContact(); +int UnHookSearchContact(); +void DeleteUserInfoCache(); +} // namespace hooks +} // namespace wxhelper +#endif \ No newline at end of file diff --git a/src/http_handler.cc b/src/http_handler.cc new file mode 100644 index 0000000..84458b7 --- /dev/null +++ b/src/http_handler.cc @@ -0,0 +1,594 @@ +#include "pch.h" +#include "http_handler.h" + +#include + +#include "utils.h" +#include "account_mgr.h" +#include "api_route.h" +#include "chat_room_mgr.h" +#include "contact_mgr.h" +#include "db.h" +#include "easylogging++.h" +#include "hooks.h" +#include "misc_mgr.h" +#include "send_message_mgr.h" +#include "sns_mgr.h" +#include "global_context.h" +#include "hooks.h" + +using namespace std; +using namespace nlohmann; + +namespace wxhelper { +string GetParamOfGetReq(mg_http_message *hm, string name) { + string ret; + char *buffer = new char[hm->query.len + 1]; + ZeroMemory(buffer, hm->query.len + 1); + int len = mg_http_get_var(&hm->query, name.c_str(), buffer, hm->query.len); + if (len > 0) ret = string(buffer, len); + delete[] buffer; + buffer = NULL; + return ret; +} + +int GetIntParam(json data, string key) { + int result; + try { + result = data[key].get(); + } catch (json::exception) { + result = STRING2INT(data[key].get()); + } + return result; +} + +wstring GetWStringParam(json data, string key) { + return Utils::UTF8ToWstring(data[key].get()); +} + +unsigned long long GetULong64Param(json j_data, string key) { + unsigned long long result = 0; + try { + result = j_data[key].get(); + } catch (json::exception) { + string value = j_data[key].get(); + istringstream is(value); + is >> result; + } + return result; +} + +static vector getArrayParam(json j_data, string key) { + vector result; + wstring param = GetWStringParam(j_data, key); + result = Utils::split(param, L','); + return result; +} + +string Dispatch(struct mg_connection *c, struct mg_http_message *hm) { + int is_post = 0; + string ret; + if (mg_vcasecmp(&hm->method, "POST") == 0) { + is_post = 1; + } + el::Logger *defaultLogger = el::Loggers::getLogger("default"); + defaultLogger->info("method: %v body: %v", hm->method.ptr, hm->body.ptr); + LOG_IF(is_post != 1, INFO) << "request method is not post"; + + if (is_post == 0) { + json ret_data = {{"result", "ERROR"}, {"msg", "not support method"}}; + ret = ret_data.dump(); + return ret; + } + + json j_param = + json::parse(hm->body.ptr, hm->body.ptr + hm->body.len, nullptr, false); + if (hm->body.len != 0 && j_param.is_discarded() == true) { + json ret_data = {{"result", "ERROR"}, {"msg", "json string is invalid."}}; + ret = ret_data.dump(); + return ret; + } + int api_number = STRING2INT(GetParamOfGetReq(hm, "type")); + GlobalContext& g_context = GlobalContext::GetInstance(); + switch (api_number) { + case WECHAT_IS_LOGIN: { + int success = -1; + success = g_context.account_mgr->CheckLogin(); + json ret_data = {{"result", "OK"}, {"code", success}}; + ret = ret_data.dump(); + break; + } + case WECHAT_GET_SELF_INFO: { + SelfInfoInner self_info; + int success = g_context.account_mgr->GetSelfInfo(self_info); + json ret_data = {{"result", "OK"}, {"code", success}}; + if (success) { + json j_info = { + {"name", self_info.name}, + {"city", self_info.city}, + {"province", self_info.province}, + {"country", self_info.country}, + {"account", self_info.account}, + {"wxid", self_info.wxid}, + {"mobile", self_info.mobile}, + {"headImage", self_info.head_img}, + {"signature", self_info.signature}, + {"dataSavePath", self_info.data_save_path}, + {"currentDataPath", self_info.current_data_path}, + {"dbKey", self_info.db_key}, + }; + ret_data["data"] = j_info; + } + ret = ret_data.dump(); + break; + } + case WECHAT_MSG_SEND_TEXT: { + wstring wxid = GetWStringParam(j_param, "wxid"); + wstring msg = GetWStringParam(j_param, "msg"); + int success = g_context.send_mgr->SendText(WS2LPWS(wxid), WS2LPWS(msg)); + json ret_data = {{"result", "OK"}, {"code", success}}; + ret = ret_data.dump(); + break; + } + case WECHAT_MSG_SEND_AT: { + break; + wstring chat_room_id = GetWStringParam(j_param, "chatRoomId"); + vector wxids = getArrayParam(j_param, "wxids"); + wstring msg = GetWStringParam(j_param, "msg"); + vector wxid_list; + for (unsigned int i = 0; i < wxids.size(); i++) { + wxid_list.push_back(WS2LPWS(wxids[i])); + } + int success = g_context.send_mgr->SendAtText(WS2LPWS(chat_room_id), wxid_list.data(), + wxid_list.size(), WS2LPWS(msg)); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_MSG_SEND_CARD: { + break; + } + case WECHAT_MSG_SEND_IMAGE: { + wstring receiver = GetWStringParam(j_param, "wxid"); + wstring img_path = GetWStringParam(j_param, "imagePath"); + int success = + g_context.send_mgr->SendImage(WS2LPWS(receiver), WS2LPWS(img_path)); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_MSG_SEND_FILE: { + wstring receiver = GetWStringParam(j_param, "wxid"); + wstring file_path = GetWStringParam(j_param, "filePath"); + int success = + g_context.send_mgr->SendFile(WS2LPWS(receiver), WS2LPWS(file_path)); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_MSG_SEND_ARTICLE: { + break; + } + case WECHAT_MSG_SEND_APP: { + break; + } + case WECHAT_MSG_START_HOOK: { + int port = GetIntParam(j_param, "port"); + wstring ip = GetWStringParam(j_param, "ip"); + string client_ip = Utils::WstringToUTF8(ip); + char ip_cstr[16]; + strcpy_s(ip_cstr, client_ip.c_str()); + int success = hooks::HookRecvMsg(ip_cstr, port); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_MSG_STOP_HOOK: { + int success = hooks::UnHookRecvMsg(); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_MSG_START_IMAGE_HOOK: { + break; + } + case WECHAT_MSG_STOP_IMAGE_HOOK: { + break; + } + case WECHAT_MSG_START_VOICE_HOOK: { + break; + } + case WECHAT_MSG_STOP_VOICE_HOOK: { + break; + } + case WECHAT_CONTACT_GET_LIST: { + break; + } + case WECHAT_CONTACT_CHECK_STATUS: { + break; + } + case WECHAT_CONTACT_DEL: { + break; + } + case WECHAT_CONTACT_SEARCH_BY_CACHE: { + break; + } + case WECHAT_CONTACT_SEARCH_BY_NET: { + wstring keyword = GetWStringParam(j_param, "keyword"); + UserInfo *user = nullptr; + int success = g_context.misc_mgr->SearchContactNetScene(WS2LPWS(keyword), &user); + json ret_data = {{"code", success}, {"result", "OK"}}; + if (user) { + json info = { + {"bigImage", Utils::WCharToUTF8(user->big_image)}, + {"smallImage", Utils::WCharToUTF8(user->small_image)}, + {"city", Utils::WCharToUTF8(user->city)}, + {"nation", Utils::WCharToUTF8(user->nation)}, + {"nickname", Utils::WCharToUTF8(user->nickname)}, + {"province", Utils::WCharToUTF8(user->province)}, + {"sex", user->sex}, + {"signature", Utils::WCharToUTF8(user->signature)}, + {"v2", Utils::WCharToUTF8(user->v2)}, + {"v3", Utils::WCharToUTF8(user->v3)}, + }; + ret_data["userInfo"] = info; + } + ret = ret_data.dump(); + break; + } + case WECHAT_CONTACT_ADD_BY_WXID: { + // wstring user_id = GetWStringParam(j_param, "wxid"); + // wstring msg = GetWStringParam(j_param, "msg"); + // int success = g_context.contact_mgr->AddFriendByWxid(WS2LPWS(user_id),WS2LPWS(msg)); + // json ret_data = {{"code", success}, {"result", "OK"}}; + // ret = ret_data.dump(); + break; + } + case WECHAT_CONTACT_ADD_BY_V3: { + break; + } + case WECHAT_CONTACT_ADD_BY_PUBLIC_ID: { + break; + } + case WECHAT_CONTACT_VERIFY_APPLY: { + break; + } + case WECHAT_CONTACT_EDIT_REMARK: { + break; + } + case WECHAT_CHATROOM_GET_MEMBER_LIST: { + wstring room_id = GetWStringParam(j_param, "chatRoomId"); + ChatRoomInner out{0}; + int success = g_context.chat_room_mgr->GetMemberFromChatRoom(WS2LPWS(room_id), out); + json ret_data = {{"code", success}, {"result", "OK"}}; + if (success) { + json member_info = { + {"admin", Utils::WstringToUTF8(out.admin)}, + {"chatRoomId", Utils::WstringToUTF8(out.chat_room)}, + {"members", out.members}, + }; + ret_data["data"] = member_info; + } + ret = ret_data.dump(); + break; + } + case WECHAT_CHATROOM_GET_MEMBER_NICKNAME: { + wstring room_id = GetWStringParam(j_param, "chatRoomId"); + wstring member_id = GetWStringParam(j_param, "memberId"); + + wstring nickname =g_context.chat_room_mgr->GetChatRoomMemberNickname( + WS2LPWS(room_id), WS2LPWS(member_id)); + json ret_data = {{"code", 1}, + {"result", "OK"}, + {"nickname", Utils::WstringToUTF8(nickname)}}; + ret = ret_data.dump(); + break; + } + case WECHAT_CHATROOM_DEL_MEMBER: { + wstring room_id = GetWStringParam(j_param, "chatRoomId"); + vector wxids = getArrayParam(j_param, "memberIds"); + vector wxid_list; + for (unsigned int i = 0; i < wxids.size(); i++) { + wxid_list.push_back(WS2LPWS(wxids[i])); + } + int success = g_context.chat_room_mgr->DelMemberFromChatRoom( + WS2LPWS(room_id), wxid_list.data(), wxid_list.size()); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_CHATROOM_ADD_MEMBER: { + wstring room_id = GetWStringParam(j_param, "chatRoomId"); + vector wxids = getArrayParam(j_param, "memberIds"); + vector wxid_list; + for (unsigned int i = 0; i < wxids.size(); i++) { + wxid_list.push_back(WS2LPWS(wxids[i])); + } + int success = g_context.chat_room_mgr->AddMemberToChatRoom( + WS2LPWS(room_id), wxid_list.data(), wxid_list.size()); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_CHATROOM_SET_ANNOUNCEMENT: { + break; + } + case WECHAT_CHATROOM_SET_CHATROOM_NAME: { + break; + } + case WECHAT_CHATROOM_SET_SELF_NICKNAME: { + wstring room_id = GetWStringParam(j_param, "chatRoomId"); + wstring wxid = GetWStringParam(j_param, "wxid"); + wstring nick = GetWStringParam(j_param, "nickName"); + int success = g_context.chat_room_mgr->ModChatRoomMemberNickName( + WS2LPWS(room_id), WS2LPWS(wxid), WS2LPWS(nick)); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_DATABASE_GET_HANDLES: { + vector v_ptr = DB::GetInstance().GetDbHandles(); + json ret_data = {{"data", json::array()}, {"result", "OK"}}; + for (unsigned int i = 0; i < v_ptr.size(); i++) { + json db_info; + db_info["tables"] = json::array(); + DatabaseInfo *db = reinterpret_cast(v_ptr[i]); + db_info["handle"] = db->handle; + wstring dbname(db->db_name); + db_info["databaseName"] = Utils::WstringToUTF8(dbname); + for (auto table : db->tables) { + json table_info = {{"name", table.name}, + {"tableName", table.table_name}, + {"sql", table.sql}, + {"rootpage", table.rootpage}}; + db_info["tables"].push_back(table_info); + } + ret_data["data"].push_back(db_info); + } + ret = ret_data.dump(); + break; + } + case WECHAT_DATABASE_BACKUP: { + break; + } + case WECHAT_DATABASE_QUERY: { + DWORD db_handle = GetIntParam(j_param, "dbHandle"); + wstring sql = GetWStringParam(j_param, "sql"); + string sql_str = Utils::WstringToUTF8(sql); + vector> items; + int success = DB::GetInstance().Select(db_handle, sql_str.c_str(), items); + json ret_data = { + {"data", json::array()}, {"code", success}, {"result", "OK"}}; + if (success == 0) { + ret = ret_data.dump(); + break; + } + for (auto it : items) { + json temp_arr = json::array(); + for (size_t i = 0; i < it.size(); i++) { + temp_arr.push_back(it[i]); + } + ret_data["data"].push_back(temp_arr); + } + ret = ret_data.dump(); + break; + } + case WECHAT_SET_VERSION: { + break; + } + case WECHAT_LOG_START_HOOK: { + int success = hooks::HookLog(); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_LOG_STOP_HOOK: { + int success = hooks::UnHookLog(); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_BROWSER_OPEN_WITH_URL: { + break; + } + case WECHAT_GET_PUBLIC_MSG: { + break; + } + case WECHAT_MSG_FORWARD_MESSAGE: { + wstring wxid = GetWStringParam(j_param, "wxid"); + ULONG64 msgid = GetULong64Param(j_param, "msgid"); + int success =g_context.send_mgr->ForwardMsg(WS2LPWS(wxid), msgid); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_GET_QRCODE_IMAGE: { + break; + } + case WECHAT_GET_A8KEY: { + break; + } + case WECHAT_MSG_SEND_XML: { + break; + } + case WECHAT_LOGOUT: { + int success = g_context.account_mgr->Logout(); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_GET_TRANSFER: { + wstring wxid = GetWStringParam(j_param, "wxid"); + wstring transcationid = GetWStringParam(j_param, "transcationId"); + wstring transferid = GetWStringParam(j_param, "transferId"); + BOOL response =g_context.misc_mgr->DoConfirmReceipt( + WS2LPWS(wxid), WS2LPWS(transcationid), WS2LPWS(transferid)); + json ret_data = {{"msg", response}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_GET_CONTACT_ALL: { + vector vec; + int success = g_context.contact_mgr->GetAllContact(vec); + json ret_data = { + {"data", json::array()}, {"code", success}, {"result", "OK"}}; + + for (unsigned int i = 0; i < vec.size(); i++) { + json item = { + {"customAccount", + vec[i].custom_account.length > 0 + ? vec[i].custom_account.ptr != nullptr + ? Utils::WCharToUTF8(vec[i].custom_account.ptr) + : string() + : string()}, + {"delFlag", vec[i].del_flag}, + {"userName", vec[i].encrypt_name.length > 0 + ? vec[i].encrypt_name.ptr != nullptr + ? Utils::WCharToUTF8(vec[i].encrypt_name.ptr) + : string() + : string()}, + {"type", vec[i].type}, + {"verifyFlag", vec[i].verify_flag}, + {"verifyFlag", vec[i].verify_flag}, + {"wxid", vec[i].wxid.length > 0 + ? Utils::WCharToUTF8(vec[i].wxid.ptr) + : string()}, + }; + ret_data["data"].push_back(item); + } + ret = ret_data.dump(); + break; + } + case WECHAT_GET_CHATROOM_INFO: { + wstring room_id = GetWStringParam(j_param, "chatRoomId"); + ChatRoomInfoInner chat_room_detail{0}; + int success = g_context.chat_room_mgr->GetChatRoomDetailInfo(WS2LPWS(room_id), + chat_room_detail); + json ret_data = {{"code", success}, {"result", "OK"}}; + if (!success) { + break; + } + json detail = { + {"chatRoomId", + chat_room_detail.chat_room_id.length > 0 + ? Utils::WCharToUTF8(chat_room_detail.chat_room_id.ptr) + : string()}, + {"notice", chat_room_detail.notice.length > 0 + ? Utils::WCharToUTF8(chat_room_detail.notice.ptr) + : string()}, + {"admin", chat_room_detail.admin.length > 0 + ? Utils::WCharToUTF8(chat_room_detail.admin.ptr) + : string()}, + {"xml", chat_room_detail.xml.length > 0 + ? Utils::WCharToUTF8(chat_room_detail.xml.ptr) + : string()}, + }; + ret_data["data"] = detail; + ret = ret_data.dump(); + break; + } + case WECHAT_GET_IMG_BY_NAME: { + wstring image_path = GetWStringParam( j_param, "imagePath"); + wstring save_path = GetWStringParam( j_param, "savePath"); + int success = + g_context.misc_mgr->GetImgByName(WS2LPWS(image_path),WS2LPWS(save_path)); json + ret_data = {{"code", success}, {"result", "OK"}}; ret = + ret_data.dump(); + break; + } + case WECHAT_DO_OCR: { + // wstring image_path = GetWStringParam(j_param, "imagePath"); + // string text(""); + // int success = g_context.misc_mgr->DoOCRTask(WS2LPWS(image_path), text); + // json ret_data = {{"code", success}, {"result", "OK"}, {"text", text}}; + // ret = ret_data.dump(); + break; + } + case WECHAT_SEND_PAT_MSG: { + wstring room_id = GetWStringParam(j_param, "chatRoomId"); + wstring wxid = GetWStringParam(j_param, "wxid"); + int success = g_context.misc_mgr->SendPatMsg(WS2LPWS(room_id), WS2LPWS(wxid)); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_SET_TOP_MSG: { + wstring wxid = GetWStringParam(j_param, "wxid"); + ULONG64 msgid = GetULong64Param(j_param, "msgid"); + int success = g_context.chat_room_mgr->SetTopMsg(WS2LPWS(wxid), msgid); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_REMOVE_TOP_MSG: { + wstring room_id = GetWStringParam(j_param, "chatRoomId"); + ULONG64 msgid = GetULong64Param(j_param, "msgid"); + int success = g_context.chat_room_mgr->RemoveTopMsg(WS2LPWS(room_id), msgid); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_SNS_GET_FIRST_PAGE: { + int success = g_context.sns_mgr->GetFirstPage(); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_SNS_GET_NEXT_PAGE: { + ULONG64 snsid = GetULong64Param(j_param, "snsId"); + int success = g_context.sns_mgr->GetNextPage(snsid); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_CONTACT_NAME: { + wstring pri_id = GetWStringParam(j_param, "id"); + wstring name = g_context.contact_mgr->GetContactOrChatRoomNickname(WS2LPWS(pri_id)); + json ret_data = { + {"code", 1}, {"result", "OK"}, {"name", Utils::WstringToUTF8(name)}}; + ret = ret_data.dump(); + break; + } + case WECHAT_ATTACH_DOWNLOAD: { + ULONG64 msg_id = GetULong64Param(j_param, "msgId"); + int success = g_context.misc_mgr->DoDownloadTask(msg_id); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + case WECHAT_GET_VOICE: { + ULONG64 msg_id = GetULong64Param(j_param, "msgId"); + wstring voice_dir = GetWStringParam(j_param, "voiceDir"); + int success = g_context.misc_mgr->GetVoice(msg_id, WS2LPWS(voice_dir)); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); + break; + } + default: + break; + } + + return ret; +} + +HttpHandler::HttpHandler() {} +HttpHandler::~HttpHandler() {} +void HttpHandler::HandlerRequest(struct mg_connection *c, void *ev_data) { + struct mg_http_message *hm = (struct mg_http_message *)ev_data; + string ret = R"({"result":"OK"})"; + if (mg_http_match_uri(hm, "/api/")) { + try { + ret = Dispatch(c, hm); + } catch (json::exception &e) { + json res = {{"result", "ERROR"}, {"msg", e.what()}}; + ret = res.dump(); + } + if (ret != "") { + mg_http_reply(c, 200, "", ret.c_str(), 0, 0); + } + } else { + mg_http_reply(c, 500, NULL, "%s", "Invalid URI"); + } +} + +} // namespace wxhelper \ No newline at end of file diff --git a/src/http_handler.h b/src/http_handler.h new file mode 100644 index 0000000..828d678 --- /dev/null +++ b/src/http_handler.h @@ -0,0 +1,17 @@ +#ifndef WXHELPER_HTTP_HANDLER_H_ +#define WXHELPER_HTTP_HANDLER_H_ +#include "handler.h" +#include "mongoose.h" +#include +#include +#include +namespace wxhelper { +class HttpHandler : public Handler { + public: + HttpHandler(); + ~HttpHandler(); + void HandlerRequest(struct mg_connection *c, void *ev_data); +}; + +} // namespace wxhelper +#endif diff --git a/src/http_server.cc b/src/http_server.cc index 9e57bc1..0a839fd 100644 --- a/src/http_server.cc +++ b/src/http_server.cc @@ -1,10 +1,10 @@ +#include "pch.h" #include "http_server.h" #include #include "api_route.h" -#include "common.h" -#include "pch.h" + #pragma comment(lib, "ws2_32.lib") using namespace std; using namespace nlohmann; @@ -33,12 +33,12 @@ bool HttpServer::HttpStart() { return true; } #ifdef _DEBUG - CreateConsole(); + Utils::CreateConsole(); #endif running_ = true; - CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)StartHttpServer, NULL, + thread_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)StartHttpServer, NULL, NULL, 0); - return false; + return true; } void HttpServer::StartHttpServer() { @@ -81,6 +81,20 @@ void HttpServer::HandleWebsocketRequest(struct mg_connection *c, mg_ws_send(c, wm->data.ptr, wm->data.len, WEBSOCKET_OP_TEXT); } -bool HttpServer::HttpClose() { return false; } +bool HttpServer::HttpClose() { + if (!running_) { + return true; + } + #ifdef _DEBUG + Utils::CloseConsole(); + #endif + running_ = false; + if (thread_) { + WaitForSingleObject(thread_, -1); + CloseHandle(thread_); + thread_ = NULL; + } + return true; +} } // namespace wxhelper \ No newline at end of file diff --git a/src/http_server.h b/src/http_server.h index 89df650..b4b170c 100644 --- a/src/http_server.h +++ b/src/http_server.h @@ -2,34 +2,33 @@ #define WXHELPER_HTTP_SERVER_H_ #include + #include "http_handler.h" namespace wxhelper { class HttpServer { - - public: - static HttpServer &GetInstance(); bool HttpStart(); bool HttpClose(); void Init(int port); - -private: - HttpServer(){} - HttpServer(const HttpServer &) = delete; - HttpServer &operator=(const HttpServer &) = delete; - ~HttpServer(); - static void StartHttpServer(); - static void EventHandler(struct mg_connection *c, int ev, void *ev_data, void *fn_data); - void HandleHttpRequest(struct mg_connection *c, void *ev_data); - void HandleWebsocketRequest(struct mg_connection *c, void *ev_data); + private: + HttpServer(){}; + HttpServer(const HttpServer &) = delete; + HttpServer &operator=(const HttpServer &) = delete; + ~HttpServer(); + static void StartHttpServer(); + static void EventHandler(struct mg_connection *c, int ev, void *ev_data, + void *fn_data); + void HandleHttpRequest(struct mg_connection *c, void *ev_data); + void HandleWebsocketRequest(struct mg_connection *c, void *ev_data); private: int port_; bool running_; struct mg_mgr mgr_; - HttpHandler* http_handler_; + HttpHandler *http_handler_; + HANDLE thread_; }; } // namespace wxhelper diff --git a/src/log.cc b/src/log.cc new file mode 100644 index 0000000..77841a8 --- /dev/null +++ b/src/log.cc @@ -0,0 +1,37 @@ +#include "log.h" + +#include "easylogging++.h" +INITIALIZE_EASYLOGGINGPP +namespace wxhelper { +Log::Log(/* args */) {} + +Log::~Log() {} + +void Log::initialize() { + + el::Configurations conf; + // 启用日志 + conf.setGlobally(el::ConfigurationType::Enabled, "true"); + // 设置日志文件目录以及文件名 + conf.setGlobally(el::ConfigurationType::Filename, + "log\\log_%datetime{%Y%M%d %H%m%s}.log"); + // 设置日志文件最大文件大小 + conf.setGlobally(el::ConfigurationType::MaxLogFileSize, "20971520"); + // 是否写入文件 + conf.setGlobally(el::ConfigurationType::ToFile, "true"); + // 是否输出控制台 + conf.setGlobally(el::ConfigurationType::ToStandardOutput, "true"); + // 设置日志输出格式 + conf.setGlobally(el::ConfigurationType::Format, + "[%datetime] [%thread] [%loc] [%level] : %msg"); + // 设置日志文件写入周期,如下每100条刷新到输出流中 + #ifdef _DEBUG + conf.setGlobally(el::ConfigurationType::LogFlushThreshold, "1"); + #else + conf.setGlobally(el::ConfigurationType::LogFlushThreshold, "100"); + #endif + // 设置配置文件 + el::Loggers::reconfigureAllLoggers(conf); +} + +} // namespace wxhelper \ No newline at end of file diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..ebca51a --- /dev/null +++ b/src/log.h @@ -0,0 +1,18 @@ +#ifndef WXHELPER_LOG_H_ +#define WXHELPER_LOG_H_ +namespace wxhelper{ + class Log + { + private: + /* data */ + public: + Log(/* args */); + ~Log(); + void initialize(); + }; + + + +} + +#endif \ No newline at end of file diff --git a/src/misc_mgr.cc b/src/misc_mgr.cc new file mode 100644 index 0000000..3aa02b9 --- /dev/null +++ b/src/misc_mgr.cc @@ -0,0 +1,436 @@ +#include "misc_mgr.h" + +#include "pch.h" +#include "wechat_function.h" +#include "base64.h" +#include "db.h" +#include "hooks.h" +#include "easylogging++.h" +#define BUFSIZE 1024 + +#define JPEG0 0xFF +#define JPEG1 0xD8 +#define JPEG2 0xFF +#define PNG0 0x89 +#define PNG1 0x50 +#define PNG2 0x4E +#define BMP0 0x42 +#define BMP1 0x4D +#define GIF0 0x47 +#define GIF1 0x49 +#define GIF2 0x46 +using namespace std; +namespace wxhelper { +MiscMgr::MiscMgr(DWORD base) : BaseMgr::BaseMgr(base) {} +MiscMgr::~MiscMgr() {} +int MiscMgr::SendPatMsg(wchar_t *chat_room_id, wchar_t *wxid) { + int success = -1; + WeChatString chat_room(chat_room_id); + WeChatString self_wxid(wxid); + DWORD get_pat_mgr_addr = base_addr_ + WX_PAT_MGR_OFFSET; + DWORD send_pat_msg_addr = base_addr_ + WX_SEND_PAT_MSG_OFFSET; + DWORD ret_addr = base_addr_ + WX_RET_OFFSET; + __asm { + PUSHAD + CALL get_pat_mgr_addr + PUSH ret_addr + PUSH 0x0 + PUSH EAX + LEA ECX,chat_room + LEA EDX,self_wxid + CALL send_pat_msg_addr + ADD ESP,0xc + MOVZX EAX,AL + MOV success,EAX + POPAD + } + return success; +} + +int MiscMgr::DoOCRTask(wchar_t *img_path, std::string &result) { + int success = -1; + WeChatString path(img_path); + WeChatString null_obj = {0}; + WeChatString ocr_result = {0}; + DWORD ocr_manager_addr = base_addr_ + WX_OCR_MANAGER_OFFSET; + DWORD do_ocr_task_addr = base_addr_ + WX_DO_OCR_TASK_OFFSET; + DWORD init_addr = base_addr_ + WX_INIT_OBJ_OFFSET; + DWORD flag = 0; + __asm { + PUSHAD + PUSHFD + LEA ECX,ocr_result + CALL init_addr + CALL ocr_manager_addr + LEA ECX,null_obj + PUSH ECX + LEA ECX,flag + PUSH ECX + LEA ECX,ocr_result + PUSH ECX + PUSH 0x0 + LEA ECX,path + PUSH ECX + MOV ECX,EAX + CALL do_ocr_task_addr + MOV success,EAX + POPFD + POPAD + } + + if (success == 0) { + DWORD addr = (DWORD)&ocr_result; + DWORD ptr = *(DWORD *)addr; + DWORD num = *(DWORD *)(addr + 0x4); + if (num <= 0) { + return success; + } + + DWORD header = *(DWORD *)ptr; + for (unsigned int i = 0; i < num - 1; i++) { + DWORD content = *(DWORD *)header; + result += Utils::WstringToUTF8(READ_WSTRING(content, 0x14)); + + header = content; + } + } + return success; +} + +int MiscMgr::DoConfirmReceipt(wchar_t *wxid, wchar_t *transcationid, + wchar_t *transferid) { + int success = -1; + WeChatString recv_id(wxid); + WeChatString transcation_id(transcationid); + WeChatString transfer_id(transferid); + char pay_info[0x134] = {0}; + DWORD new_pay_info_addr = base_addr_ + WX_NEW_WCPAYINFO_OFFSET; + DWORD free_pay_info_addr = base_addr_ + WX_FREE_WCPAYINFO_OFFSET; + DWORD do_confirm_addr = base_addr_ + WX_CONFIRM_RECEIPT_OFFSET; + __asm { + PUSHAD + LEA ECX,pay_info + CALL new_pay_info_addr + MOV dword ptr [pay_info + 0x4], 0x1 + MOV dword ptr [pay_info + 0x4C], 0x1 + POPAD + } + memcpy(&pay_info[0x1c], &transcation_id, sizeof(transcation_id)); + memcpy(&pay_info[0x38], &transfer_id, sizeof(transfer_id)); + + __asm { + PUSHAD + PUSH 0x1 + SUB ESP,0x8 + LEA EDX,recv_id + LEA ECX,pay_info + CALL do_confirm_addr + MOV success,EAX + ADD ESP,0xC + PUSH 0x0 + LEA ECX,pay_info + CALL free_pay_info_addr + POPAD + } + + return success; +} + +int MiscMgr::DoDownloadTask(ULONG64 msg_id) { + int success = -1; + int db_index = 0; + int local_id = DB::GetInstance().GetLocalIdByMsgId(msg_id, db_index); + if (local_id < 1) { + return -2; + } + + char chat_msg[0x2D8] = {0}; + DWORD new_chat_msg_addr = base_addr_ + WX_NEW_CHAT_MSG_OFFSET; + DWORD get_chat_mgr_addr = base_addr_ + WX_CHAT_MGR_OFFSET; + DWORD pre_download_mgr_addr = base_addr_ + WX_GET_PRE_DOWNLOAD_MGR_OFFSET; + DWORD push_attach_task_addr = base_addr_ + WX_PUSH_ATTACH_TASK_OFFSET; + DWORD free_addr = base_addr_ + WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET; + DWORD get_by_local_Id_addr = base_addr_ + WX_GET_MGR_BY_PREFIX_LOCAL_ID_OFFSET; + DWORD get_current_data_path_addr = base_addr_ + WX_GET_CURRENT_DATA_PATH_OFFSET; + DWORD free_app_msg_info_addr = base_addr_ + WX_FREE_APP_MSG_INFO_OFFSET; + DWORD push_thumb_task_addr = base_addr_ + WX_PUSH_THUMB_TASK_OFFSET; + DWORD video_mgr_addr = base_addr_ + WX_VIDEO_MGR_OFFSET; + DWORD download_video_image_addr = base_addr_ + WX_VIDEO_MGR_OFFSET; + + WeChatString current_data_path; + + __asm { + PUSHAD + PUSHFD + LEA ECX,current_data_path + CALL get_current_data_path_addr + + LEA ECX,chat_msg + CALL new_chat_msg_addr + + CALL get_chat_mgr_addr + PUSH dword ptr [db_index] + LEA ECX,chat_msg + PUSH dword ptr [local_id] + CALL get_by_local_Id_addr + ADD ESP,0x8 + POPFD + POPAD + } + wstring save_path = L""; + wstring thumb_path = L""; + if (current_data_path.length > 0) { + save_path += current_data_path.ptr; + save_path += L"wxhelper"; + } else { + return -1; + } + + if (!Utils::FindOrCreateDirectoryW(save_path.c_str())) { + return -3; + } + DWORD type = *(DWORD *)(chat_msg + 0x38); + wchar_t *content = *(wchar_t **)(chat_msg + 0x70); + + switch (type) { + case 0x3: { + save_path += L"\\image"; + if (!Utils::FindOrCreateDirectoryW(save_path.c_str())) { + return -3; + } + save_path = save_path +L"\\"+ to_wstring(msg_id) + L".png"; + break; + } + case 0x3E: + case 0x2B: { + save_path += L"\\video"; + if (!Utils::FindOrCreateDirectoryW(save_path.c_str())) { + return -3; + } + thumb_path = save_path + L"\\"+ to_wstring(msg_id) + L".jpg"; + save_path = save_path + L"\\"+ to_wstring(msg_id) + L".mp4"; + + break; + } + case 0x31: { + save_path += L"\\file"; + wcout << save_path << endl; + if (!Utils::FindOrCreateDirectoryW(save_path.c_str())) { + return -3; + } + char xml_app_msg[0xC80] = {0}; + DWORD new_app_msg_addr = base_addr_ + WX_APP_MSG_INFO_OFFSET; + DWORD get_xml_addr = base_addr_ + WX_GET_APP_MSG_XML_OFFSET; + WeChatString w_content(content); + + __asm { + PUSHAD + PUSHFD + LEA ECX,xml_app_msg + CALL new_app_msg_addr + PUSH 0x1 + LEA EAX,w_content + PUSH EAX + LEA ECX,xml_app_msg + CALL get_xml_addr + MOV success,EAX + LEA ECX,xml_app_msg + CALL free_app_msg_info_addr + POPFD + POPAD + } + if (success != 1) { + return -4; + } + WeChatString *file_name = (WeChatString *)((DWORD)xml_app_msg + 0x44); + save_path = save_path +L"\\" + to_wstring(msg_id) + L"_" + + wstring(file_name->ptr, file_name->length); + break; + } + default: + break; + } + WeChatString w_save_path(save_path); + WeChatString w_thumb_path(thumb_path); + int temp =1; + memcpy(&chat_msg[0x19C], &w_thumb_path, sizeof(w_thumb_path)); + memcpy(&chat_msg[0x1B0], &w_save_path, sizeof(w_save_path)); + memcpy(&chat_msg[0x29C], &temp, sizeof(temp)); + // note: the image has been downloaded and will not be downloaded again + // use low-level method + // this function does not work, need to modify chatmsg. + // if (type == 0x3E || type == 0x2B){ + // __asm{ + // PUSHAD + // PUSHFD + // CALL video_mgr_addr + // LEA ECX,chat_msg + // PUSH ECX + // MOV ECX,EAX + // CALL download_video_image_addr + // POPFD + // POPAD + // } + // } + + __asm { + PUSHAD + PUSHFD + CALL pre_download_mgr_addr + PUSH 0x1 + PUSH 0x0 + LEA ECX,chat_msg + PUSH ECX + MOV ECX,EAX + CALL push_attach_task_addr + MOV success,EAX + LEA ECX,chat_msg + PUSH 0x0 + CALL free_addr + POPFD + POPAD + } + + return success; +} + +int MiscMgr::GetVoice(ULONG64 msg_id, wchar_t *dir) { + int success = -1; + string buff = DB::GetInstance().GetVoiceBuffByMsgId(msg_id); + if (buff.size() == 0) { + success = 0; + return success; + } + wstring save_path = wstring(dir); + if (!Utils::FindOrCreateDirectoryW(save_path.c_str())) { + success = -2; + return success; + } + save_path = save_path + L"\\" + to_wstring(msg_id) + L".amr"; + HANDLE file_handle = CreateFileW(save_path.c_str(), GENERIC_ALL, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) { + #ifdef _DEBUG + wcout <<" save_path =" < 0) { + for (unsigned int i = 0; i < bytes_read; i++) { + buffer[i]^=key; + } + } + if (!WriteFile(save_img, (LPCVOID)buffer, bytes_read, &bytes_write, 0)) { + CloseHandle(h_origin_file); + CloseHandle(save_img); + return 0; + } + + do { + if (ReadFile(h_origin_file, buffer, BUFSIZE, &bytes_read, NULL)) { + if (key > 0) { + for (unsigned int i = 0; i < bytes_read; i++) { + buffer[i] ^= key; + } + } + if (!WriteFile(save_img, (LPCVOID)buffer, bytes_read, &bytes_write, 0)) { + CloseHandle(h_origin_file); + CloseHandle(save_img); + return 0; + } + } + } while (bytes_read == BUFSIZE); + CloseHandle(h_origin_file); + CloseHandle(save_img); + return 1; +} + +int MiscMgr::SearchContactNetScene(wchar_t *keyword,UserInfo ** user) { + int success = -1; + hooks::HookSearchContact(); + hooks::DeleteUserInfoCache(); + DWORD search_contact_mgr_addr = base_addr_ + WX_SEARCH_CONTACT_MGR_OFFSET; + DWORD search_contact_addr = base_addr_ + WX_SEARCH_CONTACT_OFFSET; + WeChatString key(keyword); + + __asm { + pushad; + pushfd; + call search_contact_mgr_addr; + lea ebx, key; + push ebx; + mov ecx, eax; + call search_contact_addr; + popfd; + popad; + } + success = 1; + while (hooks::userinfo.error_code == 1 && hooks::user_info_flag_) { + Sleep(20); + } + if (hooks::userinfo.error_code == 0) { + while (hooks::userinfo.over == false && hooks::user_info_flag_) { + Sleep(20); + } + } + *user= &hooks::userinfo; + LOG(INFO)<<"user:" < +#include "Windows.h" +#include "base_mgr.h" +#include "wechat_function.h" +namespace wxhelper { +class MiscMgr :public BaseMgr{ + public: + MiscMgr(DWORD base); + ~MiscMgr(); + int SendPatMsg(wchar_t* chat_room_id, wchar_t* wxid); + int DoOCRTask(wchar_t* img_path, std::string& result); + int DoConfirmReceipt(wchar_t* wxid, wchar_t* transcationid, + wchar_t* transferid); + int DoDownloadTask(ULONG64 msg_id); + + int GetVoice(ULONG64 msg_id, wchar_t* dir); + int GetImgByName(wchar_t* file_path,wchar_t* save_dir); + int SearchContactNetScene(wchar_t *keyword,UserInfo ** user); +}; +} // namespace wxhelper + +#endif \ No newline at end of file diff --git a/src/ocr.cc b/src/ocr.cc deleted file mode 100644 index 4ac88d4..0000000 --- a/src/ocr.cc +++ /dev/null @@ -1,64 +0,0 @@ -#include "pch.h" -#include "ocr.h" - -#include "common.h" -#include "wechat_data.h" - -#define WX_INIT_OBJ_OFFSET 0x7a98f0 -#define WX_OCR_MANAGER_OFFSET 0x7ae470 -#define WX_DO_OCR_TASK_OFFSET 0x13230c0 -using namespace std; -int DoOCRTask(wchar_t *img_path, std::string &result) { - int success = -1; - WeChatString path(img_path); - WeChatString null_obj = {0}; - WeChatString ocr_result = {0}; - DWORD base = GetWeChatWinBase(); - DWORD ocr_manager_addr = base + WX_OCR_MANAGER_OFFSET; - DWORD do_ocr_task_addr = base + WX_DO_OCR_TASK_OFFSET; - DWORD init_addr = base + WX_INIT_OBJ_OFFSET; - DWORD flag = 0; - __asm { - PUSHAD - PUSHFD - LEA ECX,ocr_result - CALL init_addr - CALL ocr_manager_addr - LEA ECX,null_obj - PUSH ECX - LEA ECX,flag - PUSH ECX - LEA ECX,ocr_result - PUSH ECX - PUSH 0x0 - LEA ECX,path - PUSH ECX - MOV ECX,EAX - CALL do_ocr_task_addr - MOV success,EAX - POPFD - POPAD - } - - if (success == 0) { - DWORD addr = (DWORD)&ocr_result; - DWORD ptr = *(DWORD *)addr; - DWORD num = *(DWORD *)(addr + 0x4); - if (num <= 0) { - return success; - } - - DWORD header = *(DWORD *)ptr; - for (unsigned int i = 0; i < num -1; i++) { - DWORD content = *(DWORD *)header; - result += unicode_to_utf8((wchar_t *)READ_WSTRING(content, 0x14).c_str()); - - header = content; - } -#ifdef _DEBUG - cout << "num:" << num << endl; - cout << "all:" << result << endl; -#endif - } - return success; -} \ No newline at end of file diff --git a/src/ocr.h b/src/ocr.h deleted file mode 100644 index b6e73aa..0000000 --- a/src/ocr.h +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef OCR_H_ -#define OCR_H_ -#include -int DoOCRTask(wchar_t* img_path,std::string &result); -#endif \ No newline at end of file diff --git a/src/pat.cc b/src/pat.cc deleted file mode 100644 index bd609a2..0000000 --- a/src/pat.cc +++ /dev/null @@ -1,34 +0,0 @@ -#include "pch.h" -#include "pat.h" - -#include "common.h" -#include "wechat_data.h" - -#define WX_PAT_MGR_OFFSET 0x931730 -#define WX_SEND_PAT_MSG_OFFSET 0x1421940 -#define WX_RET_OFFSET 0x1D58751 - -int SendPatMsg(wchar_t* chat_room_id, wchar_t* wxid) { - int success = -1; - WeChatString chat_room(chat_room_id); - WeChatString self_wxid(wxid); - DWORD base = GetWeChatWinBase(); - DWORD get_pat_mgr_addr = base + WX_PAT_MGR_OFFSET; - DWORD send_pat_msg_addr = base + WX_SEND_PAT_MSG_OFFSET; - DWORD ret_addr = base + WX_RET_OFFSET; - __asm { - PUSHAD - CALL get_pat_mgr_addr - PUSH ret_addr - PUSH 0x0 - PUSH EAX - LEA ECX,chat_room - LEA EDX,self_wxid - CALL send_pat_msg_addr - ADD ESP,0xc - MOVZX EAX,AL - MOV success,EAX - POPAD - } - return success; -} \ No newline at end of file diff --git a/src/pat.h b/src/pat.h deleted file mode 100644 index 7893f9e..0000000 --- a/src/pat.h +++ /dev/null @@ -1,4 +0,0 @@ -#ifndef PAT_H_ -#define PAT_H_ -int SendPatMsg(wchar_t* chat_room_id,wchar_t* wxid); -#endif \ No newline at end of file diff --git a/src/pch.h b/src/pch.h index 6e2a672..7dc3789 100644 --- a/src/pch.h +++ b/src/pch.h @@ -9,10 +9,14 @@ #include #include #include +#include #include #include #include #include +#include "Windows.h" +#include "utils.h" + #endif // PCH_H diff --git a/src/search_contact.cc b/src/search_contact.cc deleted file mode 100644 index 8eec925..0000000 --- a/src/search_contact.cc +++ /dev/null @@ -1,275 +0,0 @@ -#include "pch.h" -#include "search_contact.h" - -#include "common.h" - -#include "wechat_data.h" - -#define WX_SEARCH_CONTACT_ERROR_CODE_HOOK_OFFSeT 0xd94d1e -#define WX_SEARCH_CONTACT_ERROR_CODE_HOOK_NEXT_OFFSeT 0xed13a0 -#define WX_SEARCH_CONTACT_DETAIL_HOOK_OFFSeT 0xa2a260 -#define WX_SEARCH_CONTACT_DETAIL_HOOK_NEXT_OFFSeT 0xa2a4c0 - - -#define WX_SEARCH_CONTACT_OFFSeT 0xc5bb00 -#define WX_SEARCH_CONTACT_MGR_OFFSeT 0xa0d550 - - - -static BOOL kSearchContactHooked = false; -static char kHookSearchContactErrcodeOldAsm[5] = {0}; -static char kHookUserInfoOldAsm[5] = {0}; -static DWORD kWeChatWinBase = GetWeChatWinBase(); - -static DWORD kHookSearchContactErrcodeNextAddr = - kWeChatWinBase + WX_SEARCH_CONTACT_ERROR_CODE_HOOK_NEXT_OFFSeT; -static DWORD kHookSearchContactErrcodeAddr = - kWeChatWinBase + WX_SEARCH_CONTACT_ERROR_CODE_HOOK_OFFSeT; -static DWORD kHookSearchContactErrcodeJmpBackAddr = - kHookSearchContactErrcodeAddr + 0x5; - -static DWORD kHookUserInfoNextAddr = - kWeChatWinBase + WX_SEARCH_CONTACT_DETAIL_HOOK_NEXT_OFFSeT; -static DWORD kHookUserInfoAddr = - kWeChatWinBase + WX_SEARCH_CONTACT_DETAIL_HOOK_OFFSeT; -static DWORD kHookUserInfoJmpBackAddr = kHookUserInfoAddr + 0x5; - -static UserInfo userinfo; - -void SetErrorCode(int code) { userinfo.error_code = code; } - -void SetUserInfoDetail(DWORD address) { - DWORD length = *(DWORD *)(address + 0x8); - userinfo.keyword = new wchar_t[length + 1]; - userinfo.keyword_len = length; - if (length) { - memcpy(userinfo.keyword, (wchar_t *)(*(DWORD *)(address + 0x4)), - (length + 1) * sizeof(wchar_t)); - } else { - ZeroMemory(userinfo.keyword, (length + 1) * sizeof(wchar_t)); - } - - length = *(DWORD *)(address + 0x1C); - userinfo.v3 = new wchar_t[length + 1]; - userinfo.v3_len = length; - if (length) { - memcpy(userinfo.v3, (wchar_t *)(*(DWORD *)(address + 0x18)), - (length + 1) * sizeof(wchar_t)); - }else { - ZeroMemory(userinfo.v3, (length + 1) * sizeof(wchar_t)); - } - - length = *(DWORD *)(address + 0x30); - userinfo.big_image = new wchar_t[length + 1]; - userinfo.big_image_len = length; - if (length) { - memcpy(userinfo.big_image, (wchar_t *)(*(DWORD *)(address + 0x2C)), - (length + 1) * sizeof(wchar_t)); - } else { - ZeroMemory(userinfo.big_image, (length + 1) * sizeof(wchar_t)); - } - - length = *(DWORD *)(address + 0xC8); - userinfo.nickname = new wchar_t[length + 1]; - userinfo.nickname_len = length; - if (length) { - memcpy(userinfo.nickname, (wchar_t *)(*(DWORD *)(address + 0xC4)), - (length + 1) * sizeof(wchar_t)); - }else { - ZeroMemory(userinfo.nickname, (length + 1) * sizeof(wchar_t)); - } - - length = *(DWORD *)(address + 0x108); - userinfo.v2 = new wchar_t[length + 1]; - userinfo.v2_len = length; - if (length) { - memcpy(userinfo.v2, (wchar_t *)(*(DWORD *)(address + 0x104)), - (length + 1) * sizeof(wchar_t)); - }else { - ZeroMemory(userinfo.v2, (length + 1) * sizeof(wchar_t)); - } - - length = *(DWORD *)(address + 0x16C); - userinfo.small_image = new wchar_t[length + 1]; - userinfo.small_image_len = length; - if (length) { - memcpy(userinfo.small_image, (wchar_t *)(*(DWORD *)(address + 0x168)), - (length + 1) * sizeof(wchar_t)); - }else { - ZeroMemory(userinfo.small_image, (length + 1) * sizeof(wchar_t)); - } - - length = *(DWORD *)(address + 0x1F8); - userinfo.signature = new wchar_t[length + 1]; - userinfo.signature_len = length; - if (length) { - memcpy(userinfo.signature, (wchar_t *)(*(DWORD *)(address + 0x1F4)), - (length + 1) * sizeof(wchar_t)); - } else { - ZeroMemory(userinfo.signature, (length + 1) * sizeof(wchar_t)); - } - - length = *(DWORD *)(address + 0x20C); - userinfo.nation = new wchar_t[length + 1]; - userinfo.nation_len = length; - if (length) { - memcpy(userinfo.nation, (wchar_t *)(*(DWORD *)(address + 0x208)), - (length + 1) * sizeof(wchar_t)); - } else { - ZeroMemory(userinfo.nation, (length + 1) * sizeof(wchar_t)); - } - - length = *(DWORD *)(address + 0x220); - userinfo.province = new wchar_t[length + 1]; - userinfo.province_len = length; - if (length) { - memcpy(userinfo.province, (wchar_t *)(*(DWORD *)(address + 0x21C)), - (length + 1) * sizeof(wchar_t)); - } else { - ZeroMemory(userinfo.province, (length + 1) * sizeof(wchar_t)); - } - - length = *(DWORD *)(address + 0x234); - userinfo.city = new wchar_t[length + 1]; - userinfo.city_len = length; - if (length) { - memcpy(userinfo.city, (wchar_t *)(*(DWORD *)(address + 0x230)), - (length + 1) * sizeof(wchar_t)); - } else { - ZeroMemory(userinfo.city, (length + 1) * sizeof(wchar_t)); - } - - userinfo.sex = *(DWORD *)(address + 0x1BC); - userinfo.over = true; -} - -static void DeleteUserInfoCache() { - if (userinfo.keyword) { - delete userinfo.keyword; - } - if (userinfo.v2) { - delete userinfo.v2; - } - if (userinfo.v3) { - delete userinfo.v3; - } - if (userinfo.nickname) { - delete userinfo.nickname; - } - if (userinfo.nation) { - delete userinfo.nation; - } - if (userinfo.province) { - delete userinfo.province; - } - if (userinfo.city) { - delete userinfo.city; - } - if (userinfo.signature) { - delete userinfo.signature; - } - if (userinfo.small_image) { - delete userinfo.small_image; - } - if (userinfo.big_image) { - delete userinfo.big_image; - } - ZeroMemory(&userinfo, sizeof(UserInfo)); - userinfo.error_code = 1; -} - -__declspec(naked) void HandleErrorCode() { - __asm { - pushad; - pushfd; - push edi; - call SetErrorCode; - add esp, 0x4; - popfd; - popad; - call kHookSearchContactErrcodeNextAddr; - jmp kHookSearchContactErrcodeJmpBackAddr; - } -} - -__declspec(naked) void HandleUserInfoDetail() { - __asm { - pushad; - pushfd; - push dword ptr [ebp + 0x14]; - call SetUserInfoDetail; - add esp, 0x4; - popfd; - popad; - call kHookUserInfoNextAddr; - jmp kHookUserInfoJmpBackAddr; - } -} - -int SearchContactNetScene(wchar_t *keyword,UserInfo ** user) { - int success = -1; - HookSearchContact(); - DeleteUserInfoCache(); - DWORD base = GetWeChatWinBase(); - DWORD search_contact_mgr_addr = base + WX_SEARCH_CONTACT_MGR_OFFSeT; - DWORD search_contact_addr = base + WX_SEARCH_CONTACT_OFFSeT; - WeChatString key(keyword); - - __asm { - pushad; - pushfd; - call search_contact_mgr_addr; - lea ebx, key; - push ebx; - mov ecx, eax; - call search_contact_addr; - popfd; - popad; - } - success = 1; - while (userinfo.error_code == 1 && kSearchContactHooked) { - Sleep(20); - } - if (userinfo.error_code == 0) { - while (userinfo.over == false && kSearchContactHooked) { - Sleep(20); - } - } - *user= &userinfo; - return success; -} - -int HookSearchContact() { - kWeChatWinBase = GetWeChatWinBase(); - if (!kWeChatWinBase) { - return -1; - } - if (kSearchContactHooked) { - return 2; - } - kHookSearchContactErrcodeNextAddr = - kWeChatWinBase + WX_SEARCH_CONTACT_ERROR_CODE_HOOK_NEXT_OFFSeT; - kHookSearchContactErrcodeAddr = - kWeChatWinBase + WX_SEARCH_CONTACT_ERROR_CODE_HOOK_OFFSeT; - kHookSearchContactErrcodeJmpBackAddr = kHookSearchContactErrcodeAddr + 0x5; - - kHookUserInfoNextAddr = - kWeChatWinBase + WX_SEARCH_CONTACT_DETAIL_HOOK_NEXT_OFFSeT; - kHookUserInfoAddr = kWeChatWinBase + WX_SEARCH_CONTACT_DETAIL_HOOK_OFFSeT; - kHookUserInfoJmpBackAddr = kHookUserInfoAddr + 0x5; - HookAnyAddress(kHookSearchContactErrcodeAddr, - (LPVOID)HandleErrorCode, - kHookSearchContactErrcodeOldAsm); - HookAnyAddress(kHookUserInfoAddr, (LPVOID)HandleUserInfoDetail, kHookUserInfoOldAsm); - kSearchContactHooked = true; - return 1; -} - -int UnHookSearchContact() { - if (!kSearchContactHooked) return 2; - UnHookAnyAddress(kHookSearchContactErrcodeAddr, - kHookSearchContactErrcodeOldAsm); - UnHookAnyAddress(kHookUserInfoAddr, kHookUserInfoOldAsm); - kSearchContactHooked = false; - return 1; -} \ No newline at end of file diff --git a/src/search_contact.h b/src/search_contact.h deleted file mode 100644 index 5af1548..0000000 --- a/src/search_contact.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef SEARCH_CONTACT_H_ -#define SEARCH_CONTACT_H_ -#include "wechat_data.h" -int SearchContactNetScene(wchar_t *keyword,UserInfo ** user); - -int HookSearchContact(); -int UnHookSearchContact(); -#endif \ No newline at end of file diff --git a/src/self_info.h b/src/self_info.h deleted file mode 100644 index ebec883..0000000 --- a/src/self_info.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef SELF_INFO_H_ -#define SELF_INFO_H_ -#include "wechat_data.h" -int GetSelfInfo(SelfInfoInner& out); - -int CheckLogin(); - -int Logout(); -#endif \ No newline at end of file diff --git a/src/send_file.cc b/src/send_file.cc deleted file mode 100644 index 258a650..0000000 --- a/src/send_file.cc +++ /dev/null @@ -1,67 +0,0 @@ -#include "pch.h" -#include "send_file.h" -#include "common.h" -#include "wechat_data.h" - -#define WX_APP_MSG_MGR_OFFSET 0x76ae20 -#define WX_SEND_FILE_OFFSET 0xb6d1f0 -#define WX_INIT_CHAT_MSG_OFFSET 0xf59e40 -#define WX_FREE_CHAT_MSG_OFFSET 0x756960 - -int SendFile(wchar_t *wxid, wchar_t *file_path){ - int success = 0; - WeChatString to_user(wxid); - WeChatString path(file_path); - char chat_msg[0x2D8] = {0}; - DWORD base = GetWeChatWinBase(); - DWORD app_msg_mgr_addr = base + WX_APP_MSG_MGR_OFFSET; - DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; - DWORD send_file_addr = base + WX_SEND_FILE_OFFSET; - DWORD free_msg_addr = base + WX_FREE_CHAT_MSG_OFFSET; - DWORD temp = 0; - WeChatString null_obj = {0}; - __asm{ - PUSHAD - PUSHFD - CALL app_msg_mgr_addr - SUB ESP,0x14 - MOV temp,EAX - LEA EAX,null_obj - MOV ECX,ESP - PUSH EAX - CALL init_chat_msg_addr - PUSH 0x0 - SUB ESP,0x14 - MOV EDI,ESP - MOV dword ptr [EDI],0 - MOV dword ptr [EDI + 0x4],0 - MOV dword ptr [EDI + 0x8],0 - MOV dword ptr [EDI + 0xc],0 - MOV dword ptr [EDI + 0x10],0 - SUB ESP,0x14 - LEA EAX,path - MOV ECX,ESP - PUSH EAX - CALL init_chat_msg_addr - SUB ESP,0x14 - LEA EAX,to_user - MOV ECX,ESP - PUSH EAX - CALL init_chat_msg_addr - MOV ECX,dword ptr [temp] - LEA EAX,chat_msg - PUSH EAX - CALL send_file_addr - MOV AL,byte ptr [eax + 0x38] - MOVZX EAX,AL - MOV success,EAX - LEA ECX,chat_msg - CALL free_msg_addr - POPFD - POPAD - } - if (success == 0x31){ - return 1; - } - return 0; -} \ No newline at end of file diff --git a/src/send_file.h b/src/send_file.h deleted file mode 100644 index 7436b4d..0000000 --- a/src/send_file.h +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef SEND_FILE_H_ -#define SEND_FILE_H_ - -int SendFile(wchar_t *wxid, wchar_t *file_path); -#endif \ No newline at end of file diff --git a/src/send_image.cc b/src/send_image.cc deleted file mode 100644 index f30ef47..0000000 --- a/src/send_image.cc +++ /dev/null @@ -1,46 +0,0 @@ -#include "pch.h" -#include "send_image.h" -#include "common.h" -#include "wechat_data.h" - -#define WX_SEND_IMAGE_OFFSET 0xce6640 -#define WX_SEND_MESSAGE_MGR_OFFSET 0x768140 -#define WX_INIT_CHAT_MSG_OFFSET 0xf59e40 -#define WX_FREE_CHAT_MSG_OFFSET 0x756960 -int SendImage(wchar_t *wxid, wchar_t *image_path){ - - int success = 0; - WeChatString to_user(wxid); - WeChatString path(image_path); - char chat_msg[0x2D8] ={0}; - DWORD base = GetWeChatWinBase(); - DWORD send_message_mgr_addr = base + WX_SEND_MESSAGE_MGR_OFFSET; - DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; - DWORD send_image_msg_addr = base + WX_SEND_IMAGE_OFFSET; - DWORD free_msg_addr = base + WX_FREE_CHAT_MSG_OFFSET; - DWORD temp = 0; - WeChatString null_obj = {0}; - __asm{ - PUSHAD - CALL send_message_mgr_addr - SUB ESP,0x14 - MOV temp,EAX - LEA EAX,null_obj - MOV ECX,ESP - LEA EDI,path - PUSH EAX - CALL init_chat_msg_addr - MOV ECX,dword ptr [temp] - LEA EAX,to_user - PUSH EDI - PUSH EAX - LEA EAX,chat_msg - PUSH EAX - CALL send_image_msg_addr - MOV success,EAX - LEA ECX,chat_msg - CALL free_msg_addr - POPAD - } - return success; -} \ No newline at end of file diff --git a/src/send_image.h b/src/send_image.h deleted file mode 100644 index 0be9085..0000000 --- a/src/send_image.h +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef SEND_IMAGE_H_ -#define SEND_IMAGE_H_ - -int SendImage(wchar_t *wxid, wchar_t *image_path); -#endif \ No newline at end of file diff --git a/src/send_message_mgr.cc b/src/send_message_mgr.cc new file mode 100644 index 0000000..7a9b38e --- /dev/null +++ b/src/send_message_mgr.cc @@ -0,0 +1,178 @@ +#include "pch.h" +#include "send_message_mgr.h" + +#include "easylogging++.h" + +#include "wechat_function.h" +#include "db.h" + +namespace wxhelper { +SendMessageMgr::SendMessageMgr(DWORD base):BaseMgr(base) {} +SendMessageMgr::~SendMessageMgr() {} +int SendMessageMgr::SendText(wchar_t* wxid, wchar_t* msg) { + int success = -1; + WeChatString to_user(wxid); + WeChatString text_msg(msg); + wchar_t** msg_pptr = &text_msg.ptr; + DWORD base = Utils::GetWeChatWinBase(); + DWORD send_message_mgr_addr = base + WX_SEND_MESSAGE_MGR_OFFSET; + DWORD send_text_msg_addr = base + WX_SEND_TEXT_OFFSET; + DWORD free_chat_msg_addr = base + WX_FREE_CHAT_MSG_OFFSET; + char chat_msg[0x2D8] = {0}; + __asm { + PUSHAD + CALL send_message_mgr_addr + PUSH 0x0 + PUSH 0x0 + PUSH 0x0 + PUSH 0x1 + PUSH 0x0 + MOV EAX,msg_pptr + PUSH EAX + LEA EDX,to_user + LEA ECX,chat_msg + CALL send_text_msg_addr + MOV success,EAX + ADD ESP,0x18 + LEA ECX,chat_msg + CALL free_chat_msg_addr + POPAD + } + LOG_IF((success == -1), ERROR) << "SendText fail"; + return success; +} +int SendMessageMgr::SendAtText(wchar_t* chat_room_id, wchar_t** wxids, int len, + wchar_t* msg) { + int success = -1; + return success; +} +int SendMessageMgr::SendImage(wchar_t* wxid, wchar_t* image_path) { + int success = -1; + WeChatString to_user(wxid); + WeChatString path(image_path); + char chat_msg[0x2D8] = {0}; + DWORD base = Utils::GetWeChatWinBase(); + DWORD send_message_mgr_addr = base + WX_SEND_MESSAGE_MGR_OFFSET; + DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; + DWORD send_image_msg_addr = base + WX_SEND_IMAGE_OFFSET; + DWORD free_msg_addr = base + WX_FREE_CHAT_MSG_OFFSET; + DWORD temp = 0; + WeChatString null_obj = {0}; + __asm { + PUSHAD + CALL send_message_mgr_addr + SUB ESP,0x14 + MOV temp,EAX + LEA EAX,null_obj + MOV ECX,ESP + LEA EDI,path + PUSH EAX + CALL init_chat_msg_addr + MOV ECX,dword ptr [temp] + LEA EAX,to_user + PUSH EDI + PUSH EAX + LEA EAX,chat_msg + PUSH EAX + CALL send_image_msg_addr + MOV success,EAX + LEA ECX,chat_msg + CALL free_msg_addr + POPAD + } + LOG_IF((success == -1), ERROR) << "SendImage fail"; + return success; +} +int SendMessageMgr::SendFile(wchar_t* wxid, wchar_t* file_path) { + int success = -1; + WeChatString to_user(wxid); + WeChatString path(file_path); + char chat_msg[0x2D8] = {0}; + DWORD base = Utils::GetWeChatWinBase(); + DWORD app_msg_mgr_addr = base + WX_APP_MSG_MGR_OFFSET; + DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; + DWORD send_file_addr = base + WX_SEND_FILE_OFFSET; + DWORD free_msg_addr = base + WX_FREE_CHAT_MSG_OFFSET; + DWORD temp = 0; + WeChatString null_obj = {0}; + __asm { + PUSHAD + PUSHFD + CALL app_msg_mgr_addr + SUB ESP,0x14 + MOV temp,EAX + LEA EAX,null_obj + MOV ECX,ESP + PUSH EAX + CALL init_chat_msg_addr + PUSH 0x0 + SUB ESP,0x14 + MOV EDI,ESP + MOV dword ptr [EDI],0 + MOV dword ptr [EDI + 0x4],0 + MOV dword ptr [EDI + 0x8],0 + MOV dword ptr [EDI + 0xc],0 + MOV dword ptr [EDI + 0x10],0 + SUB ESP,0x14 + LEA EAX,path + MOV ECX,ESP + PUSH EAX + CALL init_chat_msg_addr + SUB ESP,0x14 + LEA EAX,to_user + MOV ECX,ESP + PUSH EAX + CALL init_chat_msg_addr + MOV ECX,dword ptr [temp] + LEA EAX,chat_msg + PUSH EAX + CALL send_file_addr + MOV AL,byte ptr [eax + 0x38] + MOVZX EAX,AL + MOV success,EAX + LEA ECX,chat_msg + CALL free_msg_addr + POPFD + POPAD + } + if (success == 0x31) { + return 1; + } + LOG_IF((success == -1), ERROR) << "SendFile fail"; + return success; +} + +int SendMessageMgr::ForwardMsg(wchar_t* wxid, unsigned long long msgid) { + int success = 0; + + int db_index = 0; + int localid = DB::GetInstance().GetLocalIdByMsgId(msgid, db_index); + + if (localid == 0) return 0; + WeChatString to_user(wxid); + DWORD base = Utils::GetWeChatWinBase(); + DWORD forward_msg_addr = base + WX_FORWARD_MSG_OFFSET; + DWORD init_chat_msg_addr = base + WX_INIT_CHAT_MSG_OFFSET; + __asm { + PUSHAD + PUSHFD + MOV EDX, DWORD PTR [db_index] + PUSH EDX + MOV EAX, DWORD PTR [localid] + PUSH EAX + SUB ESP,0x14 + MOV ECX,ESP + LEA ESI,to_user + PUSH ESI + CALL init_chat_msg_addr + XOR ECX,ECX + CALL forward_msg_addr + MOVZX EAX,AL + MOV success,EAX + ADD ESP,0x1c + POPFD + POPAD + } + return success; +} +} // namespace wxhelper \ No newline at end of file diff --git a/src/send_message_mgr.h b/src/send_message_mgr.h new file mode 100644 index 0000000..7e34db5 --- /dev/null +++ b/src/send_message_mgr.h @@ -0,0 +1,18 @@ +#ifndef WXHELPER_SEND_MESSAGE_MGR_H_ +#define WXHELPER_SEND_MESSAGE_MGR_H_ +#include "base_mgr.h" +namespace wxhelper { +class SendMessageMgr:public BaseMgr { + public: + explicit SendMessageMgr(DWORD base); + ~SendMessageMgr(); + int SendText(wchar_t* wxid, wchar_t* msg); + int SendAtText(wchar_t* chat_room_id, wchar_t** wxids, int len, wchar_t* msg); + int SendImage(wchar_t *wxid, wchar_t *image_path); + int SendFile(wchar_t *wxid, wchar_t *file_path); + int ForwardMsg(wchar_t *wxid, unsigned long long msgid); + + private: +}; +} // namespace wxhelper +#endif \ No newline at end of file diff --git a/src/send_text.cc b/src/send_text.cc deleted file mode 100644 index 96c349e..0000000 --- a/src/send_text.cc +++ /dev/null @@ -1,118 +0,0 @@ -#include "pch.h" -#include "send_text.h" - - -#include "common.h" -#include "wechat_data.h" -#include "contact.h" - -#define WX_SEND_TEXT_OFFSET 0xce6c80 - -#define WX_SEND_MESSAGE_MGR_OFFSET 0x768140 - -#define WX_FREE_CHAT_MSG_OFFSET 0x756960 -using namespace std; -/// @brief 发生文本消息 -/// @param wxid wxid -/// @param msg 文本消息 -/// @return 成功返回1 -int SendText(wchar_t* wxid, wchar_t* msg) { - int success = 0; - WeChatString to_user(wxid); - WeChatString text_msg(msg); - wchar_t **msg_pptr = &text_msg.ptr; - - DWORD base = GetWeChatWinBase(); - DWORD send_message_mgr_addr = base + WX_SEND_MESSAGE_MGR_OFFSET; - DWORD send_text_msg_addr = base + WX_SEND_TEXT_OFFSET; - DWORD free_chat_msg_addr = base + WX_FREE_CHAT_MSG_OFFSET; - char chat_msg[0x2D8] ={0}; - __asm{ - PUSHAD - CALL send_message_mgr_addr - PUSH 0x0 - PUSH 0x0 - PUSH 0x0 - PUSH 0x1 - PUSH 0x0 - MOV EAX,msg_pptr - PUSH EAX - LEA EDX,to_user - LEA ECX,chat_msg - CALL send_text_msg_addr - MOV success,EAX - ADD ESP,0x18 - LEA ECX,chat_msg - CALL free_chat_msg_addr - POPAD - } - return success; -} - - - -int SendAtText(wchar_t* chat_room_id,wchar_t** wxids,int len,wchar_t* msg){ - int success = -1; - WeChatString * at_users = new WeChatString[len+1]; - wstring at_msg = L""; - int number =0; - for (int i = 0; i < len; i++) { - wstring nickname; - if (!lstrcmpiW((wchar_t *)wxids[i], (wchar_t *)L"notify@all")) { - nickname = L"������"; - } else { - nickname = GetContactOrChatRoomNickname(wxids[i]); - } - if (nickname.length() == 0) { - continue; - } - - WeChatString temp = {0}; - temp.ptr = (wchar_t *)wxids[i]; - temp.length = wcslen((wchar_t *)wxids[i]); - temp.max_length = wcslen((wchar_t *)wxids[i]) * 2; - memcpy(&at_users[number], &temp, sizeof(WeChatString)); - at_msg = at_msg + L"@" + nickname + L" "; - number++; - } - if (number < 1){ - return success; - } - wstring origin(msg); - at_msg += origin; - AtInner at_list = {0}; - at_list.start = (DWORD)at_users; - at_list.finsh = (DWORD)&at_users[number]; - at_list.end = (DWORD)&at_users[number]; - WeChatString to_user(chat_room_id); - WeChatString text_msg((wchar_t *)at_msg.c_str()); - wchar_t **msg_pptr = &text_msg.ptr; - - DWORD base = GetWeChatWinBase(); - DWORD send_message_mgr_addr = base + WX_SEND_MESSAGE_MGR_OFFSET; - DWORD send_text_msg_addr = base + WX_SEND_TEXT_OFFSET; - DWORD free_chat_msg_addr = base + WX_FREE_CHAT_MSG_OFFSET; - char chat_msg[0x2C4] ={0}; - - __asm{ - PUSHAD - CALL send_message_mgr_addr - PUSH 0x0 - PUSH 0x0 - PUSH 0x0 - PUSH 0x1 - LEA EAX,at_list - PUSH EAX - MOV EAX,msg_pptr - PUSH EAX - LEA EDX,to_user - LEA ECX,chat_msg - CALL send_text_msg_addr - MOV success,EAX - ADD ESP,0x18 - LEA ECX,chat_msg - CALL free_chat_msg_addr - POPAD - } - return success; -} \ No newline at end of file diff --git a/src/send_text.h b/src/send_text.h deleted file mode 100644 index c7b1a76..0000000 --- a/src/send_text.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef SEND_TEXT_H_ -#define SEND_TEXT_H_ - -int SendText(wchar_t* wxid, wchar_t* msg); -int SendAtText(wchar_t* chat_room_id,wchar_t** wxids,int len,wchar_t* msg); -#endif \ No newline at end of file diff --git a/src/singleton.h b/src/singleton.h new file mode 100644 index 0000000..826a09c --- /dev/null +++ b/src/singleton.h @@ -0,0 +1,21 @@ +#ifndef WXHELPER_SINGLETON_H_ +#define WXHELPER_SINGLETON_H_ +template +class Singleton { + protected: + Singleton() {} + ~Singleton() {} + + Singleton(const Singleton&) = delete; + Singleton& operator=(const Singleton&) = delete; + + Singleton(Singleton&&) = delete; + Singleton& operator=(Singleton&&) = delete; + + public: + static T& GetInstance() { + static T instance{}; + return instance; + } +}; +#endif diff --git a/src/sns.h b/src/sns.h deleted file mode 100644 index db782c0..0000000 --- a/src/sns.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef SNS_H_ -#define SNS_H_ - -int GetFirstPage(); -int GetNextPage(ULONG64 sns_id); -#endif \ No newline at end of file diff --git a/src/sns.cc b/src/sns_mgr.cc similarity index 54% rename from src/sns.cc rename to src/sns_mgr.cc index d4a6f91..274e91d 100644 --- a/src/sns.cc +++ b/src/sns_mgr.cc @@ -1,18 +1,17 @@ -#include "pch.h" -#include "sns.h" +#include "pch.h" +#include "sns_mgr.h" -#include "common.h" -#include "wechat_data.h" -using namespace std; -#define WX_SNS_DATA_MGR_OFFSET 0xc39680 -#define WX_SNS_GET_FIRST_PAGE_OFFSET 0x14e2140 -#define WX_SNS_GET_NEXT_PAGE_OFFSET 0x14e21e0 -int GetFirstPage() { +#include "wechat_function.h" + + +namespace wxhelper { +SNSMgr::SNSMgr(DWORD base):BaseMgr(base) {} +SNSMgr::~SNSMgr() {} +int SNSMgr::GetFirstPage() { int success = -1; - DWORD base = GetWeChatWinBase(); - DWORD sns_data_mgr_addr = base + WX_SNS_DATA_MGR_OFFSET; - DWORD get_first_page_addr = base + WX_SNS_GET_FIRST_PAGE_OFFSET; + DWORD sns_data_mgr_addr = base_addr_ + WX_SNS_DATA_MGR_OFFSET; + DWORD get_first_page_addr = base_addr_ + WX_SNS_GET_FIRST_PAGE_OFFSET; char buff[0xB44] = {}; __asm { @@ -29,15 +28,12 @@ int GetFirstPage() { return success; } - - -int GetNextPage(ULONG64 sns_id) { +int SNSMgr::GetNextPage(ULONG64 sns_id) { int success = -1; - DWORD base = GetWeChatWinBase(); - DWORD sns_data_mgr_addr = base + WX_SNS_DATA_MGR_OFFSET; - DWORD get_next_page_addr = base + WX_SNS_GET_NEXT_PAGE_OFFSET; + DWORD sns_data_mgr_addr = base_addr_ + WX_SNS_DATA_MGR_OFFSET; + DWORD get_next_page_addr = base_addr_ + WX_SNS_GET_NEXT_PAGE_OFFSET; VectorInner temp = {}; - __asm{ + __asm { PUSHAD CALL sns_data_mgr_addr LEA ECX,temp @@ -53,3 +49,4 @@ int GetNextPage(ULONG64 sns_id) { } return success; } +} // namespace wxhelper \ No newline at end of file diff --git a/src/sns_mgr.h b/src/sns_mgr.h new file mode 100644 index 0000000..cb90059 --- /dev/null +++ b/src/sns_mgr.h @@ -0,0 +1,14 @@ +#ifndef WXHELPER_SNS_MGR_H_ +#define WXHELPER_SNS_MGR_H_ +#include "Windows.h" +#include "base_mgr.h" +namespace wxhelper{ + class SNSMgr:public BaseMgr{ + public: + explicit SNSMgr(DWORD base); + ~SNSMgr(); + int GetFirstPage(); + int GetNextPage(ULONG64 sns_id); + }; +} +#endif \ No newline at end of file diff --git a/src/common.cc b/src/utils.cc similarity index 54% rename from src/common.cc rename to src/utils.cc index 5a1e218..e2dc855 100644 --- a/src/common.cc +++ b/src/utils.cc @@ -1,51 +1,43 @@ -#include "pch.h" -#include "common.h" +#include "pch.h" +#include "utils.h" -using namespace std; -/// @brief utf8 转换成unicode -/// @param buffer utf8 -/// @return unicode -wstring utf8_to_unicode(const char *buffer) { - int c_size = MultiByteToWideChar(CP_UTF8, 0, buffer, -1, NULL, 0); - if (c_size > 0) { - wchar_t *temp = new wchar_t[c_size + 1]; - MultiByteToWideChar(CP_UTF8, 0, buffer, -1, temp, c_size); - temp[c_size] = L'\0'; - wstring ret(temp); - delete[] temp; - temp = NULL; - return ret; - } - return wstring(); +namespace wxhelper { +std::wstring Utils::UTF8ToWstring(const std::string &str) { + return Utils::AnsiToWstring(str, CP_UTF8); } -/// @brief unicode转换utf8 -/// @param wstr unicode -/// @return string utf8 -string unicode_to_utf8(wchar_t *wstr) { - int c_size = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, FALSE); - if (c_size > 0) { - char *buffer = new char[c_size + 1]; - WideCharToMultiByte(CP_UTF8, 0, wstr, -1, buffer, c_size, NULL, FALSE); - buffer[c_size] = '\0'; - string str(buffer); - delete[] buffer; - buffer = NULL; - return str; - } - return string(); +std::string Utils::WstringToUTF8(const std::wstring &str) { + return Utils::WstringToAnsi(str, CP_UTF8); } -/// @brief 获取WeChatWin.dll基址 -/// @return 基址 -DWORD GetWeChatWinBase() { return (DWORD)GetModuleHandleA("WeChatWin.dll"); } +std::wstring Utils::AnsiToWstring(const std::string &input, DWORD locale) { + int wchar_len = MultiByteToWideChar(locale, 0, input.c_str(), -1, NULL, 0); + if (wchar_len > 0) { + std::vector temp(wchar_len); + MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, &temp[0], wchar_len); + return std::wstring(&temp[0]); + } + + return std::wstring(); +} -/// @brief 创建窗口 -/// @param void -/// @return 创建结果 -BOOL CreateConsole(void) { +std::string Utils::WstringToAnsi(const std::wstring &input, DWORD locale) { + int char_len = WideCharToMultiByte(locale, 0, input.c_str(), -1, 0, 0, 0, 0); + if (char_len > 0) { + std::vector temp(char_len); + WideCharToMultiByte(locale, 0, input.c_str(), -1, &temp[0], char_len, 0, 0); + return std::string(&temp[0]); + } + return std::string(); +} + +DWORD Utils::GetWeChatWinBase() { + return (DWORD)GetModuleHandleA("WeChatWin.dll"); +} + +bool Utils::CreateConsole() { if (AllocConsole()) { AttachConsole(GetCurrentProcessId()); FILE *retStream; @@ -53,100 +45,21 @@ BOOL CreateConsole(void) { if (!retStream) throw std::runtime_error("Stdout redirection failed."); freopen_s(&retStream, "CONOUT$", "w", stderr); if (!retStream) throw std::runtime_error("Stderr redirection failed."); - return 0; - } - return 1; -} - -/// @brief hook any addr -/// @param hook_addr need hook of addr -/// @param jmp_addr hook function addr -/// @param origin origin code -void HookAnyAddress(DWORD hook_addr, LPVOID jmp_addr, char *origin) { - BYTE jmp_code[5] = {0}; - jmp_code[0] = 0xE9; - *(DWORD *)&jmp_code[1] = (DWORD)jmp_addr - hook_addr - 5; - DWORD old_protext = 0; - VirtualProtect((LPVOID)hook_addr, 5, PAGE_EXECUTE_READWRITE, &old_protext); - ReadProcessMemory(GetCurrentProcess(), (LPVOID)hook_addr, origin, 5, 0); - memcpy((void *)hook_addr, jmp_code, 5); - VirtualProtect((LPVOID)hook_addr, 5, old_protext, &old_protext); -} - -/// @brief unhook -/// @param hook_addr hook addr -/// @param origin origin addr code -void UnHookAnyAddress(DWORD hook_addr, char *origin) { - DWORD old_protext = 0; - VirtualProtect((LPVOID)hook_addr, 5, PAGE_EXECUTE_READWRITE, &old_protext); - WriteProcessMemory(GetCurrentProcess(), (LPVOID)hook_addr, origin, 5, 0); - VirtualProtect((LPVOID)hook_addr, 5, old_protext, &old_protext); -} - -/// @brief get timeW -/// @param timestamp timestamp -/// @return str -wstring GetTimeW(long long timestamp) { - wchar_t *wstr = new wchar_t[20]; - memset(wstr, 0, 20 * 2); - tm tm_out; - localtime_s(&tm_out, ×tamp); - swprintf_s(wstr, 20, L"%04d-%02d-%02d %02d:%02d:%02d", 1900 + tm_out.tm_year, - tm_out.tm_mon + 1, tm_out.tm_mday, tm_out.tm_hour, tm_out.tm_min, - tm_out.tm_sec); - wstring strTimeW(wstr); - delete[] wstr; - return strTimeW; -} - -wstring String2Wstring(string str) { - wstring result; - int len = MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.size(), NULL, 0); - wchar_t *buffer = new wchar_t[len + 1]; - MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.size(), buffer, len); - buffer[len] = '\0'; - result.append(buffer); - delete[] buffer; - return result; -} - -string Wstring2String(wstring wstr) { - string result; - int len = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), NULL, 0, - NULL, NULL); - char *buffer = new char[len + 1]; - WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), buffer, len, NULL, - NULL); - buffer[len] = '\0'; - result.append(buffer); - delete[] buffer; - return result; -} - -BOOL FindOrCreateDirectoryW(const wchar_t *path) { - WIN32_FIND_DATAW fd; - HANDLE hFind = ::FindFirstFileW(path, &fd); - if (hFind != INVALID_HANDLE_VALUE && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { - FindClose(hFind); - return true; - } - - if (!::CreateDirectoryW(path, NULL)) { return false; } return true; } -void CloseConsole(){ +void Utils::CloseConsole() { fclose(stdin); fclose(stdout); fclose(stderr); FreeConsole(); } -std::string EncodeHexString(const std::string &str) { +std::string Utils::EncodeHexString(const std::string &str) { const std::string hex_table = "0123456789abcdef"; - string sb; + std::string sb; for (int i = 0; i < str.length(); i++) { sb += hex_table.at((str[i] & 0xf0) >> 4); sb += hex_table.at((str[i] & 0x0f) >> 0); @@ -154,7 +67,7 @@ std::string EncodeHexString(const std::string &str) { return sb; } -std::string Hex2String(const std::string &hex_str) { +std::string Utils::Hex2String(const std::string &hex_str) { std::string ret; const std::string hex_table = "0123456789abcdef"; for (int i = 0; i < hex_str.length(); i += 2) { @@ -164,7 +77,7 @@ std::string Hex2String(const std::string &hex_str) { return ret; } -std::string Bytes2Hex(const BYTE *bytes, const int length) { +std::string Utils::Bytes2Hex(const BYTE *bytes, const int length) { if (bytes == NULL) { return ""; } @@ -178,7 +91,7 @@ std::string Bytes2Hex(const BYTE *bytes, const int length) { return buff; } -void Hex2Bytes(const std::string &hex, BYTE *bytes) { +void Utils::Hex2Bytes(const std::string &hex, BYTE *bytes) { int byte_len = hex.length() / 2; std::string str; unsigned int n; @@ -190,7 +103,8 @@ void Hex2Bytes(const std::string &hex, BYTE *bytes) { } -bool IsDigit(string str) { + +bool Utils::IsDigit(std::string str) { if (str.length() == 0) { return false; } @@ -200,4 +114,64 @@ bool IsDigit(string str) { } } return true; -} \ No newline at end of file +} + +bool Utils::FindOrCreateDirectoryW(const wchar_t *path) { + WIN32_FIND_DATAW fd; + HANDLE hFind = ::FindFirstFileW(path, &fd); + if (hFind != INVALID_HANDLE_VALUE && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + FindClose(hFind); + return true; + } + + if (!::CreateDirectoryW(path, NULL)) { + return false; + } + return true; +} + +void Utils::HookAnyAddress(DWORD hook_addr, LPVOID jmp_addr, char *origin) { + BYTE jmp_code[5] = {0}; + jmp_code[0] = 0xE9; + *(DWORD *)&jmp_code[1] = (DWORD)jmp_addr - hook_addr - 5; + DWORD old_protext = 0; + VirtualProtect((LPVOID)hook_addr, 5, PAGE_EXECUTE_READWRITE, &old_protext); + ReadProcessMemory(GetCurrentProcess(), (LPVOID)hook_addr, origin, 5, 0); + memcpy((void *)hook_addr, jmp_code, 5); + VirtualProtect((LPVOID)hook_addr, 5, old_protext, &old_protext); +} + +void Utils::UnHookAnyAddress(DWORD hook_addr, char *origin) { + DWORD old_protext = 0; + VirtualProtect((LPVOID)hook_addr, 5, PAGE_EXECUTE_READWRITE, &old_protext); + WriteProcessMemory(GetCurrentProcess(), (LPVOID)hook_addr, origin, 5, 0); + VirtualProtect((LPVOID)hook_addr, 5, old_protext, &old_protext); +} + +std::wstring Utils::GetTimeW(long long timestamp) { + wchar_t *wstr = new wchar_t[20]; + memset(wstr, 0, 20 * 2); + tm tm_out; + localtime_s(&tm_out, ×tamp); + swprintf_s(wstr, 20, L"%04d-%02d-%02d %02d:%02d:%02d", 1900 + tm_out.tm_year, + tm_out.tm_mon + 1, tm_out.tm_mday, tm_out.tm_hour, tm_out.tm_min, + tm_out.tm_sec); + std::wstring str_time(wstr); + delete[] wstr; + return str_time; +} + +std::string Utils::WCharToUTF8(wchar_t *wstr) { + int c_size = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, FALSE); + if (c_size > 0) { + char *buffer = new char[c_size]; + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, buffer, c_size, NULL, FALSE); + std::string str(buffer); + delete[] buffer; + buffer = NULL; + return str; + } + return std::string(); +} + +} // namespace wxhelper \ No newline at end of file diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..b5e50b1 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,77 @@ +#ifndef WXHELPER_UTILS_H_ +#define WXHELPER_UTILS_H_ +#include + +#include +#include +#define STRING2INT(str) (Utils::IsDigit(str) ? stoi(str) : 0) +#define WS2LPWS(wstr) (LPWSTR) wstr.c_str() +#define READ_WSTRING(addr, offset) ((*(DWORD *)(addr + offset + 0x4) == 0) ? std::wstring(L"") : std::wstring((wchar_t *)(*(DWORD *)(addr + offset)), *(DWORD *)(addr + offset + 0x4))) + + +namespace wxhelper { + +class Utils { + public: + static std::wstring UTF8ToWstring(const std::string &str); + + static std::string WstringToUTF8(const std::wstring &str); + + static std::wstring AnsiToWstring(const std::string &input, + DWORD locale = CP_ACP); + + static std::string WstringToAnsi(const std::wstring &input, + DWORD locale = CP_ACP); + + static DWORD GetWeChatWinBase(); + + static bool CreateConsole(); + + static void CloseConsole(); + + static std::string EncodeHexString(const std::string &str); + + static std::string Hex2String(const std::string &hex_str); + + static std::string Bytes2Hex(const BYTE *bytes, const int length); + + static void Hex2Bytes(const std::string &hex, BYTE *bytes); + + static bool IsDigit(std::string str); + + static bool FindOrCreateDirectoryW(const wchar_t *path); + + static void HookAnyAddress(DWORD hook_addr, LPVOID jmp_addr, char *origin); + static void UnHookAnyAddress(DWORD hook_addr, char *origin); + static std::wstring GetTimeW(long long timestamp); + + static std::string WCharToUTF8(wchar_t *wstr); + + template + static std::vector split(T1 str, T2 letter) { + std::vector arr; + size_t pos; + while ((pos = str.find_first_of(letter)) != T1::npos) { + T1 str1 = str.substr(0, pos); + arr.push_back(str1); + str = str.substr(pos + 1, str.length() - pos - 1); + } + arr.push_back(str); + return arr; + } + + template + static T1 replace(T1 source, T2 replaced, T1 replaceto) { + std::vector v_arr = split(source, replaced); + if (v_arr.size() < 2) return source; + T1 temp; + for (unsigned int i = 0; i < v_arr.size() - 1; i++) { + temp += v_arr[i]; + temp += replaceto; + } + temp += v_arr[v_arr.size() - 1]; + return temp; + } +}; +} // namespace wxhelper +#endif \ No newline at end of file diff --git a/src/wechat_data.h b/src/wechat_data.h deleted file mode 100644 index 404bfb8..0000000 --- a/src/wechat_data.h +++ /dev/null @@ -1,337 +0,0 @@ -#ifndef WECHAT_DATA_H_ -#define WECHAT_DATA_H_ - -// #include - -#include -struct WeChatString { - wchar_t *ptr; - DWORD length; - DWORD max_length; - DWORD c_ptr = 0; - DWORD c_len = 0; - WeChatString() { WeChatString(NULL); } - - WeChatString(std::wstring &s) { - ptr = (wchar_t *)(s.c_str()); - length = s.length(); - max_length = s.length() * 2; - } - WeChatString(const wchar_t *pStr) { WeChatString((wchar_t *)pStr); } - WeChatString(int tmp) { - ptr = NULL; - length = 0x0; - max_length = 0x0; - } - WeChatString(wchar_t *pStr) { - ptr = pStr; - length = wcslen(pStr); - max_length = wcslen(pStr) * 2; - } - void set_value(const wchar_t *pStr) { - ptr = (wchar_t *)pStr; - length = wcslen(pStr); - max_length = wcslen(pStr) * 2; - } -}; - -struct TableInfo { - char *name; - DWORD name_len; - char *table_name; - DWORD table_name_len; - char *sql; - DWORD sql_len; - char *rootpage; - DWORD rootpage_len; -}; - -struct DatabaseInfo { - DWORD handle = 0; - wchar_t *db_name = NULL; - DWORD db_name_len = 0; - std::vector tables; - DWORD count = 0; - DWORD extrainfo = 0; -}; - -struct Contact { - WeChatString wxid; - WeChatString custom_account; - WeChatString encrypt_name; - WeChatString nick_name; - WeChatString pinyin; - WeChatString pinyin_all; - int del_flag; - int type; - int verify_flag; -}; - -struct ChatRoomInfo { - DWORD vftable; - WeChatString chat_room_id; - WeChatString notice; - WeChatString admin; - DWORD filed_40; - DWORD filed_44; - DWORD filed_48; - DWORD filed_4C; - WeChatString xml; - DWORD filed_64; - DWORD filed_68; - DWORD filed_6C; - DWORD filed_70; - DWORD filed_74; - DWORD filed_78; - DWORD filed_7C; - DWORD filed_80; - DWORD filed_84; - DWORD filed_88; - DWORD filed_8c; - DWORD filed_90; - DWORD filed_94; - DWORD filed_98; - DWORD filed_9C; - DWORD filed_A0; -}; - -struct ChatRoomInfoInner { - WeChatString chat_room_id; - WeChatString notice; - WeChatString admin; - WeChatString xml; - - ~ChatRoomInfoInner(){ - if(chat_room_id.ptr){ - delete []chat_room_id.ptr; - chat_room_id.ptr = nullptr; - } - if(notice.ptr){ - delete []notice.ptr; - notice.ptr = nullptr; - } - if(admin.ptr){ - delete []admin.ptr; - admin.ptr = nullptr; - } - if(xml.ptr){ - delete []xml.ptr; - xml.ptr = nullptr; - } - } -}; - -struct VectorInner { -#ifdef _DEBUG - DWORD head; -#endif - DWORD start; - DWORD finsh; - DWORD end; -}; - -struct ChatRoomInner{ - char* members; - wchar_t* chat_room; - wchar_t* admin; - ~ChatRoomInner(){ - delete []members; - delete []chat_room; - delete []admin; - } -}; - -struct SelfInfoInner{ - std::string name; - std::string city; - std::string province; - std::string country; - std::string account; - std::string wxid; - std::string mobile; - std::string head_img; - std::string data_save_path; - std::string signature; - std::string current_data_path; - std::string db_key; -}; - -struct UserInfo { - int error_code; - wchar_t *keyword; - int keyword_len; - wchar_t *v3; - int v3_len; - wchar_t *nickname; - int nickname_len; - wchar_t *signature; - int signature_len; - wchar_t *v2; - int v2_len; - wchar_t *nation; - int nation_len; - wchar_t *province; - int province_len; - wchar_t *city; - int city_len; - wchar_t *big_image; - int big_image_len; - wchar_t *small_image; - int small_image_len; - DWORD sex; - BOOL over; -}; - -struct AtInner{ - DWORD start; - DWORD finsh; - DWORD end; -}; - -struct ChatMsg { - DWORD **field0_0x0; - DWORD field1_0x4; - ULONG64 sequence; - DWORD field3_0x10; - DWORD field4_0x14; - ULONG64 msgSequence; - DWORD localId; - DWORD field7_0x24; - DWORD field8_0x28; - DWORD field9_0x2c; - ULONG64 msgId; - DWORD type; - DWORD isSendMsg; - DWORD msgStatus; - DWORD timestamp; - WeChatString talker; - DWORD field16_0x5c; - DWORD field17_0x60; - DWORD field18_0x64; - DWORD field19_0x68; - DWORD field20_0x6c; - WeChatString content; - DWORD field22_0x84; - DWORD field23_0x88; - DWORD field24_0x8c; - DWORD field25_0x90; - DWORD field26_0x94; - DWORD field27_0x98; - DWORD field28_0x9c; - DWORD field29_0xa0; - DWORD field30_0xa4; - DWORD field31_0xa8; - DWORD field32_0xac; - DWORD field33_0xb0; - DWORD field34_0xb4; - DWORD field35_0xb8; - DWORD field36_0xbc; - DWORD field37_0xc0; - DWORD field38_0xc4; - DWORD field39_0xc8; - DWORD field40_0xcc; - DWORD field41_0xd0; - DWORD field42_0xd4; - DWORD field43_0xd8; - DWORD field44_0xdc; - DWORD field45_0xe0; - DWORD field46_0xe4; - DWORD field47_0xe8; - DWORD field48_0xec; - DWORD field49_0xf0; - DWORD field50_0xf4; - DWORD field51_0xf8; - DWORD field52_0xfc; - DWORD field53_0x100; - DWORD field54_0x104; - DWORD field55_0x108; - DWORD field56_0x10c; - DWORD field57_0x110; - DWORD field58_0x114; - DWORD field59_0x118; - DWORD field60_0x11c; - DWORD field61_0x120; - DWORD field62_0x124; - DWORD field63_0x128; - DWORD field64_0x12c; - DWORD field65_0x130; - DWORD field66_0x134; - DWORD field67_0x138; - DWORD field68_0x13c; - DWORD field69_0x140; - DWORD field70_0x144; - DWORD field71_0x148; - DWORD field72_0x14c; - DWORD field73_0x150; - DWORD field74_0x154; - DWORD field75_0x158; - DWORD field76_0x15c; - DWORD field77_0x160; - DWORD field78_0x164; - DWORD field79_0x168; - DWORD field80_0x16c; - DWORD field81_0x170; - WeChatString fromGroup; - WeChatString sign; - WeChatString thumbPath; - WeChatString path; - DWORD field86_0x1c4; - DWORD field87_0x1c8; - DWORD field88_0x1cc; - DWORD field89_0x1d0; - DWORD field90_0x1d4; - DWORD field91_0x1d8; - DWORD field92_0x1dc; - DWORD field93_0x1e0; - DWORD field94_0x1e4; - DWORD field95_0x1e8; - DWORD field96_0x1ec; - WeChatString signature; - DWORD field98_0x204; - DWORD field99_0x208; - DWORD field100_0x20c; - DWORD field101_0x210; - DWORD field102_0x214; - DWORD field103_0x218; - DWORD field104_0x21c; - DWORD field105_0x220; - DWORD field106_0x224; - DWORD field107_0x228; - DWORD field108_0x22c; - DWORD field109_0x230; - DWORD field110_0x234; - DWORD field111_0x238; - DWORD field112_0x23c; - DWORD field113_0x240; - DWORD field114_0x244; - DWORD field115_0x248; - DWORD field116_0x24c; - DWORD field117_0x250; - DWORD field118_0x254; - DWORD field119_0x258; - DWORD field120_0x25c; - DWORD field121_0x260; - DWORD field122_0x264; - DWORD field123_0x268; - DWORD field124_0x26c; - DWORD field125_0x270; - DWORD field126_0x274; - DWORD field127_0x278; - DWORD field128_0x27c; - DWORD field129_0x280; - DWORD field130_0x284; - DWORD field131_0x288; - DWORD field132_0x28c; - DWORD field133_0x290; - DWORD field134_0x294; - DWORD field135_0x298; - DWORD field136_0x29c; - DWORD field137_0x2a0; - DWORD field138_0x2a4; - DWORD field139_0x2a8; - DWORD field140_0x2ac; - DWORD field141_0x2b0; - int field142_0x2b4; -}; - -#endif diff --git a/src/wechat_function.h b/src/wechat_function.h new file mode 100644 index 0000000..9db923f --- /dev/null +++ b/src/wechat_function.h @@ -0,0 +1,770 @@ +#ifndef WXHELPER_WECHAT_FUNCTION_H_ +#define WXHELPER_WECHAT_FUNCTION_H_ +#include +#include + +// snsDataMgr +#define WX_SNS_DATA_MGR_OFFSET 0xc39680 +// chatRoomMgr +#define WX_CHAT_ROOM_MGR_OFFSET 0x78cf20 +// contactMgr +#define WX_CONTACT_MGR_OFFSET 0x75a4a0 +// syncMgr +#define WX_SYNC_MGR_OFFSET 0xa87fd0 +// preDownloadMgr +#define WX_GET_PRE_DOWNLOAD_MGR_OFFSET 0x80f110 +// chatMgr +#define WX_CHAT_MGR_OFFSET 0x792700 +// videoMgr +#define WX_VIDEO_MGR_OFFSET 0x829820 +// patMgr +#define WX_PAT_MGR_OFFSET 0x931730 +// searchContactMgr +#define WX_SEARCH_CONTACT_MGR_OFFSET 0xa6cb00 +// appMsgMgr +#define WX_APP_MSG_MGR_OFFSET 0x76ae20 +// sendMessageMgr +#define WX_SEND_MESSAGE_MGR_OFFSET 0x768140 + + +// setChatMsgValue +#define WX_INIT_CHAT_MSG_OFFSET 0xf59e40 + +// chatMsg +#define WX_NEW_CHAT_MSG_OFFSET 0x76f010 +#define WX_FREE_CHAT_MSG_OFFSET 0x756960 +#define WX_FREE_CHAT_MSG_2_OFFSET 0x6f4ea0 +#define WX_FREE_CHAT_MSG_INSTANCE_COUNTER_OFFSET 0x756e30 + + +//sns +#define WX_SNS_GET_FIRST_PAGE_OFFSET 0x14e2140 +#define WX_SNS_GET_NEXT_PAGE_OFFSET 0x14e21e0 + +//chat room +#define WX_GET_CHAT_ROOM_DETAIL_INFO_OFFSET 0xbde090 +// chatRoomInfo +#define WX_NEW_CHAT_ROOM_INFO_OFFSET 0xe99c40 +#define WX_FREE_CHAT_ROOM_INFO_OFFSET 0xe99f40 +#define WX_DEL_CHAT_ROOM_MEMBER_OFFSET 0xbd22a0 +#define WX_ADD_MEMBER_TO_CHAT_ROOM_OFFSET 0xbd1dc0 + + +// chatRoom +#define WX_INIT_CHAT_ROOM_OFFSET 0xe97890 +#define WX_FREE_CHAT_ROOM_OFFSET 0xe97ab0 + +#define WX_GET_MEMBER_FROM_CHAT_ROOM_OFFSET 0xbdf260 +#define WX_MOD_CHAT_ROOM_MEMBER_NICK_NAME_OFFSET 0xbd9680 + +#define WX_TOP_MSG_OFFSET 0xbe1840 +#define WX_REMOVE_TOP_MSG_OFFSET 0xbe1620 + +#define WX_GET_MEMBER_NICKNAME_OFFSET 0xbdf3f0 + +#define WX_FREE_CONTACT_OFFSET 0xea7880 + +// wcpayinfo +#define WX_NEW_WCPAYINFO_OFFSET 0x7b2e60 +#define WX_FREE_WCPAYINFO_OFFSET 0x79c250 +#define WX_CONFIRM_RECEIPT_OFFSET 0x15e2c20 + + +//contact +#define WX_CONTACT_GET_LIST_OFFSET 0xc089f0 +#define WX_CONTACT_DEL_OFFSET 0xb9b3b0 + +#define WX_SET_VALUE_OFFSET 0x1f80900 +#define WX_DO_DEL_CONTACT_OFFSET 0xca6480 +#define WX_GET_CONTACT_OFFSET 0xc04e00 +#define WX_DO_VERIFY_USER_OFFSET 0xc02100 +#define WX_VERIFY_MSG_OFFSET 0xf59d40 + + +// pushAttachTask + + +#define WX_PUSH_ATTACH_TASK_OFFSET 0x82bb40 + +#define WX_FREE_CHAT_MSG_OFFSET 0x756960 +#define WX_GET_MGR_BY_PREFIX_LOCAL_ID_OFFSET 0xbc0370 +#define WX_GET_CURRENT_DATA_PATH_OFFSET 0xc872c0 +#define WX_APP_MSG_INFO_OFFSET 0x7b3d20 +#define WX_GET_APP_MSG_XML_OFFSET 0xe628a0 +#define WX_FREE_APP_MSG_INFO_OFFSET 0x79d900 +#define WX_PUSH_THUMB_TASK_OFFSET 0x82ba40 +#define WX_DOWNLOAD_VIDEO_IMG_OFFSET 0xd46c30 + + + + + +// pat +#define WX_SEND_PAT_MSG_OFFSET 0x1421940 +#define WX_RET_OFFSET 0x1D58751 + + +//search hook +#define WX_SEARCH_CONTACT_ERROR_CODE_HOOK_OFFSET 0xe17054 +#define WX_SEARCH_CONTACT_ERROR_CODE_HOOK_NEXT_OFFSET 0xf57a20 +#define WX_SEARCH_CONTACT_DETAIL_HOOK_OFFSET 0xa8ceb0 +#define WX_SEARCH_CONTACT_DETAIL_HOOK_NEXT_OFFSET 0xa8d100 +#define WX_SEARCH_CONTACT_OFFSET 0xcd1510 + + + +//login +#define WX_LOGOUT_OFFSET 0xe58870 +#define WX_ACCOUNT_SERVICE_OFFSET 0x768c80 +#define WX_GET_APP_DATA_SAVE_PATH_OFFSET 0xf3a610 +#define WX_GET_CURRENT_DATA_PATH_OFFSET 0xc872c0 + + +//forward +#define WX_FORWARD_MSG_OFFSET 0xce6730 +// send file +#define WX_SEND_FILE_OFFSET 0xb6d1f0 +// send image +#define WX_SEND_IMAGE_OFFSET 0xce6640 +// send text +#define WX_SEND_TEXT_OFFSET 0xce6c80 + + +//ocr +#define WX_INIT_OBJ_OFFSET 0x7a98f0 +#define WX_OCR_MANAGER_OFFSET 0x7ae470 +#define WX_DO_OCR_TASK_OFFSET 0x13230c0 + + +//storage + +#define CONTACT_G_PINSTANCE_OFFSET 0x2ffddc8 +#define DB_MICRO_MSG_OFFSET 0x68 +#define DB_CHAT_MSG_OFFSET 0x1C0 +#define DB_MISC_OFFSET 0x3D8 +#define DB_EMOTION_OFFSET 0x558 +#define DB_MEDIA_OFFSET 0x9B8 +#define DB_BIZCHAT_MSG_OFFSET 0x1120 +#define DB_FUNCTION_MSG_OFFSET 0x11B0 +#define DB_NAME_OFFSET 0x14 + +#define STORAGE_START_OFFSET 0x13f8 +#define STORAGE_END_OFFSET 0x13fc + +#define PUBLIC_MSG_MGR_OFFSET 0x303df74 +#define MULTI_DB_MSG_MGR_OFFSET 0x30403b8 +#define FAVORITE_STORAGE_MGR_OFFSET 0x303fd40 +#define FTS_FAVORITE_MGR_OFFSET 0x2ffe908 + +#define OP_LOG_STORAGE_VFTABLE 0x2AD3A20 +#define CHAT_MSG_STORAGE_VFTABLE 0x2AC10F0 +#define CHAT_CR_MSG_STORAGE_VFTABLE 0x2ABEF14 +#define SESSION_STORAGE_VFTABLE 0x2AD3578 +#define APP_INFO_STORAGE_VFTABLE 0x2ABCC58 +#define HEAD_IMG_STORAGE_VFTABLE 0x2ACD9DC +#define HEAD_IMG_URL_STORAGE_VFTABLE 0x2ACDF70 + +#define BIZ_INFO_STORAGE_VFTABLE 0x2ABD718 +#define TICKET_INFO_STORAGE_VFTABLE 0x2AD5400 +#define CHAT_ROOM_STORAGE_VFTABLE 0x2AC299C +#define CHAT_ROOM_INFO_STORAGE_VFTABLE 0x2AC245C +#define MEDIA_STORAGE_VFTABLE 0x2ACE998 +#define NAME_2_ID_STORAGE_VFTABLE 0x2AD222C +#define EMOTION_PACKAGE_STORAGE_VFTABLE 0x2AC6400 + +#define EMOTION_STORAGE_VFTABLE 0x2AC7018 +#define BUFINFO_STORAGE_VFTABLE 0x2AC3178 + +#define CUSTOM_EMOTION_STORAGE_VFTABLE 0x2AC4E90 +#define DEL_SESSIONINFO_STORAGE_VFTABLE 0x2AC5F98 +#define FUNCTION_MSG_STORAGE_VFTABLE 0x2ACD10C + +#define FUNCTION_MSG_TASK_STORAGE_VFTABLE 0x2ACC5C8 +#define REVOKE_MSG_STORAGE_VFTABLE 0x2AD27BC + + + +/*******************hook*********************************************/ + + +// hook image +#define WX_HOOK_IMG_OFFSET 0xd723dc +#define WX_HOOK_IMG_NEXT_OFFSET 0xe91d90 + + + +// hook log +#define WX_HOOK_LOG_OFFSET 0xf57d67 +#define WX_HOOK_LOG_NEXT_OFFSET 0x240ea71 + +// hook msg + +#define WX_RECV_MSG_HOOK_OFFSET 0xd19a0b +#define WX_RECV_MSG_HOOK_NEXT_OFFSET 0x756960 +#define WX_SNS_HOOK_OFFSET 0x14f9e15 +#define WX_SNS_HOOK_NEXT_OFFSET 0x14fa0a0 + + +// hook voice +#define WX_HOOK_VOICE_OFFSET 0xd4d8d8 +#define WX_HOOK_VOICE_NEXT_OFFSET 0x203d130 +#define WX_SELF_ID_OFFSET 0x2FFD484 + +/*******************hook end*********************************************/ + + +/***************************sqlite3***************************************/ +#define SQLITE_OK 0 /* Successful result */ +/* beginning-of-error-codes */ +#define SQLITE_ERROR 1 /* Generic error */ +#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ +#define SQLITE_PERM 3 /* Access permission denied */ +#define SQLITE_ABORT 4 /* Callback routine requested an abort */ +#define SQLITE_BUSY 5 /* The database file is locked */ +#define SQLITE_LOCKED 6 /* A table in the database is locked */ +#define SQLITE_NOMEM 7 /* A malloc() failed */ +#define SQLITE_READONLY 8 /* Attempt to write a readonly database */ +#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/ +#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */ +#define SQLITE_CORRUPT 11 /* The database disk image is malformed */ +#define SQLITE_NOTFOUND 12 /* Unknown opcode in sqlite3_file_control() */ +#define SQLITE_FULL 13 /* Insertion failed because database is full */ +#define SQLITE_CANTOPEN 14 /* Unable to open the database file */ +#define SQLITE_PROTOCOL 15 /* Database lock protocol error */ +#define SQLITE_EMPTY 16 /* Internal use only */ +#define SQLITE_SCHEMA 17 /* The database schema changed */ +#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */ +#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */ +#define SQLITE_MISMATCH 20 /* Data type mismatch */ +#define SQLITE_MISUSE 21 /* Library used incorrectly */ +#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ +#define SQLITE_AUTH 23 /* Authorization denied */ +#define SQLITE_FORMAT 24 /* Not used */ +#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ +#define SQLITE_NOTADB 26 /* File opened that is not a database file */ +#define SQLITE_NOTICE 27 /* Notifications from sqlite3_log() */ +#define SQLITE_WARNING 28 /* Warnings from sqlite3_log() */ +#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ +#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ +/* end-of-error-codes */ + +/* +** CAPI3REF: Extended Result Codes +** KEYWORDS: {extended result code definitions} +** +** In its default configuration, SQLite API routines return one of 30 integer +** [result codes]. However, experience has shown that many of +** these result codes are too coarse-grained. They do not provide as +** much information about problems as programmers might like. In an effort to +** address this, newer versions of SQLite (version 3.3.8 [dateof:3.3.8] +** and later) include +** support for additional result codes that provide more detailed information +** about errors. These [extended result codes] are enabled or disabled +** on a per database connection basis using the +** [sqlite3_extended_result_codes()] API. Or, the extended code for +** the most recent error can be obtained using +** [sqlite3_extended_errcode()]. +*/ +#define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1 << 8)) +#define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2 << 8)) +#define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3 << 8)) +#define SQLITE_IOERR_READ (SQLITE_IOERR | (1 << 8)) +#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2 << 8)) +#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3 << 8)) +#define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4 << 8)) +#define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5 << 8)) +#define SQLITE_IOERR_TRUNCATE (SQLITE_IOERR | (6 << 8)) +#define SQLITE_IOERR_FSTAT (SQLITE_IOERR | (7 << 8)) +#define SQLITE_IOERR_UNLOCK (SQLITE_IOERR | (8 << 8)) +#define SQLITE_IOERR_RDLOCK (SQLITE_IOERR | (9 << 8)) +#define SQLITE_IOERR_DELETE (SQLITE_IOERR | (10 << 8)) +#define SQLITE_IOERR_BLOCKED (SQLITE_IOERR | (11 << 8)) +#define SQLITE_IOERR_NOMEM (SQLITE_IOERR | (12 << 8)) +#define SQLITE_IOERR_ACCESS (SQLITE_IOERR | (13 << 8)) +#define SQLITE_IOERR_CHECKRESERVEDLOCK (SQLITE_IOERR | (14 << 8)) +#define SQLITE_IOERR_LOCK (SQLITE_IOERR | (15 << 8)) +#define SQLITE_IOERR_CLOSE (SQLITE_IOERR | (16 << 8)) +#define SQLITE_IOERR_DIR_CLOSE (SQLITE_IOERR | (17 << 8)) +#define SQLITE_IOERR_SHMOPEN (SQLITE_IOERR | (18 << 8)) +#define SQLITE_IOERR_SHMSIZE (SQLITE_IOERR | (19 << 8)) +#define SQLITE_IOERR_SHMLOCK (SQLITE_IOERR | (20 << 8)) +#define SQLITE_IOERR_SHMMAP (SQLITE_IOERR | (21 << 8)) +#define SQLITE_IOERR_SEEK (SQLITE_IOERR | (22 << 8)) +#define SQLITE_IOERR_DELETE_NOENT (SQLITE_IOERR | (23 << 8)) +#define SQLITE_IOERR_MMAP (SQLITE_IOERR | (24 << 8)) +#define SQLITE_IOERR_GETTEMPPATH (SQLITE_IOERR | (25 << 8)) +#define SQLITE_IOERR_CONVPATH (SQLITE_IOERR | (26 << 8)) +#define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27 << 8)) +#define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28 << 8)) +#define SQLITE_IOERR_BEGIN_ATOMIC (SQLITE_IOERR | (29 << 8)) +#define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30 << 8)) +#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31 << 8)) +#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32 << 8)) +#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33 << 8)) +#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1 << 8)) +#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2 << 8)) +#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1 << 8)) +#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2 << 8)) +#define SQLITE_BUSY_TIMEOUT (SQLITE_BUSY | (3 << 8)) +#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1 << 8)) +#define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2 << 8)) +#define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3 << 8)) +#define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4 << 8)) +#define SQLITE_CANTOPEN_DIRTYWAL (SQLITE_CANTOPEN | (5 << 8)) /* Not Used */ +#define SQLITE_CANTOPEN_SYMLINK (SQLITE_CANTOPEN | (6 << 8)) +#define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1 << 8)) +#define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2 << 8)) +#define SQLITE_CORRUPT_INDEX (SQLITE_CORRUPT | (3 << 8)) +#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1 << 8)) +#define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2 << 8)) +#define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3 << 8)) +#define SQLITE_READONLY_DBMOVED (SQLITE_READONLY | (4 << 8)) +#define SQLITE_READONLY_CANTINIT (SQLITE_READONLY | (5 << 8)) +#define SQLITE_READONLY_DIRECTORY (SQLITE_READONLY | (6 << 8)) +#define SQLITE_ABORT_ROLLBACK (SQLITE_ABORT | (2 << 8)) +#define SQLITE_CONSTRAINT_CHECK (SQLITE_CONSTRAINT | (1 << 8)) +#define SQLITE_CONSTRAINT_COMMITHOOK (SQLITE_CONSTRAINT | (2 << 8)) +#define SQLITE_CONSTRAINT_FOREIGNKEY (SQLITE_CONSTRAINT | (3 << 8)) +#define SQLITE_CONSTRAINT_FUNCTION (SQLITE_CONSTRAINT | (4 << 8)) +#define SQLITE_CONSTRAINT_NOTNULL (SQLITE_CONSTRAINT | (5 << 8)) +#define SQLITE_CONSTRAINT_PRIMARYKEY (SQLITE_CONSTRAINT | (6 << 8)) +#define SQLITE_CONSTRAINT_TRIGGER (SQLITE_CONSTRAINT | (7 << 8)) +#define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8 << 8)) +#define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9 << 8)) +#define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT | (10 << 8)) +#define SQLITE_CONSTRAINT_PINNED (SQLITE_CONSTRAINT | (11 << 8)) +#define SQLITE_CONSTRAINT_DATATYPE (SQLITE_CONSTRAINT | (12 << 8)) +#define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1 << 8)) +#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2 << 8)) +#define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1 << 8)) +#define SQLITE_AUTH_USER (SQLITE_AUTH | (1 << 8)) +#define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1 << 8)) +#define SQLITE_OK_SYMLINK (SQLITE_OK | (2 << 8)) /* internal use only */ + + +#define SQLITE_INTEGER 1 +#define SQLITE_FLOAT 2 +#define SQLITE_BLOB 4 +#define SQLITE_NULL 5 +#define SQLITE_TEXT 3 + +#define SQLITE3_EXEC_OFFSET 0x1e24f70 +#define SQLITE3_BACKUP_INIT_OFFSET 0x1dea900 +#define SQLITE3_PREPARE_OFFSET 0x1e2b8c0 +#define SQLITE3_OPEN_OFFSET 0x1e598b0 +#define SQLITE3_BACKUP_STEP_OFFSET 0x1dead00 +#define SQLITE3_BACKUP_REMAINING_OFFSET 0x1deb440 +#define SQLITE3_BACKUP_PAGECOUNT_OFFSET 0x1deb450 +#define SQLITE3_BACKUP_FINISH_OFFSET 0x1deb340 +#define SQLITE3_SLEEP_OFFSET 0x1e5a0f0 +#define SQLITE3_ERRCODE_OFFSET 0x1e58550 +#define SQLITE3_CLOSE_OFFSET 0x1e56cd0 +#define SQLITE3_STEP_OFFSET 0x1df3770 +#define SQLITE3_COLUMN_COUNT_OFFSET 0x1df3c80 +#define SQLITE3_COLUMN_NAME_OFFSET 0x1df4570 +#define SQLITE3_COLUMN_TYPE_OFFSET 0x1df4410 +#define SQLITE3_COLUMN_BLOB_OFFSET 0x1df3cc0 +#define SQLITE3_COLUMN_BYTES_OFFSET 0x1df3da0 +#define SQLITE3_FINALIZE_OFFSET 0x1df2740 + +typedef int (*Sqlite3_callback)(void*, int, char**, char**); + +typedef int(__cdecl* Sqlite3_exec)(DWORD, /* An open database */ + const char* sql, /* SQL to be evaluated */ + Sqlite3_callback, /* Callback function */ + void*, /* 1st argument to callback */ + char** errmsg /* Error msg written here */ +); +typedef DWORD(__cdecl* Sqlite3_backup_init)( + DWORD* pDest, /* Destination database handle */ + const char* zDestName, /* Destination database name */ + DWORD* pSource, /* Source database handle */ + const char* zSourceName /* Source database name */ +); +typedef int(__cdecl* Sqlite3_prepare)( + DWORD db, /* Database handle */ + const char* zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + DWORD** ppStmt, /* OUT: Statement handle */ + const char** pzTail /* OUT: Pointer to unused portion of zSql */ +); +typedef int(__cdecl* Sqlite3_open)(const char* filename, DWORD** ppDb); +typedef int(__cdecl* Sqlite3_backup_step)(DWORD* p, int nPage); +typedef int(__cdecl* Sqlite3_backup_remaining)(DWORD* p); +typedef int(__cdecl* Sqlite3_backup_pagecount)(DWORD* p); +typedef int(__cdecl* Sqlite3_backup_finish)(DWORD* p); +typedef int(__cdecl* Sqlite3_sleep)(int); +typedef int(__cdecl* Sqlite3_errcode)(DWORD* db); +typedef int(__cdecl* Sqlite3_close)(DWORD*); + +typedef int(__cdecl* Sqlite3_step)(DWORD*); +typedef int(__cdecl* Sqlite3_column_count)(DWORD* pStmt); +typedef const char*(__cdecl* Sqlite3_column_name)(DWORD*, int N); +typedef int(__cdecl* Sqlite3_column_type)(DWORD*, int iCol); +typedef const void*(__cdecl* Sqlite3_column_blob)(DWORD*, int iCol); +typedef int(__cdecl* Sqlite3_column_bytes)(DWORD*, int iCol); +typedef int(__cdecl* Sqlite3_finalize)(DWORD* pStmt); + + +/***************************sqlite3 end*************************************/ + +struct SqlResult { + char *column_name; + DWORD column_name_len; + char *content; + DWORD content_len; + BOOL is_blob; +}; + +struct WeChatString { + wchar_t *ptr; + DWORD length; + DWORD max_length; + DWORD c_ptr = 0; + DWORD c_len = 0; + WeChatString() { WeChatString(NULL); } + + WeChatString(std::wstring &s) { + ptr = (wchar_t *)(s.c_str()); + length = s.length(); + max_length = s.length() * 2; + } + WeChatString(const wchar_t *pStr) { WeChatString((wchar_t *)pStr); } + WeChatString(int tmp) { + ptr = NULL; + length = 0x0; + max_length = 0x0; + } + WeChatString(wchar_t *pStr) { + ptr = pStr; + length = wcslen(pStr); + max_length = wcslen(pStr) * 2; + } + void set_value(const wchar_t *pStr) { + ptr = (wchar_t *)pStr; + length = wcslen(pStr); + max_length = wcslen(pStr) * 2; + } +}; + + +struct SelfInfoInner{ + std::string name; + std::string city; + std::string province; + std::string country; + std::string account; + std::string wxid; + std::string mobile; + std::string head_img; + std::string data_save_path; + std::string signature; + std::string current_data_path; + std::string db_key; +}; + +struct VectorInner { +#ifdef _DEBUG + DWORD head; +#endif + DWORD start; + DWORD finsh; + DWORD end; +}; + +struct TableInfo { + char *name; + DWORD name_len; + char *table_name; + DWORD table_name_len; + char *sql; + DWORD sql_len; + char *rootpage; + DWORD rootpage_len; +}; + +struct DatabaseInfo { + DWORD handle = 0; + wchar_t *db_name = NULL; + DWORD db_name_len = 0; + std::vector tables; + DWORD count = 0; + DWORD extrainfo = 0; +}; + + +struct Contact { + WeChatString wxid; + WeChatString custom_account; + WeChatString encrypt_name; + WeChatString nick_name; + WeChatString pinyin; + WeChatString pinyin_all; + int del_flag; + int type; + int verify_flag; +}; + +struct ChatRoomInfo { + DWORD vftable; + WeChatString chat_room_id; + WeChatString notice; + WeChatString admin; + DWORD filed_40; + DWORD filed_44; + DWORD filed_48; + DWORD filed_4C; + WeChatString xml; + DWORD filed_64; + DWORD filed_68; + DWORD filed_6C; + DWORD filed_70; + DWORD filed_74; + DWORD filed_78; + DWORD filed_7C; + DWORD filed_80; + DWORD filed_84; + DWORD filed_88; + DWORD filed_8c; + DWORD filed_90; + DWORD filed_94; + DWORD filed_98; + DWORD filed_9C; + DWORD filed_A0; +}; + +struct ChatRoomInfoInner { + WeChatString chat_room_id; + WeChatString notice; + WeChatString admin; + WeChatString xml; + + ~ChatRoomInfoInner(){ + if(chat_room_id.ptr){ + delete []chat_room_id.ptr; + chat_room_id.ptr = nullptr; + } + if(notice.ptr){ + delete []notice.ptr; + notice.ptr = nullptr; + } + if(admin.ptr){ + delete []admin.ptr; + admin.ptr = nullptr; + } + if(xml.ptr){ + delete []xml.ptr; + xml.ptr = nullptr; + } + } +}; + +struct ChatRoomInner{ + char* members; + wchar_t* chat_room; + wchar_t* admin; + ~ChatRoomInner(){ + delete []members; + delete []chat_room; + delete []admin; + } +}; + +struct UserInfo { + int error_code; + wchar_t *keyword; + int keyword_len; + wchar_t *v3; + int v3_len; + wchar_t *nickname; + int nickname_len; + wchar_t *signature; + int signature_len; + wchar_t *v2; + int v2_len; + wchar_t *nation; + int nation_len; + wchar_t *province; + int province_len; + wchar_t *city; + int city_len; + wchar_t *big_image; + int big_image_len; + wchar_t *small_image; + int small_image_len; + DWORD sex; + BOOL over; +}; + +struct AtInner{ + DWORD start; + DWORD finsh; + DWORD end; +}; + +struct ChatMsg { + DWORD **field0_0x0; + DWORD field1_0x4; + ULONG64 sequence; + DWORD field3_0x10; + DWORD field4_0x14; + ULONG64 msgSequence; + DWORD localId; + DWORD field7_0x24; + DWORD field8_0x28; + DWORD field9_0x2c; + ULONG64 msgId; + DWORD type; + DWORD isSendMsg; + DWORD msgStatus; + DWORD timestamp; + WeChatString talker; + DWORD field16_0x5c; + DWORD field17_0x60; + DWORD field18_0x64; + DWORD field19_0x68; + DWORD field20_0x6c; + WeChatString content; + DWORD field22_0x84; + DWORD field23_0x88; + DWORD field24_0x8c; + DWORD field25_0x90; + DWORD field26_0x94; + DWORD field27_0x98; + DWORD field28_0x9c; + DWORD field29_0xa0; + DWORD field30_0xa4; + DWORD field31_0xa8; + DWORD field32_0xac; + DWORD field33_0xb0; + DWORD field34_0xb4; + DWORD field35_0xb8; + DWORD field36_0xbc; + DWORD field37_0xc0; + DWORD field38_0xc4; + DWORD field39_0xc8; + DWORD field40_0xcc; + DWORD field41_0xd0; + DWORD field42_0xd4; + DWORD field43_0xd8; + DWORD field44_0xdc; + DWORD field45_0xe0; + DWORD field46_0xe4; + DWORD field47_0xe8; + DWORD field48_0xec; + DWORD field49_0xf0; + DWORD field50_0xf4; + DWORD field51_0xf8; + DWORD field52_0xfc; + DWORD field53_0x100; + DWORD field54_0x104; + DWORD field55_0x108; + DWORD field56_0x10c; + DWORD field57_0x110; + DWORD field58_0x114; + DWORD field59_0x118; + DWORD field60_0x11c; + DWORD field61_0x120; + DWORD field62_0x124; + DWORD field63_0x128; + DWORD field64_0x12c; + DWORD field65_0x130; + DWORD field66_0x134; + DWORD field67_0x138; + DWORD field68_0x13c; + DWORD field69_0x140; + DWORD field70_0x144; + DWORD field71_0x148; + DWORD field72_0x14c; + DWORD field73_0x150; + DWORD field74_0x154; + DWORD field75_0x158; + DWORD field76_0x15c; + DWORD field77_0x160; + DWORD field78_0x164; + DWORD field79_0x168; + DWORD field80_0x16c; + DWORD field81_0x170; + WeChatString fromGroup; + WeChatString sign; + WeChatString thumbPath; + WeChatString path; + DWORD field86_0x1c4; + DWORD field87_0x1c8; + DWORD field88_0x1cc; + DWORD field89_0x1d0; + DWORD field90_0x1d4; + DWORD field91_0x1d8; + DWORD field92_0x1dc; + DWORD field93_0x1e0; + DWORD field94_0x1e4; + DWORD field95_0x1e8; + DWORD field96_0x1ec; + WeChatString signature; + DWORD field98_0x204; + DWORD field99_0x208; + DWORD field100_0x20c; + DWORD field101_0x210; + DWORD field102_0x214; + DWORD field103_0x218; + DWORD field104_0x21c; + DWORD field105_0x220; + DWORD field106_0x224; + DWORD field107_0x228; + DWORD field108_0x22c; + DWORD field109_0x230; + DWORD field110_0x234; + DWORD field111_0x238; + DWORD field112_0x23c; + DWORD field113_0x240; + DWORD field114_0x244; + DWORD field115_0x248; + DWORD field116_0x24c; + DWORD field117_0x250; + DWORD field118_0x254; + DWORD field119_0x258; + DWORD field120_0x25c; + DWORD field121_0x260; + DWORD field122_0x264; + DWORD field123_0x268; + DWORD field124_0x26c; + DWORD field125_0x270; + DWORD field126_0x274; + DWORD field127_0x278; + DWORD field128_0x27c; + DWORD field129_0x280; + DWORD field130_0x284; + DWORD field131_0x288; + DWORD field132_0x28c; + DWORD field133_0x290; + DWORD field134_0x294; + DWORD field135_0x298; + DWORD field136_0x29c; + DWORD field137_0x2a0; + DWORD field138_0x2a4; + DWORD field139_0x2a8; + DWORD field140_0x2ac; + DWORD field141_0x2b0; + int field142_0x2b4; +}; + +struct InnerMessageStruct { + char *buffer; + int length; + ~InnerMessageStruct() { + if (this->buffer != NULL) { + delete[] this->buffer; + this->buffer = NULL; + } + } +}; + +struct Unkown{ + DWORD field1 = 0; + DWORD field2= 0; + DWORD field3= 0; + DWORD field4= 0; + DWORD field5= 0; + DWORD field6= 0; +}; +#endif \ No newline at end of file diff --git a/src/wxhelper.cc b/src/wxhelper.cc deleted file mode 100644 index 73e9397..0000000 --- a/src/wxhelper.cc +++ /dev/null @@ -1,24 +0,0 @@ -#include "wxhelper.h" - -#include - -#include "pch.h" - -namespace wxhelper { -WXHelper& WXHelper::GetInstance() { - static WXHelper p; - return p; -} - -int WXHelper::http_start(int port) { - HttpServer::GetInstance().Init(port); - bool ret = HttpServer::GetInstance().HttpStart(); - return ret == true ? 1 : 0; -} - -int WXHelper::http_close() { - bool ret = HttpServer::GetInstance().HttpClose(); - return ret == true ? 1 : 0; -} - -} // namespace wxhelper \ No newline at end of file diff --git a/src/wxhelper.h b/src/wxhelper.h deleted file mode 100644 index d0f60be..0000000 --- a/src/wxhelper.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef WXHELPER_WXHELPER_H_ -#define WXHELPER_WXHELPER_H_ -#include "http_server.h" -namespace wxhelper { - -class WXHelper { - public: - static WXHelper &GetInstance(); - - int http_start(int port); - int http_close(); - - private: - WXHelper(){}; - WXHelper(const WXHelper &) = delete; - WXHelper &operator=(const WXHelper &) = delete; - ~WXHelper(){}; -}; -} // namespace wxhelper - -#endif \ No newline at end of file From 7220b23cb390129a1eb92c3bd6549832694647b1 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Sat, 8 Apr 2023 13:18:27 +0800 Subject: [PATCH 59/59] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=80=9A=E8=BF=87?= =?UTF-8?q?=E5=A5=BD=E5=8F=8B=E7=94=B3=E8=AF=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 40 ++++++++++++++++++++-- src/contact_mgr.cc | 80 ++++++++++++++++++++++++++++--------------- src/contact_mgr.h | 1 + src/http_handler.cc | 15 +++++--- src/wechat_function.h | 4 ++- 5 files changed, 105 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index a813e39..9d31abc 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,12 @@ 10.取消hook消息 11.hook图片 12.取消hook图片 -13.hook语音 +13.hook语音(不推荐) 14.取消hook语音 19.通过手机或qq查找微信 - +20.通过wxid添加好友 +23.通过好友申请 25.获取群成员 26.获取群成员昵称 27.删除群成员 @@ -597,7 +598,42 @@ |参数|必选|类型|说明| |---|---|---|---| |wxid |true |string| 好友wxid | +|msg |true |string| 验证消息 | +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "wxid":"wxid_o1112222" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + +#### 23.通过好友申请** +###### 接口功能 +> 通过好友的申请 + +###### 接口地址 +> [/api/?type=23](/api/?type=23) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|v3 |true |string| 添加好友消息内容里的encryptusername| +|v4 |true |string| 添加好友消息内容里的ticket| ###### 返回字段 |返回字段|字段类型|说明 | diff --git a/src/contact_mgr.cc b/src/contact_mgr.cc index 4b2582a..9525e41 100644 --- a/src/contact_mgr.cc +++ b/src/contact_mgr.cc @@ -129,17 +129,22 @@ int ContactMgr::AddFriendByWxid(wchar_t *wxid,wchar_t* msg) { DWORD fn1_addr = base_addr_ + 0x758720; WeChatString user_id(wxid); WeChatString w_msg(msg); - DWORD null_ptr = 0; DWORD instance =0; - Unkown null_obj={}; - null_obj.field6 = 0xF; + Unkown null_obj={0,0,0,0,0,0xF}; __asm{ PUSHAD PUSHFD CALL contact_mgr_addr - MOV dword ptr [instance],EAX + MOV dword ptr [instance],EAX + MOV EDI,0x6 + MOV ESI,0 + MOV EAX,0x2 SUB ESP,0x18 MOV EAX,ESP + MOV dword ptr ds:[EAX],0 + MOV dword ptr ds:[EAX+0x14],0xF + MOV dword ptr ds:[EAX+0x10],0 + MOV byte ptr ds:[EAX],0 SUB ESP,0x18 LEA EAX,null_obj MOV ECX,ESP @@ -147,7 +152,8 @@ int ContactMgr::AddFriendByWxid(wchar_t *wxid,wchar_t* msg) { CALL fn1_addr PUSH 0x0 PUSH 0x6 - MOV EAX,w_msg + MOV EAX,w_msg + SUB ESP,0x14 MOV ECX,ESP PUSH -0x1 @@ -155,6 +161,7 @@ int ContactMgr::AddFriendByWxid(wchar_t *wxid,wchar_t* msg) { CALL verify_msg_addr PUSH 0x2 LEA EAX,user_id + SUB ESP,0x14 MOV ECX,ESP PUSH EAX @@ -165,29 +172,48 @@ int ContactMgr::AddFriendByWxid(wchar_t *wxid,wchar_t* msg) { POPFD POPAD } - - // __asm { - // PUSHAD - // PUSHFD - // SUB ESP,0x14 - // MOV ECX,ESP - // PUSH -0x1 - // PUSH w_msg - // CALL verify_msg_addr - // PUSH 0x2 - // LEA EAX,user_id - // SUB ESP,0x14 - // MOV ECX,ESP - // PUSH EAX - // CALL set_value_addr - // CALL contact_mgr_addr - // MOV ECX,EAX - // CALL do_verify_user_addr - // MOV success,EAX - // POPFD - // POPAD - // } return success; } + int ContactMgr::VerifyApply(wchar_t *v3, wchar_t *v4){ + int success = -1; + DWORD set_value_addr = base_addr_ + WX_INIT_CHAT_MSG_OFFSET; + DWORD verify_addr = base_addr_ + WX_VERIFY_OK_OFFSET; + DWORD new_helper_addr = base_addr_ + WX_NEW_ADD_FRIEND_HELPER_OFFSET; + DWORD free_helper_addr = base_addr_ + WX_FREE_ADD_FRIEND_HELPER_OFFSET; + + WeChatString v4_str(v4); + WeChatString v3_str(v3); + char helper_obj[0x40] = {0}; + char nullbuffer[0x3CC] = {0}; + __asm { + PUSHAD + PUSHFD + LEA ECX,helper_obj + CALL new_helper_addr + MOV ESI,0x0 + MOV EDI,0x6 + PUSH ESI + PUSH EDI + SUB ESP,0x14 + MOV ECX,ESP + LEA EAX,v4_str + PUSH EAX + CALL set_value_addr + SUB ESP,0x8 + PUSH 0x0 + LEA EAX, nullbuffer + PUSH EAX + LEA EAX,v3_str + PUSH EAX + LEA ECX,helper_obj + CALL verify_addr + MOV success,EAX + LEA ECX,helper_obj + CALL free_helper_addr + POPFD + POPAD + } + return success; + } } // namespace wxhelper \ No newline at end of file diff --git a/src/contact_mgr.h b/src/contact_mgr.h index b522427..677d564 100644 --- a/src/contact_mgr.h +++ b/src/contact_mgr.h @@ -14,6 +14,7 @@ class ContactMgr : public BaseMgr { int DelContact(wchar_t* wxid); std::wstring GetContactOrChatRoomNickname(wchar_t* id); int AddFriendByWxid(wchar_t* wxid,wchar_t* msg); + int VerifyApply(wchar_t *v3, wchar_t *v4); }; } // namespace wxhelper diff --git a/src/http_handler.cc b/src/http_handler.cc index 84458b7..1b94fdc 100644 --- a/src/http_handler.cc +++ b/src/http_handler.cc @@ -237,11 +237,11 @@ string Dispatch(struct mg_connection *c, struct mg_http_message *hm) { break; } case WECHAT_CONTACT_ADD_BY_WXID: { - // wstring user_id = GetWStringParam(j_param, "wxid"); - // wstring msg = GetWStringParam(j_param, "msg"); - // int success = g_context.contact_mgr->AddFriendByWxid(WS2LPWS(user_id),WS2LPWS(msg)); - // json ret_data = {{"code", success}, {"result", "OK"}}; - // ret = ret_data.dump(); + wstring user_id = GetWStringParam(j_param, "wxid"); + wstring msg = GetWStringParam(j_param, "msg"); + int success = g_context.contact_mgr->AddFriendByWxid(WS2LPWS(user_id),WS2LPWS(msg)); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); break; } case WECHAT_CONTACT_ADD_BY_V3: { @@ -251,6 +251,11 @@ string Dispatch(struct mg_connection *c, struct mg_http_message *hm) { break; } case WECHAT_CONTACT_VERIFY_APPLY: { + wstring v3 = GetWStringParam(j_param, "v3"); + wstring v4 = GetWStringParam(j_param, "v4"); + int success = g_context.contact_mgr->VerifyApply(WS2LPWS(v3),WS2LPWS(v4)); + json ret_data = {{"code", success}, {"result", "OK"}}; + ret = ret_data.dump(); break; } case WECHAT_CONTACT_EDIT_REMARK: { diff --git a/src/wechat_function.h b/src/wechat_function.h index 9db923f..59ee100 100644 --- a/src/wechat_function.h +++ b/src/wechat_function.h @@ -79,7 +79,9 @@ #define WX_GET_CONTACT_OFFSET 0xc04e00 #define WX_DO_VERIFY_USER_OFFSET 0xc02100 #define WX_VERIFY_MSG_OFFSET 0xf59d40 - +#define WX_VERIFY_OK_OFFSET 0xa18bd0 +#define WX_NEW_ADD_FRIEND_HELPER_OFFSET 0xa17d50 +#define WX_FREE_ADD_FRIEND_HELPER_OFFSET 0xa17e70 // pushAttachTask