From e62a37b3542c2f7abc1a396b3aae067b0233a1c8 Mon Sep 17 00:00:00 2001 From: Gy Hu Date: Mon, 26 Dec 2022 16:44:14 +0800 Subject: [PATCH 01/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] 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 z%#WoxEY@ackNV&vYl*elBBT%#jl-A0WyU$LK6c}*$nC}yFp6$^8mzJS?? z|K%e>Pw6DA5DpTs#`RS>JKk4_vj5dwW%*sQ(a<2$Pm^p_L5<$#J`(J#xM`x3T`9tb zI9T-mx|6_>TNC>ie;kS)**BPpY*X;Lu}1v5ilnnmC^ILwg^`VK4r6US6~JtB!tP-) z!6(&Bh1PN)5}aiab7n!~k_@3=S4%Z zA21w8*gNb5`RaTnUoi-DT3+H3R(}~B2_hDQM`WxIQD#w;Vk}d9xt}vkvL=P5q|-E* zK$h!8<;+61C-}@MTXdUwH4Bf0=r`i-K)%5jt;rL}`7O3e380`+v>+-Q^c2F*Rzle2 znIr@*=oo1$p|M(&LhYWKR8x1JlY0o*-c!}Q#}~QBQcZGh>k*V$RxSU8WaugPbYV)F zGlc_U{hJ?*_&8|iW9P+ZOxR^i*ycK1&|EcSf#Ub;JT>k4HJHcsEi7oRKKv(VN6KYy zk%Jx0sf^m#j%Ko^AXS5mJaY3{ifHG}UDtG@(4Ch5K2IFqCpnMb%q|Q97pJKikuvn! ztV_1CTxF@;>cVLt&BZSf`pAz%)tzVq&}Ex@VmtNelb8nlouTSBgkx0oG3!M}yo{7) zzlUuUW%c$|9gYgY7kRNW}ng$s{^{Zh<5wXx(uYwOi$ zrA@hjA;ro@XSc58pxM1{XuZKEG6G;d7GcWDG-@ui>@!+T8JQ#Ba=kYAEp>7yEBK0I^)|3o!^_A|%PFlqvea$0O46wwmk1{rgFkY; zE#(AXbGT+_`{wa0Mw0GeW1;I>&)hkoV$Y52%4+()=obm=-IM7b{phN(Q$YpUkT{hc zd-uet?C&}(T=IN$D%)y(16O1fPq5f#=CG$R<~=bYyErhjqJfBV>x;jNy#{-I8TJ$v zH<qy9%71u&sP5o8d-} z&2U3+hEIJ%ZHBMg*<(9=Oxmiff=TXGXR&3TXFbxNiL=EcAXKlOH}`SpjW=*`VO~qo z#94b&e3xyVtG;cLqDvXI=C0N6O8@t7tj>j3`{;0w=6}Y@jN0FunDPCwz&%$`)4PjP@tI8w6=K=ivTd;-=dZW)J6UWt=$j33^VNH6KV+K=Kt zqs8APC63`he6hO5&hB=uS8-T_C&Tcr;Wid!Wv#8z{Z@TRS3O*$q%)Cc*etO9mlOsU z607qvu{tku7ZiD_aw)o)x|BMp85Z&s+YFeV!xECT`$7e{#!$A1m%sQ$Pa|Q8jsl7J z0>cH!+bPOioEmS9oy{02~=aF|HA@5m{5l4fOti@>!Sv0ic?A379$TgVe zy07UNs6JL1LDA1E38pMOrBEc3fK_}e^8|c5-L%TAkI*KB(=Rb!3XgZ@?&4HF3x3?J z0Ff`018gAsR-3)gUva)ZE2U`&Lt>360C1 zsV&-|VZo?1dK_Y1olo-X$zNo>30X21d#FT|wc#oG)?pUnp(3nc4Wm|!Nq^aiNv~cF zAVjon=n@NGza^`C+ak5ze*pcD%-4ZRBkZpi+s@y;5{a-{D4J+axn8KS&OyB-VHeN8 z0Wn5>SKbl`g*^h>_3sO}PY=~e^Wfy9bO!{QruTsFS@w;T%jQ1Kb+W6+22&n`iR};T zPmFhEh5PuSKguRr+i>W>zY&GK{^s-nLNmJHdaGWNDvvZyYdP;V&q`U2s>PKU-@%t~ z4tX4fUSDf3;p>cb>WhOdnu*j&ih70G`mm%k734NUxldw4o24z0&8iK$MA`DL*+RX_ zw$QreT2!d#(wjBc^sW9HM-I&0&A`{_sgxbRG^>o7ImJ5#s}VcLNXhXrq(Tp1lfxox zyQsPA{jcizP82lKkGVO~emsxFg1HM&=|uZ+kkHzx_hVnxkDH_)|F*C9e&lL#b3;LSEZ$1+CV3U1XOzF1Ekcj^77eIcL3Dtt{cFTCanC6wy3If z=#ebDLpNDZig2s!hdnZ{Uzub-+(Yr0U`g_-p-SlJSF>V2bpIzX*pmWY8vk#kfC1LZ zCjhi;JEJtuEZL1Ba98l%8PTJ>t;w5* zOCHy2kSJA(i%jcc$gE<+P?t~lA*%gha@mfi+fGU0ji-=rT!oS%#?4qijl*9{K7#u- z<~VOb{t`Lw?Gcl>{#PPOr+zwaYzhVl2L>jFD}CLDuX44cm^Ci z`GiD$35unW$*@#;Be?D*eazdV1rg1kuYNc|UQN)qBr#`fS^V#_$_lNhY4O)WJm}Ef zp%wK3YtrjVWDS@6AS#}&x0;?6SXzgMvjPE7c!R>}NK5>_`b%*~SeQ&8=% z*uXeS0fMV3HauyNt2tnu2U8?>W#5WJvio%8QQ0eb2FZ96SE&0pnbnFlA0(J0Z6twl zysQ#!B+>erBw8BBm4Pb+L za=w=DIy}Np&0Vr&_+);F9ao3rhIwVI>8J^tJd>kM?N7pP7xAAItvQ4r-ej{=PG~*C;rO)7PUoecgf6*PR9JOVXZa zS`w$PJGhH@d^bC1(#sx920|;-!`C^Hr+lF)zVO%#QR+;WO3Rc^=RwpKECjOr0FZ+~ zrT5WRe=hePEE;4^Js#XKBKN4hhn8s`Et_dA?MD{C1;$XS)&dK#G3Cum}dI{R0mB@@B=84`aIz*$ZOu2*77}H zU>(b+U9i|Ei{{5?mivU$i1J(}>gd^n!PcT2{=CG9$PXq?&uR=4@B)Huh83K${Np z!{AHC>dytKOVXZIxINo^Q6<~gp4(W^77g{F}Grr+0fP7wzBViBwWd$#dv3FN z-864~*~bOrJXJ%msKzNzd>qadz7Q_^81w4M$s<(7#K;B&xWlEsf|R8L#m1O1)HjsU zjQ2Ta&fFAm?{2>XC)Kg^h+-OEu^;yeaRgVUJB+%!>22GHo!0a5uh_Ep@3DOEub1!Z zUx2kt@rKJY*^I@Czxv`xifzztuFNRLRGyKaUdQE`axIvdBIlfhnG}ao+CSl!M5~W} z$V>Grg<@SVo8PhtpAlZ^wk_??z-B$Ir=flDC}RPO_4@0x`-{1Hzj_Zjw4psgOhXvj ze~dZ>-~_DGbUZDLUMAy|v#VWe`#uGFeC#_V>xLRsdru|j- zT=hIfSDhKH?qaG7PeKoe-X>DzicHCdGnbx$_>8w5qxRP%w$GadS9&io>V8B3DfW_0 z&L({*=$qmde>W!p+q?Qjs`+wgUujX$V!F;z1aBNe9S^Z(TXjFipH)A`TSwJB3dFkk zFb5h}80=nYtepis><1m1rxb{f^E0nOd19fb$3js+#X=piP?^l4CA`OIklHZ1bR!p~qp3-+T2y_D%zkepNHFDTuo|NY zQ!4vJ6E3*989aJP6-RniI9kISn*Mj=DNUb{ ziH-g=&2NI+6(AV+2+K(vP<=RuyY#1wm&d=M#>*ytP(~_=`&7rX;wxWuw#cDD=vn`R zreZrr?IR#ToILzeA>+d8Ak4&OycssXJu`MjDDh-d0yjBH>+70g-H_aLaGoETk`4$m z9pL3^0O~2u;`J*I6=im~`7bQdsQrY+8Emm#7HRBN+|WcvrPG0SM>*~VX^xwPmYvZ* zaa*!m_B6dX$A2D{qkf6s8hV__Q9{RLe}1CU(wOjEIg1uvsg9$CZ}&-Nv016Qu-PwGD)l%u zBMk|NY+p|dAsG^KyN|&3U4o|GPOnKHkC0%S&*MjFMbmR{c!Z)tG*C{7Z}IkBaOXw1F3F?aJr$vMp3IpLx_eAj|2Yxl>rY>#!{ zPJUX0$1X-kNKXcj`Kve}kVlgA;IUhbx?M2bb!tz7qjg#Bhe$(N%7H%!LfFp<&Bp)R zZ5RtJ&z{J;SQ?Gudo=H3tp(I#(}OFLRyjo>7opkpHFm=Zh8H-M>Q zZ~zshwSKEcN2@Z#U~iqN)Tr$bsInCcT*0j`qXtM1c3)wv^$;vP04zut&R{q2I?a0V zBU+9}I?5OYEst)O(*)!-8>R8O73I#MQiYTPA_@hW(5&3hA^j^II?C=`IZ2pBrxx-((fT1pN81%e4s7fzyVX9Rnb@at zK$AWdx$)`ePDh#Lr|s#gs7EL&HebMC3T4|8eUzAnrPPdeRP|w3=gTSiTzw|>Bu(Ba z8XL-l>_L7u9gzcQqosQ<1%ixlpq0w<*@qQkX7~6S*(0V6HX&C?tFYSB9%zJ?SrWED z4}dj5_qq&t#)ObGY$MXIrm(*e5~G9Ac7kQttv*O5l;>BXTmEApl zFY>y*)xluk$Z7R8!SCZtqlsmatFj+_Dj(ymAsflSQjq0vp=m{44~|C27rf02-5v-P z&1ev2t5)f-sqWO?%eeoA@RhmX+P9p(pYY`Hu(Ly+ z*x&j)BrNu~V12Q`9{Y=ngsiuNhq{BEW8{ynNd4yk(S%O$4}3r z;IdZic@(_=S65voRlC%DQO(*Qxb-i=?&-$b#k^DDvc;;x06#2D(uapv<|%Hl*LtWE zwgNudMP8D)+3~p5(YQ7{?vi-iEz!7V>^N4;(K07T<2KlF-gw+a(YRVWu3tQEU^K46 zjvEk<`)7v@=k<16-+0^`(YSo#nEU%SqDw*+;P5s#u(+Nc|%zgzeg>4E%bN&a}Y;t?O_-ZBR8{8UkYtJ*w3}CW6)KO z0}jXX{#Qlb0pIt$MGC;Xszy5f?z{NEuGQRGg9Fa?RA!>VS49}gt4^8Klu&bT_BGxb6(swEGTpIj*1)S2FY|+P2cB;IMlwjhA7=|x+~JFH#`<{@hB5x2FYtef5z z7nf}2N{EG)cL)pcH5~}L{tA*rw-`U69p3RIaY8h5b<6t zxYdv~AJC9p6U|+g1ld>QuD5fG+cVX!p*nZgDb;JPE_hs^q3RvEE%DWuP;XPv~?}zn! zg}ncdes7R>*~}-6=LOzh((eIzZ`AJ_}_mY0}F5Vx~?=|u+x2h1{D(`jr zJ?q63U&nF+7s|_GUbf0hB`?0+yxh#oP4Y5}m&fI0Dld+gcq!r~ATL+(a=*Nc;>D7e zTwX@+;bk~4HS%%>FFWN$6cRqnm^hlJ?U}&!L|gH5BRC)Mk;eJJg-+ino&V z1s5(@zX9B|@jS!x36JkKr!SAEm}fpu4No8GhrS7a&V=``d`(d)T>N+2Y5VY-+BeN3 zQWYmD)&AQ|F>w-0AlVi2Rk=sL&@oUfn>=+dRC&fs8NJwr4U-7`vg*QwXf)D2(+xJ# zNd7FdGC0**brU`#ss<^?5g4y?P2&T2Tie$0A7)eVpld9is@^4393%{_d%4pyMBx_{M1bKsJdv`;RP7t=TLPvcxw#-=ly_NQmb6p^$l?pffL%Q8!(!Zz_SP(O2aOgfmJsR%M^pDJQ!m- zrcb0DPTI2t6I1d{VtlTFVeci{LMlrvr1DA6tRyTiG@4uZKpUSe4rZuGOQBnS=FJ7} zmO{~ZJLa?$iiTQ_<`nwa&&=Qom$-B*{oxZ8RhBsNk$t)xX(3Q?@?(_hBJn@}W&I<4 zqk3k?1FU>A3f?r<{($;8Z_;h76|P0o3c*YhUDODpt)378o$x1NyRY6gfq}O=L21m0rjc=_`El73b9$fijs=Fmy+C;m@-KQY)jJ8mqO zo+^YSFKzORpdSHxr$e!C+r!Y*c6BqdgC=Rq z^}2QhRtkCeZXC{<`O4A2Vpi!?9yNuo*v}-1J#vFKOSm7K5og8y5JbRgNB0~Qui^Js zWGH8}zy2)hJtBHO;oJ=g_As+fhX2EuTZl_cOs-R`!5`6}DIV}&FvVNtp}|G*22XKY z(>GBo61yP|w6Y|W%LHrH2=YgA% z_qFGNZ}Jwcj9-o4If>)B_wz==OvSQ(T%;PKS?=Gy5B$dL>BW-XK7G@}n4SYTw#gG_ z>g|sT-<@^51|Of_GH>*li`@l_Jylb2RuEpPdoa}wc&P}^FfqMugd8v>i{Hu0t-@HRwGeeW|U@*^O>dTP1r03cSVv;FvQ%8 zZAa*kldJi1xGZ0+?=J{pffTSV{BI!(4-4Y7QvO|_4h;N8pk;H%Y?kfnS@ccAf-s+ilAy zmjeDxt4Jm{J^{Y^zft{vRQ-QK{eMRN->&|*%K!D3km>6@1w7+;Ch^>m@RaaAohMpn zsksmB=bf3R!8|(I9G_=8xZ!-~Vra~XWmJiY3_9}Rvq99E(Z?ct z#Po1VEBHc6X=KP)ipQg7a620VMzK_q@TcMbMdJ9+>Fszzy0lR?Nh(cUCi)6 zXb(?LIr8p0fBI8b@xAZ8#=oYZPujB8@bBU1R;Z-Ybk{iwUB0-(s9s%_;9(dBfG-=H zQBa9Urq)6lGBSc=_%SPd#$9J)`?6rYI2VW0l&5p%(AeTmYqYo&iVGnS+J@o@q+r+L zVd4`c!SRTzab3yK=0c~V-qZdv#VTvO+(u;^`8OieOg<1E>kO@EQwDsc)~%vo@Li+v-4unbIq-YIR*J$_s;e;(RS;DU4G+_; z7~WokPn*z+E@owt#YhY1DD7%ZkF_i(i~N>NW}%YZiqh^tR)zrok&uM56#4J7#54bo%yYK`LS zdWEm%4+M+^e6@Wkz8+?XDSSQnw&cU5kf(hy7rwl8*aO%G1}motjmh zDoL>-!BVj&NonMqN3lIgiq@SfiuxsTu_2?r5=Es7MZe}W*uQ|H<*ddOitb>8NTW!R z6rHW8Y6itcsR~pnu9i$yQ?SHNFW6z9spX0X*&3NZ7xhAY}LM-$fAa z%WQ&>SwoGPem0MZqGXXKh#!gC;nc^>t+FR0=%{#8^2wMHrc;JR1~L9C({HvLCjQiQ+me7?WHQCQ)c`$ zJ>$1&Y}uotcO=x~=LJV=&>H}p$W&oX{{}q#4gZj8%$yt#el=dchcfEqcuYQ-|MowY%=Blk74#C_zw+&wC^@tDgz*7)Ht*D>*o?J~DDTEkr8G01xr za5>M`)>ET&UmE}l#gG5PcoJ0+C?K1c6dyi8gJ2B%TaTxDTFXD=2!p_x4 zRS|3D*kw2~)cCg)`#{I};Rz#1aTl5oG;o>t<_&H~9dFeBm4NWV5y8J73>6*k7+m*O z)qv^Nac+$wUi($1IhkSi^Wm{~u{IRzu5{&-C(SGrho+{Vbq9BwR~xxpC|+*0qsV}q zI?l=6H;vCXMXx~;mYNbDo|GQ2npKhvbA^NB+J&@KwJ~>ZB09p}8bl{sdhOYVm#RKo ztALcvdfekza9)Sq4ty_C2f5^vBa zuIfrW|Bt%2kB_Rl7XI@xfdK|)z<^OxO*PusP{jrn9k2;x0xH1?n8aQc(AwOX+7@93 zumw$=1adeWOK+vu-fLUD+Lre5wzP_3iN{sSN*5Zt~KXQ$k5!s$m zgJ;%9hF#gJ$fz;XBdt#2i%~+^@o(QzIlXhg5$>{FBd{7W@d+Qta*Jjq)1L<# z$RqLK1D!*43CJRedi|UuBO}*kJ{!(9Ka4HLMzaSh#&( zUuKID*yRs*h0Y?#UR!c@#(`Rq_H3Z&_DPieDlvfU#DjqJne?(1_uIcmA&UmoN{!WA z{Eb>#>%+a|j-RGw74LeJrvn6nS@>|Q(nm)#0&a6x6jtY(5(a9F>4zgd+2$@Quqha; z?18nAPu%fQ?+JyN__`uF6Ulk;UwpN~`NQ+#Urv`FRAK%4w*%Pwo>lhv_GtY}!ypa5@sH39b9 zhEV)u1OWdHJssdfP9xh6=Hte_MQk~>1yN1}NTYJU><38YymoM~kEwQ7YNyz@@0jHp zX9Nz|cf%sJk5K|)F&y6Nvzqfp{0x#EvYK12*_)QtbWSU9`-v$Xt2|@`j^8E|l%}cVG1ASZV)^*jgn$Bu+AtcO~3&<1INYJBVp=^ z%NOuRpAh`Ln1a8%!QTOmKZQo3sA~KrMaAxY%!BrW!~yuL-9UM?SO|8I%UMdhhmlDW~Fer`9|erW;Tff zoB6|Ht}xaR=X)_>4%**k{o33sOy*&{vhGPYeDQ$7dn7~ETzl@H$g&d{>37JtexmWW zD2sGFau&UCx9tw-J%)lp&lrPP%kb0#q=%z6qcK0oQqw`4V0R*uGc>B7 zNYA8rLjn4-KDqWllRD>Kc)BCE;X*s+p{2*9YT$eR3 z(*98tE51Mwd!4X>Spc7hhq8}6;5wHdnw{OAc`#@!N#|t_AH`lr3MA^(<5$@u341XP z4MV9H0lFi@q8*Q?i-Uw-?jV$s6Jn8rBZ{*HmSo69(Q;}GcVuNA1BA!Cya0&UyYH!} zh=p@0O2*lHV93Ryye5O5I{Hib-o3O^Px07zW*ucfn8DAz6a4` z5H-AHYzLt$^H>J=nA0XY)DT1}WC~4s6;0IR>t-*9Xa((?cP6PIU!vCu6(mnpk`k|* zD|^gnyX(rdWK3=dP`Euvj66I0Yb;UPXH!xf{qDt+#Zc@~0`W8=6GmSYD260Lia=2D z32<`BQT{mdv&|its|s7$$>8(b%StTQ#T=5I7U2!9mF%( z>;E5Y^pc%;20K!XTN}@3r~;tOcagq%1jc1+Kek+%jbNf@ufLTY#H%Zh{r2v~HEPnj z73+iq-4yYZZ~szk6}U=Sxnd_y<5btS9J-k^cT?dGlAH0DoomWQ{N@+bH@P%J;!VZ_ z9>nK5%xZ6n*HLYkI~7o_pg<-~*$d<|wIxDkNcP+3scBK9CqF5F^F&(d1w|tLIsaqx zIA%}$K3QIz7#lB4XZEpa{DGef_GZFgAECd|BV|e}-&6If~oruk||BD(ZfJ%}>oY^j+;If@zZUHY; zIOaSV9DK?rHjlCC>k78~9nfo{68`Zc5X^Sx%V^@L5$RtGSUH+6j-8X84^wzkZa*1c ziFSjl{MdLAml{{H53J|~u%(1#iiCVFn!sIuys2-&8__TsA^@GA0);n(<+sfpX!!&6 z;q727n*n~FefS>1+Fg|Wk74af1=SE(`-B5pU`fH-agk7M`SAe?$vS8~vH5LO!bPsw zq}cd8hVXuCiK5RC({Ib4BHn8T+fyX9^;lMlJtfPrr%XFA?ken})V>4oh~)K*J5LHldUCX$2A35!Otm{z38`V6)> zX722eyb04ve~ZRt%Azq*0qedK_L011)VMV*b0>BcdQDE|&V*^jlQgX~a!ix`t8-FK zE2^h3{z?2eX6k!WCL@anPDTPuM(U{weK~1gQ1*=dLr-7zJj883e3)HF%?oe+Dl-gb zbJ|G248-^B&(pHQ(Q`yK#p$CGEZ)6Dbcs~M zIfWh8P1)COdAa;{-;qBFHx4V|)0%`oq&efb^mQH2A=6MZL^=OX%@EBQKBH+loyVL( z1iKaLCdUZWn=@`Q0&C3~w-|&~nz7IbJZMH90x}MZH6zj(>_28?gSy#jMjlr;9cE;c zy4ht$o>Vt`%^6dWo~&m{2;Zqp{>&N0&MXq`5d=n?7Evjg)1pnG_??bT#SvrCK(l%k zkh+<-OU?X9b^J(W{IuwwHvN-o+3*M5p&Zpstj#K%#jy{5(opOYj_meYmLzdNO^5vk z7K(-)5~Dlv_2UG0BgzT>I(>uZ#=?-Ib`yPxL79`Dd^PJ(WsY`ZX>RxUkA&aHUZ=P= zd6D>&DC){GD5k>K%<~FgHg6Df?#_PJ+ib^D+b=P0(p8ZwZ*Ujxth8!RVbWbX{CCvu z!4DvJr2j4N^ta|Qi~j*L=u{;%jCod18ENsWZo%Zuz!MP3H%bvLxN0&1vB;>NjZ&xaQ^G=4}H)xDm zl)&%x4u1C?i?3M(4mfyABu%TVtQ_xctVgn$*SoFdpw?K~700f;Bx`@y#%j)wCUq$>wmE?U3Dkeol{i@y_PlM0Q8z=fFTWu2mo8)G@$LzF)GgBQ z%|1B*ijqJq^fFd5?NtP`T*a1awYA72;;h5q&=wB{XymI9Ym_3^k?wKxsv*`BE?#J2 z%^-q1lzK!`il822?Z1dj4!Hfj_!sZCfA$wGQNr%2%mU520Rz;1cIV_eUA%l^zg#`UZWr$mW!IKt^ohsQD+e>NLA81Ce(S>cdO zq@=?k1TDAiw{LSe<9k@{)j-V)8~(%;)H?xakWZ48GjJw4*n?G zn&1yY5e?>#GxYmY@JCQH$UMa#3^UMLFgb`t)-ieO&m#9q%8)E_$oKz(MIyiZhgsxh zFD!BbK1s4j%c6u3Q7qE3z+sUL>?ID1#Md`PUh1_oRyWmfm@0Qm^EK1=6TGFYaB9iR zY*Zw#v%>iaqPNoDF_i4JUlW&H8^wr0%qM*V3xJjTdy&DJZ+XXNMrR;2(Jp2v+l?9@ zGvh~y%j4ae8-XhU)e8!b&w0xZ*4 zb><0{jrN>DFh^Q)fVKtuL3UM@{nc-1(zOp%V5ELIV8kOjPqzR0(2RNJAFWxgszNmG zQkpW>y4GXf-Dj5cMoX0X8Z9}ba97eRKgNxt4p|k)jp4>JcO>fZzZ(8MbLr@UxPPgZ z7ni5X3*S)ViFqsDTfX>d%}7q6j$3}zNd?XbAw%!`t$#Z&^Zn2_8cqnaEL&b>?zG7V!P@9=!xcl zM{@Z;2zLX{yy|NElCLYYQQ#n9y2Jj{7|1x!d_yUA3Wrrg!e8zxEKk#m*ADDAG@K9} zZrKgh0{jm~(8xZ^^^}I(wc6nDiE;*SSK)hAIAlD9WkJ<}P&x9qr2Z(Xo@91m+kw3> z&f9YYq<;Sn`v24~DY2e?*F1B9%l|=W4CB$6jn>5WHvoqNL5 z&`d1AqUN1!o!cJ8PI2uooVDqKr^HB|a2$@YoRU32!LCqeNhs&v&sUkZx>yInVIp;; z^fvZTr4G<5)9dX#$$4dZzG%+A(xs4B=()1!-BcHX(1M~j(eEc;nfZY^40T{^9j2+d zPig2G+RD|wCZ zIwSmG(F@XtjPflS2jveN8V5clu!iF?lWZf z87tLm?c0vZV75*N<|e9}Z!ss(b|%*Y2Ax3L#^_8O;cK3B2GNIOfi021)@&q_Ii+xq zYIiEp*`jCC%KJs@H^Wa-X9W@`D2Fz9tL*pL#t%k`|LP$s&TjiR7z;Rq)F0U3ZM0E7 zv^|rui(-Y^)8xoOXEJT>kzW5kvI*H|tn5Mj-K0eb$}vFFyaV=JnHcn0o~O*fL&nOj zNIcX*a}fu5_n2-iN$zSROZip}_}>7gzc87)ni_=48-+AR_;Dk=sqiT1*FDGxZ$0w& z(*@vm@qD{Y$%P4w^S~kO zOw>JfxcnZV;&i*^O`-5Zkrt*84+)){&c*b=CP2<(R@ycekSC!$oo}t3$%p33hjDpY z$V4Z+A(XAhU;01Ah;$BbV75^Hn35vW$5Lgp8BMxFO)!_MBSfQ-7CAyR5n#UB4*lBU zvdh}C*C#2yv81eQ!D+{Oik$ncb-koyt^4RVoGrZ73iPrXAak%vPL^`V z_Aw$LgU{dynAF*fz{&Dn>@_U>k-j^;O+N;WW*B>oq`qE+6>bxFEjGk+!>zbHm4 zPE<@Teid#n4%+8YFa8F~F`)8cJO_xx9hYh4uQ)yje`ry{)tZyOZq2Z=#ug55O4Wpx?$W?nJs1s2mKHGI2yARIPTbwVcED^OGmNm3KTP8O=hqj?pQUMW{P{@0r~&~y&7tb@=pBD zQeAf(`}qKWK=CKR0kF%y?T-+)xqj_aao;+azjxuoX65m@X5bLVzKADXxU&iqIRLv1zB10d`*>vg1%A){n9jUCf<2ioADqDH#YMZ@%c?j$1Kqm+I_ns8}dpwxV;ic4ZAR z%8lV=g}v<;sxyeLt`^i2OJNc?@Qk4SBi@_%Uc0bpA&9!Oir%C=SY4cuZ%vx!&c*JM zhheT->@E+A_QU>LZFkXj)DODHDLZN(OyJmEX1c1yH203AT!}7$n7xkAv}taf__s3N zqWlYfm38h0w>iA9Bl4%NgpBETlH#%{rW_f4j%pI#q+J-_XgL%;4*`?8Q<^{#sbg1v zOWa*2PQ>hgCmKo8%rXLDDeb^ktIRA%FBb1QY_^pEz_)DEupu zx^O#O>d^6hxSn_hg)g=q%@S{M@e5a!EZkl#rjtYm8>u>&IBpbD8&-`xhOqzb!Ycs@ zDJL1{!uLL}FCF(Eudl@C9a}7;f;q-->vhPvwioCmBcQXK@Vu-;&b5y{Ln1Ie9~3Bw z>5vz+-ysc012yTIDA0WZdM{Dv-HJl0mkzS&K$)G!FnVofpE2KM-0L=5r?VOU%etS1ZZIDTAyZ3)&?(|OIgW$WMuvh7W z-lwMy^FEzZ*%J?M^8 zlobd9E&(=FFXP4Y4)%nh9D7Zw{Ky|d-!tFC5g8vyE3lU>Nk>FY2z1sj2fvp_T70-* zRofZ#NK~8iWF57<(_l{s$E&4w1iePlaIfp_DGv~P$S;y(82T0d-*vN`$!IjtC8)I5 z{8Y&d#DTb&E|T$&?BcDEoD4et8Ik)y;Y=T672ZByng(e1c~{Fkh4*5ei1fLajH&Zg zSK$(5{|h#Y?$SudaFuWAaKe$`zoXCmW(jtBtAXRxZuGhXFaxe<`0#SxtTD{;R5I^M z!;`0i_K%?y)F$b)7U8+!_fYU;U)qA zfr>b=(kwqvqv8?S;SZ@E7uj^PwY`K*e1q|5Pd|nvnZ^NNS}-_Q(O>%6_ik7AJzv7U zSJG=;n`aO~;1$t`N;*-6%!)3vWWRa$foRDA1zbs&{@E`-So~N*@fgdp%@W&~nPXP; z7}dE}MQX;~TbI*+{${6P=K+)Orn`iyY4Im1JC_^`0I3YJ?-lsE~5l0U%VBqe{HGF;hz zZAoMvYq}&XXZ35ucQB{>djv#tW2W{4dIkmMJWX z(JkScB={V(w?wLP!d7t_sK*?RhZkcXVw|ID$_Pc|@ppvA5=E<64yueOg;v){g=0K@FR6_mB-=sp2U{aH0{MWCSN0LGGs-!D1s=YE0r7S!0pA#t3?d ze@#ckO|oE67euXp?(jhNl6V<%fKp^RLg^-7H6lp@d}VXay4$Q189&bdmic?{^qt7~ zGYj{w^FBQ}!~67<8nnB!;_}`9*3!?YqZ@B!E7;8pJm<75K(x;zz*%rWJWFTL-c5JsfUGRcXlxQG z?};2$UZX6TpRU*FcQ>d`<_~Z6Hm(Ci^YyqnN}v`l6R2Bc6N4I0Q|CkbFiGt@Hn-aT z%UorYa>k&b0?;1!G2oZKVoq{^YB_zNj-v(8J$N6TT@@M&h*q-$K;StGf zstk|HC+6is9MxXVj;oai`?rVygM;L*FqXQbS&B!f$DTnjFrx`mQOw*_aBy3Ov3VjO zn5$ZV)|^vosPjC{Rh^m0Y;~rlx#}h~0z(~MX|CGBqKDDcr#{IWEB6E>&g8hmL$=CuzYW{zY-K4+=wAjVzwR7oy4|e zn<+|=+BT&0t#i@0;yq|%&|V91(YjC_dJf(LDH>F-)*99o6JR9O`_!4C(WV^gljpiw zRFfHP%H>%=wa@vX>iULCv1a+ij!Ijs%8lN!=}Gx1c{#_AdCwN_jLMC=O~!f;#W;Fb z$9Tx61+AlZ&8@%-$^J72k?xv-deyJb^Q%9fD4neAjd`1Xc#D26`>0*o*(c($c zsA%zI{WC@XOw~Wd`lnQW47b+N6z&}@Z4W1mVsf-f1z5$tR>fqsaZ4E3C}HE4Fr85{ z$x_FO`uB$NOiLKqRELbJNjTAnU$me98);5C!pgG0t4TPSEnd;4N!%z<1)jB<7uYj4lVjw+)Mcr7x{vipvf3(;$%Hs|~8BdIZ&HBwqD%6am15-kG)>PcA%u{_i$y3q(*B#hj8M95Y;$TYu&-g237=ZPmGbea zP24R{+^tC5u?@}1*O<6#PTZ|^?#xwpF)mw{lnM%^NORR9k{lFD$)*~$6DS;IC>ZQZ zyAmjruZKY4$1KGSMqxD`wdVJwS_g%TXtT7)JGN;dw@GJVg~H>M9ruUnhe~;&Q7K>9 zldct1$}On$^hagBtWxMUYE*uJq=+`HqXdo02NQP>CGH+h+-*qQJ(jrJn7Dh~xieSo zLanhZDHT*ok>;wsBsr**l1-)bBv3ipP%zjB{*XYWd_4pze*-fNMy2Pkf=a1Yqf)z6 zgU989N4;LJkl0751NOIClBV$|x$WMWfqy+~a_-DkZK!{iC8dHtDbifkL6U<%DcNi~#5I9Guc2VDA0yts z)0^e%A@H{t4j7ETUAEv)s&(+UL7Kxzw9Z))RLGMm zVP|AhN8B5m^Q0Us1!rJ@6aSrGHyy^u1T30!7XAZ>9OfXgj6D;AK~A>Q=%RsVnDw&I zVW{2ugHdZ=%F4XMbSFXR#}I!oLaX1Qo(T@a*$%@c{9p#{@7As`b4!K61n=4X@qVp{ zfJvsJyirzQRZTV(Ul_yyS4}d?CNT!nHuTN2H6hF<;j^T^$&xocOZdzvL(>#CGs;S} ztIQcP;r$}6G6N^PgZwCDcOpjr(cjh6-Stu@JHYh%BQ^suzt#{Tj5hcUa&enS5m@AR*{H%`S4e}hzm*MSCRO^J8c>ksA z7%8Kkz7vV9??l$apf>B2 zGUlZaZ=AJWB)l^2aK!ib!I^e-?eFKR3T40=FGe7RU~QR$C)4EYjjt5Nie6CLaO3){ z*_2JWnXen`MIrFdaub?K<>Yq`7Pkv=7f=1XVfl4=hu)u-cMR%`AWK;wK$* zhobIji`PR;Way_~HZ}3eA9JMrx>HK~-|8>VbG}?J+MY=J4T=nb#~f+D4p1sgOZ2`| zNcuZqWB{SerGhYc;G&_toHvntuv# zWotH=J1-8dl&Okq8a!Jttq@>35HCC(h$Wy3rMDWIF6&olhzD$_bl{c}HQbG-hC4%- z{I9y?Xp@wz;jT~INimXUS~8h+;GT6la6bmG1NVqH+_z8?m`==+_4L?*TT0Y$-$Axh z;QKmV@~@zotaUX>$r|p3i90Dq!Cn4}!^eZn^n(-(agqSyYF)0mekx@MZz!7-YX%M< zw`f31d3y>#AJGL4ivubJYCw_V&Ycva0o^KqGMsK4$ixGO({h0P&1#1o_Vc{}sZ7h5 zl^q~A0whD}0p9ip^5jG50;kC65DOFXPWgoX@0% z=^pXyEturVN11Za(ct1Xsp8ybC&h6p&rrq9v%eWH?suxVTiD4n$3t;}LD1c%it}(P z(u??LOp@a8;4IBgM2*6AO`5?P(b@4rpHqdd$j6n1DC(hW2Nmj(LVGETwZLKt^k4>k zreK4Di{sdtonNd9uCiOd?=*g!DwzCMdHR|_S^(o^U*SRP`gB3un#>tIA1!3 zau8-rcw2j$9;GJAKz7B5Ytl$G&@}%EZ=-mT6EcWkk={o3m5Mu>(IiG9*4`>u%@;@X z%gkXVjwjiG9_d}Zj4?_r>i<5%jrvmLTw+b%OPNF)!84xy+rLUW#eRZE>;$vl#!>I; zGo+693A}lyszX{Qb%^vx>#xelX0ASO6TvWJrpAZ$7`adBeV*Ol``o*FgA6y-cHjMu z*#0E?n2PYlOtyppps=R9z3LARy}uwkm2lf@@ivVk$B?V3zn~@&T8fdjl1mzAm3Ks& zTAT;WV`8#n>AK(g+ly3)IVWgY^49*-WOQzo5j8R0Sq1j|5qww=@}9&rvp5%*981a- zDSoB7UX^>=tJQBeXmPHX)?DP#N3Y1CD`p?Q0atJavf=|xp!l&XaHB?$f3vWcxA7)8 zkmFapjbG+QQ%e}LiHP+$=F>g$p?E!B2=u11 za{k})>A3!Uy6l^ZN1v~k@}-HN%%jR|^J#eWCu_x4qp2+aokKBt9 za;@Aw%3aF%C;nB_=p)0I&BX6>pmX@k_tAn=Ifws}OpFc*=kPDTk}|^LuqPoCT&;h~ zI*A9WSJC1Js*t1)WE@1k_0bWO(_@}E*Js+>q_>-YWEVjHpmzShExpwIFtU5NI6lVi zV`HuSbdTIJlKkpa5)~cne_m%U(n+~___!|1wkESdftWPLH+}Q$EQ~a~nVi`o;k71P zu5*L-d&&a9AVM6XuSq1lTW{W;lR%-w?=iOn*n5N^RQBm)JhyoG!Dg(3aknEq{yd#J z{&cubI{s9uFj6atbV--r$kKSUX$x1{gUxlTb2m7kA_g~0a@&8HL|gHDIWAu)d9v5} zi1J#v#Wf*biiD3W?jO$)VNz{blFxpSk|NJam2SE;KO$4SGt_O+{^>7;;qVj-4=*}= za1sQni`DipnDuYq_f@8sQ`&RK+AtTvIQ3 zYtukI1G=$!3QzVK7-dxTQ+eV8HakR{COY?k(#b8dE81i`Y0;)0?t~p{aL3>l+lG7# zZZdj44S%N2<$Y?qWl0&eo|n-Xn~J%$ z=U~_GzE?8VzaN}DXJF1l;M2*OFF9}2IcHA3mYhCH)Guq(D6xB@emQ+|dH@b{^=8W2c(t!mJ z(9cqgk6+NmN1m1DfDCB`dg}~zqdQGTywk7(|2oI0fu}|hBIf#HGMk%)%WIlm@;>le zg?~{QH*ps^QS7}RdkT7FqzkVBA-H$j_8RXuS}@(oH{OLUHQw9!TeMg0U-SAJNB2su zf){JNe@8oBs~<^DgGEx3)g#HiMDlj;Sm%-CJd)!j$bF=>*x#{uL-(*X>nBmZfA&4ml~?UE<%p_!(>BnUcCbQH4_+fzPVj<+LOtpH4)s)@ zAE4(BqMl<}`FN?*9YtIT4MlEHN!lR!?-ag}_orkH=8$5x*$^-M6$d!vM9UE0545I; zoVX+Y?N&Yz!1nU10h7`k=91DJIkA!3fqM$nMy|S_l}KRv^y2p^@9@KxqdnC23_Y z9@zNn#nS3e@{GDo*@wKi?Nrkw!n2VYNnG@s`?9+^l5rl*$ zcb?~3JCTIimSgF@D*L~GDg}C?S%MB>Y{lJnm3{3!lG7ELCQmVM(`OlVt+Fij^3lnX<1Hl_T?dErMmR|G z&;3dMY3Q**&Co1_|FHdSP_C4=*$(epmdFMiwVY=Z14)Q&dbn#_Mr_ksuJ#odLcE?F zhXu8;C0L?;&5vcud?VR|_P;4w=4uX6`i{Bz4&D@Q7kZ0II*$$(nI_D~ASxdz?()tU zt(frHT2kCq46CZVxfQjN9NU~rvSmHV)jm%gw4Uvdd{&dBP;v$z$ZgQBc+_DgZ{vPZ z8n2|8=59k3K%;h%DcZE3D{O%%>T>ReaA>cxEXi%}$rmMFGE$$Un9r)KC8faJR8I&- z4-b)}!@T#S4yQ$0Ke9Zt49h1u7(dN@Vj4;-+`I2s=**hPlm#Jg{n;3U@y@j1LSen-Xf+ACNu9`}CL^|GqF`e=;I}PY<18s(0N7OjQdd zb^oAHL9wroFzntY=^Gl-#H>O!rx^SvW)+emvx-6zWmd6*XMxmIC{i3!?<9e|ySNn+ zt_QIxvx*edfDUF9l26Sj9=za$o-SBT2S@``F0ll5HBf_^RnSXSC3#LI_+UDBVgNia zcs8Orp+-p>nUX|Kpkq~G-;#k(N{NsDQeScg5vo|TUQ*UHy(3raf4SBlW4BQK(O`Jg z)H&hdwK6T}zO??>Oi8?!#4-}q9N_HwV{E>zKiU}{b@g@SW84?-bZuz=Gq@XZPPEAAyg;JJj2`f5< zD-JxB_IIr=Ja(6+k`l1y`dv~=pL;POKm($iBqG5N0&ax<{yF`<0JooSg~he7c7T%t zO-lv=2RP|Ur)>G-6hq5zcYw2Gu#qa-_|;WNiB1(#q7)8q-pK91!Uyu^JPIBWFF42t zQt%!8>Vl;zr(h{fFAJ!7NHP}D$+>dV*EBl&?EqCOv)6!`mH)(&ayMj)_FmDW}QN`acKfvCwS%?nEMk*ZRqj9rdqv{^HAJ8u^?J)Zq3i* z)_eo}_^cbS4K@l^un!K8MD@{I=GT0MNB6}d-_DkE3C`gVm3+Ps!~O5c*z~TqQE9Vj zB(m!?SBA@9V|f6Tvd5k=nzdH-GJ83J1J85jqfe*T_<6D93IcL)$X9!*D<|FmV)!qz z>(C0wpgo-^c}u*KZ~Kzr^($OCY2j-B&d|+~gBjg+JPWn>yiXTc+2s79xu?eex6tQB ze~BDT4~<5}xVGl1N#XaIa%B5o4vmo9k-uhizs&LD=JxJ03SXAxvhGoZJNF$mTe~wO z9Wirlehw`W^Dj}~huz86q&7j_cD;{2j?jemo+rW9(E9C~z*MzGu9(5IxoI}nHG$%0wNE0dT<(P;b!p1NE3(KwSJpz5*QtIF zd}*=G@*zQ3%tMB{jh5xhcV_t%(f3&L9ciXlnQ5`TMp-`Efu&Az;#E3JoI(3{OVAnH zrdHRbn;oL{*uTw|)u?*=H~%GGvEmj&5!u99RNoOXe!U!F9gNNOQ3J2WnwMFDY)Rkl zed?vTu_~WA)KdME{|ZQY4`Cda@>^wI#y;vi6>Py9v<-4Gp@@n~nO5hnvTsuf?nGEf zDCv517WlSr{{i#LsoX(i7X;i9nKqlJw9v3Ef~y^NNjyVsko4MOA4*X3_h&wnmeum- zZ>Bw3OrlkiRq<#kH+DXm;o4}Hlp$z!q>HBK;~P`xxt6y>(^H7KvOXG6U5R1Rss(%T zdGPK320oWfLJu+Tn@plq(jMr-PQ&^!+kc6(4*oOjHKRoR>>5UZW_A`cixDcCZG2ak z#kzRyRCKx^3JRJn`@phg)zvOqCTQbWC8^MM|Kdb!jk`TKjmmp3)Kp)F15wRXQLKUfq-N7_6I1-K01;SVh~8fG)S2OQmF$H%54H9ZTE=Lu$GA%$zZCDvR{$6?YznQ0TU$j^(65;3M_!C8&NyU z!PJNpu$G2XiFdsNR@NzrH`-o6rMl_qy6GdN=|j~qu3sI0`5CO(yJn2{sn$r}2=D4! zMwWLUwC*uZ zf{O(2Q)#jef>TMF8K~#@Cgjprn?c)}`cBgD5NK}tHcSU$%iP|lhFNpmGEMN>-*q#4 zcuKwdLHn>QcKjPv9^E{jZr*5V-i;0lfjL3hB4q)IQ`Rlb6JeHaPx~pifqJ6!Eh@DF z>_5HbLDkP?zdISm%6|7C0sDgX4kZBGL94ur7vt;;vXu#e`!iD5>JF#!RJM9bnM>l8 zo7A|59@n?#%x14TE1{>kvdgS?o69`Jt}t#ObQ~c>=z~s=&$b`JIHH3AjumCAz^qIO z1~^c8n~m}|`*z_N_RwxkdVz@?Xb-gTcbwRf%H%W1W9#Hv+p*hpi37V`X#&jg_ItyI z?sgMWc<-(u`1|{dQ~CRAKlwD>?o9jJ)N0P0EzLsE=jgdC@d#i3u9~IiD1%w|q+o2> zEO^Yp2iqM}Hq+ZrpCR7KBpp8sEGd(tFn|$mG{Vge3&0yK|1P|tSb(f&lhytVUL2YQ z&q-F(54HsOo%skk85{@G=A^5g z=7f!Dv>}IF8#6*xa95=3VXIFQk9qxiQMNGEOvDIBIYz~N9(tOl`- z>7jsGzJZ7j$qFzyt{pfHIXU;Opc>(4#eL@L5$`I{-(E5EM63ZH^%F8q%aBa&dIb%vK?WVn zlTcpeUAE5`XNL1MGq^<_H9E}T=HvKy{OC7R!CHlPL8Lh|1PUK`8=&YKi&WRx!UL*Ze3w7C*=RpZn*?Qnjgj4Xpx`31=)NJXclyTK zp#7N0%^o7+9RD0S%w5J$Rv^QJ_VF=soD^uB3>TcNb8s@8ym6d-N0}?)V)Bkh92xyN z`v)87o%HC>t`}L|yfH779qIZbaB6n4fAEA#k8_(ZG&a)pcWCUq{+7I6y;k$g0}{e5 z7<;;zAJt|X2xxPB^O7>{Z}jqow7IikcfCLuIR1HlhHCbIe=pwbLUJUVot0?zMBQwq zbUfrVJ9Tt;h-qI^HTybkRYOyu{a<2>rrDoIr>EJCE%#i7-T%V-Kx?uejT5I!4#bdc zYL}(M6M<&wiKOFI>MelD}o&>0uf*};6Ay{`VX;OI#QM<37>_-9ibM?>|Q+R+YkIyeH7(1Z(P z1V2qS?$%A}rNbXTmlah(0A`(J2f9k~tUlx*K-Qi?fOxB(v;+Ms6izTnoNI@q<7+oavoHCT_!aR#uMUM_Bc7IO zLiM`zyxzKz=59vktL)}-6jdKV2?8xxjn(n3fxUzLVD(t;XDs*L|5>n!2Yq5Z_uent zYWwdV^R9XiBxxHC1PGBqsWE#=l?}hN;1#*G4}v{~{g%jX5y{J|u4@V4sIi}~v47HGY=3R4?E3Y8I@rI!e&;_l_P_BRlG-=AMPeUG za@1e`n`BW*v{Z886?GI?x;{!WW*b=!R+EaBv9emKao5op)!o=G5Z7Vs%Xnw5uIj9%1Eb1iC%%L=R_ct*+yHokS|!fkg1H^8OvR<@oCI%S+N?<`iFQ5 zaG;D*lKXs%ClmSG478Yg9pA;X&FLyMwW^QXzVp1RI}eFmsi%@xs}!8E&B7FH*5^tS zCH9K_DuLUE*q`tmUx?9zxRnjY z8jWHWh|skbA}Hq#?FWYM+!iuwZ!p%Sq=?8gF&?pHdMgj!#$Qug-LSg!x{TQP(62Fq zJUv?8n|*+w%t*k|LCy*s0OdC}zL8u!yvT!IV-40$LU2g|i+ELseN8gTf=V;bO)bkM zeBWjPB(_;9<;8Md$N|)s!j*+7euQE|{l-^ypHu!y;@AlFq_7oy!%sawaYq+%*IzG< zO30PPCsz$~ZdA;xNtQ&tM!LeF!Vj}ywMqk(Tz0Gvgv!|ySmypdPrgsF%tciKlhBk? zoOGo27kCpl^*KhlXqG}d*bY19xktowmnG&nWu6P#(^m^mVc%C)JaP_c-xvF9ZI9$s z_E$0D#V!;JUO)TmJ3mU{{{r~(B>ewgyQX#1(80lxA6_q9_eXSRyfg7*@V8Kmq$h|W#Zo*|!A-fZ6_ zS<98%j6IwC`e#d}*w9^dBP{DU%9mZWk{OYAjf&dRJX{+`OcnI2S>e4|Wiu7Xj?rYj zi!zMO(g+aBcLTI!(<(CvN!6=LjMO?y@JF4iY0 zR8#A(3wf&|fJIeRI`tJ&6%9Aq5AvA~uizGI#MS*yB00H~`OOWggzk~`D_?=t9|wa8 zqTlxI|A#~$ZQp(f2rJv@krU1GU3xd+ac3MKXv>gL&u41ThLBfh@dkOtN{=pY|Df_( z`m1V7H)%C3&WJ)Rdt4(PBpVp9HSq{_hgBnY@+7-PGWABbJ@hAxS=Iyc<89mlvdvW+xflEEW}bqg!Lzc- zZCV98=yG_NC=as#F~|0$!5XZL`TIh1%^TGIuR?S9N%w#KnEs&mf0ZjD%T7I?I`F2Q z8_FEc_{_>CY&+A}EobfK3+sG?l|5f_gc@YYnnkgYXSpu4`%4<;JONXH6ZpLj$UapO z;GRuKejj`*5c*df+2&SAStUQ-#wYmHd>}`VLNJ@3@Klk4l;B{be01XDnDygzPVN5A z(4FS>4r($_ike0Y9SSwSnVm#UbO!SZCPy>W8V#qqDd-b8;v6@SrE)k;1Q_Bq(yZ=4 zDiRbhaWn`@gnAAtIzh!w`!C{KB-Egil=36nYBeh4$J;2g2lIjD4l3GtIt40vKL!;G zaukbna_q~g@vs7mox;N}t{;R4#VIF8Mv`IfJ_W;^NhL7MOkG9y^i*6e5EfVbG9;q5 z-G3591vR4NCyuCvr;n)UaY59iPl2d;jOq$e4cGni5cM}yM-X+cu3`Y9CVe8J)}z7< z$x%}@qU0yE*nD7?LQ<&q6rA+ak3bOo8MJPgWnXL|xE4an=`T^E6qD|s`Ojn0YpDbr-J`1*K;h+|h@;+nPeS3@Z)+UA zt|Zkz%Yg%@j!qRsF|2USG z_Q%r6_0Z4jN(SIb!2Lu#{fIfjka${tXuHUaIp+6Q$lXh!QY>f;Jm#*sL>vzG4>@el z6y$I`dC-2A=r3Z-`A#QQ2klFm(YR!kznqE&04F&<5xDe|0p}Xs>Hz1KyHf!7i^Fl? z^5VetD&W2=GX(+eAr$oiz)6lz1nv&L84`0-dRiRdrpnzZfE%3vF0#zQoOrAS?L(^^ z;6D1+0N@&;lH(-6X;m@GHx6*AIx2%ufig01$Ot}P<7NnB=Jod|#GcmBb4UbXIVV=3 ze>;W%j;e|VTEGg{|2FyI;7Yx}HY2iK`_C1s^Q*QD^w)RHxMrni-_aus%loY6;$3gj0=Lziw|fJ}Y2|*Er$Zc3;+${doYb|Dv*-FaSBpa~ ztiXOYRKzOXVk^uxj}H~1Av)8eDxInHV6qXvK!2p}pVappS>ga5t30%(c#1lx>$qh} zTR6JwhqvJzx$^jB3+8(tY>~5x{{K%OvqBinV{gDD>>T}-T%8b%L589f`gr2>fl%vA zIqvLa^odpCFavqmwFG&rl^sy156;DKQ;ktUu?JW-0!jRG8rpH^P&S#0Dt_-g3BN0T zaah7A-#a9~zcQHLMR=qMvma88gVW1B<~~eZoXa)Tk#9CdB>6qXzklD+1iy0vTo3O4 z($uB`#r2m5HCC^?8^c!fhII)%d5l2cLS>jv$Cq-YyV|PUYgzJsTPtIiMQ1Sq?BsEr9_~;_|r7>s-ckH=kx1*SXEsM3yfOfUlva6yj-fr}+C8 z3n?4(XYqq(O<=fN5MOJFt!J(kT0Fq;R@Z}uM-6TZoWMh~yN2~y2idq`}lm-)E zoGAvHwF;UAbm#d+yDV1|N^+XLK3Q5ni%3IHKGVHqd-8aqJrntnw#YB2cat<(-@2L( zhd*1pfih#`XVVseSUv*c%Tygw-j3?po2a%fJvM%q7}Wcug|YB?b#DF9f|Rd5Vu7cj zO?_1>u|B~|b`^-3jTsb_=C(KHUqG_-=zgJBZhtBXOUJ*R$UG^ivhvwP zK1*huCD~&`1f0 zp+RsUq0O@>OSWt^euE)*qtDw|!!Lykppn)SDn3R@hIOqP01rK~>viH6q}M#Mm*06c zMv2Rs>8cD;T)sdU8}Few^9!W{;M2m5=4ag88^haI%ahtauG& zT!}r?`RTryCiQZ_TUG2v@rrjJoXE4R6$xwNYR@82W-#_L3p*k@Sk$=n=2YIh@9G5a zIa%Ol;dyc{p!eAGlNofkY3RuVb;mhVJ)-^Pw+2(?uKMxJWF0 z%YJUXfz~8;9r3`Lf$$??FX#opR6vmeC`3rbE-WBNvP**ummq|$$4OSP z@soHPX-f*tf$xh4yjMrFd&osBqA9$SvSQ;)6S;VHtH1GE;(`l7lMt7?c*lzHlN#B~ zqf_4rl=}UqlfD;`F_GFNeoUef8c20C{Fb!qGLx;@CFgg6OLc4)U+5FLZmPt3>*fHj z$P4KetVs*-FUT|jcxELeTvU_MS5!E%ut$tha7ggNN6l4o-JqP86py$ zEeAh|d^G4>QPjXDtylXM!`Ibr{X>SYmQw}OYHMMIx^(+%c|vKQoQ36q#%w7hH{Tc3 zc97M6{5*N>VHX9G{|?w;>y?sFi^N?gy|%x^^)`Nw9vu1ET3&r@EX%i+xs3&`*bL4@ z%Q0paL{{Wy`|8g1uHVr-Kc~hl%|E|}Bf(1Z^End?&?g0}?EM!ZC!QW=cAz%4cDY** zx<_^JYDz`$nH%5A@HM`M*_QW|@g*W!euaxF>PA(|9t@#%Hde zwnrND=DDgtB#OOOZ4C(4-dp3Uk1flm8w;u~ujMKk>RdsVl85nztg=}qE%sWSK$;z_ zbK^9?GatPMW^&AQW4Liuj z!ihTMVG=lHUbWs2WS)K1VpsUH&q*%-i?UV4JZg6Mw}%hUHO`B@sy|2CnJ+iJ8GdQO z%@Uh&q777yBd?O;?oHNlZ zo5b!Iuh6MIJb7h5_@hOmmd5vimjSypjUl8G;W?5YRV_mII`wpCaE`drI(6=&-X zFI8;>{=%raC$nsFXaf2x!DH&FDoqr#;&eH-^uvqqovd-uB%O@0&G3a3Rk$ZJYOKu{g3x@Sgu-6k z$SV7CmDK}(uT|)oe=DN10k6V_Hp!kGzZ!Z_q*LhG!_%6^eDQ4L)8jnNGapvJLS=cb zYAzKOyt=jnLD=+y1mmo|-GkKpaSJD1=SEsGm1qyz-^Jq{Ng|T{$9!jQ7I74p@61xn z_E>mwt35gvE{=4i2hH|Y+Y`ST6~7r7zZns~amR0l$8UzkZ?fVyuJ}!6{3b)+MBYi? zXUi>z%oo(|r3LnNir0b&l5ElBhJl`YybVf%iZkZH4?%_ZfxmM-(j+YqVd`yskmtHD zqI&KkMYQZ;)18FJoP-Zcz|}kicc};qdxoM&9+L}M026^;OtN-Ol9>^SvsmdY&7UfR zr!2`$j*Sl@0oDZa6ske{nf#T?xOH7^2#nWI*d(5UdMwT0ajJceYL3GCA1J?mWnP*U zcK=RBtt~QwnICye`>5>3x^o%s-es5?Vz^sa5v-P+z)8SEFoW{(eaia(P5d{^yyn(k=N@v8=Jg2OBY3$Vec!hzwO$C_6no z88CB$!krR=wr*JW=c=rgQ|i*H?PtcS{uNnSoE9D>3GQ){;N}Cy)!~(P9~7noI{feK z;4GzAHnd4+Np>DIDm4IAGe`89e9pm{?Zr813pS$DCch-ju#WRJ*Eb8x#v51h)NeC*_BUl>gN8`U-W|l9kJ{@?^ClU`>brkyKSfCsfe`Vq!=UfeKVR# z?;cF-?@Fxv}Ti-VTy;-KAnKAhRnJTo0$LT_yLc9+y2y*hk8=*r97PT9zpQ2vzb(!*z& zJ9x~AoJe1qJ!NFrH-CX(PXLVU$Y8s4zw z^m|%TVD6AD+#Hv<%$@n3HFHAY&dk;-22acqTgK3)!W}3fRrXyxN7`}(4e(_W69qh_ z;6_ak6hFJQVl$QjGCs#h9U)1V*@~Kb==2l|Q$vqf&y)Irno*+{h@wjeb63-Fx7C=R z%X2XHr1~Y@S1Th9@s0DD8Ej!@P`jSz_#9zoHkhTs^m$Z*vDGnjrZzI!51gSoNW%k4 zFe9xs?w{+B}O>Cy~(I^C=2g`$?CcZ&{J zBX;uq5MZ&#zhlYma8YjN-tNl*EIackDdX3iSO3wMP&w6$DQAaAmK1mI)KBv2^P!9C zKe{0FSt$liDDZ!rfu#6zt*3^17Vmn4D8@_CMH$0AFVWV6Wf<^R8aJpk& z(VJHhINQ5QRu9Y_kuI-rYj}Foo1xG8Pb{4ZzH)+Qw6MLlwKBL zWH@J@dbv08vMTv<@y&onY)O|>LT4h=Wn{|ir3}gNTiF$|#+75u>SE?qe{{aLY%l5A z%{RJo==5W~k$cH7L$>#)`32VdZ=_Eg-r)^>ZifHJlF!t3QLcjG#^1$3+}rDbNVf1B zl541j{34zUq)fDC5*qksZy8q8c2_|fN5zAtYcJMlva==+u-5KE-eo%yc%u17VwF2_ z%7q+7FBQIlaLbOgR zUd6`W#8e*N;im#(-BJ(I|J&Rp0m)q$4WCU#x%E@hLRpc&rH9*j%7eU!lI$Ja=fHf~ z@aM$okrt7?*-GjhEAZYQ26<#G5~-u?47&Hj?{KFFojP$mQ} zv4O2|T{Crb$USvr$ThWo@fBjFt)F_$$S~m*u8EBdeSr~UDSS=J7)cqeUYr;i$}aPN zu=q(`vG6q(H29k^yTGg&obf|I$ZbM7PNxepWzR$1It;?Z#*kBGR;G#IR-DRMB-j)c z%nZ9kdu!#Epug5k2{xZC6=231q`ww~agE{UBT>|G}tLL1G+5=Kz z(B8&#q-~PWGIFmAKA25?=6sKEM+vv{>b?|P>b4)o%3w147E8dHCMUaJmtMHtnB$w3 zxudX(qu#UG_+3|EmE{F1X;+?oCZE#}NHofrqNuy0`~qM~O-XE@S%&YLqL+f=)}O}`)oo5G#!crc&$chEtzz0+HRwI2Q9 z1^PpJk35p}gmqZ_ocYoxWPj$Jdi;Ct%dfz#6Ubo8E9D531^l0KU~~}TBKAQ|A$M+HO$FnBR^#j;>2^w z^$yy3uKAJw50p1NQJyUW`?h$_aMn!!3CfGA^0NIWq`cw5T9xaec&?Dj^^W;R-si2! zOq3;TWG{~A3TMsm|ADf)3;#}?=P7%LcN>wHRkrKn*+MGYJ3=KQ%jASEZhAN5sb6tv zEH9iDc{e?L2>7p*!GgY(Ym7w=Htum7cY2h^;Y8{BkyanbDkz3;eel6VikakHOKc)X zNTe8ahtij1#+?n?8Lcp^KCbS?wvtDh=ubY2H5u+jf0ADr)g1llEj{Np`o6A%^?13%zHyLSXF5+8LtYO5^2jPIQ~dyR>eoC zMgok8H^#zg=cgz~a1G6MOK~f{Pv$qx=4VSb z{JCRGp1Mv}*J5>@rLH%sYe-$|)peb^Zcx`v>e{BRyVSKyT@R`2admaSA?4?&YreWp zQPUvyV zJ^!Oxr>>LLb+)?Rp{|YU`jEPARM)NQ`ii>lSJz&3b-gL&bh zOR~4W89?in<2Xb)mX0QrC6ry6G8mGFJY&B*XVKf6wwa15)ka_XYm; z@b@ZzZ}Rsi{{F(>yZn8?-%U!#ou}SUC3V{f0yxhC4bZSb8w$PjYl=$;)}uuyw9G0LH>moU-Y@t`QRVOQ;-;+lz!qkL0s?lzlVmJWA5|Le6R2|%XN*v z)g0xWc`S2xVca@)ja8V+jNLU(>P5e%mc$p`(84`LFm__%Qaf zgkNHgIjQ~pio`0bjxQB0lffJtx>_t)pj^S`?_i>NXkKy_)+qzvo1yDD;mLp0JADtO zdn#gA%(I{Qg2-r(IYufb3>Krz=$dt@_WERf#<|A5E>%9uyE|${v$(E9{H-O3pP;?`2rs$Zw*X3;QR|$IHKF}h2cKC>II@jxx^*YfLLREvDA*S zKGns-&6@qMNHVlp#?L|$1pDN4gdxlj>7*c>Xza*h`V}D--bJ@ zf}Nphzb}izP41~xql?Y;%$rZY<{VWgUz$B-isv~BW^!eH=zgy8fS)Y$cnnkJ*qb{ z4h<>7@oT$1$9xui<}ZBPYXRSil=!%-~c^Xv)QUc~o;j z$NgliONeKgTaa+aOf-*3XZsuMBR_j&x>6hWE#NrE+4#V=}xojKv~8 zKB=6e$|BqKSx@l_jz0Sl3H$T)VyViRtw4R2*nCc}w=4MeaQzhj7OZ~ZsxKCUn$0TyE{6+9e&p$%PyCPsJp(BpnariU93_Z%Km4c*mZYE;CiG+VLFA zJ=i7it>Jm=8sVzrYmgFem8*-Y1Ll+)8>oES#16+xs6Ob`oZ}WkIqX(34g@EiZ0+Yf zg|E-~zC5>h#0>Yq7?CZGakLY&-+Q1RH$ejy&z<2`WEjvMSj~xO4dMNO=nuJy&((^* z1ERM&iC3bH?HS8HYH#(=OgstU<7^=r_$0;8Y5T4Ha1sn%pTT)(*xQp@d$U<0s*?0h zx_BKa)~b*>We zu|1rY2sfmQr^<4j=_=S}fi2Z&j=?A-1PwqxY6d%0XMs?K`oJ|Dpy>rh^H{Rg2?Mxc z)ca%Dv){2NYxRMe5XYJLTSH!m2XkWrF2m$8jG$eMRinG^YHuB)%*6>%!cxKA{dVpl zH=)zx&573O*>JzJY7Z-euP>7IV*`rNUq5(j!+WnyfDjvu)mEdKUla}Rj~902Am`R` zK!9j4?8}L0>_ZT|0cqHm6V?F0>ey2#BTENmQ0O>b?p2l*W#;V0#?_Y{qGIy?INv?{ zG?yrFl5jfCcsa(|&`1=g8&zr`>Vb*`C@bP_3mrYY3IR)_wuIuq5GufBa=k}8xuCs- z5Z`Y@b!>H}kf~n8*cT!MLJDi-V}Q<_fzEN)w#RRE_*oFG~NrksJ}D}vMBTehfuX{0kJ0*|iW8p>KWKg{_WPE=qcydNSP8fn~R&1t(=gx%I-G*&Ee zQ1!Q@kAmK+pAvfEyC-uD0a%!^4;CiBpxGUfw%69jl{?VM*R%c98UkzZ$vIkrY8}3M z0c$5#Bb-Sz_n#;TEcrX*kYH^;U!A%*P~U%Q`}uydc?J42bFOdL4tDeH59k}YQSD^< zWf#3`m%GodmXK4294)G2f2Pq1LXe*@O%Qtc3Aut0>?bgaqoqX?FdbO5qKZ``o?k@+ zcC<`#Gz~xXf$_2fj?c0kyM|BY#>N_Ux6l4UlcMAp@`Ri&@wp(p8*NZsQ_ZDMFn)NQ z4Wxp6_o5F)4e^-Bfm4*HB@Z#iXCR|^=4tWb&zqU z`)+6sf4*nulT`6jjrri2%cecz12NMoF`7$|=mPhvUy8!ba_ouq+y|8%4%h_iOZ8Sc zDr&S=3+8=GM4s4s6}V4hdcfK!rb$mIDyDy_&RbKRQ_}G+u>P3;_rgphgr_&daYD7v z)R{OhAtuaW=;DC5KN0motPh45iiNQm3zs3d0s$H z&Yqx`woLwrvVxsI|4icc;+fR>-DKKFeT%AZM4r%j6rWK4kShz7yl~y z5rp&fBKmF{Cl+YwD!)aMD`^WIzXjS1=2{2^ah4=r4<>9XYMeasg4m%v<}#?Xz9;Cs z4T+?E@}MMQier*ofyF+ZSeASolUL@0H%kkgr{zo)Map}+2e9nt!3&eyyPKbKeE);{ z(<{+WaLK_tLq%^f28M+{*M_LnyOKwwS;sEDa@Ah;Ijg=>FkQeYfF#^1 z@-^_G2n^3Y4QA){>1iJ8g-&JI8$qv4H3sc7y05>S*%pSZ<_?HqrqP+18uS_>YxK}oAG&YrmT;smuRwBl*hV7BzmQ&()DY@deoj#Z8$H|%O1r=jPKCJ z3}kWTi#;`@RCV`+9#&lR3YZ*OKdwD9+|JX$5ok{BfPDE+yUQF9p6yQi#`=m^0-ujs z8y4rvI5f7`vhAyWg@IwWjad=8s(m-Scoq07yPQ8c-*UL)!?(T}c;Bwi0)x(6_zCR_ z-}-AnP5Ld>5r9rNYC(tS8^m#k&RQ1t03!J+}M~n3X^=5oD{5=dnrtOg?y;wRXcZ^4Joz%iRGrkYV@6<-^#V$hHhte<+Ffe+^X%N1-cqq9 zOJB$2@jMQP>Iio+&P8)a5^I3SzZs&hh;sHRXdi`DmUe4!*9Rf6&t5JcE5#!`;>_Uc zla1)lmGG>#ppymOb23Oao;z!@#c48KQ`)B*ok0+Z2bo~2c)ViIz!%s?F(~Hl!YkRH zmY;kG$GmbhssJm+oaVH>D`K(lt`i(e!S+yauo^Xs&1e*n`AsoF02v)S_Uj9rKhDHL zgP4E%RvS=h@i3orc`!b`K4G76T6==~koax{3n_Tm@60e{79Jz__s0=zf+~(b<4HN( z4pd)huERv4z&%smFK9h!1bV(wF_npIb{53r%}2ZPUGrndaX7J#blpy9%y)n87Lyie z8*JVz#29>H=CDttoIzeWmpP}z2hC3&HYHh3!)IdZ9m#F`SCHX6wDBCE;*-1*LfJJ77e8PFO2Gaia0_?RVr4ufsFzm?12W{lguw|#{BRiQO09gcS$z>V((~O1uu6cgfsHN) zo$4`cqGv1h*}-52M-OoP!^^=9^i$Q}$hn|So!F1n`tXzckdqjs(s`=~fMO&NBbNVV zha}csi`xO-aJdQsaPOfyYQhxzrL`+A{(Xe~LV$KWVOVbr(;~H+)=s{-z4pvJdoM2~ zp9dZLJ8BGyJsGb(R_t;0Mnmhk+B50)C`V1OVjtwF303To&fKv2++b&}p&lQQ@C%Hf zLR&u(6Av|xum{ywMNG({W^5BIX3tw+6)DCSHUsU9P{fRxN#8neNltq!+;I$@4?gIg zt~fJeS;Lf|U04~581@^MNg`2!46cDO!_L_Hudf|DsIRTJ180#$4z}U$xfk9Vifnft zLOvTrcnHE}8ML7trkx6RI3VAZ6Bx8RZC|)!DGz0t75s0Hi!AbA5_H@2K z>}|*H-%;NT7&+CTT`)H)=(YM)JONYx*BxB0$dnHsSaYW??wya76Fk|<8k9L1eNI@r z)hNj%-I5&i-DspDJR6B{{2sEDr*C6$))YqEj@R_H`wnwX$5#G@QY0*vtu>{%4MeWWP{b9Wr4M)>(-#4U$8OJ&cvTa;!h&+ zr-uFD_%0AVSR|(n1+L)Um`l2XFEzTqzzJ*6lY>5C?}|W?JKsCnLzebKGF`#fFiA!v z2`R+A-LTgJGC}Q+I8pVt^RP?(S{}3UCb&I%Ki0z9qYsMfA#pt{u1(^4T3q*t>sE1X z5Z7(ux?Nm%h-;&`?iAN@bP9{Z%xhG9zvUQitoCMqjpJfr&SvLIL-;@UIqjiAZT0x( zsOhZY+;>C$O0Ja*81@rL&OAJNSc5Uv`DXi!h}O@x2dv5d8gF*M4M)3s5K03tvMk|q zE{ZZ6Biw=A82wioDBB*oHv4PbH}+|_N3c^teNvhiN17XG!~z(Rbh6%lmLGz#A5F6x zY~zQm46DZn-hB$+g2J5E&+x9|`s@}b{?TI`b7mh~m0E1AXt52^UJ2pMKGN(oxiy=* zn&Y02+dXIAvHDQTs0wyZ2VNiVUIM^}_*?OqMyR_O*leUaoXA`a`D$LXV`V71(%CTA zGYS{*lc5ye+4Gu#>@$^vnPut#1_;W$gjYo|kbrp8ZuQ%e>EiT*JiKsNhqdA%F5u zeTJbOp{GisB7&svv!Hk9AV@zYKo?^jfEl;7NBC2R-uAn-V z(OnWQD?||98{nP?1P?DPR@{dmv#x12hT&%SGEh;$1;-NU@98drr}bMfyJx}Y&c_7^ zlI9*G^w=+PtPBgVUx@EZp{nEQ4=jJPeW9|q+Xv#CzM_H7M(uLyQR_E9+dd z`|q!a$yuEnC=L3NWY2M2p#!y5vG?2?Qi})y?n{v#_d5uIk->>gkocNJdL!P8jMwVf z9w4f5?YLElqS)}5!jS;|_JOw1kx%ZN(mLUQfevBx)O$ z*wh-2JB9kbw70(R8gX4Gu4Jt5yFpwx;<`74`gm%8Ui0D*_b})NpIrn;ZOYUFIT z<{^%nC}qW!THk=pk}J@gII)etDivb!O!F?hAj02yU?-(xZzjs^cGv@1;e02py;oAx z3#?Lic=L@}&4aZoF|oLSeM2ESL&DjBvuA7h>lnv8X$Rfj(>(+st$k!(hLh{mk=d6u zojP&r7v~2`~vu`c>OB7s2 zz3>&(hiadK7N3dc2cgMOzlyWG_5m9ZW&S+RG$52T^Hqw%Cb|%Xv1`0y4|nuyZymb0 z#9a)g*{#PN|{;FtnDTSt3*l#7sv?{0IuYQ;2aO z;GxeHH64M}nvagX7C92ZBl$R)u&8pewPl?KnoV(F<*E<1VEse1b{ZvxqJad+FPUvaKH zhVMnWA7nb4o%UnyL?~f9zEap5W{#)5?ECP}ZEVtqj~+)$Z|j-VD)2yBR&d5k{{$2jhDR6N6y9zngtmzv9J$8Cx2Bw2POA# z&~aC*)u~k)3#TL%jKiGPEAn z*f0t?E~ezTD>u104fN(#*NkFQccwI;UxI0(A&8OlO)1CQ#xQ*dlj9$T))4n(WTIc! z)36lp)1H9nDet2EMCBarU*bcLi?VhU|5>Ae#=b6IvYVW-Kii26oB? zyd&%X>0CI5T=;WohmrMbn0%SE5Ro+fliHJ;f znA&iR15gj zS*2+r-jszqm6S@GsT}XrLSLoDbUk`us3^Cd5Ry;e|cp zntB8%wyVZ1#KYRQf#a$O28=WI2*M%)hyXo73{vd73Vcs#!rfV{ziGKut!Fb6(pPW`tvZ;Jy&%4J+28 zq+mZ78}oPz2?&n;!7`N9#22cB+Ln_`G1zXx=fVTGdfT zgj>&~rUUxrqJ!quH{sI;?J0<(aM=RuB?cGVuojf3SV>ICKAZHKvEGDlik?Q9XgxN{ z%az?$!j4msBRqPFk!}}k#hq+@581I4nEzFbzJqss?p_GOCK-ls2Tr2mXhdPCvM(ce z2U^v>fM7hUKrvX_apj$Ge~2lYc``b8SM~{``)|lWQ4wX}=&SJB^TQWuflC?a;pQjm z4@S4qA)BF%cODQYyQh48|0G)G;vn}K7>z*a5r)Y_C=$&%;_NvGnjO(6P#r|U;Q>a6=>$H# zaKd3atssgKQJiRv*?g@ZHfe#P3dnD|!sE(54yL(+sm+ylHYxjf5^Bh<1I@DQ#FTT& zSLrWd!KuAH?JZmI>No9IyEmXb@m7_$>^)z&0fDXkK<(YP|8nj6FTs-*maR~o(zG`z%1 z+E3O?W;(}X9PaIWOB)6Mx!ev`4$eb-%baiR)Q$RWM0xj}8{sP;m_t zSA)1lh-;*{o)-KkaFw^zQ{@^+z>3mUD?_ma*<|Yl&$IB@r-wg14_)wCN87axt8CXk z5sdn8@7Z`K?)nZiKM{xuXS?Q!P}CX_|0;w66%@|=A3jqX}|k-eCVe!H#ErL*@Mvr;|snU?Ad>f_JMI7vm4BBlR5MUJOpvC zWY+jMcEf)R{9DLxqwqyK{-d6a=rGXZdkoQC^&COGGRCVzhvFIGi{~8%btatv>v*yr z{T?O-OIBU@A%qWN_&|y}hPDhLy6}St|NS_^H$^c0UBVgv6NZmvI6Ie)@ed>X6^73p zfbhC*!yjV!S;jBv5>EZMGJGfFXLSju{`m|yh9Nwv+wk!WZ|slo;4a~`ZxF*z_dz(P zBOUFh{NGy^8iP1@Hbs`^G%tcXZDlGs;U2Qo7t@g=k{z$nTK*f042A z{8=~gW4epqhWNFNzpfXw=bSITdvG`US$oqNe<4%f!yDg~cia;i)3siay+7Hvvoib# zcQCR>;frOSHIR?**?4LNZ1U6qicnp0>k$9#Z_vF>It$-E_!{@qaFX`+8 zf#}MU{Y(Ja-AwX6FT1{fVEgz4o2-pSmc~}e!t!W9zr}n}x{=F9_XH!=QLe=&SLnw@ zX98Jo^~uMEJdLE>hAX<-OXjI$p3KI2u}`i}-a)X$`Q)9zy{eFh^mijqq@T=3*?jg% z=8pEYK;9=`gJn}+rr#?s!gKm^^xvyZp z$@9LSrH{tatg*bLu{`5vaic$EA~$O+8#R{oewNoXmX#WdU1O>6vpk})+z3+VQjC?Q zxSi*PMF;sfe2q=4H|01bDcI%3<*H%!e5rMfeTAL|Pl(Vni$Pe6pT_d4U=f*n0(~aayiH@-si;;(J}w?!aQzBaAr5^#`#8!^V>b?D3oSavwdAvOxljtbm$AO z{JrosM*fUex~`*q@A%JCBXa(5+_7t6d-O5hI-v7*?22rUZsXlbp1VEzC_J6gGKn+C zMHC-;0^)~v6fegk^t-OS7EjY9goive0zc|mFYrB{BN|=prhjIq(2LS*IB9(Arw*9| zQz$p)Qp(-oX^WIFQpn%$i5GGkJT(Fy@fZc)5l=2)=Ty`}&lYS=%I0t{e2rn$IIojN z7s{gndN3b$>g zOD5Ch);b*KSLcY0i0JvuYxk${H6Cq)-H&M^bmI*tLa&ga3AbyWI?2!}{2?KL2`}jq zej4#_^@V433FnAD-xnSw!(Et<9ueho(6dmaV~;2IVwTepPZQ{!)3X&qE`bnP4&f9x zo#}ZG&rXt;<9Vov@AfEwo#S(y{GbJ~)^nQsuog>jb!KJ7eVVLpvf3o8%XI`}wdXym za7D87x(2y!Hf~bzNzuiTuGOR&-2*TJ6Bi1E1Em@*)UcU{hcG~od^Y|&~-O-=|o8dE2Su5 z_)Wr?NJ=g3$V-~hJ++fDGG%ExW8S)m$*HA;I+mSOd`U-PQgN_O@oAZPjP@60BLkY;q>BY8Jt8tqvtw5|Ca-rG9zl7{ySOJ08P)iYo4 z*05OfJMxldbdQ(3hsc{Kc<}+EcF&NGyrdc37f4<{{N(8&cxTauUtzi~#iSYCUmX=` zQ_h0nLydUFRxd^Fo_ zmRz~ynkytW(L1#R<7(23?y-`0AsONY?|xbu-I13xquU^PYsecccpI4mX9jdg6=_EI zv5!O!tS9dgFlcS(jgGvsJv5S6=1Ulx9E;iLendz>cRxA;O~l3hGEbb}YlYY45_7pm zKXo3dXgH6FP|+4{?5I*!$!uw!sBxo(d90M4xK6wx(=YLxCwc@~C4;1dSv2e33w;(I zg;z7{O21iqNrhSc{AM-7tE&srtfxN|xhBkdMDo(Cdpq$8vu-D^PJ%S6TuKmTDSorE z{bsH8Q%7iKEfb+M!4um_6=+8H#Zss+tB>C-r4z3(tM!BK%zBSht+AI~;LEM2;YGJg z#m~S7U20^0JS3wD#dk|1nICs_;uVUkyHz|_r?{=ZPaE^2i?(>3wkW^0OFHoiZNc4Y z`|gN8nYTlmR&DoDF^WIjpWHq0b}`_eGP+3aqmq}&-Pnm&7~mkUPJ%4{3Y`G~{^S;P zk|0VoL#HjpukD&nyh7W6Znd@l$DiE48GOk-0xy%R`VDvwUY)VBbem*!k=z}Um&tvi z6R$8}19^24WO8rS8E}E$fSH;B4-2@-bFb*GMKzn;Nw~;~Q9470`3<4{I+c;akUrfS z^7H%toTv|HH4|OqCp5sdojqzZs5)V?mwZJ=Gm_zd1TT9;&p(7$^lj@pMw1=p9Woj> zOFXv--i9;qR>7-NK^mT)Ag`nbv2&#^qdQwjK<9e2pRd^7_Ioc9Ne~9ifmf@>ilFjv zo#$EhnR%N$FU#J54fw~7mdYaj_&t#?qKMxX(V2^$y`6YP&i;$MI{Rf2KOiN{qKXlI z3vKXf{p<^V>U=>J7G9`Xc$wcqV<)Ym?9+4>j__L;)rnVF7~ZXgXAb+z-qly;%VV

4zi^qubhLdBd^SXU{+_^g;)4W36ad?-u~p?;P+-} zDPJd&D{6LvAZ_xTk$oS_Do$u`q#@0HI_AHqEd8V&(Ov@uuPCcikfbXW(v0rU-w`<= z8tMB&t0>}scjT2N_#Anq1YINDEG3AlpA2(cF5Vb;X7{ESYZJKt5Z-O{UdU9SLH7}i zqPi*YYIPu+R200+Pf8Swtb?9W9c_~Ja>!wI8q+&sd z=Gla$xzy$nJ(iob=oMju$nShG=<<-kM)yNff~ZAzOM6-4?toXv8!2jBwd57Gs8sL@ z)8};LmHD0Hw^!sh6&T&|Qi90uJ-wL84K#AL=oK2MxF3Xh@7A#BEmu}qD8IQ}=}hLw zh}P`o2-b7_s`XYc={Z969H(5aLzHo!o&#?u@N4&Q!*(VZxH#of)-g13>Se_2Of((v&D z$t&(|eu0(R9QJ#7W%fn$5RA+~nfi9*9vk4!p1ur>RHF{ z-ZW}Yu`nTez7?cuZOnjpT6>=XFV#Ir=n{#Z2IF)>WunK)=%S(|h=hpDyt*T=%*_6N z14JX2HR;zkgaM+_+|`5H+|1%*v_-6IMDuw#eMM+LUYQ&RDG?P-9t8gnnkT!e^6v~BsJFw z&7y`b*J(zz>nIZ6nw7muW(UoDH?Y5HwzvNZHqn6%?4()N_lu}m+9O6?X1md?NP9&8 zz5)CDTHUMnd*6gt%ghTQSYv1;6_rx~NvoXC!mCTKtel(wEz&D0=LW$mDrbF1URgP9 zw|0k;Z{i!hUvs2Ep6FdKMzQV&QL0 z$7l>Ty1#tAqdAOkbiYNa)>w;$IU>&+;O(?nxt11Tz4CXcmrJ83;AjjMW ztO4vKdD13`{S4XZTw8}81IS?@jgj0*ykANdG4%VnTq=H=Tw=e}=GA}16C?ECezhAt z+$UI0J!0F;!supho26(Gum1pq813I(uektGimqXYWYx2aP=861VAQ+E$ zW_Hy&i3b|CBVn>2ABV4T8#l1ZJF0Z$4fI3`F=3u24BF1@gN_zoaU4W(9QK?B9mRnF zpCJdaTz}Z}DFZqe$9^atB@{EQ!>QOv#XlWkT3vZ1o(4p7XNY& z`MJYmMmX{l>s5lW$A8ap&TPc=mBdC-5|s?-lnIm8!=Al+nFGx5o$xhA z@XX2WSh>OZr;f!Y>Q8r4nh>{ENMZVxQc5Fxm$8)M|FK@We`7v&u9pWrhd}RQ9XsS0 zpWH!|dyrl8F|5dl{A%!QM7++4T7zgmyymUGU&Gh9kSB|tlF@tyiSsXPvltH)$l>^K z`fOKuG-C^;1hY3=AH+d$A%6P=TIa<*%F7(YgfuXYoEBjPXj#kVlNo5!tE zeDU3$)NZ<4)~^h5?;v+iU#gthhjsnUVR3%3;g@dmmjVIyw2w~k9oGA=_cKj7fN}vDDO=y!sx4mzCIiV=O??sx&BgcZk3$6 z_IC*3uQ1%r@VaipA7Xea!%JkiE3eIS`V{NaG0*1HUF$6ybr~ZKybO^>bT$w9y~)$q zi|uNir&epR`yq$&)(d)r7++Xlw+nicr=c4??C%1gN7ma@P0+Z48S*mLX!IH4!nv4K z$Q3kQ;yFLu#aJzjb%!sOj)nR25Lsd_XZj@z>x+3H?LX>S2-sOJBb71Ip@B@lPp;P{ zo^OrmCLZ%Gf$=sm-l;?0R4^Zy-<|caeg=SFOaH5gIhHYB_QjMMwY$47FQdr~o~KxJ z@*Kk>BuVeR!W+-7a;IQy^3?emm-`tjh1W>NzX`9Hgv=7WRiv&I)P?NACkX0#QWpto z4XLq$x}DUiq@sJhM3ABwI$n^DczXCr!!*)QCuzChiquFgeo}}=dc#lp@lxc@>KJGe z4OOm^5yjn06!l-(O}D+4A;{XtU-4$`CioiDS(%HlsDtx|n;7>?4hgPp^xg=Sol0jd zQw=E#5my#a0ep>BETDmu;{Ra{@mB~LAnb0Ux3};vq__QNYB@sh3E|D9*CV_&^nLoZg|r8%6IR;jN?hLU=nTB@6-3e~C|jIQ1W<{`auT zLUxzS%!-q8f=Ot>aWe^{HF&uO2LL)TdFU(f2G3&xHhJ>2@SlH@@xBEVL%eT>S%(?s zI*%QEm|RP5gzqM znB(eg(4RBFiZMsSl$roja-g}oNNLKsR^G&4T(TRse#h)Ryw8hQ3`7&@&Ek(f1CrS@%(vcbdWL-E(UH4+!VMH zxTSDw;WoqVfO{RT1@1ezU*LLN9i&9Ssc`XdIdBDVRrDtYDY0;oaAy;O6gS-Ka8JW+ zf?ESu1Gf-v8e9%s99$2$@8W}$_u=LuE`NK51}Xin2~wuR-4FL6+@N8I3m1v7zrfuI zoegjY;f}+F4GB_m;p*V_!kvXnjDc>r_u5^f>z9Ki8#{M`y@gKGo52jE#i{&oXy2EGk$0~~)%QGudI5r{!C#KSFw zL#7yvaOrT{;nu@#h7)|agyD@eUjH9~p~nIZP1_*9Ho!2Wa!I9SS*2>KDK=Hv$}E)- zVAO?g()o)DOVq;Z>cZt}rD>U3ZmL{lGgF_{R$W%PNVQokYPqFyk#bxYXN-z|v9R1~ z0-vqgPM}m2R*kTlZ2kzKl~pb+EH5ijtu{*;lq|AWm?~|ccfeJP-KJVf)#}2^MUYhf zWuT!A>1ewxz%Wl$lsSv7mdZF^O5zY*X#`LFQ<5iEEw+@~D=NXNsD(DQ9PbODKlCfV zoLM@5gU)|i=YM_g{rfKdz3R!M7#0s>mV3j|ov)nz*RuALz>fkCdw)sB_kS&TrT^LX z_G7@up1p1TQ&${v#RA7|cDv*L83{3Sr@qSv22TKg_Q2y$-L>te2Md9p1^#d6cULVN zyy=@;fCpnxugI@S|LeqqPu~NaD?>Bid~V+I3&!65FW?ctM-}{Yr^Q84X^1%Be*SMPn}t-ox(40tl|FSlKD@y6cC-=_c{ z3;fRiNxA0NMXCS8BOF=4)2`k7&Zmd>*DV1)8TgBDynE@8tdCz?4SYKAdk#du^yb4$ z{bi!GY=6~LX7R+NVH|uj^J%x#vdU8-Txj z>(HvGWoJIM0pA4tuEsALAAb4M$L|8Z75Ijgwl!}pc;fUEz_$T^>w$*5{#KRs!XDr| zfG-&k{_5$r(6yfe-wFJ=gap%!=mcA1Ho?#}2#Qz&Sxwl%j2~wbnevUIYE8 zQrBz4>6+2kuHs7p2f{rwpyz>G-n_Rd75*r=s=43X@@C?#cjdz$3umtNJh?UXo%br> zPln4LeE-E)54qvtHSptIU&@eQ_Z&`mr1;xM;Ln0?la_sd z|9y{EJTd_|+xY4wD|g&d)9c5%z*hiY7Wwh=Eyt$sSO&Zf`0m#pm~;1E=KO_Ue^>{6 z+`N1Czm}h}pD!=i0Q{NF_x>_>$2EW72YeIoFBWXR@8yScKKTszR^ZX=Mt_vN`+>*K z0^bIF?!y}&t-gQn$%`;R?EwDfE1v23uO59q0r*bfpJxpI@#3G(u9*yc5Aero=Dz#D zo9e+*;0J+!nt$ajKizoI##O)%10NHA#l1(L-Te6jz>fmI=b10&=D+fnrWNrP&I^k6rodW58p9-_@Qqf5fImcQye}2AHB z&H5bQcYIQgR|}Co;|=-K0hDT!)n0Bx{hK!JhH1bntc#RNi%qrKtEw#3HnqZJGh0gV z79Di!7#PGLQ)X4Qp(0MPEw2*e$qm;lO0lIx_$v!51S~GIEvJsEYRl5H%3?{g*eh+- z%ay|7V)~S2HD!s6RI8NMj1tQxkP3vG~JF%i}jmQ<8+46ZO$6q%~6 z8k|^yd5~f&EGjp_V=k;LDTj^JQ&ea*P0`XU!ZpNtQ@K(No2x`>z+057#S-}}5?*Y_ z=vtAXD8s!RVZgJnq$9y9oc>j$q? zMif?+jexxqwK09hw24YI?Y7yf#*7#dA1D6CD8+@9EN-Y%tL#?uf}+CWB{4(QWo0(A zis<@qOO>g*&}ONgGfxH=dh3ICs^E>K$tj6)39R@!(2$NfkP&V(>h~=x73CI8SO;U$5-T9)6!YNdFIsLhSrIY^Z3x!Q1H)je zy@+*?{FV0da@u^0ml1sPV*MNpBM($4V@t}c#f8-+rjqf>=#dG<2`LFBiKf(1rHLa` zM;4AwNhus%WJ*jfNlF-5nvgUyv8XgLK~0KFNQg^RWQtHvI1hs?*(^|BEtYaqVI}Hq zWg)7#;!`2?vM&9X{H(-`tGcY%H}}#qbgn%*KDh>crhr}yeV!T*s(;=#V|vc;RF#>; znN?Y3iK#}F;l-AUs&W(Pl0_Pvrj{34ZGvA)nrkmDEk*tR{?(Utb3xJMt1MQOoS2zX z;{SWUfKI<&oqj$4%P%xE@Vvj?LAqaXP*9&9J$i(M`hWQ5ZBR&PPgoJ$qmwBTRRqAI zn-&@0T%~f&zjxRtY=fUO55r35iL`DI-Uvj?S2nnU$Txl39xOW~ug1 z?b$vSn5wHSsDJy>-fEv#3{`M#7!TI}OO6Tg@$m`qiSbGC$?+-iBjZQKr^b&?h)+mJ zNK8mdNJi@znJ_9LHDPpOd}2akVq#Kaa$-v2$iz{JsfnYL;*%1R5|fgWl9N)BMkb9) zN=+J_9G{$!oS2-HoSdAJJTiGya%%GEl=zf{l*E*zl;o6@l#wZ;Qc_b!kBlFgFfwsu z(#YhIDI-UY9EH|5dQ|+Vgi(p3l13$uN*OhB)TmLZqeiF3rzWH(rY5B(r>3NiOdXY) znmT$kOdJjMqak`UqKzi$|D@ypQ{_=rSdF1YRzBg^Dx$?^HdSkW+;xhZQtR?cTVai4 zR#A_anK59ijKy19%%R}4P76az$rygL&|Yb(sX~1>m8b~w`^U~079E#7YdpmG`mzZ( zbD^(YjEj4l2_wkpP*)ZajD`V}I{(%eQ1@ECTe91uY zi+gJwfn$sg)y4-u1>HQx0zW=v&JbnDEpYSTR>F;eiyfkj)ny(NVyZ0Rop)Jfm0iqT z5HD6m#8?%rz0fbuuB|87EK5X!y!Z7|uP&EL$`+N`)WTAvL>)35x#>^)`FZ2c&pTuo z4I3^E(7NKOkaWI;3WddbcW))SVxE#Y1s>qOQM$oV>e6m^y zjuAtcSwpTF;!QSUc=_e9IHrr(o9q@Frd(b4hRn4O@ztkNOL@6v8LDB?ay3~sW9StL z=bVwh&N{4^uUZyyel%lPR%(2FTpZwpoSbguAt+O0$;lC2{Q2eSn58^vTqjjsWRzN} zi^@t$OqD8T!_|doRH#;>a&T5?#caXcB~Gyg(|Jp|F2;;u=!4_cm?<+R5A~(SgvnMF zGRJ07=h_)AbERv(oI_rM8k2uR?z!b9{7HF8>N81JZxBMCBNoU5Nv3rIbHj;vhwobnD9+G?;C&BxPm>r%hA2_aUK ze%(`RFAKbJnKtjbCKl5Qt4*pm$jY0tlCsiL6IY}q1+7(cGP_-4WK1|wE2 z!WJqP(Yu7p%C)`UXT(9FJzPS?)l)qYW1(d5v*X2%} zbp7PKDK{9WPRpN;Pc+V&ePdx!afzvPk-2Q~lJbg5OVv%)R-1k4vYO>L``4m|ap_t7 z{_n{dzJNlo^sCG?WnzVXW?5xiiCE-~#&-{sv9mQ?p)aw6PxaBpbmLPL+?hVNzLo;|7a0{lRHo{&GukVfn-o87nXUI*m8ax+s5X z^~9`k(dFg#G|JCY@|EjgL5UIv$JwU*Ex?W&DTQL^P43slAt+9%QYw{2f+-HLOyNw6 z^u-AA(u;V<=VFVJNfaW7z<<0hj&>%Z+B-gafyg$$c6^ ztF5qtQn^fjUA4vT_0PaQ1jyLi!Q1??UnB3t5!Z}G3~gVfO52lRSfR~cP1$3u#nok1 zw(+I*%3>}YtJZ2;jIAhEwHCu!V%3tN>MctPu}WF3j#D$(AH-pw$Yx=d$63p<$q|Qa zE=QcWYRfWftQtQwR`A-Y|=wKxId3ge1R<>l5n*5Y|MZ;%K| zk&aSHmg0ms^eY)Q>}a7inqr1<(`v}jShbSu#fj$#wdutt&N~;EUgbHs($s1b@(LMa zK@E(7;IV4FdiB++Em0jm4&H0k;R)&(qTYZ6Zvg2?#kHb$HBGFv#Z+RBH(ZBe3OR;p zw3%vbnX>5OkkpEpp>d-6nlfc47K4;_-?bl_=(A*om+#6M9(_;>=h_fUuY$*5YwP z)M2O*xD2`4YLS?Q%G0z+mdbJqPP~j$y`_ylT2`AP5KAn@;*1K5A-mkfMWG4HCziwv zu@wy&nntTDz%>GTV&}0$olso#CeBOqtG84ZqrxshfX__RQeTY(*|apyhFvm~Ew0*BE_0?B;mjdO z9)~O#D(b;FlzE!3sfd<>+z{G}v2M2%O~iynN?Vqxv=H02F+Q`Ip<(XpDk$m1`J;ehdzm zYIKE#!-|x?=-9`oyy7enoK{hXPu5N~M~oQjq{L+m7M#)FLt)3 zSX9jfMVV&e=_ntn7z?YpbLpexA^XdQ=R=NNjPn`gT#{a{PBU?5S?ZgZr=t5TQ3Nmc z-_%UA1^t@f&%+FvNGgiC-W4D$YXAdLqBa(Gmy)WT2?HwRxez$L!FK>2h%Z@X(`$cmrj)H z+VISraKm%|<@Ra}bj)#Y>pOQkI%!VxMWMEM{5Gs9IEASYq-g8nh1Srf{u*n6|va zN*22)R8}a>tLCB>V)~L(W-2eS%3Kh+gM6+;1>s1XiPdsgI}=;{oDid?V^N-m15r1Y zB6o$=r~~R`(<0RPX{JJ){h*G_@-kKxExtFkJY#?vQzcmQP1Tt9tJ!j^QPhoz+<0aU zL&8}Pufvk}GVGd@6>UM)=~T2ZU)^B)R|_zarq!%y`SMT-^RS?>x~ve!WR#;ro?=>r zV-b*Sg)LU>VwV*QOY?DKpq;&#Y_U`!$8BP(2x+0X>>5mIZ~_3RGR0(DW~p9s&YJ5D z*YeS8qc5$ZuS$qZP@_xG=k@I!Z3QQ1Oj>U?1_6D0%f7DIj2#-NosPR0EAASGXIJ9r z4Ei$IA<``Nea7^Tj=M(m73fpTEJb$Q^_Y0e#C~=7cwA_A^z7l$6~n1CcT9BNnCN`c ztkQ}x(IsP|Az@Cmbsqen^NMhD&6x8zCAuWe`>%-Eg4x2O8j8JyM@LfQ#NU{dB#<=P zoJ4H9j!8}(880tmusxePk1S=DatqTHJv@n{LCLxQ5D4?s=<*Wo00L9S4#%D!Hokff z9}Y6d65W4!3G!HsH#nD4xqzohOt|TyP2RN*N#htXW7=dC0N91Skb+Q z%TM0#uqMg`id|XWuZo@l(UBWko>nZXUQmS{En!=9adffDi~LiTS*>FrPMjs8ui_?~ zjZmB|0(}_}(#WJjA_sV$U{Q%j$0zd8imDRx|8T6b9jC9a^;76;%ZP9X8r~`iG!){V zrwn&{6^a$tW$;%57UP_IwPJ$Lsw@YkSTVz2t|Z`^2p2Ezv&zMZM@3lz&T4R(fF~iG z_h8#63Q_C_%%9yrB0m9#=s~?JsH*{!T;8k zpVyj;^mAfC{c z?svlNLEh~2$%^j~XXAt*!*QHz^b#rYEbvgARg8ioojl&8$~uBmxu7$i8)4+(Pal@9 z#U-6_jXHUxErbh(i^&W!lAy1R9RRAxQ8u9)Eg$l(7P`kTv3` z4?Bo3jVyE?1>T+PM#R_WDD@cO!Zh8KLHPrL$HL{p)oNk-xO&};+XlJaEcmV1NgSkMsn?&Xg_F)- z3}n~pXw<(R_$D~7e)4puUmuUMHiPe=P8Rd#DDdN*rS3NF3W2c;w{wV8P8E4aO-x&v= z##A=j1C=-6euGQFwx&J z`8(Wia4YbD!?SQLe9+-`$bd_STMhRbTpQej_Xa9o!WC}}R9=Q_gNwc|P%*+CfvdSc zQ27pS(F1|XyKv(-fett5!9Zmx+*5GB!3}>1>4f_VZv4Z6%1XHHa9_ejZ$|py9)f!V z?l-ucErH5Ca7}P6aLOadOSlK&K88~s1rApM_aNNQTLYD}#{!k-;X)n{RBngc))1)l zcp^}VL^%c42G)jRZcze%IbaOn&vAiD{0QWGe4sKi2YCnA0@tTFQ0WJEA>2i9SHi`> z<-tvZE2s=qO5rwI0+qvXldA%iU2uEg_QAal7mj*xF&t08o}7yODuR4eB+NIJo5Ju6 z2f~ga_;S>d!QP!?@HW9Nv1%GsQUH?NLmH%2GB+jXFcwaB&|k?M;gN+TjI-^ zL`B(-vVr`#GCoBRB{X23$M;Ywaq;oRrMPjzH^jww7?(M1dep4U3`VO1Es8vnHiI;L za9q!KBWd_LxsEnb#NFPJmPtN*%3RMU;x=}mO%`!6kJIr@7JN+|Xhy-erz1`9?e9RF zF8B_D76JV-9n%G03urA^BaEw-PjEDSChLa8qo&hdJPV`L_3-jUiF-cGla&ntKAN>g zdjJIUTk-sn7JsRw+*ZNN=Ny(3HjRGs%{P}6@ji9yw^|yjZN*U&GlZS^@r8bIAyc)A9-P6QzyvMOCck zyj#g-{zzQd-ja>yse?f)vI?BCyZP0UHaO&Omvj^6BXO?Hep!7e&_G_OR*vC*!N;KU zFpRuS%ly0syu(Vz;`4fW4G+ukNI=q5KfuNjuz0K569X1sIR-9|3gvk%v0kYNg$74K@K% z_I?d#I>>hjIP?DqAexcVqCt3tuAYFK{)?`Kh(mreAZ1noQdbQi^SD-nJ-Hf04O)m5kE>=q>Yu|0Pb7UGh@Cf_Lu`bpCns;n2M@{0JavUR%BS zjb^LFo=1i^Z#JBVhiP`-BK5QYdi4Y8yJGOZN};UN#iv`dThY=L3dr;( zXmF@T_tpt-J|9jFFtA=QpE>8Cyjjpiy~k~4Sv$b0s{Dcq_NCZ&88U5=7}04qgNO6EM8~3x10`jlF2yyd1W`6Tthz7U_kT{{LLCPggNPUF-5nc&;EMTk#M*(7LtKkO!^a)ZqJoQ-FH_ zZvZ@@!FIr@zz6&#&@c^9)!;BdPWUqbd6P3)gEIkV0BghK3e!0k zkmL~%FTod4ZfTe){)Nn$E6Ox~h@d+u9 zaG?gNk2v)c(jG$EM@V}KX+I&$m(T|IyoR%WvHZOdUQy3>Bb@c^4ZxcLk7)17>4JTwgLdx?(+CiK@>Tg3h;je(30DG*F zus`5Kz%S8Y6yRpyV*s}RW^2%>(FwPLUJCda;0g^V+^pf0N1Tv$5b{U;cYscKw+0^s zd>Z&Ofd2%1MS}-4I^q9-{wd%yfIn+EA>$EJo)=OdasC9Xmv|q*mq5D`a3^4_21jXh z!dF4h18f2;)^I|`Bcwbp?5w9!3ttYnAAGAcxDN1j;Ew?Q8}KO&HfnUjH$i_B@GZb& z8cxV~gp}un)JvQ{+Vefa34a0n51?Ufpy3GMWg5I1kaWV20LKCz1)QeggbXL7953vg zzIj@>8SohRsx){D;8(!^3ivhPCJp{WqZ57u`pbZ=fbVKJA>$EJo)=OtasFt}CkQ9> z0R9B{1KLSAD9|SWrUK5=a6-l-q&zRAJ;eE=9mNR07Wfjt zbikz=PB;PhD!@#@dja|GnMXAEltw4K4)kWgT)+=BoRIMdDTk1H2=f4GhaMgS-3a<| z4KkegDGd+26XQSd-hiYJ(4Y!96ZlBLS%4WDoUFl_fO9~v0Gtb0qd^DYeBk#1E&$xB z!G8i$9^(}OlCKz$^b)|=08M}&X*eOn3CT~$_=L363-#$`J`gA5kLBO%2b`k8*?_kIw*amHyjg>*0m;ws+W?8z z0zLq^3h-YVPRMXV@)J@YA?+c&1CZ$8oc8m7o}vig12x#!XqOKj%(J@e1p=ec~&ep~x(?X}ikhr9Q?z;cB50Ph2S3cMe9 zANByAB)UPQc2Jl;RBs8=YZ1N+_%!fAFHHCh!cPF}fX@Th0AKa+b>OoIe+;Y#e&*rV zK*~?)&jBgi0Q}X;fst#0BY+epq;x{cPe}CP?Ofg6BXz>UD= zz*m5EK*~?)o4oWUFa0$y{V%{bkiHq%0c-{y@bDmTE5Z>E4vgFeJO{WPI1>0eFbVhu za4v8M@E#!Lqx3hu^tZh9x4rZh;98`=1Kb9D7x=D+dw{J7e+AqP{NBTpAA%l7csQ^P zIL1Q>_yNLGfgb{A0o#FdfO~*ar1tIwQhz#u)Spj*)SrDo zYVZF6seSu_)SkZsss7J^ME?Mg>gxg${m+4$fL{Q2dtpLKC#3v@RGyIN5fXhus*jNB zC#3ceQu_$0y@b?$Lh278%_kwvFCoo0AUJ$9?GZ3DG__Kg{z$oBDK#HgEP%nHA z@I{29fo}rIiQWc07q}OA9`KZh1Mw6--wR&=!aM*{}~MGqY> zo{-uz6G;BI8@w6i5-ZmEhI4))V+)ozIYQZ8#21s+ahM!EI**eLulIl;Yhe(Y+T)agP9LK7R+KdRGH! zp8w>f)BIAt;5@HGJn_e?K;nrqx*j!+KjrX9Yn7c&(xRBKJB46AHKoQ%@_7hHwG681?eW>nP`~6 zAPqu_qdfGEpsG(ArPGJ<(0s6Xheu%*NO2Q@)aGeGnwKj*oC73Y%>z=o7Q8P+kZA)3 z+tYHKx+o8okH^(lRIcwQ*q#pX9ns$pq_}PmgM82zqcJy>KN3iFP@7ZxsUws|uP~?QwOBA*8n6Ly}?W045W7mJAh{bcLC1=e&nS`0nbJ_21xH9 z;(+G>$9w6w0O=nd?gX9-ybpLDu*yrn3V1%k*8wj8UJo1&T&a7zzc!X zfizCcvOqt=fi%aFKpLlLAdNN2fe2h-1vutI`6&;LU(s>$P(7jZd?Ml0nn3%g9MwT{ zR^3k>ktjorj0RE}>POpg%1|ESB{uTs`$uh!{Y`-GG$8eP7LaIP3#4}Cdg)_bO>a8o%pLPAv>bNOD zYrltmX&o<2?WH;*&N}{B1Zh&}HW88h{L-4bDO#i&nfVKpAr3uf(7cG6- zM{&L&QQ!sV_fI!G>PNcKcqSV0zz{MnulhnF_M8_u1C{cUydca(8RCy%w&3_vnA%P9 zq|BqA-w*x$!;hukbOQ7{J^C&E&|h)EvGill?!Wy>$U}W)fz+?yhvIy}0Pyk=$I>r7 z0s6Hb{fd6*`_Da=e#Z&W4;vb2e|JCh|8NfD>H(Ttnm4z9C`D4T+o>U-$y&s}Oc}qj&Xg6Zf}v?Gl>vIe-@b&VHJ}{jFZJq7M;%mW4zMhwPWpWV zs|ig@?L)W0qubPnF8!_}lx{2n-Bm}osmm*Sq)%D;O+#o|Y7^V1Y&@>Sr2eSD(EiZx zEBcnL=u?LdF2|D~S2LUUPxa#Tm(Pmm|NL-KCyCvQ_o-u?aLjej!0 zud%?O{EWiYk$u}i;b2>ey)yYBWhNh|4ACY1TyvZ}G#;5x1=^AWq`LAwEC$lNlmcn} ztnjb`Nab39R0ox5J5C)wqz6CK>joUNK7(mQhrVIBAGCK&@Jw-Wz+gT?iu2Kf_c^t; zf4UhVbn`sACH>GHzq)_AjUjZKJ-V$wL6^oX7Gp+Dj`zlnWLX4mgY>#_6i^>&9LRRe zI!+#<`&Mm$Zp*5`9DWES+Iv0x!o$Nrva7<7pKRs`56|&%B#><8aX_+}RUp~S6M!Vc zrUA)jz7j|_a~6g3-^T)je3RKvnamLSmHp(6MP8~W-YY}n+H{;UzL1u_^iu!) z(*s(;c8B3IO6pe>FoaC64CwaydMKo?DSh$=%LVy|=DGGbd8lvs9^IRPRBkzt`neKF zGN2krGT<2?wUy+`p?+wxs{`|50EuQYkZ4{3B$^pOqB$Q(JXruF+Vq@;3lRawe25OU zx1%mV`+$d(k5Cp-(s%zxik4cl=EJu0HX3#7Gk07(1IuRILa8^mwD{K4>`XW;io{um(bHzgp+49mk{zKIA^y>o%I-@L)Y zU^=3h5%?COeItS8zzczuUVHI~Q_zr(a-)Fdz>9&EK%z^Z(fIxz;W5BhfMbDMLej?} z+=B2Wz%PK~fd@mY)zk{&)6|EGs-3q3zt7hxdf|Ql6mQnK2^pThXA+Nxv5geoApaA^j#T?!q7AYH%-LE#jI& z;$B9aZ!Lbe7!ub(_YNX12fsB6e(G>n-+shpgv7mpxOT+(LgGF{TodA=LgG$gaVH<* zYC_@?5EqNzhoyzYr68^q`8q@579uVS`F4iH-H*5w#3h8pH6X4YWq<1PzxyZucmL#n zpeWqGA8%U*!`7-`6CoR^k; zbR=^JQN+Pyi}SJrms%ylUm50}bap8&AWB;_J2#7t>?~lShXfzf7WJkxyS#n{8ok1; zm!9$8EP?vm!2LLL3TNzi*Jh;-$-@b%bMt8os@#50!|{dz3e%}Q^w@i(VZM7(tC;&KPvFGFz~Oj( z(jTDo(1UIJq>pDDhjrp`%~eY$V6zthxPBK~66CBm0+5+GU9o(-SY9Y0ZGFtKiN@o`ls|LG#g3OEZm=})6>?HYrjfNx-c8Uve>&?P{gOdN(ScNW?2}EUn z_P+8Wd(XMkQy_Qk?s^?B5w!|F1#hR&c>g9F02nhF{tWa3YB< zCLR=0{)6(D&6qVIl@r;3e5}KnYuQEq{34vAIeP~056aC889xM8mLKN>;z~sv(1(3O zAg1?*U>uW{zYPA0W8$-LXj#FcW!RnhClnV$A1+!t@y832Bat7`$ptvJH=rI0VDcx^ zU5!u>cz;Sy3vkFLGnt;ihxk1c1B!EV3h-u*p4`VIw*zd*-IPw}I&yOf_JpWqE^huQgpus` z&zW0LL}wAY(uXgpRo_pjT{$m^GR+G7O7c;LgJNpJ`JzQIa z)9dbG<}D@T1@F~y)2Yi2j1NzA*fZXzQYm06gA?R)8RmW(pM`UA4)z0qComF5c`44u zE|Nv;D;fX%1%d8>(~x%_1>l!v{9f$!%#WNN&bXo(*L3Hg^>nQ%%}^raFU-#I&&$oD z>7}#G(K8J7yxjRXINOhlBf;l>7!rZ8|MA|jU{dzNCAkHQ7SfTL_z!sEnc$V0ejJ}# zoLl5Cat|+MYzBuH&ZlD$3xX*@ok4R$#~tIr#5PC>6jD-q9xDVbx*L`5rKt0qs zM*H#EpUvQ!(Qyk3$Kf31aVR;i0C&1#OvdGL|Aa#J+9l+l9XAn0lPH7+OW&9GKtiLf6hbDwj22A_Pal1n?xz z#?6}%IFS|qZ89h&8<)8r6JIoFO7HacNlaXrH6uTFVZdBHcR&)(vM)M@Kmysn6Y0cg zoF)Yu8)DE4eK~kGj7FUDIs-k)%f8&b)?s2Xv(2;1-E6R+m-n^v5%&tk(S9H?D+@=G zVr6d|Jab_{0%rBfXqeXMD*k((+VHcS}+c29H0O~dY}pz_hwQg1I5*iq5J_{AZkIwJ)u zgbvO9DJQ`H;{IzRQ2GvjTYzuoMfjiPQNYnS%Mf2?4E|>uUmw1i_?F^ZgKt9|{+U=inU9NwL;O96$?}N#h1H*Hn&qw_TrA^l^c!W==aA;ZgxZ+<>IJ9iYJ)pi5 zsraa}A$P%?P~kp(4w_rRi(F2GMri#ZyBY}5<7~;X#dOs_T~vc@I`&o`R~$VDA@ojU zi%(N{)EoxO)5a1F!r$X;FMna1PH40GbM5JGwJ@c zVtx3hXU~Hz1ry|`;xtU5;{+E3oGsW*`sZZL!QB8j>DaqM0CRI9JlKaGM$)4f822Dq zUs9-R0Tc3bJN=)TiW6!}pRxII1aOrS@B1${?I-5Pl`*uR35XweUi$kJ=4Ke+o)3t3 zRi(lF!FclCvp*4kGBQj?T6*dr<|+v5&Oiai#8xJI!F74dVv85#EiAM~;tCeM5Qlo> z-k)&`a_1H-qU$imVHc;*DO_;L5^iKH1ZM6$Y(~A4=&h05CG1H1)Kg+(O-C{#ygAP*wzk@BHWLf~ z2WXGNCFe`{75qA3yYR5IRgT8R;*-@!v^s5rxz65Z6JzBdUm?4X{hD3JZQ)k)FY>d* zN5rSab&*YN1nOp_8iYw*o`RDoF z{D*uPu2!EYJR&R+pAy%IJH&Uyf8oybWa+S!A}1-+l_!)sWv#kNJw-cPTdq}U?`eCq zsm3y+%vft|H2!Y%pj}4{#vE#1WL{y-w;r%oTQ68`mSfMii|k^%#4feV>=kyoU13++ zRcPfp8;we(ojm+AoXk(=SBvXKS(&U1)#9~v#&#pwc5m3ubk`s=gpFn&;p;F$IpRL~ zki1oGQ)OeaG0IY{Li>69D_d}8IrAMRAMYbU^Gfz^b|@Fm-O8;JmrK>sXt_b&gnQ-_ zm2FC*I$M25?NBe*uGNb561`L}(^u%_dWBx8SLxMyjb5wQ>GgVp-iX=Vs<-KaagBYl zGu)|nm{R<97xWWYgZ(4>3Oj*&h13?mk(bEw4ij zpUXRx!%CPMu12VlYLptS#;CF2-gq@pO;hKqx2kulPpXaTHuYomQ+0qArCqF<+C=R} ztxS7Vdro^no2M7)2lau*UB(*2G%q!0nb(=C%|>&-b-De8Eu&={sBJX?nRpv}4aVbM zK3&e1??x}F4NH_em2a47W6EGI-`|E5~LUa25~9 zjZ#!40d1^Td2ORMT<7#e{R(}K{s;XB{Yv8{BgQO7E7qIunJHF=^|*B!`0EL9)hH*| z+3S4f^iZ3c0^@ZG=4utYk=>29&F88(h9AcBd^-O){|SGgaJ{frI3zgYP2y`}xOBPH zD7`7|lYWq*Wlo+XXUfau2jyqwW_gc%it?DUM={l8bty*cka~u8i}sopt6!tvsBhNW z^{@3g#?wZanP|>5pD~-vznT-RJFF(F)f#0x;N}f>m=o<>@7&>3J4{PJv7E$S#XbsY z^BuT#Joh-)$sOW;&Clga`2XWoVX<(x@S)HnoGRWXekzWXWNC%;r1Y}XA<6Q6@;~Ho zWrQ+OxlI`k?kU$^$EqB!PsS*H3{FZh?>3(>|AEmRVcleXY<*=-w6C>qwtwZ^h4EoJ z0x~d({XaI1Tf+_KRsIV8dj1Z64L?-aFN_v9ic!+l(jw_K=>zF2DMH??ey%C{?fNEt zmwu698?%k?jajH+tvSewu_S96YT%u68u#u1?OOJGHl6!3_c-6k4-@3Y1Hzax?((KoMt|1cA68cQmfM1XZ>JB+k5P(&eKF2 zK3iIU4m$-BZ3o-Q-o`!0ZRDD`W^OyTlWXBxxi+qy>%d6v=eoFVt_LG}1alt7hx6A6 ze-lxuNR~=$Ci{2P+r^FNQ}{Ms67LgV6g$PUBwJb{ZIc*zgv`rnsOL}0M&%+^gXDh; zJv^iyQ5h{v3)dpFNUXGIEk+xw=~{)>q&*3VGtIcwaLlP@fmvm4HFsfUgjwNMgcWH; zSzn$;myZIi>`^Cb0;Im=UJn+~KX^`9q z4*L-Da*4V^eL>x#zNx;a{!>-8Roe5~KeV}!oe%4u=-=s4#xU?hrM1r9LUNrQ;Of^K zxYgVX&}-Skv%)LF#o{I65-~;+rCX$x(gvwnI#r$~FOf^-GI@nuF0Ynfkbh8yK*J2y zztN+OCB^|G+8k-h=0x)^=2$z~o@syJFunn#Hkbr)qBu`nEN&Hb$iD}rHPUm^OVX>- z5@j^jXS({WHc&rXkI|E`9#ZrSJyXxpbM#S$WISNh8GDRZ%vsP=V;m-RfUAd3V`X+S z+r@s%J_OC!$ZzJa6^euxgg=U#F(U6tpGr0IX7DS?vNKg(6|^D7P$SN0HNG_GSSGY@ zrPJUvI_sT{PLtE@YP2Gq7rlX(t=yAqa<6)!Pc*JhDzqDhWYaw$!aP~Q0Qd{x|P;+4aM6n7xfxR5o z&wRF!y_LNevS=0i9J_(t#_nQ2W)HC6u!FeMxoB<#H--~9lbgs*gGH3d<#Gkw&0HCG zA6LiKa}8W0xBi4zVFVw^N5Q6w;bZwYKAunDSzd(vF?k=K1WO==Pvz72R3S}B7czuQ zNR1pJPsqo7+zVOUD6f|{%1!b-jLjOXt9|O%>Pl@LY?4p4VUSCtGY6QXz%{w%gJw0X zr0r&uooC-@FST#6@38N-AFv;`AGe>hpRxZ4%i$$^6S#JV-D0=GervZo>`r^X-DP*% zJ@z5{h|M@*PPh}{L^@;frd)PR=Tc{~GtEhJu5vP+EGNgwbMj$p6+0zPsZ-`0gpDx3 zwQB~lB0GnD2$tkF_6%;Np<2&l^|o2_ke8_#;Oe>gVwqSWHbQ#DNQu%~=|$-s>1*kT zG)h+F`(<0XNx568P@YoO!ET9BH>+{rrvUerY&*574frl?4!lqO|MdD2bN?b2VR zgVLFheweVOj=|na7;OMzAATp4He% z;NivWbgO95m>R5sJ2CIOt#7Q8?a{Vm>$VTJ z`qg%(eS^!D57=+pyX{Y5!-hF0!$Nq%dEI#*YxMxF)$TxF$^Lx^8t+wh3){}_;70HQ z{{nxWkR)6wEQFnKtI!}MNsmLWwnJNgpo~<SSy;fVQy{+%o_vwf9iRR@P>3nms zc@xI^A@fOhZCq+Sg0|(r%A8Af34WzS`;tr9O>71CF_*}1;ZG9{@m+B?tfD>gVdZ@7 zJbk3`m@yx^@>W@>AoA4<3?RD{EaR78iCh>VJ&S3|9 zB+iEwv{q8&1@cPyDfta~5=Q-1<#X(qPF2rRhpX4A&x}kdNW4mZ~9rrbfd_qH5!bUjdzU?jX~xO;K#enm1aG7VYgXl{n^@X zy>ESKeG5y3v!Aw~gT!dJm(u*Ofjb5Ev6CPVABMGK@gJe}Lxgx?obWrL34Q1i28(|X zA5~hFk6?@Y)E;$|wg?h@t#-10rmpHA=?C;I<8?a#usM{t0=^jbKpB4z?14x4YW^u`qd#GtzYLpTE5DO}7goVXd?)`I z?1F>*5q_W$E}SMr3Fiv2!YCmDwwx;1;Jp;#r=)bTP$HBHWx@)fT&NH#g({(1s1b_A z60sE0ZiQG5iB~CBiPd6_SS!|v^+FcgNMTaA6d^@QQBt%NBi$<3!wMd(oUV+5zPwQ_QJ1T~g|wNaU8gl`?b==~Og~K@ zsgKrY=-v9i^b3uN#^uIbqriC7c+==G_8FfWUm1f<1?ysk8EXmP;ic9+Rz2*#UDg@) z1@>(FcKcELchI6;(4)U{&U6&V!k%Y~^9F1O+_3-_5Lk=sVP zZtp?#ut9uP+=l)1K5-287KPG7(lgkny#qOUh8zPK|EOH1yr=9}PF4?S-)R?P|CeT5 zZ`^FGgcZCMyBrO>qyp?FqO9@O66+*ev8UQhCU_3~aESY!3*%3LHXRCGIue@n66jGK zS~M9tbOwJfX7LFByf6~>>1OdmaguaE`ju?Ti{yvpT6u%~n!HVZQ{Dy1^|9O|pR7bG zLzQ@iSAMHJqr9#BO}Ridumek0Q?OsYM$J(dsYU87kmnU@jrue!=Z)%X>P}dMd(^$^ z=df+VVAYPm?mbmY*Ro-!EYoh&?$uUeANho~Mtc$axW8ySw0E&f*{gk~P0|ao^L`Sm za1++yJ6MJ18Pkk(W1evvq|5h4B-UPpHOji$%CZ)r?{`BlJYlVZ4g4H@3mdJ?)*IG4 z(54?_hwz1U(E8pw#Xbub@db9g&D)pS)9vdZUmw7(aFx9VEA|EKzy4ysj+OPk{Sl;X z7xeoPNZiw%DCb-!4nC7aC)vq%7Lj&_jWm$y0gPbBK*KDAY`TrThy6WU$G*>Y!217& zWw?>tXfBbv7P{tX?lsK(huCSH#gFE{=Fb*}V}(o?=3sZZRJc#rCbS6e3wwmULIk`5 z!{7}d{h2P_Al@i06>kym5bqWr5FZvF7oQZL5&tMYFTNyh61TXXnD@m!;$HFZ;y=W% z#cv@CPm)fR&XR^<@82l>4O}!uE|M!@6}4gxqoB>wlp>{4X;i*cc(nj);eeWq-TbTC zx0-?7+giO9`d2jOxW1cK=>AGG!Y+5ZsL!PXnMOb~dl7pHykcu0Ybqd(d$5BLAw5>{ ze}$*$Vu38v!rK@VrE5sy0o#PkTvw8|}JSx3H6_(_hsG8yu{H-xx0#e>1)@{N}yTQ3If1 zCRn#yf3ljb5!gkS*$wtq`x01R%Sm5V1lHiWY%-g{{*HZvJ;+9IJGgJSKSP3@0>9QD zAcKDp#)^rMFuYU`%gU6iMSE^h+64W3q&>EyK0-A#M zoJrDTDOJjmilq|Rz7?+h*N8pncFasW{3)GM7kpw0<4* zeJk{PCvQL}Auw&P2t;yrp2~ zGL$U%qKcFfr3|xIiTR`1YlH`Y=_UNOX*RLz?&KYPihRjs0pyKP3+E7;7iR= zv*3j*f(=pz>$6g=hIgqRp4E+NGqhwY-WPPLU9kN~Pex!3#$fG+Eda%n6(dV9Tb#yOE{m!An~L zKXy5M*wye~*TZ++3~yR1{F0q|m)@fv(Zh@gBg%+@*FuDcImt+Yt&w458F{dNOP~+R z;V-B)>WxPD^qY;H@NKjkosa}Q@a>10QD%%8XC|1UX_`r9ikW6+z-yKVZ&{gH4lPn` z*1}$HG&h>f=1#NKY=;N0$2qsZtd1jAm>mI6SBxEJC)lQ)WT)6^SSeZ9(HD8^rP8jpYwdcw z(cWn9boUyaz1lqj+C2u^J)w6cWw`682)p$%>?JFqLu%pIZ-n2b89tj<_-i_$4SSp; zSXqOZB&@42$h;^vhK*wru%kEGBsPUj!}`i%^Vp)JR#`pU2tRW(yOV9jTI*ta*dthR z5nL1(!^Lq4oCps}5|@INm%(Lmd0Y`!0v}2_SIJd#weX`f!iUlfFJ>#O`A+y3dtl#1 zz*iE3^_T!1Z1%3nELibHSe4~`CA2~5( z(HmjC@BE2%9V5j_36Q}i)^`fL1sT1oy!;og@s#7N@{Rw&IzN#$o~q_!ZC7JeuXk5; zXUIy9>$8?~v=vyxjqdtw(Ymp2V_{WH)>Gk~%fwnO4_T#6Sf4+x%!FgsX0cIa)WB!i zV5~Qqj25g$#tg?B#}qT&%*5(DcHQlV-Q0Jrpoc4a#P7dtSMpt8X_E{l5>~M4VeApbNSQ~v; zMK>P_TO$^Ga~Afw54J|yG3#KX(BbN?C}=I>_dHk^6)uPG#D28f)l~_S57tB$bXB=j zBQ?0%sS9>*q^plo@YX0t&UbZCJuHScxdZPyy5w%Ww~T=1iC5TOos;Qmol;l~)t0WGp0{twaR#2oNo72b3b_jSWJ6lp|5FHAOajC`ZmC^c3X6-V*ic5q%BcFg-> z7anpsFw%@RW5I{4sX`x+CMbsguGW?QZ6*VG&%!g53U5%BwE`Z!25UXug>AQ5Alr|Y z>ME@IOqc7*dgXZY2}trZ&n7xrmIowxK#m8bI1`KCl)_93^Q3q{h951#Yaq9q;92Xy zzAhG$ItlhsDwhsVcNWL^2I2q4@nOSQvVS8%CmQ-778)TQI)MdmtI!KRXoktq4XMx$ z>Cg|E&=5J;F}6SxgyUUw7Iup5Tr}Q1R>6vjhx}a+D?I{lhf`q_#lse*O8)ox|2+c# E4=*NzeE Date: Sun, 29 Jan 2023 14:23:15 +0800 Subject: [PATCH 08/97] 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 zVYXSwKPXu;0`q!`v%aB9-ViNW1SCBt0-I%B6f2p}cBsVdSb9NZ^cbWaas;fuaLyk3 zi1ssy{yD&b2CQSG8kY?=rEF^9<`~|a{hOj4TSC~04%Mq})vK}qP3qMWdZMMakYFiA z_o_%jHE zNetPZ3D{!3fe6h5NEms{0nnA#nI%tM5J6+1N4|z$9L!yAmTj64yJs#v+1ahJ?h`P? zU2_wbMvGJu>W0{@j5*cdsI&mcI>2hth;hZui8J=pfCKD%J2NF~J= zFUqd2OR>rk@>`zOUROpvYWnjYIZIT(wcQPvDp=aH!R)tlixGCG!l1 zVs9>68wEPnY1J?isJ#7^ezZ+FSxR1*DXW)H;6-|2b|&@2S5)6$4@`**v)@k?wlkUy zj&ME$1;7RgU>y&WPN~NcUnNqRO!{)z@bu;2vv|lI$T8!pP~2+y+ioI{w_ zd&yiwSwpO?glXo_XNI1cM8fn*ms~_4XdY_qDgPYQ*{bxI2>RfE^)^s4*J;r&9;W8F-p6i`As*;SALU!k|fc zfzV4#TBjQ#)@Q=vGt?C9)409Rx=8_7Yyixhl?3ceibNP_PU)sKQ_;LB6+2^`W>`2ycOflpf_*;Df+)}Mep__Unc7vzl; z_+~5kM55w+&Z9l?cj*8}wT5puDXil^NP;h4!3Pr;8tm=?-#&1mmtDX&O2HRhdC6R0 zZKJK_SqgvlG&9;dN5QwqhVRP)-|Y%Mk*~4O`dLr-t{C88LX-Yg9Ob5D_^#COxdlFv zTaxfMQo$!60pC>$J|(x1_7;I}81N;DdXrTeAXGS3%i#L^qN73W(?}e6Eecp`zLhi@ z>;_I@{wE?j`7y`KH7)7oRwM47UgvlFKgJK@JaiNc`tCw5<7i$A&B z6RL9Rsy?Fx=}A48*d;tp2~n8nZK-NCeW&)yk`kK9RoP@;!ujunsvuWs)iY9p5Oq=s z*CbSxrK@^V5;`J15@sb=B|{&~1_IWX5~^b43b5}>2@l3vWgY%;v^`{*_Ly*hO77&` zWIaa&{NL7_#*U!fUk?UFx1gshGEKL(3=v9~{8k?}sfkg=f;qCwC%k|DN#l+7r!KKq_3jCya1m-LX7q>OQI#C+z@x(py9)y>u+=2G1? zoJHHu2S->M+SL$@s$~|@FM7tB&w4hdG?^2fvHmhpLhTuAZjTx3DVa->W~_Nu2E`=L zSo1A6VE}ZCp0#?+Sj#9x4^w;2`dWO(Qe*V*CMCuV24$|Kumu6@^=~GPQG3R^75@jx z5zTl8Wm2 zQX~@H^WRMB`Sx{!PZ=%rv8;ILwLTcK)AQvw;0;EE5q5XUW1XRe8X&(7C?$*4pSd(( zeHqh6C3I-bTD%J%J~N>UZ;y82S-ylW{8qdR$AC!d7?mV<;T={P#Uyv(x2*}PXd9D@ z;$0|Gl=Zk2lCNe0yLaxie3C&C=3)R*vdQxb5WAO`#Cy41%2-4hnhZo|Mx&Q%9hyvs zs)yc$(lr?*>duyPbM!t%?oL!Wvsk#@)Z?2jrPgh=nM5{d}7@zMSWug%H^mXP((V?GO&VoycW<288S`r;+T~OcM|{irymKJ+MwvH}Wc}lijtrt(Oc);8gBf`UtR%V9z?l#1K_Dik3|oue`by z#{YTlTCj*nwY{{M`zo@c4T+F1BoWLB(z@aN7`{F$Xfq}h3(W!d^0qKk>}xJO6SbrO z(3=tu%1vXECsrs|30)U3=38o8?Y3&nv}q#m8$V93i2V3HQ6g8efS`&3`vcBV=12_*!MDMg*s}^Kh_y5hG#c25sxz>4IzD^b^KZNK_28Y=RaFM=@ytm-_K3fS%|CFWs?84c-i{-6L> ziBoSKrZ1Jf1kK07_3u67@Lk@-ooX!(-->4)z8TLse0k41e81Y~@V&R+;j4ea;oJA3 z!&iRL;Y)3G`1b$R;rr(64&Ptjarkz%JA99JIDB254&S_^4&NEvmF;mjeIrwxK8G`9 z@L-pF@ZdhFX`aFS^_748^&8yZ%g=zpXQ)qu|GvQk&m5Gl{WXdEIy{i3m#K?BZb&zq z%y0X0cj3v;SJ1lb@~}HM#EsG|qTop})54>3jBR)loX+aD3gr+x8qMxuII@hoJM}pJ z1jbjJ+jDmn>@e1L3d%?42KN~SA1oCoxEW^C3s&xdf;~&qwn@T{G*=Vr^oe)^cgm)5 z$>ShyMYhiu_U49e@EO~1I2c-%PQBuKFzg+MdqKI=G>gylvc+FBuVnsz6Xnfxd=-wC z$9i2m}AeG{5bd40+-b$!bJd|jo_lIchMYuk=nJ@%5F$!vABe3taQhTX#` zn_{UYBRPxp>F=YCurzx2+W5`r9l!ZK@GGszrm-`vfmHNG*nQ{U#qRq+7sKwdbPz0v zr4@o$YEDFIJ-Q?TC!c4GwL&yY{{~WT!aV@#KL@E7N6VjS4d?aqkh%#?k&Vv1rSz!> zej~mqGvu*;v=-(#IoqSJk_g_#6~`|?s{_pmzG4?18+WzhdyC`^UfwA^ygz=HN z=9tA?Wvu<<7U&7@^SPXvlTDB z+2*dJe>c0#e*(Ji=v%I%;<*=X1u?;w9j-TmFXA50_0K_#uIF*m?Hgo~F5TGN-apvr zZhE&raQY}Vz4qN@-(NgZU=7~Pu-9YEhl70H3h|R{zXlsq?2llhE1oE^2L**VZRE$( z^j6$)W3ng10m_r%E81g0vKMtBA2Pd7;RDf7IUm+JfiKB7y1*e7hrP8RPw4yeiz(}+ zm}vc6m;SjRoZU;_c>U6T_o>^jTk*;~VIiL6R%rO`{PFvllJTeW|M1lLzqsu;_Li9j zX#ahKH^lt+1v|5+_jU*5U+|5}oh{>1FhRn{xm+nGXMx%T^C52Yz_n`saVepy6O8Dp zakZn?EC{c6aI8|o)R^Jqcatm`qwHC?=w%Keo2_%K4gm`_;MVaTq1HKmblh@EXf zG}~awR(05PnGVhg`MZ=K)iUhM!-1TFDyuTDKBX?z#N+I>UU4-%)!UrnXqkxYhMYB# zwG`Ar1-`1k8I{yK) zsN0-+BIIukl^kj=>Oy$yR%I8RP-XiMhl{(BJ5r3Tg*51wNFLNz?>`WHPn^RzsaEW^ z?h-iWwwkz|F2DfoI zaCa^ict4?z>6~SHsV^|Nf|#pKa<_*FlwMmS0IX~^(-M&QpJQgHpql z=6-D7XIYtVVcBxU5y5suDnvL(pFPT|wO3PU)WMkBRLYHv~5p z2fNdZbrzuGB`@9Q^qHf~ed>PGnby-oS<=bnrgzxvIxE!=1qhkss$rTV+;}JV| z_bIqYp(^F2n@;IKw;~JR|3Ew}qBr0*=d;E!5Yi2&?|Pn_c>dMn*~qu>rRbj?-`|gq z?=_w48M;4R6ma=Q?P4U&Ot{jB<8!J*l&6X8WTMcl8$Jv zVpLX{C#}slB7LY^YM!p75WG#x)+}qRv>2JYB_UtxCr$1?;TXn8i1ndmK3pnB!c)8& z7B~V+08#&jRAJO6c_`D}6`6u9P>qVJ<9}}(Efs~~+IxwE+t2}kJ2l(jl;0j;8nNFN{6Z%3dRNl&Ew=`V)BCk^5d+2}a{U})uGWP2H zFVfH!|6w2$+mM5piHJs~T4Zo=J5||mPQt5cNHc*=B&!DL0dn6Lx z*BRU=(D$Lm!_8(c!JOi*I*Sp#Kf;yi^69BcQ=d59F7b|fvG!TBsls6tZ*OWJ9*9(> zS08a2TbsgR!BZgeg!H&2H)@zAmaHm9nq>#$+r~WxMC|INX_jZUCe`--PMM}AYYqlc+E_`6YM*-CX|be>J`6k6fb#RjYs zpC|w{5v^h^2?aqoS4fA>lo4Z8%uS-4o9S3Cs1WMqUW&^UY|^&Mu?io7X_Avc1u=+s z#uzcsENPJBEB2^WDM*xzReJ+c(teeZ;)KaB)H#!CPNWnOems^BC1x}3fYfa>r5S*@PM8wQJ8o53l3tPJYQ(?m6bdB7#=k7BV*`1#&3X*3k(-e{j(Yo* zXGX_@VB|7d0a;uPbEv!sJ}MmOGtRe=GNn8xmJ&P^X$+9C>Q&Wrq*``XU(ktN<$2M> z$vv*ew%JVgq=Mu;J+Awg=SP!+%^ZrZzAM8~>9c-c?r=;Ca5Y~$=JQ;5VpO=x$HkHY zzTz9bcX`sFqM?(?7dep7%B!T67wY@!rJ;j0?0VcMH}Um^CccLT5kS15CLqj&|Nu@CeCIDCUPJeOO8NNfV82B`$+n4NBNUS z_m8Q9<}30OX*|5l1Jn1`zPLJj1#bA43_JRl=G03hJ2f1Orp?k8iR6=2h|u%oYh zTa$TLq~pzJo>HS7VWw~JII4*QGP^M<)cqRUpBenyDQ_M9hYUzc$^X;c>A#(F^lde{ zH4~6QR5!mlX0sjTT9KVkKqkfK001e+kU8DeB3JALUloPKtGwn7YIF)+lXB33+!fhl zhJIjdKI(koJ@Stk^nn1!cj|2|3!2_brF~Q ziP_J(8<^J?t2P8D^GX}!l9enOwKQ&c@OZx^=Rk?0tGDOZmC|1`!D3u8uuGN%lx`PY zH{r9M=^{(LGMXns8_{n2!0;7$P*iqnY2hI#66bjMxFE8&&&4eb5(kK9ov-^<5&L8CT?Hla$ z{c4EQ*L1ei*ToYY>h%4P=Kx`Mk)|wsdv;B*b45j@{N4DXB-lA4wjx=M`hg>eA1!!P z8o{kz;TnENy*BE_P)XaUkL%`s7@B)HG}j81b&f6d8TZ@in(7*0meB-Xi2bRM{{1_V}iEQv3fEPP6;ZvLp0Thgs4#;;+V5|A)awpWKG$KU7~W z>FfL-vg{06h>bf)h_qLEVfE8>ygI4|$Ez~SI;p8{@|9DIB7Up=DR{a0K)Fa)+g0B0 z3t;5!E8=49k0rXKGq}=ngeOFdq9!DH(|?$4q~p%2>+Isgv!;d@IV;nf-M%?3?#rtd zE?kHolxFwk3pxf6_C9aV6!Gurq6S$Fwe~T@x0=SPOIgTxO@kaW z&4DsLl2$7>nN)Vr0)mvuWGN#&y~04z>cS)?|KN^ zc##RB(p!;Qz5@7RQ5_GlDyXsF!`2@yR^D4tp!qp^UH>IfrTICFt2RB$XOJvlJcjd&s)&RwiPKT7IG) zerj;RxBqZm$su!=M{fEqv7#4zi^iBsh{Gj}Kcz3S?reuzCwWeUuajeu%;($(^JOYr z;u*WdU0Gxnnp}&EyN!;26V?ijr`~ z#hP1mcz_>ixYEUM?igf#$HUp`m0LRcZj;WCD^SAzHuJfF^(|RH?k+gns2wHs4yWot z!Q*|G4P|Oxt_!cfuy1$TKmTm^S>Bm8FL2Te5~@TZg~8HN~J>({Fc;TnFz z#W_sm`#bs}1}gG1pQ|3*w=y6}S=`ib(n%kdq$L~a3~N#?kobAs+|d`cxj^eIGW{E5 zD?*mB#V4#rS=A|EMeW;ODk7zS1145-$@ylT$lQtA5(bbFTI8jT0qZr+{hUNp%6cIM zsuJska&H@=9mYBvDk72T+J;;h0Sxx3a+!GX=XkC#YM%sDUgUzEuP@`$@nMrN$aq*~ zZnVCxGc&U*f+uqgHVKlsZ!~j_oq0N$*JL|=5Apn*2YyRJ`CGXDe-gsC>NuDFTe$v1 zzW>hio(|Vp$dh$}(>H`ClP9sPxqMILDN72E^EP?Y{HHo`JaIXyJ)&qtOw0?dJZu`H zUNl#G;oc=(A?_zHYeUDGW%|9jO>h=?QBzwVmmln0Swqj1wV73iP#2obxlMN5_K?4g zQ}q6>+Q?n!nJwI_>hDt5s&?~n^0lgjs%ur(G>9P%m5yjJKNAJ(@!{O^c&)Re&!}gE zEpD~fTd=aLs!u^lXXU7u+G^9x)XF}_uMc#%j6WQ}RUPSF6G{Cd!kS7QJVwZbjy)=W z#}3)Kn(Dn~4Hh+#)yBH-Q=8o7TRNQUC|jET*$42^KK2j0A1EktSGu?;ksU0ysF!+G zX)m=EpEvn|6J{9~Dr%v{o+yUf6UC6&L=nH~)tSOQuSd*%`i^vGKi$T~?h(x)v`^cU zxTL6BxlVMSwGI2v-uln;vxNDhIL;3fCG}9r<7Sm|vgG%gE7@t=+~T*-UZqwHC_llv zExvmqv^6v2-$Dr1*J_CJ#qn8g4p_r8dH zVpX%nB~syX^dQADXGluXDx!DYXPx;5L{id;wBoS!+O4Of*Um>L!>SCONG6p~&YssP zXXWGOl$>Dq8Ah$lfnm8qSvA-9oK-FOgjWpaDmnf;~BKYPZ%{ z`!52_vc_18jkSM~kfeQO5V1qvz1>wF6}n=1G)%LYU@JP(BoKZxYS9%$WF}q zdglbBGdz8pn6N*_T2pZ6Z3b^FO++e5_Cm(G&tZxM|LM)1`dsj|ck zqg;eKOESf~E-wisC!J*54IH3l*?5f>{}ypSCJVQKwQZ&>1EOt09rs0Tk|Z$@qfKhP zOf_ko-J~a9i#4e*o>;f%$Hbl7o^_IgwV&QzN$B2FZ?8P|72TTV)?Gd z0K6=JA&Z0N%7gGkW72+^zPB=L|0IUV!S~#Y2XaMhN>w^{KL@wF30U%e@Us&}&Ap_l zcV3lJb!G6gPmP+D@;SkI)kJ62g~880GiosSCm`jji7BkmKZ_VOEOZEPT{Y2V)ZD=P z(8l0yXZsZ1Y*S>`OA%||-#&|81DM0Nj7#fJglPlHLLKA|hb6JtS}u}IPtFy|t6Cat z8G)IUrmiJezEA1pNEF6D?axqB`t2B{OB0)LFNvf*+KE6#!~2 zhxw+Yl@uGSuaF|aPTej9+s!W*AS%QS*lmEY-ONmm@abx?-EG2_4vI$tvu=c~M8usToeO&E{+G>*OP5(hD#+cL+P}b$;G8`jS z4c3`>vkmUd=s2f50Ev{(;a`QL{3Ci`ruA`##H9-v_^n{KZ}BDJVs37>_s-8^@ephp zk=qh%lzU%en~t-r?*sJ7`{N~@tikUmN~Epi>1!`rI&#-?y0X>s%?xK=RM4^d!h+V- z=c6t3l~!Q!H!@(2y-#l7p%m-wF4>PnS7dJc)f{wn`^iNlK zVt!pwO8xf6w^PicDce&TFWyZC(vm}xHw>@sD3z3bbV?w*ZTrKn-F6A6n=>ed{B0>= zuP^0Ua^?^Q!ZRqR?u`09@}tX&cS)9Rd&qU??{P->gROP@>GeKq}9Z*W&_TwqP32@6AK z$gR;`RljQgGoKZCc2f=sgtW&e?eVuVX0O1u9pFUVlQ~6Xy})$qmfPYDSP)2PKnJDi z20Sfqb^|JJWW9Xa@OMg1f&bfAd@=my4cbAQd8K%?io5nox_F4<`(K`i={+Y#?%QFrRn zh$1TE;@ofdm!h()e_-mR0UBdpoNKpWm;mKJh)w=vo%GK%P(VphpbXcb=u2_!x<20F z)PQ^qKq6H*2Tdn^sG^!S=QB^I=S8O^(m=9|OwWrvP(x%=&ugY9`%ewQ&SLhT zTpMFdZ%wCY!eVqVxu?)<`d6sMkRpJE#vp&D;%mjyok9k$#K-_&KMRBm+{k?4k{Qivif+MK$uNIL~-ou(x%1dd|=FJ;%)a*pIMb{uFP>A zS@S9_uI@=- zV7&|9fCA$cq(m$DdaMFhQU#{Eun8S~OHCiCb_PeP?66{Yc*2HI&49@SYoXk_n)>xw zZkE9UUdg7&;KS-A&uaWCeOHxfuFQ&5%?Ovwp)q}}=Ux;#U2fCqg43bX*Yl%v`f5)e zH5p3%E79Rwi|6j3pPw%leQZwHpCj!r?`lgy&#~#~XR>y}IkR64^Kx;0&wQ9BODZ3M z_U6mYs4hCkI(=^N`Sz9U$MQ?}Indd`g>CvbJ0bl$;-nw!ER4~8k};@SBzn<1<2OeN z(E~tOP231{RO3lzpn9TART98cz^NqwOT92s#4oMtY9#@@F+<3}XWhd(gL`b%{6C0R z>3LITe+P|5e{#VBh5;6&9^5nDYR`$6^qkYrOjd6!Tcqa0`Ijo}+f%uM*oG0`*ffQO z?i3%(*&-h&t6HX{>&P^y;bY|2sRWr0v^LyZl zH)@5d=PBy>89X4d9vAJA@u{m5D`fuEAeX_atd+xeI-`~CE1f3N!vHE%ia%N>$k2Tk z8Qj@~`bKm+RcMeNHYQ=gEQO!}y1$}dU}j|S{gPm(*X%B+y5FdImna+v2gjUm)O7GJ z7T#>NP^gHDcr+>%Yvi_1DP9=uWv6MAT$u|!l0|&6s^%mZBvZBw;7v;Uc*;6_vvl%C z>kz*&Da2#FyH|$Cza@o4F4o-rB>7XMkk5#e5mH`>e#9;#xD@1-FLT=*m&thB+Y4s$}5c}+GAm*nJ<-bO48mF$#*B6Ij$xW3*~ zSMpBvvEj=mVH8g92g-|;`ueaa_zqX}h4MQc);*J?D-X+T6L8jh3TvK4<;%7+%=s|$ zQ!~fRKc#SUX1ck`7b^!~`wv?nFDBir2mf`vL#r}ZByI1r8LXZjDhwioO+ChnFIQF2Js{g2+o z5MSAf4Zl=ea!~5_9|{$vhuvc-W!B=!n4ovhDzzqDMVFKuf*9-l@6$Q?BcSald}UTr7}63 zJvn<0Z(g&BuvY7AAN%$)QFfuX3rWGF@D{$(6|GGudIc}OS_Fp?~nR4*j*?# zlIAXsdgh^k4epAl^12optM&(9iE!F;w7E-6DMxHKUm5WNkz%MRCsxPtTev3T*z==; z#m1BJvki|Dj`j`wBlxX<^^stHw)K_ZkP5E;;e(FF-Y4Z}n}?8&sx^_qiH3y9&o+@f zI=;FlvVqGX2$P>}$UTmZk~I-I7L z?rCawNxiinuMW{c`Do>`$(x0WPz^5y52JpMv9XD*>J(Skpch2{W^DYx>H1g@Z#G-w zE`UU4WLE;{_1VQEusw~Cz0TbalW{+jmqccbz)n^{v?E$ntLU%^N*{ELd>NvAl_+E5 zn@*|eHK&jYip)VxM5h}YcRO9rl3Ws|7rbh$!zd@R*i-PKu}(Mx=ifYc zUKDnjz)OKxHoc9lY&0|!6$$H0X_=_`?L^IeC;CM4Xql+^*TPVWoIuBbsXCuiZgnYZ z-h#VT-__@u;Imy5LrneiGz`*AwT5%9woJ#e5t!sPJQ0 zeAijB-{5~2`*${`;Hk}pkxPm@2Iw%S4hxt3lD(yO@tA@4tCi2OL1mQRqBd&?u!RRp zwv%m5k~DUpA1Op8a@>~)uWw{z70M#Ye}F4sB)p|ib8EV^Zkx_n-g{qj|o1hW-7Fn1Cih?gP3y)nwDk={kmET z4lFyPV9zr5sCPR&W_#{GJ5tP!+!fiJKs$TNET+LnE`w97;7}GUz6_X0&$8~3pdGop z{M*Oeu4ga6qho^Mer|2km_9y!ebsdyGlj zT}KLct4*wMU!Q5)OQOj@`nkbBcGMYQwh z)$h1j=uXRjm;54O{d|h^_|5FXAaHS-nh_~OuhqJ=k>x5&)yJ$C8Syewn*AQOQIyr^hkTmOaM=M2KRt27PxHDw zb9z>tA5+3m8G<@#F}i9JqpMD3bhW#uGmZ`~M?nY`qpS3_VSj&2oB|b2R+fG%5WX%8 ze?pN+^~(N^%A3WyaM1~{Uy8Y>HkNGTR2y1pQ!Y?Ru`1bXsV`|WyX%M48EhgW0M=m< zrmRe(Mw1o|k5sjVODwXutbxyPX81e#`uI7`6p_O^v8@;LJ=_+XmC@u!B~G6VDPu=N_BH?I-a$;Jj`8v{ ziqp6|+wnMyZDtO88e`rQBeIc!S>+8x%(SMyDfSxd^<~&oRNP?db9S5tKo~HgUKe;0 zm$(Edg_#_gg%=6-n$c&U>t!s-_AN)Fj2iKFaniQ(>1>7@JvPG)y&3+<%W5;ces_=U z@UdyDvkIoTR}W>&JkNTlKNDw*M?k1rGk@L|=gl{AaAAH+(d1j+Oz~abI8S}sBt@4p zYRx@s-je?B-&B_Vl@=Bb1WBhP^BE6*dYCnqqj23^FlsJ|H@x^lQq-A$| zv%k$@eccbk8(;5lQ&!g68r^Tzm2}m?MM^pod4|mb+kZ-7a3QfeFB7ZtB6mTNr!tqK zi>XVglbT^6eJL5!b67%>c3-Ff=OW4$@t&5?dKw8!bQDO$7Z@%;Zewf0Tm_-l#?hM~+BjnPbeNeADCFkNxhs1;>`0&1wI^Z3tM*iSz3+%pIYrV}!~i zvDb9u?mqe^B;y?#8F4Ha$y$=ukVV5fhOL2%M#f``>mJ`RP<^a6f})RC5=&WlYN1FX z=%2p9ya1n0H?1=3YiKjV>6e<%hbKC7_izfI1wL+7fXElh0k#ka%kx7XTHvl6O!X3& zsg;#xQC=vgV>sXD%rR`U6?tzTVV))nm@2p?O=Jz$70PHvg3p^5Bm5sv|Z~Z-D6=u}Pp|3!&GY7A& zVK2yOnUGC5(r8r6SNme+nPy6xOPVQLxHt-nKxk^B+RrBlB~h z!U+58#Fq2VFGV7(4vHq5Q?D1QQ;tbxK86`@Kx|Rp6}JXLVUNJJxm~z>dZ<>K2j?cG zJ0Q@!^iSY>8Ua+frS8*QE4z4XDCIGD*zT|f6-jzFtF|28o`RyP_(JHwzYzt!{^l%$ zFm183vly^QP89954)I?7w3OwjTvCCtotxL@+7l@7`dXJxK*%ua)fWd@G}EY+6m<%< zbzw6Jfg2!&#l`l`l(6y|jT& z3<;?8%+$%Ww^m$2w$vWtzwWw~*>t74xv)i5r9;0#sOr#7))2D6!zs@r^Y~Rs_QI!L zYWfGM5<2=-uRI9d{{am4q<~At{~uDo0PDNBQ#8wVGD`EzlD#PY_5|NLVXqN-ntBJ& zRz6C9(Z=L?MH#mIoER9qoH$hFaT20Od5y`Ng-f^SWm1$XrOz-W$DXt?$K}&~$klKo zSe@B)+bJn-kEf7tTxp&kJ1Zqj&76RLmV5;D@#X|?LH<%X-|Z0-xc*lmI;Va-VO$CZ z2NUwROlLx7M8%|;*JPS~>jsW$B;7n?@&R>}4o)c&HS>>nzU1aaG2G|9)g=~V?w3h@Lu2seK{67?l0mPRJOQssT%=I8aXZjTl` zG=IJ$&Yy#w3Hp^J`4rm~|GTWJLMy9V{522{I&*huWgY&%UQ!}zxa7X5IJ({%@h6eZ zJcY1aNecISnfp%?6TYlfFvp)HVO0ae+$^a(4ORZi4bXiG5PXGV!&A<5H3zHQQ>@vnBiXG%@YlZLknLvK!rcHilpo8}@?3{U_-RM8ED=6h5Mqbb z;ka@BJgw@#K!Yah#Iw<9V{wQb6rI{?`=b2_QCti*n2=O|+m2|zrF3qYgxfY7zeK4K z6ApNR{*{rcInQU_kzVlnlJmopWFIZ~sqCU%QIEh==N{HPndLo3y6^@dQ2S+O6XwRU zpSBAyXNAAx4DL4yj^X6>7*1Yy;pBC9LHp9QXPB16$?Gnz37^=_PMP#_`8=kqRq5gD zoXAtY&{SV|T!tudrc0$|N{8bW-3uxnKLF$)Q0aZMHJ{4G7>my|Z#WU$H6r(zy=Rtb z9xIz={bK-G02dfTsaoqRz{Zp{Ptt^FX=x<%HBWIm=7&;a&9VyFOHag&ylXw2yI_^4 za-^(H@cWl$`~92Z3EgnA-#?Me=L=^+Uh~ef4(<62Ygj(*g2f(LG(A3ZrcXGHD9dHy zoNmrbFL=|aIh*$~Be)x+n)|h;Y)|fX_D|QMZFHFL2cI|Ad@4v?n)bB9?J)B>mF$J~ z+@^xoXs8FRUV&vyw8DOc@_(Ve2;Gs6###JvW`tIu<8+n=Kk+RtQuuxt*?JOqUh|2< z@L7W49We~srkKa3&$8A42FS}&SPtFcn~M%m1Z%WmvnhipCI6--M4_EpGFhq`Z+-os_5MAU>;3g|y=xb&WvVwkGn36%toEx$MpA48c5_um zF{bg1{PbEbyJgeWVW!BrCSfMUp_KNI_$ASrWAF1)^-`f&)5~VJY`SNJSGjG=`m?c7 zPwQz|A3Vlbz(T$5`t1HO4Jad<%t*Hs!DU?$yswbmckG>PAvscnW$q^fZx5S7d57 zoVoOD#Am$g7&VWQ*gjttT;;vgsQoVjNU@)6au(@*LElub__sL;*xuDGQqAW>J4!=~ zsX9jwym1V5{Fp7;$_Fv_to%OSIV!)aK&)K=bD(jB!R}SYy4k?P{!h)Qsnw@b12Bqsc8nrC^BaT-MS;8D4uxcDpin?gZx9dj z=q2`A%k0R7>xY8uj<0cMna9QZcz*x!Rj2P$p8I*<&T|{jAfAhOE+dV@;r*gNXDmUA z%j>nZvi!JX;iq`%<4ujmq}eYX_1jl}_4D?R@iKY&zdk>6dz0cp(P56~7=3~UG)_L4 z;^QDq@Yuyh%@4q2@Yto59N|^rXbErJ@t=*SG<_l_Hu}>vzj>!oGs6jTeko}{_2Jy@ zvIiJ1frVmj`}{JaNB?H%{&mG$cm~|kY_bWBZO^8w+?Q7QrWAa#_mcYKdm7~9S>_SzzmCFDr+VvOfL~>a5-L35rC_&r**Q>3=C>53c==q_*wW4pJWyrmCB2o{bH3;hZ8f>kbubc^~4a8At8782yEXYXzJ~> zn)LAq=C|<7ET%a7*q0*m0qh@bZPts9fa4<+X?cjttQ^6*s)uB^Qu(z1Ql&v)~4Ab5NvIzoCfc-&vffq*=cqz8}R zYSiw5;nu4?2#(TawI3!8WhsaKAP8YUCo~6tZ?|I{v?6;l?_yarn(r~ZkFyq1i%k!% zNLuwuCH)A#dY?V*(KdpUdxDP1pkr$6@ZJEXjzIxblvdYM8XX6e;RSo?Or=Ime?XP3 zSKtaZzK9wiJ=lGvvCc!V@BpwNVK{#+pO*BW1Z-HDD_^A;yQj}TQ!Tx73=rN|6#L_{n>6d&8@sxLGoo| zT@JDekZjbDJSc6W6;|gFoFlH`ts_?xOZy4>PR2%c4~n7j<3L2AAQPIE8#$zZr9(&C zohv5?v*^@9z9(DXqv&Y6qR4@bePOrS2Qm};R1Rd)ry@5FwOgm;qGTH1+ymlZ_hy>jS`vGM_r1ppWhYjEAaqReL^Se<_Btqu+lp+1Q%A1lA-N%+Mce8dW@oC z^92m1P_`-2M~Nv|O7$2=Wgm8NKA(~=&}UFj(c~Oa?NBCU5Aw6=h#Wv0Bi(x$5M+b{ z2dFHceODo7c8kZeC+xi=M#vssfp$zgw%VGOSrWED4}dj5_qq&t#)ObGY$MX&!~)VG z(*e5~G9Ac7kQttr*O5l;>BXTmEAplFY>Cr)xlukh-uaM;CFDIv4dri ztD+x#Dj#F5!5Ef_tv1WyLeq-88XSX=FL;|Dx+4%On$aN4R;|)uQ{Ab(m+|1qWel;) z0MzRngy%-f00aOS3Jv82rbh;EP+2_C`gZfn&JKBEf9uaCJXWk@;d(3SJ1!EU4+|dd z4t9=?$%>L^1b^IK2TxRKK{xv~pNu15j(6gBg z_543V_x&-!?kO?#zgq0YjCJ3nml?I)Ra$P7pPom*Wv$rr=y(4wuKJ0sRP9ppMHOp< zVB;IX?&-$5CA?E%#xhl5*HWjCg-QDP;Z=Ex8|=4!+{p+7AMGM9N!*-x+?r^dT$3en zm&W66jm9x$sW?{5(K4@z#%-|Uyz#h8qH#5LT)%kSz-U~#9XB8z_pc5c&g<>CzVW!f zMC0;_W9~n@30)Gh0EgxAd%-jru5;u>B3RS&zHA05)?pVE?4D!PULbSpK5Lo!t}+e_j7U_ZfLnVK`gH0rz-&l79M#RX??9-P*o7(!hws)L)wd0V(v7-OgkvGBjcV8m~ z;9XrUoqq2<{I5S??ykn+W_v0#(V(j%4CU2lRX^Lv83XagJA@XX`Wxh}?zp-7t?>eU z6SJ1L`q}F0J=HL8b#mU^&pss?<$jnz8$k=H##SGvm_^?HgOGGDwj0KM=g}$HZy5Jw@SR5?{xkjlu!R4Rcah&? zi{;xTMYT>*DJi%w%+UZlLIH6zLckcb5Jx_y2Go`mxIvPa>f~MWj;}bWsPGluIej7T z>*f6t{oW?;V#!H({;RwX*6-Ew-dDdLkaszTLz=8s-jC?_Me_bP{oW|=vYAgB-(Prt zUcb+l_a^t50q{+0J1>-Rc&ms?Z_ZS=Pa)6s zJhOQg@GRn4!BfX`KhFl9%{))=Y~yL+Il%K5p0{~Acs}C!lqZ!5&)~`6Ige+?olf7= z)Vm*f@0+}jTI}?FmFGU5r+Gf%8B6@dq?I$fZn-QD0cRLKxgv1zSwk|;K4)m= zxx@GOBY_4bJ0Z?UA%C8;kTW>aW8WEvUq+DjvRdx{+tQ#U-+7$Qn>iwE)Y~^BiLHDv{W=nq^?5jLe zYLyGRz9x<#a5`IgBSupaIF!I4H0+`oSaH*^Ofi_sgE6LK`b66ENjpq1F*V;L#^)Ls z_Fk$jq_V_9DxdUBlfv>sW4Lt>wDH;EV1|mc6uRYS{ygArDHM&jV{S{KXsG2VPN9$e z%nYt2oe zCEdn4;aW7U5X|fVOu@@`jh8z<-mdX-tLuI8`6T>Hpk@SKgGiZi(d{5a5eV4@PkAi; z1Ys&|O63U9G&~7SvJ%XFSx}TE={26R!O?7uqwLehk#Boe3`fRRr|5~Yk$M;0t)$)= z{BVZByhd)ZQ1`GYd*ed)4t)e-;rc5`;pZ94b3Kpn8vUpB%V^blTQ7W`ucicv{S+Eb z*RK(87%!i{U(&DgyRvB#iaBu7`~8DA#q8yRopbCB#gM;;f1+d9facl)PFLGcp)VEGv3e0C60cXSMD{Ff!- zfA*>He*|+2ae;}6b&9p*JsLFC1O5x9dMiCNxG3J>sc!2J8>tnE-4F-c*PO8aaDc{k zFU#t^v0E`p)Qt_3x5bo$0J@zfGSxA#A$ zHw;1kf2X(5s5rejX|tlYnty&iy^VhKWO~cr|9SK_3L>-Vt@fXq-p1F*>CMH`B(Wbp zQuKE2?@y+;zn}$AqPIuc)K13#zfEsW%OwNsbLef-&ywkF(!S55x1x?{V};&kh2r!! zltsqL?fmlZPNugb=n9hP?a!aY+ZkC9Q$djdy}e0VtFz8kUgVI`Zv0?wUVIC+rQaL0z{`^Wgu9zdadJ{MUk;Pyi?#hl zAuN!%zk!JmYaJHEX{G$TNF5O9BiLng$847E>fQ8NaA%GbObKorWG?$E8y<_+a>VLQTe?t9tpdjDT;8y=V>c3b0&s6{O)c?uy-$xy1 z@nrIx&y&M5GT|A``&B%JJViX$^GxHJ%`=Z@0Z#(C?X~5T%K+aI1bn&S3Gmhb&FcT- z>i<*f|4#LPulnCA|JU!Qyq9@i<9UncA3Vnro_BfwkSAJasrdrh&$}~CgL(8Cb7G$9 z;70S^OQ10)mQg#*iCL!HFgx<&iR0W=Ip*Rq>bmoSBTF+wi^p)P-^e{OA0cgGUMSz3 zm^sfxpXj)8$9yqQ6{nJV{B+gD&7xg0+&YKsAKsHSUH^4Ac=y z{7r=M-R^0P8z9N|Z05yidm7p}l4|FbFzCpKj|Nd^Mjwmp5!1t|tl+aLrIEqoC>{@* z!JTXj7{yXa!k>nJU*h=B>FqpCiLc?;zxvg$>{GtmMlw7Q+QZkR9DS?apZ?_4e82F| zrnghjCv9ys{QEen6)Nd8-L(!b7L7ZI!d8luXbF@u+qN6Sqc98r-!(R)pb`WThqZ`? zjEdk0e(Xx0anCSpUly*%`#ZO^aNL$NhQ?NRT9d^kP+S0k&^83m9|e1s3>P0D364fw zP4y*1nhTwdI#0Veo>bO&xlPJ8@*ON^NX}Z#-->;tK2s94iX3L{QE8Rs9?nLd0j;Y$ zu-qG%Xe?QPbSIu}@R-4Ez}m^ks&t&k2g2i=p_Q%5fUnegP4o^_f|rq!A$3@ATN4DP z*m=+-FC)gf$pEG-?0$DxhQ6m`5S~UJpbT&}Ot)fqJ02f4p_N_C z$|Q@C7S2%G)tVk_Sxy%DEt||jC3_X6-G$0~LbxcMYfI@h2gDXC!5Q?S|AgdrUoKcY zU*Pbsm zQ-mjmHLM-5U7jT2X%j#iXar^c*8c=w4_+qt!WEFD;ko40ekfhcx!?#^0;Y{ayNPC|?}*>4vWg<>YBMJLk)I-!$i0AA1d0qf_gj82*H)AWqrs)vs~ zDtbpk9e!PKtoE89p%#>51wSe^# zRYWHzJCp<^m|)^rc{teOLOLu!I$V4B|55k;@ljS+;{Sw9V8DSHFkq~yrZw8w&>9UY zI;cT10hQncNMct7Y%NVw+ak^Yc105>19^COEZs`iwz%SITWsAfyMm$xn?TG&K?Lg$ zthA<;wGU0yC@2Au`M%G+&-};_K)av)Q+bMa&a$r0QlA{!ze!$6X`DixmHoWAd-vbD71Dtj7lkdn zIc>-F%9M`A!>Wqqa_!Q}H(K8s;#}zXk~QUQN-R4JZbJ6TKMDz;j`uaO61%JgXGh=e zG%AjEof>;1JaU$OtA4NjC^MXV%l&(6(lYjjV%?RieDDl2%fx}H8D!rDBeAj$7@8dK@ql^^4^-7i?ec7B+p#x;<|PjBUNK8elZgqmbU@X$&p_B zA^jX+%$L4bmf=0VOW1&5?8WK z0YQhw6?%9!+EU#S<)J)@mwp}Tv`3zA4vB6ZG9SyV4~t*jeFVL-RW)S3 znH6nyGM|s*x{m*LAC_pHdky@PxQC-PWaAS#islx{O13}uHd03XzX!dJ-z8@&X1X_- z$bfCuHAC2Nt(tF+Q0+ZOd)3|2V22f=K@XuP=5uQFVe!`8z1b~Ba7Q5089p7SdTr_5 z83DAS?YZ=#%P(H_YeWIE9SZ{Tr!vd#yvKe4fh-3bUBly`dIDU2oBKs76Ix_LosqoV&_~{V*?6r%rZ7?I9 z4t~CZOqhX~(9cqc!6T1np2ABm4qkRxOE=Ues?nmMIRgW{?a>I>Ya4>`;~@a}H+H+g zsYD;!3gnZ@y#-u3MNK8+hFb&X>b-IwAd~Xifx&LJ+MSt`V)yPNmV1m5+-KhgiPQzhIXur)S|Vo}jEA?KmK0-}|;=X!9&f9Vu3O64kv_pq{vG zyB8nQiRyz!@aWC5K~b0WjVv?fVKZ-VgG~7qFz~+<%TqbIW-MPN^2}I)O5~d{mO2U- znK7hf664Jn`&besW-O!6AK zPXOnD5wEElerZ;*b2s~-y#TQv{OUwq!k7h&or+}~v|?_iQOI8KKMFdwh=x}4`l|(& zJ(9l+YVE{#^jzr{OqFT(X6bv^OC?hilF24_W`3qqU63>ebbI8tO zCYZZ~$h@p))*V@fKj}dDMJZ4-)85LU&Fw5n%scp7AJ+IZ!XguolBLfb`m1kM>!H_A z>8P@Q^9Mdz>NU4xqt@m&YTedt8T_^nyezkJ(;b>M)Bc(?bd!gc%Hrdvpg4@X zksPY(qX4b*(_Uh1As|rsVqBm+;)t5wUu#~T$D85;NeUgq&p;x#6Js!hFqEy`TK{DX-XL8c?iA})&*Hw6yP zaR;uG9G8j&iYUt!Xrx^%0yCo0c%n-SSRlOSMMd<8yZhd%szfB8s$?w=@$jnITl$=u zT__GSW>(cMfsmU+d0hj{LO!pR&(SiYXMWYJ?kR1VZ@7P8ZnYK+VLeMr7VT!l-rdU$ z@|pJk&=Q`A?BP^sRu)jF;{(9^X?y@|eo?wHz@e5f&?=!gucCB8i9~BsFi4CQL^(Lg z;|$HgNrA?RTb%xRD6h-SO~r%K_-OwO_+b05bl(H;F#sEK9kHE}EG^&|+-qJw-ob_d zQUOzN(xY&q9$zzi2$~hLx9?12LB2$;6D&xXnlvU}GnaOo@pktmuCxzs5Kz1|gik!X z@H-q)+AF9j34YJQ38E`Ty`e(XW-cZKEtu0T)_f^W;~h_x%9dDhIgu|!k_7#0aqy! zcVhw78hF-sk0@BBMlr1_h`u;1a0Gt_NAPE`4Sxo^0)IqDFU9d^uq{)$619w6!uhTpQ(E|(aIM+v5P3K zZFx*HXYZ!m9i%VgFFwMC!g0*)Np2@rxpEJv|?f?up3zML7bT!F% zpH;Isyv($hBInVZWC<$k$976-qwi7kLf|7pAQt#BLo z8n2jtGUS2T?tIzz>}W10M{~rm^AYF64BC|BiQ$zRH?S&9j1_jNVfDB|Gtg2@GJ`{& zubNS?>kBvaEpWs8WjQ{hamoa^6x?8z|7>nU%I_}^Zv|qxEbz1J8U?kzTqgX-p!O@i z929CN2*?6S3fyjWK6T&*XB}{!*!&hE;R1J}Br&$2|5>8QGx+e^^p~jbih=qR$!$H7 zlc7(^ar7ydpLJ(XmJ^mdrAv|G=u+erH_~$D%l`TlqBy|<()tut&2sTP_wCjukOtKA zE>}J>zGO{18x71dZnfgV{?g|Z&5KIq?-aAHKRfzXr!hrTDlOrWA^UXw-rPgzi8omK z3!nM|#l9VvI7sqb1ey(Z+A%9lxn4~M&kmd(OAX<4L?=E($O8AY~5|zM%-JL3tX9OF}Dc2jpmFAQi4csbCnP&v=Go$y@ zGlIjKQ5g*SA2YgEB{rMU$5f)jjILLS9cFZcO6)SHOoV%Ko+U1OC$af6r<6InNTf#q z7>_NWQ3|`_v2Ze_eNz!qEFNrDuY%Gx`%Y<@A8C#sX^fv1{nMs@*w0C((pUvTo^YO; zCe}vf&f>^{Uo@7vg(ACrmL*vPr0K9^N#p<$Ie|JI``ASl*aG ziKqPR7Z8NfRjq|lOHqY`}B0ba1>(I&moJZ+-_6s)|*LlE?_F|&r9s}v_kEXzMZ=zv7vN;V2x1ysb%0%<5wY$6%!LLc zUXv&!M`d7QC3X{`yUHx@c58B;QQ1ik>~GuHT7ee2=tEKNHviBywszLcD!UV98ayf5 zrm$AsT{|;WWp9^E75b*ke6fbL`+2`)bY@uhXvzk5=uK&|CKR%tT_8qYURYVgPDJ+D zuY3dJHe@gRNpuVL;xQ?{tl{hbb%uVk;CS$Toxzx;DfnLNz<2kNzt^gs!}#XkXy^mRC|**4#sZxNk~N<1tmoa>AmLy9l4!M4W&F z^<8zvPga$^tyO4F9b^oV;!Dns{;kuPdbDetUUeVV@6BEb_(V+r7JL~?lb(7lcbVm0 zZY}T%JL@nA(&9w{4SyA6ouZI+sB6ruT9EZP{KY}m6g;+rsfQ$|6zs9q{*&bSMNT<+gp`W`{ynqqRsgJxw1a z|Ht$Z1zDe-K0<=p0rWvwq!Z8wuEL>@hq?0S&_|9G?@u35{r)8M5z+)QOVJ04PH-)d z96%zg*u3>6k-H>kP!gGqpJQ2c{&ysD;2F(x{{a#y^+F_7QNXUC_&G!Z1GDBX=(AG4DoL#FuC`YcC-V%EB;0UOT1 zR@n2iUE^x2nctJCLAHt3lspPB&uc^Qfnv#J2gb8v(AD;Nr*N$Z56pO9R)>wY2izuS z6h+JA`pxJ288_?%yOCG&#L4S~2 zQ)53{uTj_DUx1PJC7_5`WS-o>v*9VT%r~uR?wVpG?sA4Q+Pd0n-qveQ?uk!U;%j{J zK?S?XJ@RABKINcQb<`MUEcQg>-oPu77uZY3=O*n-HNW^~Ccp3xww{D1Vp+VT#@udK08#cxH}zQFMMl8*XM_pG0V6ns zRx2;Z^l){YYMK`tLBDa6_~y-m8-LE+BO2ScI{{BT4>(dPa3In}cV^Yr+V_52fsG0W zFw<@JU2@pc!#UxPu$xhOMK8!(ka@22HP5%r|78{H1%g>v5h?T9pGX$spP_)DLx)Oq%; zv&^~fz=7~6)}zxXBh2PNOBeKef+*kUX%ps$4lZHX%8l&=l z^HS`^`&w%Fny}b&p_K}5R^!Q?C~O(416!(+xO~nL1E+JGza{l~FG_sRJ@!|=aV$YI zLx|EDzL*bHgsBgikR=I@)!r+&<%^dZm5yVOh*piIo-|m-7H3 z&hAN{2MmZf+s5jQAK~9V=`5ma62VQ;;O1O7k~y*XMK$hBq;o~jWR&-y<)EPrv{?nm z2}zI!Uyc0%*Z6@b{$IU##o1~9H_8HHjs}8jeN8sPht_9OcWI(nTbdl&?`)>c7iHFe z0B?f#8B4oif7fdkf^bYH8Qwm7rfdv)E$~Ma{A~fWzy1yx51(04Skj~?Lqo2UW$!?{cSEqC&d%z ze#yw+Wb6m8A7s02Pk2k%y0vQ8kDtsks`pvzcg3E}vQ{nl7ak*RMs=sPsdI(o6qw{J zZ9yY8?01yhYprsvd@?I%ZG`OLW-HjkX@Km(s);1!N$h4tfCryS$d}CBjP#S^y~L{s z@6p~{e6a@sqZvV8Be}1YqJ>lHBlc`dH&Di?B(Vu5v=ptI1d+Oz0IHZXm2nIdkt1V= z3re7RG=XdayHNvDRdpvxjFnWHt68MkKa-DL6sr{x6w`-a#aqil_7BnAV{f1g1FF|x zIe;hbBu}e+*|9m8(WaQIH6v@)ilG%vEd;N9Ke}{>+lZ_+BfE^qy6{Ep8PBMwvCF8g zhV7UUd;~W&>N74=cU@3F#6E{tta`ym7#~MA&8HrfxtSbg4ln?n4#g??WEd#Fu9k@( zvc2@F8ygS>^NL8}>xlyZO3#Z~q^s^`2KCBt)JuQmDXXlJJ}?I^;Su8Way z$1b4y;3l`UMdY+sj~#QSSoGFHPgZ1P-KuNQ!@T)JVJaFx^hWxD{EU7aiP;Jsb@a!O zY*MUcqS*gbq=*S^GK=*vN2|#;Q_KVR!CYA3)lzFz?!oRX({#ttpZBu|6nhd306XoQ z-vqJEosT?`w5@~qd*-b%tB=k!g9i!wB9?TKJvFGvQIKoxHJ7|ZO`cY+R>={eU!Tk% z(^h+M&A?G8M=N+fSoSNr*9a?24D&6|mMQ$fCVANXZgIN=|NFf>pjDeRF7UDMs*i#2 zq}IRYG#*jsD`U*tjz+hhUA)tp>_kT1$@VLYc^?nTu$2@VM2wLPkNnh{FW8gD(@Ib4 zTi8Wl34&c{>)&#Wh&%gOjj_~Y|E@*B(sGv~X33FhS_5eY-Nmh~y*V84N{z3gL1?NT{D%)hVdLLr-3SF+(MJH%^TyT(LJV5RF4sl8e8%Ec(?)=P4$9{xgL01@$2yJ){k=( zUd-*(4ndGPT03(FDCI`+vdaGIZw1IoDW?w9 zQ%7OqIPlbvJ&N}x_Y~Y{v|vPCIi+t5&PDiws?sX_$t-QbslqAaYyto zohcsECvxKAiKc{zHvdD0B&s-+Z;Tv_o&|%+-Y!F+ip;gEuO@ECkz)z_&{3FNO3gAH zW+~%9SF6-4`_B{WI&`(Cpv@7Q$ovm|i)=mpV$e0N`0sG);;m4rgGcTYaec>A{Cw-- z9I+M`yKsfc;;pr!I!R5i5o&^o;l>%XhE}71CG3AXu}XkLO61~v=-%)1XJP*1^H28s zMwiK|V2%pd`W$kmefJhVq##}AIN@12hn#8GOD27KHYB~or$a%=t|1RY1GVWIFTFbf zc)zN^JN_{ZUidW=D0`1Flv$hIYs_&QcX`Zb=iVS;Mla4`AyPbMN45P5bZ`FIyx6+72>A!vzSeL51=Q3}n`Qs@H;jQDA5BON z;T>~tx`CJmIzAbe)L8U~ln%w;@Pm=qox*OY0RcMbK`B z(>}$mh4fDwr(|*8-b1Jj3aM-Nfsf7YzN=n@@ts}VqvS!~laq$}p3Ivc_&9O~Ce_jX zS%@l>mXqGVo41cOXNn|{Cy!^eL_3c~Te6mP8Py)W=#EjCl^z7$(%Eo>tQXHZ&=b0H z^fj6Mqi_&;&wL+4WNaX<;4ZEt9TG7ixTj$W@I61;;>QH5*8Tt>AljTI=ctu?4DN&w zUM+JXXpMB^UGBFg-iz;{fN+kX$XD2Z*H3dcqw!#;fYLtls89g&lr^Z+S|IBm#l>17 zy%}`uGop6`!l{1NDy)63yqvDx?OQJU6yA$=BHHV@eN?@_wg!_R`+0O0UFFe^VXEAs zVVk6QmyYqx5$sj128L5Rk?YDKDNN6>;U&JTG1T%_v+r_Y$y3Gn@1hpOCYiLAqIJaP z5Zl9wqIg9K{WC#+SlVAUJa_=WQ4s+u&B}fAl|Lf;mG@~L z6WJ`YwS6*|_y)q!{`ZgIBoFkaC|`34kze}g_r9+5d;XMuZ*q@yb%B8gftN)hn!E>5 z$gJu#C+{_H+ZUg_Pjzl`r~cV1KWO|oLh%}lbIr-NF*VPu>NaZgt*V}Aya4szZ~LhK zls|8lf5oA)YmJ~N$00uAsWLs{L9ePf=+{5F@?+laFTUN|dMpRte0d}INDNzC!M@IG zHvm8I!J3=Rg-^c`$u*~VDGnVL_tZdgM>m1qw>!Z@_8|kYbxPh`BbaAS$)95c^KZIg zn|&{on#n!Y{dKRtzwWPdLsFuTvQKNJRc&xm*w5eu`;=t)dZoCs|K5@+Jis~y`EfD} zUHFvxX+#4?+JT4kRd5O4u0YKao=m)x6LG?`6({^6b;K(a)TkxZ=;%-jO$AzyLjv=t7VW#=R z1DX6`@c5VF_9EDc$q8x?%mNkf9mmyVoDYe9>18@Bz90Xc-^RNg767AVSw_K;G5>qW zWB&hwH>| z$w~GL5-=&c1SWL7zZMoG4!v@TqwX>5g{O}R{KY2=X7_<11{Kjnk#1EZI_ zYO?(pvU?%~Engq9Z-e}St!xF)MWtfCnAFk9_PASME!w*N*Hf^0X7{+qJ{5y}RBcZk z#Xkhc@YMI0UcvmPha`usy`q5Ap@3wOJ;d{3dJ_;U2elcOLMpqXhn1BmN94+E{!_%O z9{q@#RDsB5U(+hOXug(IKS{5J%B0sVa%n-WpPBog-Kd~;ADLNe*IuWTP|lhYQXRC% zd_wmtUp7mePPKgcPTl#i>Xg~ev2sga!coEUmhfIw7%6<6^B*bwN@Mpf@Gv;aB)Hp_ zLSPn;74cUFXI|6;cpo~*AE*1q7O^_uY~zb&ud-kHspy*Ye&Hc0Y^nke%O~bVf*dto zV#U?TgZ(@#z#wqk#l|8}JV((8?bx$${$;3ZE9UwoUu_#=Y#dJ)%w;X~)|^pisQ8`c zvOU@GXca@#Ty{M&xuJqpn#(o~mo%#rpA?LibdO4}S84Hn99Iz)WlZtd%RK9p_&(OD zy{Qz3HBR2XR0df-j-Gb?Tl_NUF-+S!w<$Wr^FzK4+6}O7%KDCSx@x#R*5w>KF_9G^2IoteI6<9ob(; z0n#1B*K^qb;>vdsKWADA#7p$@iHZQv0lW>N@P&+G9`Y+%O>ilN&2Tu z|CGy*;n6agg1sY^T`8739fnm708B-`R@DS`QA-F|UDOhypn`%0%8G`$~qdA7^gMG!m)bx!qnRmhN0W+fo|6MFU zYGwqx_s{P)GxASO%?u&5elufO#>{AZAaiCckeMM;2rI|HjmOfLnHjr$Qz~0DH#8Le zIWyz&F_2lG9Y~ehflNc&(N#Uz%y>%Aj8;7})Gp*1kw#VvSg=#oGod2a80E|bb|Xv& zwD*ZBy9@pp4XxATT-dIRO(hIWiIPxfw8O*&(N+^X)fy_%b{JV*;LC4px;dhY3AZK72evfmg8jr1~1`WRrsnn)aYI7>pl1jCuQXQ$(4ku+U zYeW3AEU6XvNtNcZ4ze8hNzG>LAb(Tv^BJlS_6Gd>JF{879t3{(Kmh~cx7QZ&@|tAp^@#Y%Q)E3_1?1T7WKQCz;TO)3H0168k*3wdx8nI% z>*VM(sNYHtcg@;nHP z<{)+qgwZC1;KUOOHEisj-^O@?5}kfb-+}ICR$Hp2Dm~CuGUxbWova3y43SMP_t<4% zj;Mpw!Gk@H^;h44Hyd^h>lk7#m}pKLZ)_~*W$8!G%BDiv8ygFx9yA4~qHq&?o&Sn` zgiQ%hG-u5F2LL(L0b&JrB*s%Uk?b_M7~rX9gB)xaF#-EP(ArmW67LY*aR54!X)_R@ z-+G&N#yJG14&yTIUIyIf)}}98%Y?uL?zw&8{xx9%C8ol>QBh>oOfVH)82J3wt^z7b zSc4e?^5*9>BDBYVw7mJvebf;XIx{MeG-bR-MY%SOIaM~i7h)RIf5SV#ZbEJ&qU5*k z)7#zE(k3^)^m!vX15vlm$0HbH@DpRc)U<>YbeQ?%fy#hNJ-X6~PNj+pG)0(-7AiDJ z;3o5)_hgH%_oNNY-Eqi>$n3tRR~2zg{0E4mVtSG~LMNDCW1aa`^7_fh;TWhINDkKv z@N1@jMtb<~E0Xjut}i`YEiCPSNe|V36-W+F4`YQMzB&j!^bfjT1uZoG%crA-iw2{G zab};xwUp(`O#91DwGOg-vxH|TWXq<)obi8$?9&`%PkHTR$R3Sl<3MD8M^n99yS^*XY!zTR|1yh7q0asB_MAmadRi>&%ub8R>oN7;Cj~c%|GS zr1vj@nKo_hYuBnOWx*ONN+1Pbt(k)+T@q{K8>NZT=fun?WzTKKa;Z1_HDk311c7ND zT+xq?mbu+sV=a|6lgohi{A*c3RY~jtD^x3-u4FVbEzq-YgQMn|U4zFaNRo}hiLLPPKohug2BD^;c?a^Fcf{kL_6GjxUV*onkP zvvQ?$^dfV$h^{B?>nL6I0m~uLHBMifbzfKNzAD*QcUQ#xlXO?EVgtDcb)E5;)TyeA z4csl5R+w%&J)ZYzdMpl9sC{y>$9L);_q#yp^jm7w{cbvWzlZ3W|F%-WA|8{Pb-x=@ zDXB*COzXyUzo&hgem?@O({Dr9ZmzzOnxKl*E;$~P8l8Sijk@2rQ0ye|zDC!4ezMJQk8ttBlNTmCxtH&ylS;LWTG{F3Iywn|-N)O$ zojm?h`sNBf?)V@F6Y@^kfL8C}Fgf%`{#?tSj1#74ccH{13Kvd7e8GVbCK=`o&f~c8 zg0O!jei@S1j?as+qkXs3SmWk$>hlurM$VV+NEJ~&m}XYnSNvSM1=hY+(UIl;Tf#DS@wHBbE^BDs_sT^?#%E~U2s6}{$fb)yhK5I9-E93sSbXTmil8fq^0K#hzjbYv0B%M06rK+kzmCzUj67vGYF(E_8$~OI zO_leUf=nzvEMqaJ_ybdN!>1{y7s_e}6rud~N2hRXAyG4P350#E*M6E!4TX!Rc<0U$ z`?e{0r7brR`KVAGRO|D^?_{1&-NL*)&iPbOAEIkjh!tytEbCn%yZ)!*jmN7qIQ^}= z4>X7D23FO2*@wqt_dC-Aw{mi$+^%br66}&XWT#{=qojD8vp<7hh&HPm- zX2X`XhKikou_{X~N|#$jSy%gF57QELz`GLo zF=@j4rz>#G*CbZs_^HD=q^}9fEHOVbVxlDC?5&E^d@($~z#KZ+u^j6+BYn#kvqq^y z{mSJe)R%qE^ws6Fp_roOabNpC(?+l-uz($B_PHqPTYjpv@d1uD_b-z+WOUMoa1U2s zQARFr_4;C8VUVVVhxG`hPik$R+t=FMw|uQEH?{T>8kD9^yi6*@m$BIr0szAr@Agmr z?BM$=ax)1ty%t|=3?&9VP5rVAjnh;`NSi4ogR?3-;;|Oz0ri-u>^QpabN==`4WiBo zS(d!Dt1dw1Rs|6gvz$|4@0-BGx{vo{y3DeCOmHlzSGf2k=4w^%r#`LzMojZw1r&3E zSBF}WAS-4swgDFt^H|Y=Mo==$3e>0p6xb-_Mg*gb#bmWfeQ)vpfSGhpJm5>OwEM%%We^OS`#eynV6A~hk2)R()Jzke0S3gTk}#N z6n{PDRyy`RE(eu<+LQ7On{+qWjCL?-cBI#zzh$mJ9q!|_KdGER_tDm$`^{C>~cP$z7~?0CM0VScad!DGS2g#hndt` zmXx#I)D+z!O?sHp{0L9+O;O2^{lssD;IIsf3@iO;e+dYxPtyZ zFTf;sJf^O$x&}sFpDnh=b)*{bK3LK+#GeqqaIqfB*0}&Z3%ap!B2V^@f8+pZ5>I@< z<%W1{ypyIYdq~2&;xXIFi^sZ22|3Qk9D^jf4fz(>WTV5Ta@~$Fz6rD}%lvmaH9~t% zsTI3dJp0j06ZTxTEAshzx9^FqmL+x6dEY{2jFpkJXQ0>bx=RW+d=Q#2qkqYRz|$#N zC?#j>l2a#KO-VmB>X&scYU~=XUtTq#vhT}0dFhL*24Mt=x=2yP=B(NR(*70qlg~1gkN>e$RUh3V!vPpF3gp%) zDxoLMkmRJ{1pYO`rU9oWVIt=0G76g;h05p0Uh>`hw1U5gjO$57kCplEVT*?x8STVs zKoIVGX6t<4_gYZh$v3`vE%SXh^AGRC!f$;3rjb2Ts_6OozW0!hSL#QyU64pdwt6Jn zpUU3q8|^%jT|joS21!R-%K{w>*LDqEvATr%13Pc$Iz*W>Axm;SlLI>!e!vYsHNm-1 zs~AEEf0h%~M2$oeiYb^EO%j&zYHi|G^kSAkOkj@QMxfRo<^%TT>$R>TaHoQ}pfX8T znwT6^N?rSJ6yZlTD%}zYACHH8A`n*7(p;@E!41G2f(bVH`o?(l2$(?raQU8j&io+y zHW2?_YXQ*l_Ci3E!0X{OFybV>gD+uFq17k^0R3*I&cK)K|Kk{>-{2>Fh4R zRl{z9qpDPvoU5Lv(kklXJXp5zd+Y;eBM>d^rb}`h$co3ceEYY*0Mtqj%E4BntM(xo zmpQVHzt;L43)@{e3(nr-dbp71TOJxu1E#n6p@}>N8lh#E?}yLww{AQcb)jQf{u+DB zFQr0nJV(GGgsrICu`K>HXIR%jCR-LOGJ!w_oMf7n`9d0avh{p=y7;cHub)qQO&9V` z=%P+WW$#dY&mFy7o)W&;7g%+ziX8Rw;R#Y=o-VO}7;l7vH2wT{ntmF)Z9p?P31L5M zn}A%&jME+3w=Cfe7-~7s2nLdoSU>H{Z9@|4C-T?+}CK%qQEjHt^RzOANHObVxZXCOOnRgbyScvTuIaAtqnbUJ)8EVVLGlLsh__c2FoD z+sj|b0#?-Nqy{l)udysCZC@$F-XaBQPjbvH>aQiWK-@Hs%SA5_(ZfT1_i&Ekine}i zd8ZkcUrMlkn!80cV0sqvlLULqRQB0=nD(>W2dTJ^VDGF(DvH;$#Bn5@50c^&uA_FvlgB^vG zQ#%TG!7;sEuwwhj15$3$1kQ`mg4$IuOI0HUP9xZ0Iw?^A?i;up(UdS>aT?i@M2{h3 z)nvr{aM`-y@Seyimu)3ewi;bIb$euA7Tkx+^)gmU&bp{GQ^F5}fIf7p zBz&RLl!UKHHc8xtys@w95#kn@Lu>-a#74mh>Ruke`q)rnf?cn%>ibak%m%^!bI8Rp4#hq9zGM6@ENZ zax*RIsf-ozS~}Suhyw()U$P{!!0lb2Mk*joqEjQ$0io1oT0)9W<%$ANCjDKlE5BP; zj)l9_vU-Qq((74>%g><5CaF6;13EWce}84Vb9?z#NL({(r*l%FX~`nsbWWzyNk{$| z)iCnEe%~P>S!`tdZT#vcq(-L+sZlD2HfNLUUwMDpoJGY(vf>aQNX57Ct1FhKoQkD3 zeJr5%A!*-3$EV5?r{X?0KGiLv3wfhN%5^C2>}~Sr-}sZ%1nS?UVE9EPTIGmLrR1fM zec^-j1qHnlm87TYig4fCi92>^_gl&<$K2dN*WyW>E#;YUxvo5v3x!;ZwkCX6zD6G? zdcnMqz36WG#bFa1Uis4>>L>v{uEtM94dPq=DCGyyJf3N8S6oy(8A$3Q33z*YQX9$T zLHlWZ>?=K_YF4#LHwr>@!2S?LfZQ`LZJ}}eo_|&P`4w}R<2XO%Fn`+7C=T;mwS%0+ zraOE@_UbJeKyB>yLZ({0YBQQ}-hu1qYT&cEuz;lbI`HvZ*P$D1 z5~!db92Sl1P+R7A{KbcNC!+tHD{%eXKE&dUlsANiZyI<$haXkUd_yxV{N?gb|u z6@~JhSvgVUJ&t@wm{~QZE3wO{C`3B2R179wWpTtAvWpiWGqz2ttJ zr0)=B!n4M_fP2#g%DI9sWDlnlppA=2>8khC*ng>4RGxAJ2`1eSPXpfe!k@CQoJ0yF zyFuW#=;hOSa)pO(5?F1stC9ukLgd%&Iu#+P`P|!{a^-|NzVCXtj7)2CPSwNZBof4VlDC87Q;@l|Aui`aO3btBfL?MR{L;O^b8!iBhR zk1HjE*HR7IOMZDQvBJ~t8fu2T&6Ul~#Q&b=;exQS1b2G2QH__5X5E_GoW9T3E?)?C9U^JcIkh9@oBp>%zktP3+B=Wr$4GU z=HiJd2zl&g3OEpQ_VF4@!*!Ri?r_zudrm_MAtco$OX)k3U7C5EE-42FtPg!5HNqYv z-C8VWBm=Q_ihXc6Z`*m3^HVlR$m?n3c~!UQRQ>R}X%3{g50UEDM@xjnlim6qr`UqL zoRcDNqY-=pp%0g#57x%N^l77(Hq4$CqkK=aMtg_*mftiIHc}Yt@S{e1$*S-) zusaNHHCU|38#P`% zJii{^NEzO22ZZ!F1;WRb#v%#AB@Z(^VP;wOfBZYiU;|$H7L;>aHDupK3Q*OtQMTPvalc;tFP5?~2o!eJVCW zcXM^8S?e(ud+}9aT!*VTT!t_Qof)5NZ$deuo&Sv%VXMfj&T#(MUwE62@V4WdiYek6 zF>M7Ve4stp!awm9sZ>4#EVhm>wf(@UvH!FyPjz#wJ(_L~Htoh`(B7?s(D%4Sne_d} zLxXdI!KdA6_Rog`oT<}gSTOoLy_d!R;9EaZyYxJzFzYG_B^FPE#vFKX-7(`bz5OL{ z36f#DrvoLWaufnEB27l5*&zXFW7oe5Z733;=;tVETceUxa7M-`mkct>l1Sz#KV176 zsUY9}?;N3m%?$K(#|7lNw7flF^U*@@poFd8(3B8tPMg|kN?7M&40)7VHzZsGbwxWr z!2_ldY|H&6#{R!=6s{aPno55y)Lh!VH28RM-wTP|D3J1-~wRn6;H-N%%W&dr+ z@u~0V*YzkA^_k0;ur!JV%|}Q9%zv}3Uh%cTp#JpjnNYu5L>5`wS`?Q)`uQqUxh;)5 zU5KGh_e0P{a;kuTyPIoKjo7FklXY5_WJ))v-ar}@(9Sz?-BsCXH+Yl8aF!+pkMN@= zhZsD3oLrC3?$a&%-##NT6xhGWPO=2$_!UiX21__TARoUu2}pBl7!cn37M)^hEKpNp z6Ay@X$y@%wW}WRFsX-Q87u{I^2+pO5o*OcHXKt(v*&Bp!cH;r(=$9yA?l69FCp;W} zdP))|MH(i<1SYE-n8-FV2@{{vh9r6P%MUm_`b+k3lHyVHM?Vr?-Mp?KoEz%adeua~dXG;^=GYzrlxZ01LeS>0YjsqY91O0}N{9)(PT zdm48(NDqTYzs%2I!`|}asTdRawwPuvuoJ8Wls*+jJS7o{41w zo8PBFxAV6Go3##X#%b8BIT37*GO#3UT*%SX{MF;Y=AnN{!sh%wu=yGe;WTVYGI-|N zL3rlT?+dvmVbh9oUcu&k`=g&}*xdC)1)E2uCt(Gi1QrW}gXcom$-wid>_)?tB&UM7ql05A$hrS?ld)iC-e*;fRcAC;bj$DNbF*=c%kFJSM-ONTxll@nC~ z0BW7|1bS4;EI;S~K+c{4Ku;0?gH52<=zWSv3KRe?P5~f;Y23XkIr>ZbjQ)~g$Dvm`b+GW@7JTh(K?nm)b64a;q!5<04sl!HqqaWER&-=i5+wL zv16j)>Njb`kGGmMeC_9J4?wg3?Kfgq#Qz2L2n_46w3Hp3eMn|rPyGmUC#&g` zKtPxRN{rb>F4iw8*Aw>FfS!VWOLV6&&G*^9nR1%2F+Vk;vlV~IO4*3}}WS>a)ve~6_30c1p)jQ08$PQcT* z8Ei3kIktwYFS3Q=%bpTB^A62P@T6nA?ARi0N$ zcTsxar?PaRFs=qG*dPe1>*-{FYLDW;u7>AF=+LOTuY&TBy`lv4wB?goS63uw;BBZt zRxM*dng70(J;Z+{|4R5*!@uM^t*^NFzcTa{AtwEC^c4|xCGk?YtFiGBn1VTu%Mk)M z)JY7Dk-5ya?B}Z-r)ApEi~D8c)U{!vc(3Cl0p_IPn5EEt`BqIHWwOWokplLU%s>w4 zz3RY_;@S4b4-vRhb{5)-P+MC_o2=X%5$$p6*2Z7*lscoac}SnpC}IH#Ap<0+O0^vr zwsV^(sI9>`my#-?my7aO{!huwOHE)utK1V4R&rx6p+7U-R z_mHUWazq`c)N>(w=5nwj`hBIvqvW9aebK+xbxTR5e-$NO;vCW7_0hkM`&9=07eSZD zq5t=zcNI!#^cBnQ7qV+QATv-@sXGjHk2^lOkbJJ(6gsnk>q}$IA$UL%eX6N%{7){# zh}N%}0e=(rlBIA*<&cQ4sL>T-{zq>-mS|Y&p^bFyeoO726KGCWp>lJ^bm+%Y&SYec z!B*CyV^Pob-wYWR>Xn4O@;u=#L|63kq&GM2ko4y9FLTbkMR=O15OUg;7MkdTdD&_m zvJb=-TLH)>Sm0{NN+;;9C8h$1~`&Dx2*NDO#z_X6((R8@7~7wc%Uqhg;TB zgfF}O4t7Mo70PQ%^DwO(F_qJ=W(Ct`Rm@afc9bTo-PB=hltE}0T9)!wL|=#6i*Y5Y zQD8r|jCCashSqT9@W^SRecDkk0~%(}q)kHS{2X!HDcc}ryFn0>9T)$=y{#P=e9ggE zw6Xw!N}+DaElJd!OJM=lIAWe8Q8$LX&w#q&3U%uQb)BOJN1b@t*#VdaVsGb{`e3h_ zft{kT_hAv(Tl$KO)XNya+da}%kP&ENyvlnQ#w1j#u~mNyZ*_@Q z(WzUV6fXGFu~UuMjbx{yrOJ5;eGq+tN(CGAPm}!Ezqy;Xn_Y=p(YW7dHTy3-Gb?BP z)AF>2C?xaNSeAU4sJ=f@eV-P`G24->Dt=`&Di|$g*W$>(axE@vVw)j-v$`Sn61Npv z_RChzRIQidiNLxFDQt}>sDDEK{8gqz>A|N8y2YZ9qg+I@uuBAPLSfk5AAgdFRz8xY zIfC@KYkVlNC|9Wz)ZR$gwWD3IpV6gAl@+s z)c<2$|6J-fC2EJ#0jeSQ)jSA#&;d?TN=9^mss+(l^;ZD)pOqKU`z+o2sez?yeD{2x zSpg2s);^;24chYXzzq6!-F+OuyFKHnMV(GX-&;qB{YR#|W#%Uf8Y zH$2n4PTiL*HisSezT|_fAL{PY=jl1%lbfx66%eYO1a%H$-Nq6CHb2$hC9$Xrh1GtK zlW>1pq8osgX;cf~Ix01%y;AqNK)NZNlm7dhF8ftOboX;Cd>7t@0h)B7Z^O}Nu2(== zCO^KW$NAK}w@!e9^QT|%RFwgg&_JLBdI8FVtDM#Yd&1u~uXR9^eO%Bqp}10@8KOs_ z`W2tTJ{(RlMV)C9^-%Rb1ta1d0xVU+86utW+czL=bpQlf&W8p$;65 zT$?5l2M&r-jt`79!Q6Hdf;o*wWc|}kbX}DRtGPnrie3f<)Ukg)4xrX*K*>)MQ0qTE zpoV=UfGYV6fSQ%#0BZL&|960T*R25cJ>5hta2G-(0PkLtk7^JK+$<3_b*NS()^?MS36lbiFT1 z_kN!J8#Vx+w)fvJ^!OsAu8>P-@!X zK3k4lW-RLY2Uoun6cFw5TtXj=yjF zfo`PVLMq)oA)NjkIS!mUujvq;F~j`HK1sb4F6Tsug~!}66A#^?zAn1$-h3ZG+d z;mK-l+5Amc4j;0~>m?4{yhL>`R*oHs>Vrn`DB;+SI#J>Dez>vwpfFb1>Za?J4Q{Xn zD0BMOCO-s<75mTrd)w+w{qM^-h9PFPmrX4}SmoR54mPm()FGhb0PmJYDCL(9md-O+hGrszm1J=ei^;Mnt+66ATH2`Kh#}I%tzrCsO zY_erW_i=4j?)CB756aGLSdzFboVNnI)*Hh)4TsNfeyyK3y3=?cWrD{1|Ayi}ui(|irEnOf3syi?tw#3hMW z(5m|Q{X<-?|0Xcf?;2u4qrRpnH9Yk)-7?!hB|v;lhz^OT_I$Q)E|+!*s92MjEs-8Q z`^WPvXSG6_Ty^0PHanE~0|%?ZIyh9k>4r?&yZf>f?KwrDW}$gX&Sm!4bJ7L$v}x=v z0Ceg3B1FaH5P(CCxiasCJO-clxYJ@TJ=Mt=p}1%wYtvqmzF=z_x(<8b%ueVLw=>KF z`cy=n3~w%7+J33@S$ZK%DsfH`CDKzGV7LV!Og$pUCB~NUHrkeEn*HCG^?T1*cDT^1 z{*9f;J5v6{*z!~z}gIqfznlfTaqP#}_&Ro?V4D*h9d)=yO?B*n~*upOb9|{h5|#?FwkB znZBPt`WZgo5$&!^5<_|6EfQRKf#6so3@*X)10>=dF%LVXLL~Bh5!8StqgVHZBiGbz zes_q!jwqh4x;jXqKFj`2fneG%vFNiy|XaaUw@`=^|t0YdGpQk z!Y|GzBwu-9Au;*rdP%6p-g_>5;>n?A2V!Gur>Awl=add!O{@w%HT#_*{-!rJP3Ol~ z-YR3grJh|>JxYE(EHgE%Z2Lad?-GKmjiumpY*vfvx@ACboT&yxrs#kDbQeZ@*L-(F zVsRnUSU`1A9e<@jy*q>{$57r-R4%Kz60g=vPjf@{9uBQ|=A&1EOrDu#3^Oi`{!m8l z#)%I<7?JX~w1dSGz?OCx>ZKW+ZcLqS21CZwkQppDrs8#ensMDUSoA4XFu|t?_s|nd!NAO z_n(*29z39kS;$=KSfH=3#V^v;5YKVs^0$U>=%{0zRyrMsc+nIley%Bk7?hWD_bwpric(fA_mh!tP-yZph zSg1n?of}x-i42REDXCgL`sGmwo4~pn9*YDX^jg8KLc?v5z|G04dqjx-^wi+9?L}&! zdhm;i>ekEPx8|$Cn-zt^#Ye#l1qfVz6P9nz7;jdTa68Q>cxumbSyyM_;5zbQV$t}- zD^4s^hSfKmDSp|_ib-RSm7>yMSzmu=qE$N!=s-NefKVC)~rzOLT@KIt9F$+V}j`7 zzQL0ZX~(=)ST0#d{dS}ATS z6mLb@O@$r&khy`MSmF*^9;y$+xY~K|5&34uguqJmjqjdTRfpispI9_amawY&;hCr- zTTX>CvWf3-`x)-kbU<;Au z^P?@eLHKnEy)^TI=dKweT|~E5R4>OB)Or0=MG<^ z;-F%Jy#GT$;k)-e{vL|S2!xsXn(pJd{z6309ps3VJ@hIk;}Iw0!^yyE7L2=Gn1ww> zVWfb~g&e2}LoXv+n+D3xh|Fo6oR$|(lEqVw_$DOAhTs4zf&~iHfc-T7mCL$yO z&!@5yo$na6X<4sNT^d>K-UlL`cjRxbYi`$)_!WVnqSeS zWv<96lHnO686G}hT^&|!_kv+2phN%84eD}cWn-I6mh|L7qEZV`Eqg@2$>#*FZ7<7n z&0U{0XPfNNCF-Vf<=GypYXC3c3!cRPVPi^$bkVT^!mbz|;c!FGFC(j!_GHT&B$hs_YQ^V4%%ObT6ONU+-soDf0 zTr0dq4TtB;KDQ!rWm&X$=$-EX+CuB9!^FKE1_Q>Z6JZx9h0jGJ?;A}3ZQSfm&0U{0 zKR$H~_PkT`vIrSZ5y43zR6~n3r5}mwxqE0}lPCaYV#FXRbg`IvCwH1a}3| zk4G);nlCEQiU8XJ-#y=AB_Q+mt7$nedicxX7Z^#AxlLRs5UzBwC;NSC>bT|1$`w&e*JpvyEQig?O^joKb4diH3+W;6?Aa*vU@LXt1L6}9)! z`579f#%|HZr{x2+qedhV z9$_k1_+`M%d9XaXHi694Mj`v&Q`H1%yjL-1xV7%Nm!8)$^bCoNtWYY82646>9+BQA zGuOpAg4zTBQ?bl+83r?*=~en%X-nxlr61M8b_xO@V4*j#?e?!jMfusgx-Oz)x!H%w z8N1@FhL5jAJ*h$$ zP$;{HI;6nUU#004?mTN+Cp)i(!*hHUyU5RNp6$+K(vS8<@1npIaj)SDNMG*}Te*+6 z`NChC5;%1G=j%GDSM}n??a40g>T$YAvB>LEYOsO)I#~*!jJKxZn)?P{1zOW~cah6{ zR^+Cu&(mPCJrnwAYj?o!avcsl-h7i-0ZyC%K}YFJ#jnHMa-%JUGTWW~32_yCO^uAj zs&f-qpdj#i9#dhf<5HQ`lsVE6f+RZ zJ+3|5W!|J#B5Oh*&~tkQ0P@p`;y6Nrf==Kj9BwpBDmgb&&~U}&kuz#%VV^%vaT%7y zv!M~0Y64h)#T5A{@WI0En~rL5>NSQ;iufyv+e7u|*4POY7D_ucDb@npoa8HqHh5QK z@5)zn6>GUeV6jOK5L69wZla$88d(uGvAXG0N`FHwT2IBV{W}Cf<`A7@TROer;(iAP zbku{tZy1WEzZIUcrss%Lft&N<_QY-8#BCm8QjO!%1(m?Z1^6EP>Ci^rEc;#hgkV)@ z_SVVv0+woxKg&KtsbF$t-?9-D)ztOCi>qfD|F(`Y#=ps`ZTxnv{C(yzm6m;4eAy%X zWh{yKvdvD){&NBIV1}nk*FZda^_*8#w@+#e+0XDCZ7UI6M(=V%2h(ZKoZ}Vhm`rk3 z{gsJD9(xU129w>lXaY_%D?HXUS;bq88UAV6+lp&E1|fQDy!Ay^MM0>VaTVC7@oC*b zny9fSfx2iL=Ca{4P@pAB>{HaP#bc=MvcFQDttCwrtnGl3W{!W^I1medHDID znGf~CmM(oX?AH*aIB*pRIxR;gFjGqaO5jhvtGcS|{L&sk`R`N}r&?1!p(d{$lm1vc zSxU{pB>#o@rrxe`O&ar4%%r z>O^uX0v|5yL@ZJwPT<4aDK*q7Wy3#Z5o!smQg0)j=bIk~{!D$tQuW#3#eODPGLkbj zaE$ums=nO7F{y7@s7{r-KUpfQO1*8q$@_v8*{QmOjO>NUQjwe~fj?7MSMhtad6xDk z{0yGYRk3T6#louC+kz!LJLQGXi@g)}Hr#oBq9BqJeJ3k&kp5pHiv@El-x!S;Y~0~7 zzU@^OjN@hMM_c^>tEddR^+N~asiwrYaw{{LdCsiPQ0NXOFUgKO7qGKhp;&!PrA4<= zK%U4?ev301(jq^}udHg0{PZWi=Qev|A3b&5Hqo{3tgV`5*MLe`cW74KdNc{u_D8u) z7^2qTe>vg1E*`3>wr?M+Dw-H=Dn374LmN zTr2d_be}ZUvgYz#@}#b6`<`gna1AtB7`u_5O}WtLwoL`JZR9ghJmbaFD4tgF+$5em#B-l`9u?14@jNA- z!N_T(vv{V8r%^l?iD#8~ZWPa*hd_zGa?eDpHv(P<z{h~k0BwM80A~O{ z0fHt48hQc30RsTn0ImlN0SpHu0mw%Q2psVL^e-qlq({%tUcLKt5fs+N<$vnXw_muS z|78O%=eS}4;#LGG1yllTfR%u?fO`N=z=ME?0gnTo2K*85XTXbqR{%|bLx48`?*Kjk zd;<6!@Fn0oz&St%AOz{y2hbmICE!}X4S=D55r7mx8XyBO0gwyO(%zdiVIl%UhhBDh z#DJk!481aP=v6~?zpJJB8lBV6F%GuoH>d4!^a-EvL(q|7p4Hh>%qtvy&|?pK!|`6wt)W;z@9-|5 ze-VIH*_EN}3$@{=od5ECg#nBG*uu|n^t!smrqr)8iC>Yn!p_l1fvQfWY?~_+Q zsD4eb;E(OyCUY2;mdwv!xxh1Fz9NLYum~t7%faS>AaE5xI|!J^V;9Q;-+H*Aw~ZVxrSu8Hc!Nq(|k2nT4b^ZxgJO4WwH;@*17Y|GeP9~50VK< ze-Irt;;9^z>mM{aYzk&imD;Y)6;90Wuq9H3MO@^6&zE?WLt9|dA;;ZZ;fU_Q7ry)K zsTUS4$W=}m2ZS2q4W;^r`dAkVlQr9~5pU+4)~AG&K%9i3Xh*>>{Z^Io`!H&?h3(KQ0$vv}nbdlLsVY@8+#f;#RaZ61$T!WU;V{Eat z-}sEQcbBy;Ol-UjrM?N-a*9i%GSY|DHH4EQBC>&`^i|B5O)c*BI5REnD{^Ocxwa}QKK%ezb&f1BDfm9iMdyqNL8 zQp*7@0BTXc9fi@3?+i+&pLv-ExZai2VA^lCS!H<629P9q+8g^*u<;;RAq8*J*oZ9c zZaj;u3(jY|f$GGeB*(4V_%=)%w??C!ccjec)-H-&(jlB&nASWUgV16lUWgYS+Jy_! z2`uC7`(nC`RJm%$b1V;Gm%zJ*=P7H%Yfr96Ogu%dE~*YThuqje;oBj0IR1{}gI3Kk zZV`k-Zx#JOaMJ0vevUKv`lI*Dk4r|(at(|T$zmTzJu&*_OPH7h4OlXNmP?U-u=c=e zPDEP>=L4cWhBqOI)sn2hGgKAFyE&&Tl(Q77@9tV zqh;8eliPZ;S|X^D^j5mK4jAhqxMz04fdF({L1%}xgTXkwE!uBZ$AB$puIw1ZA8WgN zIw!vaWqm>=KoYORfl+asNJrZ-VbuFW=+kW9m$mLtO^E$m{F;!L;z8VyfX6U-3?pdI64mIcyU|mI z$a6^ogfLeyyWhz@b6_rGBxKN}9q8zA*fU+v?&d|}rtKhLL>WNSs z7(xNKMy~g0Cl|Ea2=V18R*Cl#V1=?TpfG+?6HM& zP2-)QE9&z@FK@2rw_NWmUlpA8&ho__%OV{)5x6>HO(;v*ZDEf6I8lL(@O}tvXrOY} zG^Oob6?SKj(O9v>LDipce0BPgKz;kE?dSWP&8yI!nJ0R? z?O+#gUqRc*jcNzuFPrE+dt80?w1%8%vA3%B=1ijl4naPL8N#85&tal)2=+NJh`qH{ zV=xo8XhjvPMm)cY3T$tkY;PQX=3mBZ4%t7+w(l7}of{kL+1$SH167KWeaN$NxWxC2 z@Luwuy2hHTpJn*)IxC#=^IVJH7bV1PA_Y!SeuY>(tDu&I*Uf+v%58+F$>30imdIex z7|lJwMjj+uhSNtgQIwIAJ;lhf?J}e~PG|P9_8iCl&xR{gnW?fj{2FwNYS*ZWb4{q& z<`p~4-0RK4ZtEc9Y}W&j9R6a@&L^qjryKJ?GoMv^#J|KytJr8RMx^syuYD#mH_N^+ z*8MO<`W>(d*5~RP*(+*pZIu}JEfr~E?UnC3i{SxFqZlSVtEd?Mr8?H6I;N)MF24R4 z|M$X3B!s6o!*N2j&-B?iFChlZVQAuDaeXApgIFIrEtsg*zWJ^vB-?e3cYfh1B4_p} zNJm^!w}yMkeR*C$4$kE5Wzc2H2jms(_#XF*uba;0)G1LlS`+jQ%fP-eza~LUZO|j! z3Ocb89yaWsvu0xvN!vfbI{a0RHZ*OXtpObO6lSy#nb;8Mhm$+F9~|`D5O@=v{cTP9 z@jm&7)~<;}(b$0$#*pEP@E7wV9L?CVltw7^V27umAoT+uIm4>AXN5V@`a{DGzUx zix=1rP7p(N*B`~Xjo|RVHJ9x9B(!9YE3~9f^K6_x4tMbwIP!Le(RKP`3gbroGII<^ zqkjqW%nAZr^*He$nip%oJo8xiy52iYmr%!Wo^`~bw6ikMXSzT$y@A4K=b0BV)Dt39 z*g`}}4B@zg_dJ?0J9hAJ&Aqq=w)Rfs>};I0&Waj@UGOErFhn(AFrx#mmze!O57p7oFEvoIv%y;9oe zU&f#{76%d|jZ1zM?FjsNdJ%266*0HDzcPwI$Xur z;eG&*?#8F=-+$)*^lG#dTypS?P|;e9fnwo*Ye!jws@-oZQ%z)60F>-JD3$9OvrpSM zfK9D+2of!l0%qsAo`4HpFlo<+d6sD52cpk=ypf9)Bynb@&T=eGcP5WWvy5GK{n`U; ze%Ag+!Egbm0Fp3Ow<+8C>GX2xZai;m8PE!trAmk}MR{8^k(Z6v=@zI&MctRE$E79WaGVIJ!;RX zR-6~##d-!1~R+y#GV>rs=9hY4ht??1q_ZXAJ(25ZsTd-2vnyIU<0w!91x!E zO8dg{s)qyLkGeN3&Y96Nw%78VYk!1-VRw#M6}q-#FO0Z~|J6N??;LN~UGd@Djs!lu z=aayobC-Way~4Ns7!cGEboTQ1v(vCKQT>v;0urDCPBy&dw;>@KLfo2;<1ro9n^-yx z_l6DH^Y36Gby;6$#%p6koEe9f1vxXGMWdM0=gqV;)yL;8bnF!kE!Gc^UWTMYNG?jo z0U&f_NFe$CHMT{f#OOjCwuva&7_=WV7yI)_M5NKcpdYaj6m6@-F_^UX!$qA4|J#HB zT$PAkZl-9Pu>Wjb4#z-07ZWaQzXLBdD);ugsIHAUeOmDSF57YDKXi*(KTCnGYFh+$ zykha~zOln>Jq~0mYFp6yY$a3fv8TOn5ht|Vc{0J|wrpq zP0DdC+x4UlY-8@iKnt?-DOvSNjqi9H6$we(e@T#eHwR;Mz#P#Q@|qEugVmTVI#-bO zFmKa2fb8$65fIQQU62Uj9KFF&~wj&bE^6af~Bc}-~tR>fl9UB@_-jBTNyU@>YEo6*Q3^ARyXfHN9) z?APZzzMYMQ1~LBht~Q|1;$=R^ir};h;o}b)XLKaETEur-m`TCIE-=B6Sa>bn*B(c( z3aU8%j5p;l9jLzET!(=~zH7FeFK8`k1X{ilF_ejHa^%P3=CM6_&f8+gu{*Jhblyd5 z%ya$QB?c{!HrTvXurYYq%wwBMK7%}bu5nC_54tUR*wkb>44;jmcO_Dy)(}+l2p%pEtc5(&dA(q`pEcoFt()C*YVzc@RZlveA4q=r5 zO#>@k4jR=H*hJ4(>a&AE42mA0_`QdM8|Y`MzmQ`=oj9={tF_^$4CtS|_0L(p9drg>PySjGOl@~_XE(hws8;149FfCA<>E0<TxC{`MM!VoSzlV2UlS-e_nWS9>nq7Gh%XOW5+(S4E2ch1EbiBNQ=fcG8!QmC0#ugxgP` z@yTcZ=*Wy^2~&dhU}Y>~*iTp{i9`W1I0wcIJ7?{`v3BgBzSiD$oJA5T*pAtAFWlOT zWOuY6oejc21pYD)+EEYF&V<|T;P1={4BDG^Fx3Db8oA$jIP~HsaIn|&&P&X=QfBjmXfT{oWE-qJO%9jr;6Q?iforjeZyxGbUlsN@$ zPFRPoa5VDM?Z)0%Q6iVEMYce~$hdF0rEB{c^m*{E@Y43&G z`97oPeU{AFVLt`!6={NWh-5naV3pDIPliCb%yfxluo$!?u=I4U2@3iF8xtLj{8>c) zG$May*f;jC0?~p+a@dgX4DOAwq%-(xqw7X@$cSJX1EvzH@uz0qJ=TY%&6wkBbxlcT| ziRX6l+##Mj#dDW8$} zr`ZhFn}@9qt6!}X_yz>#w0(lR!t1kJ9r#C!al(;(Vr^=XrM%TTM7t8gk$tSmVRC6Y zbydf88>V}X+!OVosO54KnmtHDLb_YZ?o``6={{#{K8kH17L!#T9!4njDAi`_9@=oc%VuKaSAipKr&1 z68`eH*IjdH?GK95nqe7>4+1ql+~oL2_{(2J>~Tk|{dY&lb;??I5?a;OSDpD6&d!EE z*JKU-ED$W9zPoZ!@@_lmU9WzCgCMMY`^T z-RLR~mjxmS_XfBYz=D?-mME?k@T_Z`i*C5dwH&S};DTbQH1~8B!qE05h+T7Fa^>Lx z3!>&4Bjnhwvab#cuw9PtyPb)c_5?~0RmiCBR;OT-~%Ot6C1&CzqIs5xR)8O z4R>yeVq80JErKXky!Wu6DIawgohAB;09&EUjwv0|^G3{22TM&@*ilz(;pUB0EJs?c z!x9_Y;xSXG?@N8_`>q$y4dO|{`o5dQb2FX?LMV@?_7^lQ32_aBjKg^GGlp$LCRZcT z5;f%h3ge~G9LE}L3!WGYw3h4c24gm+yHj53gBGP^^m^p7!FDqW&P(*ezR7ggU$GiF zm$i9_y(UUob-mU$V6)^pv?dO04gA z8ed|Oy2q1lOllsiU5$aoWo#P?&=?ZW1)e)sOJDmqrb!3fZ9QE>;M3Mere!#(&K#S2 zP2-uC8?r-1W-xTKs8V!3HW3*&7;!_U&0uxm-lcheCDzSaKr|P1Ye`R+n@tY(x3{9a znYU5afQ@MS%?4C86fhXY8bg2cOg{%}j=2gBSa`CMdIQZ|V4@Av& zCh{($UYPRhL$$B5i!ZhFo9ra0U(11B$AC=;a@ztoE&*m?&!7DoSz#YtutN8BvtkRk z_v>gIx}?}u1fscZC+#O zT93Jo%CLYSAQK1yuR_i_j)*i6EIbU_qCAnNFihxB={1hEaL^J1I)IxaJIwA&V^sfQy7kthtXCGICm|$eOAHD}eXkoYQ z3PLCpyIfZdo0L>OZpFjA3k{cf2OcgHDhaEcZK&&yU*=?H0tYD|;xdl!7(Vf)mT zhttk;Q8+tSU=++Fi1}e8gPFtzg@*JroZ zuRbqRBha?gbvvTu%EdIp^%NfVXpu^SdNh<587dW?i}lhoA6;|$hBNiH6P^gN@?wJ9 zscCVnK7miRxqioZHaTo3Tq7WaRhcu*!x*h?q`o7eFRs1EK7IJ;an$rq-jw_=a1{B~ z5r83oN1!#tvB1!lfCtv%P#_LbB1Uv4xRv-6EYkT=?$UUiH+Y@j!uzo%>t(oOVyl&?m)Z8@;qB5;*AfIp>kwk^2(nF-tjGIUTT`S{mQbQC3ila1aC<|% zIrw(e9+ucJGI5JhIVR>N7e|7g)asg0vWjg-(*)H-MG&3m5y{6>$1s8jlU*Qs*AUkv zB%)6iKCZ-F42=Z&HK2?&nSE~+4CY?JN@K?W1B__c@Irt=R7KpVgC(XVS7Ox`PG|~4 zmITgyedtpyv)P#)=5s(ZpV#i=Go_Kwal7#>*)YH*P~YPMUE3f~$S#4RcM3Fo2hf)L zLt24wZ04z>0tY{J4A_|+`qT;9*BwW}J{Y3H9H0a!PtfzJ<0uCQLt5~p;s-;H!o+%d zFr-y8EeW2pv{5-&djfV=IB9o?5o-OEkh8GwX;mD3t!!OkH}635x>}J;hu8=$33V}) zmI?zqr2?+W`hPnWP9PP2S=^yy{dz`UMlD1{O+UUyH0{wGLgQMtD0|)^!SS@uHa-}>+?sAIPpfH#v{nFp#2FZguOZNUB}c2vrVd z1n)wXIv5a)cNNG6OFPcoQ?Bxt`D8 z)baUfEuSx};`5anJWDqGW}86H%>q4Y6=+?RKu?zm^qd)J%l+%N06DUQ>(_09!AZkb z81MlLR9H4xj`Z_OjP>g_3g7M2K~eU!V(MQnTHG^l zF02<#ElxC_g?iW)fD~zVaUh0y-A|!lG%?s78ijR4*T;X;Y=N*fHH7i1q`drBcl;D+ z_&XnI-&wAj?YJ48aBs&OS{L}9{Xj5YLUeN%6|}oZ%g2AC?T8M=@=!-~n0Ok*GeSHg z#dDx|s^S?Xo-yJXE1vPn)CvP$6h<1r*vv@X&=Xvo|FhcBz4i?W) z@eC7BgLp=WXQX(Z74)a@lvC>Iat$P4Rms}bp;&@!wDyAG1sH5I!=GP(CiuL){g&-( zt+zZIjPh^m*>E3beTSN!4Mc&n-t=rJN(~%;EgcOQ+QEhvjwC8p>uvoS?iaR(jRIk! z;%j3zZp4~OsB?UnWAe$QX4eILXsf||Rgl5G4_ys98hp3g-EhMN8V?`V5xYSACW#FJ zApWf+*67!FLw^GFPmq2SnJ?DSA9t@ugMk*`tqkfa=NQ73GTb&aDDDy7a6%t{l}>^5 zW|E%h2sF5ZC8 z>A&hm_}6vo{}}xT(!aQiKjp8X|8e?fb@8YC+vs0)E&QXp^}m_^#|FbcxQjpa8$|y| zhUZwsUw`ty&<6it5r66L$Zp4VEV%m%_1kW0zkuf1JqP(EJ1Jf2vOqL4w0Z%lf42;c z_s_ZsAK6{_c7(5G_#Ib6yPQjfKXy$w@>zP*8NQaW@8JpW%suHot9C6{B=1l178VB1 zCy^NV;fsOp8t}*WR@_wrH@eNh!c!OBI)s1o3p8(&&%<;OrUn-kcX~OA5a74b9V7T` zcdHuTNR6+dE8lzhBllP9$sANApGElR?smYe3r5s|7w;=-z&{jL%jL zZwBrZpL@%s+zW0`lup6aFy{s^dl@r285um8E_1qupS%M~@9_qHk%2b`BJgT`U@d%P zH{o|9{914LqEs&2{y%TqkrxX9)-ro$o*&fMefiJ%u2 zU4F8i2_SiB6vz*H$n{Mg>&HjfWNoNoZfuhz%#Y1zx0o*NkzM(+(ml&S+nAJNLJB1({GDzKNN*q^5Lg2R~(SG+`kgI(LKXQa*rU{ z?!HAM8Kseo5G0GprC1{wsF6fyBw>O?2+Gh%&WY$9bAKanquWg^bs40QysMEM(MS&X zNPfg8J{g0*Y9xQuNS^YM9MecPY9#A5l3E|h3mQqeMq<)P7WhaW&`2i3sdFyI%3R#Z z^TMKmd$?Y0RsgL9_jU-#X@=k!mN z=^uZ>lm0it)DU|;(*NWs|Mbrda~}f-M|Oj*pKm}vztbI$OlhL@7rTm>yc4yly; z128qHA>hBkpTDR7=cy4nemH60v#2Bb1g8%CM%bRnj_7t+P?0;LkHgR@E|WQOTukNRn|7a#LN9W!<)HD6@B9)6s*rEw z<>b4|-P&K`P{F_1tqQ)|-DZK0xzh#RF?T9(=UCK2&J)<0l-1$4Ff~L_;(|^Royd>P zkc08CeNV9a6*SetXmJ0 zjn_Y`i$8nx+r0i!(%*^kXtT(d!)~L9$3Azef%$aIy%X-80BA!=xU}bm|-a8o&v#+byYBm>W+Oj^wqRpV2Z7QFDQkqyC6%)N(xvlSqa4pgffxj zn(t3bH=}EMCuJB2_Y3Njd5sC}tR)Al=m&-1UjEFa;9#9#mq3fh z2ys2O(vEt-sL~ zYF!Y%Lao2TSC?v1t>@ntsV3BVKtw^Pb&o%-RO>F%>NrTX$|MJ&R(l_>S}8uY?toEC z&g(R_7W>p1=r2mDb)`XOpG^u!*!f z4l=rHbP5Fe6qukXaKFG?-1ms4okHDX{h3QeM(Gr}#-|AN*NK#h^yyZS@89*M#EMX+ zyJ#AZPyw;w;r=UJb+gAAplMUuQG8n$R+TmU*Xhlc8 z2u7U<*-_6Ut#l1y<4RdZSGM4Q#`SPdZ?^668GAq~b8srKmZ{&t*Xf+XG~CVxGk1%7 zr)Z%>vVP{TrcBn>cSNFy{M|>6a)9-cKdnsGU8L11FZ1^?$zcwq4)&?Hn7Tl{Klz+y z`qb;CsmJzLYg}^tMN0KDbm~R>)VtcBR;o9kTlG4Q`f}agLng{2qTvvU(js&^#y-*n zUtR2^0*x}bEdPR*!rTwTsH2q%Y$C1BRV)8G1p#b0`VvMRt;{ReTOtKS^*k(SMHcVz zrgM4jdbnBSfDBg2trZZPQzErAXb<<-NyarsN*79p3H?P}1N>=aT+hGh)n7L_`VU<(wW6j=gr-8;YY?nU zEh+6~Nh_p1CNvY$9`vV`((db4T8&QH`9P6GOqbh5%h<{?n&>GGmSh&9qbi@4`l2(-2uSDF0>Yu`><12IbBb=qw=Pm_c{zq5O{7>jBW{{tV zIwmUCL(D&IR*1!Qxq{0g@DoY`6V-%ZO&*Hi@H*eT@keHMj@~;|&)^JZlVlL3r@$2m zJ`rDQ!`DDk=wc*mcbL>&%vRb#;dF{({uzuqTDlost#62U#&Zs^Ptb~-eF;X7>Z)Gy z$4+N6>(}}KUF6dBk$a8XH>E!B=vvH|!|Sl$udEfb<~z}VI-Q~9GG0f9BLjY99Y_MF zGo1->4asz>%YqgyRTopJUPASB7KjTV*3r_<=sJhj(U6^D3iTlfkVT|@%b%8RM%U|- zR!pJ(ENFMKd_Ln(OE;tI2}vuaQ0oM(s6=zLkIiZ>0T6>Lb$ zbe4(K@gvBv_NtOb7adt2Z=zV~+LP@pv)bs2kjjV}y%}<}rsP8yS>U3}ZAVR}$RU3N%LscgYE3cZCc z>-=e@F13tbghB-uok!-OA=@wrFiQ3Ur4FzMJWq z;onHfLE`yCBIaV))+*w*m1*~mFt*Ux+dmk+jjsI+ri;8B;{8c76OBxgP)C%N8W=k* z=H5$1tPt%=tosT zxd49jeGv<1Zlt>gVw|~(yGr0iZnMA>-HQZ{ch3Rt#Ch@-u-${?blh8q&JmVfur#D{ zr}YlWStgNgKY&Z=_mN8MC(rcAKjuDrj`ADb?ZBPoa35tZ<%n&?X$-!e+lm7;Av*rt zPmd#k*pE2%FA=}4`&>KR>mf(%bKR*4UM}!4Y}cb|abwJT;Gp3rR`A&jAUrS>k|$#t z#5m|?dURHqMiQLa^@a&hf1K3=9u#H1iUcPG zZ90vgz^J38o6&XrfPX<}AfxL5U7fjG-MOfpo%Fjm3?xu7NT|q7m~)WVaGd9~UOT2M z+9I4YS9Q+;6KAf$tpGc+asQV0d{7Ja4IABOQ3yqPju3>H|8$i)nFl^LLXgbJyD4%P zH~6Obi*)7=bi2+_UYL6$up|4hE;|k*JC3@W;g0NphgXrq*v>fW{u4bqMR#&D1Yaox zGp>Cpcn$@>jfEEO{}j7xguIz5h7meLW&t2|R_#-_V-5-O1_&wlGq9*SG z_jDnL`*0&ECya8=W93EA$_LYPvtSeEP8aev+grV*cr!}zQFjg8A#aMd!andl%BFcAR$velj$6$L*Ev$_5$sJ6 z%i?>G!D_|%FBwcGwg~Ui+S3+y%PD4GtGgMvGe4@amRzFPnoaO>VRpN^p=Wka_sXBZ zkf*rq+SeOWs-SBRxd%LmgyQ_t39Pn&#NlN1cSnE6A}*Ss_Yqq*a@9iH_3kACZ*@P( zkr}l4m9RIuFB7Hqn0vCYZ+DL;b{=LYzFd&5caMNmr-@=WsvUez>e%X-0$aUW$Qk6*x{$-OSB>z;Qf1^9T8#$X0=wSqs<>r0{ zSD53oG&u}^_FI|K?ZBP&+>G#Zz2T=Zd?=4~4Dg0`xkI~YZdtyrC-qSk)SqL;MJ9?P z`>3wHIV#R~)xo4cXs zagw#9KSA~rbo60v*EcK&Cv|$Bgq|ndYIk~a*FBYtvMBfh7EfeEwI}-#$*=Rd))Vep z$mtSq@^dwUUk&*^-_S|HtSb8Vmp8ZvEHLe-6WENq=W< zySw>omWLDWs`jqsft7j^1C1rq@XqQXy|=hyLRhahxQ$wk-3&hDrwI4$qJLq{t`+W$ z?wW4ou)X`_HBY&H52l7KOpr%g{1cc9gWH2OH(lg8K74|q6h>~bH9d9;6XO_v zNy74S>`Ll?+}#2!vawU3Net9JobmVa^<+BF|8DLk9Mi2o!|i6c_ptmTLogkf-ks&J ze17t(H~ug+%xB0)y&<*m&K9$0vfE&Yg7qGhNZVx`k5|eIFA} z$K3DwoE(}{lh3I}bK2>1GHFha`kZFK$+0d5l0-$73w*R<_7X+;-*nS#-%B4P?cJ|< zl6DnL4U1Tq$72Zy=QFo3>(j&OGVx}N&RQic(qV(7meYsZHYVHT^M&==o{VFYT@>4bgz((UA%XMS%(qk26r~-Fv5g~cZ6Ao5oV)1mL41& zysdFqDLF*rmDGMi3UYf#5^iv>B7t)i<9NNK z)z*A9&UeCxdAH3I;8tIN_Af+av}IC{DnH2p7EgeyeF0wc1Q08Uo&aYU0OKi705_?m zO80pJs9cHj1o&7==4SXwD^lK z8{DP9ol6P}0ZPgnFL_GJewZ4zj6g|oU`auci-*&>jiqhfROl$H-ap(dx}xfZcf*QBL#cE%dvIO0m`55fa#+8NuolH`fA@EmpkH&8De$7SN`%N$eB+Z%7dB~1s;+b>+V zJl63IK0;@d0uF|dElM|Dk0w`XA`6;ux5I#TyikVmeBVgCEo?s*T0P1!AQ(nxST0uW z6~|uOF-Iv^IE?4*du|xE*WP&Ee)hU$J#hRMA*au9{pUpy6?~>PBmx1Y1aiYIflA3! zWY$E-|NUJe1?G$lQp|u=fc1di0iFl!1H23P0-%fvQX&910n!0e0lxy20IYyoz(&9` zfWHA+XwD2$HUMe>^8ph9Hv#$q&W{gLT!3SMM!;VH&j9WNlmMmzMgn$&mcOX9Af*DZ z3-AMA%-A4h4WJQFGbTv+58x}v3{4GEVgXYCWq|E~6M!2>LkGYEfb)Qnp#L3!KlSDy zr3c`~aY0HdzzA3Zs0BO(*b4XqU>D$3Kr`SifD6E1rJ~eg^Q;zSwiefI)Zzk!T9m(9 zTrg9M`>bj)tf#s;?j{I(q3(u!pTWu?Wc#adYkA&YJ0rV1(ia2jf;t*VE08RXJk|tIysw}gW zSAbMe3#@7x?nj_G^t6x99G!W)&U{v9{>Ope9lY{F)gO&h8>$Ky$Up@cl!Cp>q&>7e+YK2^vpi;w*@OM8++HEVUK`) zRQ`{-BR3iL{uB0ruut1OF}3oGhc^BPc1{rvj=3xPpX;peDHz7Y!hY(vul@G*H}2kg z4eZIVf41YMD>wH}{yGKrv9RCwmz0}+T%7tBBkWnQr`>Ykt&fj3*DZy83hXbx{`S>F zvOe6u4)&R_KXfSi??)bA`XJ9r%!j@1>B7}fWjj828TMk>Prtvg@0^l9KJ^alWw77) zP18pYp8fZCnCx^|VaIzLf7t%q3oqd4T*oTd9g|m;g)A(*CmQxT*z@A|BrodNedre0 zH^4sY#+QG+Vcpb+eg*p`*k4&Qv?^-(xsR=|Z-M>(hR+%vf92z6?uUIF?3-3w*T1pw z*|X2Wz616*9@&2XgH>5C?Sp+6>`MoPzjn4g^xltQ-wpfU5)w?aqIbT24)%Sp@3?bi z%YlF2@^A#qhXFm(hTXK!F#kjx%trxlKVT_+I4bH7lVCm$==TrB{_P9*{8R+<2|(M` z{U0a}{cQIdm`?%@uQyiqI=p+s!!Vx$#G0=9_na$VeDkj`p9i2w;+MV!bEgipf#PR) z`l!nVI|qDmGT3&)U5b)OlJrAus^4rE#n4k@JRdOU^J|`oEzNzPFFJu@ z*uQXAU0wWD!23gBFN1yZ@~?mQ@Kfbaj)$FfeBIL3yH?ip`gT6-t6*Oq`QeHuPR!i3 z9QHcc_wIjW-UD~fyPIFT*Z}*u1rIgv&r50MWeuBP|LfM@{xE;nO}{w^`xe+gUAXn( zS02y#=o8qt!5+O~^asg%A9?yb>^oqe|M=#os(*Lj^cCo!cENt+Rrk!i*N(rK0Q+v( z|D7@T+bh32zkUkr`(S^%X8zlc98nLKzIbbj8O|JwdH*iXPdHtYA>{$VYF7T?zh zdot`94I8#RdS%kl_hBCk`?J@4y6b@tu6pcy*t1|i_D!In7VYfJ(m=yhz{VK?hOKh~ z3=?6FN839dcr0KfplW%5L0J)Ch==(mKonrkxA@-TA7y{F2=Oyc%bN+LRGTceGAqj8 zj2Y8rz+P@ytW;E5Rg0~vs9|mRU$T^Eu5%@66q`=USvb>TArb$253xWIb{Wl6`RFW?cur1R9#xK zLO3&TN|};1)_SOh=Qup5zu>**0;}^F-eK0{#I+% zm=Po5rKq5S*$%O4mCa&aSXfZBG-jx}ywqw|5nS(IS!Jp&uvS*jTOhp)JmtbO zWblO2cotQg3alozvT%v1$f}l9SC*srlv+zM*0{q|jS+wany)$&XlO+_ZU<~dIbXS2 zQC4E$Iv9hOSb;INSOB28aD~-mfzLeDC1|$*1cNQMLY76+SJ=wRsPjq>A?Oyw`Y7l} z8i-KF7MEIz3aX1u#WyRXMuVB#ulSSui>!rC@ZSDKWV?DPd$uLej{@ z!ji-UH7PD3AudsoF+zFacnrK`^+36;tSmDXRG|D;6riXpUJ*hr%hZ2K&kBsZs!NN! zV=yg2=i8#=lWWj+3hc3=?Nj67>Knh!nwc{^Rb?V^gjHHmY^qVEe^F(5RhbFyl0+(; zrj`|0tb$&0nr|y9DM3B>`n6YdV?t5&t12zXIWa;d$N%qs0iAxmI{kY7mtSaT;3a>( zgLJ>(prAfIdh`eh_5JXz-=L7tp3ow=M<-RJRS^h_X_{}Kz<~i0YmPdb$K8V^MNmMH zMk;Y~e2siQN-XJ%#RFlUybzExKHhWM->3r*G4l_-DBsBg8;D~2jS zJNku7@wGs zn3$N9n4FlBI5KfmVrt^(r1+$Sq{O78q~xTOq>)LZl2VgKC&wozBqt^(B_}7RB#%rU zm7JP9Iwd|OAtf;-DJ3~2C1qsFsFc){(IewWCX7rRnKUwaWXi~qBS)e3jUE+0Dq&RO zsH9QJqf$nV95rfG>ZsAF@u>-^iK$7c$*C!+BU4AErlyV_4HZX2{AjQqjbNkc^uO`= z|5bie6;z{Zk%do~wSrh_HJhq6GiIM+a%x#oVJ)bU#45`1ax*$^m7zGr#aIfUHCpIe zipTI{jkXF?O%=+!saS=d&pdY4u;{qtIX8oiw=Ek#EjLXqN5`ucn$UD+=io7ZJRdn~ zX^FbHbeW0Wzn8wO)QTRjOc`sd@O511FF2P*_a!|+FXq@f2lg>KtJXjGT+qy;FYwui z%p0N%SqWGGSPd8hh#jJg)g>MyVyY zi;**)FzC&=LV>(2wTPloREF*eMP$fLBdAyxI$10P#fTwHtRXiI@kARTJoK_#9MgsE z?Y2rQhF)FhhRnAO@s_8O%CfS`CpDMP)nF=4P(g~YK|s`G91m#NYYle<8mlH75H~{E~hlzhv;6HuK{8CI3QxDd0DC`o;OB{6c;s z)tHRTTQAOUKF1$1;4zDtCz|~V16N(cCMEfDSxBYn2DFjj?sc2 z8|9zLFGG!)l5=tWG6X+%Mn99^cr_;f68eq*xqLF!n4C-Kl_~Tp`T2TfsWF)|E*{S; z!4JdIpQ~Rs+kuPo%l^51a=>TO#rfp?Og;%{=dz|`UzAb8FKp)$(9UILCS07~FKp)$ z(9UILCSIK1FKp)$(9UILCS9E0FKp)$(9UILCSRQ2FKp)$(9TWGxOlsi@C)0y1hjK` zbEjThzhBtSC7_+lo;m*F{C;6OmwDN8A`m)dymT~i}Yhp32pxUH* zyeyoQ6_=Kjn79&+eNN0`vAc-{Wc;}H;vEIc2}Z11h^7 z0mQFq`0-A5k2~>39UbjkJK167uU0&Ph@}j)wXPT&5ApGYf!_upP$Xmc%#^OjXl${H74R%S~h|6CMUqmCFjSI$5obQ#05O#9^PvTFE4jvy@?zBM!-2hA?r}mCG%$YW&bxL2Ip!vtaWk z#>+XT05t{NP|K^;q6Dxjh$}Lcm09LliWcC!K_XlVbuJYoDN2Y#dy-+rjuvX6DP{;a zt%eMZRVzqdlz0(etDb%0f{RhVU zK}@^S3>})Lt{y7NF!}^Fh6!7WWQk7$8jCE8lLt>=9X4#JiYzLqszNc##6n$hjJ0}R z=>l-TQd-RQ$mZqPfv8+AYfQraphJWUH!Sy5Jr z6EEXbPi~`amc^zJj>VNl;*1KjA-l}P#h~#kCKbmFu@(*)nntb5K{Wz$V&F%QbR~D z!g}2@R1p&vC#_kgk^*eo#tcncy*jOP&Em2oJjNp4q6(sQ#8+{>`vbE5W}W6QbIg28 zWr=nEvZAV`i{}^Gkb%}}lgTo_9H)pXD=nt^mg=JUqpzH$@2(2fD{s76y*FsxANi-vuS$|ueO0kncT ze2R9eY2wV8Ms_5|Su>Ty_+;T>Wn7A0q0TUIXIaXdl&hloELH?9_TAJ>b0yj}L7$6}QL$a_vn=?&H|`YF}TW*i=0eTSVS6g zV;u6So0@@C2G`Oek+gc|njY#*^f?%YF;7cScerGtOpx7@BJVGa zn*qhhj}@Y2n27^#Q&_bSUgJF+d)%sU=Rr99W>sjX8Ia<+NT~vKCOR1}JzQt-Fq?>2 z9yGR;l#0k?iyZ|S1x>6nX-GMi*sZ35s!E}AZmC5kJ-K+(xJs5pa#6F94)Pqtl**!| zOkv4O%`&mgG*uKaFK1OPt}ZAx`63NBzj%|mmO+eLZb1dJU1TZ?lx9>Xq7-8Il2d9b zE4Ii~5UGQ7u0R1{Pn?NWa%ejnTl*XcqoiXoo`(ZbHkKiEh1Mto>J-yrl=vB@0-XJz zjLfo978NbLC$>CWfDuy#Nb^k981Jjua*I)vjY-^aW(h;YSq>*)se3tg%Snp5pz35Q znxD69u>Pz07(i2N7PLHhD1~X5Ur=3I0A(`D&>&AWEyl43aJE1f3wEzdi-e|mm=I`Z zFQ!yhRw2c$Vv7iIA-n7v3~8|Q534fOWL;iaz4W3b*W<6Hqen+?Tt!=z5SO4v7o*MV z+dJ9U6C0(GY~ryvlk0y3&XQ3aC8Q38T1fw7W+M8dPm3IB-#qJ zsil>LHq3TRoGP(h9ey(&)H{0a@aXd46gqKCbnckwJi1w=7Gt7|$3%m}ylBe;nBmSR z{LM9EE&)n(ah&I05u*ijC68z*wqhO`NsSYKV^Wgfq`A#Y#CGeL&SfHhT!VXWIZ9<-%Eni%{unId_LbvFm=pvO5`KK(mSjK>zI738J`R!IKu{cu%_vNtAjYKLqvV-Tz z6_xhr_(UF7QB~S}KOC#}eL_oZlXO)Q)kBYJsl+~az!JY(v&cQr>d1WS36{@W4Wklj{^HK3GlQ1 zk1eOpb>gdWTBVbh-0xj6w!^yv3O~}e`G-Jd^S=WxDYF4D(KiD2A#HYhdByvM**qi2 za1!SlJ&xpf9`;b2Rg41Aoiv`P$})mexo~GV7yL-WpWZKB3rlx~HR|}$Z4n?C5R(^V zDAUn~z-<-mn*cij?SPi=0u3ht@DlV4$Ddvvd8`62B#rp#{SL!Va~3j>!`>b5287qA zDCHOdVVZ36Ape1|#{wn-YBfK7SiNk9Z3o}((yv$xOTLtAg7an-% z(?K7G{w<&@*6|VXhrL$Ei!|M)n=TxAZ3o>>og4G05%v~QySD@Myt^3=;u)%@wsUkrHH>f9)QBkWrM9{Hr{ zPQE@Id2I#VVI41~&2iXIcH&36?$Smdmi*?Rz7NDZAT;wwezCBR1?cr7U3dKSVHv&{ za?3jLTLt@uPW(vM9Y1|o@{5Gr-JSRyhW&Uaex&P;pFS-49f#a7oYSM3KT-c-j|J%C zOSBBO=<{;c6ji2!%?s)0LkykV5V$e6z%pb#~!k(_V(O-Drp)cBZ^0abvFfSrJ^0C9H)Diwf70UrWVY6F#Wz;l2v0mJcP!WzJH zfEGZ=UEl>+0H_5#2WSDL*ufX@V_l%K^6o(8Bfx}v0+pu$eb)yn8vy$O?EphPcmRxm z-vHhLDE9^`uRk8B4A>f|JP7y_pgs|(%mUN`LZ1v&wgUz|6{!3c5VQ??145q;RHg!+ z0elD;{tR>jYy@2Kd(Z%$1GEALYzIGp6L19Z6JYqWfyy$#0YE#z@CWDyFacf%^!+33 z02AO*!0UkM=K__bfX@Lb&j%`h0UX~MsEqt`pz=5%5_uI^8(15L@kKGrRlqU8AtM8o zbimG0fl3bE!mS0g1Fl(ucmZwz+z3bjqyiQI3IV0IKxGAB`?5gg1YrL1K;=!qQNVkE z_W{?TEDQng^lOi~fy$y%mumMJ1R# z;iKuIPmIf)F*9mTW(I@R!7Yk3(rp&q@P&0f-LL3|&#~*=CW){+{oOK2hi|v*=|tED zKes6&EXHy=x+#LL(a+5&==S-$3A$!Kx0!)Qlq^Df-$am2T2RRtg_5n&cx?9mV-9U{f;~C zC@$n&b^A|R9ILHGQIj%+p7_kFjJI(6mC$pi-fgC&+vVpb=`a?SVXauC#BaCG&OF38 zS>`wVqkbjB;4UOZVc8Lph!W@fn-hb?yGUAD;j*rBI0^4S18Z(cbZdp1u1qD|@QIvM3pD2pe9D6GhnLg4kbZ^BAPw~NUE3^nZd3W=x^|%cV zdGIQaxjW1;H`{09Gl2%uLcDSk^M>wd=||d@H9p!%+)Z^U+VtN`|B1j1Zv>_s_N7_> zC2V6oK1GXPGcd!hSm_H}dx@~=p!NJ7c~KEOXeTd^J280@c7qpnLXInkVW!*hPWmmg__jP+9Qv-e2c0!-e=G&|!#x>nek{w`otC8b@%FbY{cfw%n^Syc!_ z`Wj&JtOcg5^}tNyjT$Bm{WoiLp0pHky@(9z#yC^ASjE?$lb7e`iF5i^sYf9&-O7Qf zM~!B;YyQOKN#3>pg=ZD=opNe4T&rQ~PWSb&GfrE7FL_e#_W#0jr>#hrST-TCK92Stl#s1|RJuAcNp)y4aU7w6$gn=O~1VVuw2CDWiCn6k;A_-ebv z(ZHlz|Fop51@^>u-z9i6UYuVLCyxsd<&$~EvPk}C5te*d57Ke_A>BfOfxJJuQTEnt zQucOWx*Y;$nRpYJW%CQo9Y(>Mm}PAz+-YW<$fvc|m;UQJ$s!$p9+~P|AMLLHLc2&u z+jMFymXG;klZ-r%ZU#xh^%pz;eNK=D36W7C{!} za_w$A%yg^jB;O;q@hO>J&A@c~6qs_`G&_u<|2wU-Gd?Y_GhdGZlg5+Y2DA~h4+kc{ z(ZJM&d3Lsw4y5Ccbo_bZ^j1oM$YX|Kyy%~d>q*InGFQP&w=7^F?~iWGbEBry98KpH z8YZDf7Sn8zPIpf}9qYuCVfgd#Zhun7uN9bXE?}ltfAAvi<&mkCzlOUd{Z3%gzX|L~ zk5Js445uzX(vb#J6UBT98pbv5aaqns12bHfW}gPk^fLi7u2sNH%axk_Zq1*Vo z>*ZfGJ2Ar(Q;r9F@>#t%&=3y)VHzF@jJrvcDZrNj&j!96xKOi~YyQMnz}*gvJ4=*@ zH9IlG6O*q8d*s1Mq5C$?ze&SKfK|Ak0KOjhYYqRPxf4gh-QWl`L<8RlOgpivVe+M& znDU7EBTj&OEbv$jPXNa7R#^Z%0@$o!i{?(u!CgHt2Xv2Xc4CGjCO={x6#avSUjQBr z`#*ul0Kco@Pk_h5eg=3PaNq`s`vP;p{RZH4;5ZGB1|ARlG~i6&UuoC`oDKU*;2hw) zHT-MfiLn13coOhmH2g9!UJF-V2j+pRV;cSxcq;7YfTsZmJ0%VWo(_97@C@L14W|Kf z&_5lR6P@`QUJN`N_SL|1fY)pIL13l>J_No4*rnm`fLFo(qlSCj7id@m`#|73frn@~QFAB0 z3+@wv?ZCHbc4CGjCO;3R9<=jE`K9nDwgNkV@6_QsP@MEz5pZ3lMs>&+g|Bs4B zMGX}xB_%4ECff7<{&G+>DojdLD(j$tLZLE%h-FDeW)n43R8%xkky%kukq%;$jj zgEJX3nLV)%_9fs$Uh=w%tQ)Dr1(OlbjeH)<>xoJ6OX!O zVDv^X9^3?uXH=Oz@dMbWf}P+zW+qZNBE=J8@A55V?xkQi{FO1@2yTPj0e8cE2KWQ0F#4E1 z@h8~N0)GbcnVCr8h!h|3H*hIP%E?M*CJu!8CU6k=AV_DRJOK^{pJ!&`5iqxck>F&_zE)T7b4|{%=Az`7Q)?wc?swP%b1xs3FaHY$>9AUok3I2_#Cq*o(21Vf+^q@W+qZN zBE>_bbcoYH%11bN!afc5yBW!y%mZ!@G73zGIfj`>FrER<#cPFqv^WvnSTTJ_o!P zypoxT6pl#ohDiA!Gd+~!{8e5X~q|sJ@FCPzY9JJe$LE93P+@PLZo!a zOb>Av+@FHK-@vEAqpE{uVk68ag3p5E!F8a@m;^S#JR5u-oX5BjB>&{z43hap@LKS{ zz`K~4NbW@PPo(sSlpkU%Nckn!G4qq)>oETvYy&&NH^864H^Ecx3i>Da_00XB%zXoM ze;f4RuN|BTz5`}5UJkws^EF@xSOLBV-U+@BJ`ZjLH-VeL-$3$5?wgtW2h6>bxqk?b zxqD#rN1zRU44%b!0r&~bx!|W@5#!ZhH_TPwR`5Q?N5RivZUMK0Z-JkK?}J}}zc70; z?_lPyz>)U^%S+|^8fMCG4@l+t2Bh+Q3sQN$11Z0|K+50uAmwK_Na_CoQv7>BO7BOI z;{OTMz@Ncs%uFPABKap$cp}9|r1*)H9+A=~Qhtb(KO*IqNcksH`w^)t_M;&K9J%`0x8}}AjLBod1z-m=Q#(<+h1!ecgv|7ioO}#_#4o@LVgEfN_czS{Fh??; z0A2#~IPg+XX7rdnaW3plI%pO`h$OlGo5f#n9P)>0~eh<_p2WU^e4oW>2L2TnEy=PqJ2hsO_!e)lOgNVW@Awrw8axv3^~jz4Q=W$4dt*O&s=Ys07tb@k3{sxnVCHuizW{x(4?VNT z_5Yr@8);KMD860zPGyNW<-j^3J0Cuw=gQIj$7>^wP`s)5PI1gRM7-pO-kmh{atozJ z;ix@)c(+M*DInR+04dLxg48Z~jDH46R+obmj$0cH7YT;))6+*<dNT!WL;igrr9pWv=qHWvI6`@^?;{NPISuhfjOaV>4d^>mu#Z@u z+6Gcx?gS}cKQi|hK&p#ZK$6q9Kx(V^nfrk9po||0l8he*l8ncJ0M`KG=OJ7ZNPVvb zq%ry}=Kd~7?^Zqlj|aDaCxAPddjfbO%!weqn~}j`pvT;A0_le!w}U5x_kzR0waoo8 z@D!K}!BfF2z!Bhe%>68IB+S#nQQ&NFG&m2Wwu&wbmLnDnv@uBaG!~?~rg0!K{`c#e z{80T>_2Gxo32*1aF_%G>L;ffYYP05k((tiX}4{YWY^j&ezGG!RNwKVe?M(Xqju$f*&_4d$R@>2ZM(dmxU11`0%AFqz@6h{ge7EM4_ePI?K4iBCUOMXdq ziDd{w@)7bDs(&(5zG*zEWARt^6aV%R2gl!a81YApVfE5a{HsqrIDQ|(_Lu)`7JnKT zDkrgby-y!`aQwB05q~R-zoDP_{U;wBKX-cn`A>i!%4;;(SN3;l0UxLX6lk^eH5W+_W^HCPvxCjI|Mc)W2i zWZo4F8~VLbC|+_)>lJT1i?^#+ytTdJjYrs?>I3r>g=;*iP2#}tJkkGrz}4XJM;)h0Qob*e;Zp%HP2B6>7@{grhWiKjcP!;~xp?Wn}K` z$8S$qypgypvv-_i4#k^+c*&f`;wPDw`w6p_g{ch-liNoal6#t;Tl(-rWvqNOn3rmh z@>k2a7Nj<90IB~pF}8pdj(co>-R$Wj4IjdWo^Kx;a8UmYxl!8mNx+q%p*cmycd|x1!fAcZ>`B$+ya@l?ifAn8>yNIJ94coyRYAnDAPfTT0$ zf}}GSfi#9)4buAS29R{-Dv)&M`#>th!yxI*Pc!r1LDHFDW#)f^q%&_~<}Q$Q<}aD~ zJCMrL5++;exYrY24tl7Zo#PngQE_#6B2n+Cs>P6i7@_1e`(7++XU zFMP3o`H8`Wo1uIs;5(H|2E(G+H*Ul~5iILiFcc=gm)}shko-_PxAx(O%2vzj@G+3W ztpll?FM~7&tOscf*a%X(G_J(rl1AX5C)6&PEU%Y?WLE-GoY#O9X9Y-c-U*USJ^)hO z^u4&BxTBv6*28c{1PRp97-l9tkK%G*-_lQ9A)T-b=~F(*O#HH!Ib{Fc@7VwRJNAQ~ zJTTt38xAxd&D>7_sqYL2X}vj`F_dnI*LME*vB&EROTmE{*-aY`KA5pE3F44w|Qf)p=33k*16MQm+d%p~;yuhvB-y6& z5i>)w0aAW%1xZFo7S{^DFUxS|aJ^Sx4MBH!g`3~V3(8^0y)Dc=_Ab;7PT z%Mc;nH*Iyf)lx$IQ3{3`avPE?hPn^XP!7II=Ks z%oSG=_&zlchn!Bu!DDbu7;V7nsbii9u5rv>{T-+eIq~nmp z$$@L%xO1mmaNd+uNemw6dgDOPpMm3Z{5bJ*ah9*BINRsDFexMBBJL(~2_3?Vlgt9= zJ?`_6HYwS65qC5B37$w9JbbX1`(MdD{J`K|?q_nI$9oC5#w{<)j~d9CyAUTn`I2XP z33LI5zqoL2UU88xIT$SX_w1x8K4g!(0bzwd74_`l4kLOFDc1?F4C?bSs_Ubo#g4yf=uoc7}WXhL0%r@Bj=Ac+O(u8 z8A+371rAip@rgbp#{ClE;j*JY-2R9v1vz;Zp>v8!R6|8ROyNa%w2L63b&~z(AR|); zd!y5N@vdy z{h@JlH0Q^FmF>s*i@3592Owgd5wzKN12c}HD_Dm8j)Uy8aVT8j;$>Lf`6ri_Vm@5F zbjtn8{{EOKyqD8!OsD&2*N2?D>Fq;Csxh$)vs-ynzh{}~Q3X5|**O&@&= zd`%97AiFrk$j)Q;@5>!`%AsuO=)Ay%mI}wb`f~#}XBNtELb8jnUScK3NuK^Z9OLR| zXwBSr6Ai;dH>iaPyav@`CsLxJfo34xA6)=rC>UV^AnC zjhl-b#X0U~s-Hz!IA{E?!EX>G)bdiCJzlKJ95;gVFIX5X4kQhJ=aT{Z+MJ)+UdHX0 z^hho+G~*idT;!gvjinY!;`~K9x&HZi`P960Ry<0Es-B;>00)!%aUmz<+z&+}Soizy za|=1;EGo$>T)c>mEXCr8WF~Y~sUOGFmgW`v1INL0oXg>G$OUxlWMRl9G-puT(DBkZ zSUltu41^L=3!|eVB5oK=fiIjMOos}`8T-Hcb2wbzI$>ea1e{Yn0U;+8;?7*u$%K4i zf-oUCIP~&?(~NV&oqNZSn=`kdFLx5l9HjL7DPX|KTil0xKq?Q&V(&ckE<$g2G(AqU z3{RrB543MiQ9#c;z?U;%3I>kC$&g8$J)A!yc;YR7kv}LkXKqQ(LH5Oi&feF&y&RJk zWzQ(cTNG4RPacqry8?<2iXf5n-zjt=H{N1E$Hp+oOy3Tk4W$vM=*~b%@^hvKuFIHG z%Dv9?@_;vJ(5rgu`LKJD>}WlZl%0(uU(vH)A3SqWa0JZWH%3FXMo~HLddkD^IK6kJi!#RH{bwK^jeJ%kP=KExhCUsh$G;C~A6DM-ZcDS1itzISS6;rR>g!-ecZ=BS=v_=#T9tiS?>ZZhy=Q1S;CB)-5}2In~T_P2kb zbAa2vnUMty563!x_!{ST!e-~->XCdOUE1xRJ1@^)BKZZuFZ!U;;JD&H5I8(+*!`cr!%6snuwi$=99H07We%wWp$l9NCywy+!?bR6@;;V~ zSONv6CrpEdSVltkXHs!s8-&GoI7?7!!UNhc6rL77$PoVavHbaic{;4c)bHh|zlCY9 za9EP!{_&!mP%Rx;=l-)77tQmf9HM{tFUXmXxgJyG0l}%7!s`i3WNZdtwd0?gJr{Qa z;G|=Ag#g;-aAdGoe5gqlU$EXo(fX3Zm%eC_-&=|PPE#CKUV7Ed{u;m)N}}(7F>8lt zpTHPH`gU;m3A9Upe0B1wh z&RIAwe_4F#!u&-=&L~{LViw_0Z`}JcVPRfY;bOWDV**wg=G>x%6HA0q@fa}k=3}wM zPNH{42^0BI?lDKj$J@ASqo|lJkvPoQq+mFNEP{3jEoE6Co_IWVVel~j9BdEfl|a2& zkW;kJG2D4dK(WM;_^G%G1osQmUuV?ZqO%v3;GW;YQSnRilCtO?3};l}LgG=bedfNn z4-C97{xBa0rH*?pLbS92K(|qG1ISL=Aiu|`Qf;b zJc&P-KcByt&*F3WLVgv03x5xPKmQoNj(?4RpPwqs7KVti;u^6DH;A7ly{2wbztx87 z33{hK%g8m>m`&zTd#pX*DRo|RH@RFq*5`5m$@rq&Dexn;gaF8meo+xYMKb;7H{8u2-CmVAf&ki1TLRk>f=rhTtXHD(%POwC+pzG`w- zv~{Aj$4aor+tchB_Huij{i^+&eY~^NIoTcQPIk|B3*9wtlRMm-=tHZKKynT z$mO5rpXWd1xA4acQ-zsArSPCoCf1A3i0_FXh!MC_eTsC4R3bkhKO(;+za#&IyV9pB zKPstevUY)XueMfuN`FZ|$~e(jWmFsQ8y_0yTFb0*>nW?*`rO)ueC@S3d#HVyeZI56 z`KzlQTzQ+iM}1A-q^nknHOA4MBKH~hJ6G~%c?&$Q z0Po8X=Y{+Y{7_-6utIoHUZvD19h5B^e%n6aj8*iE;q}}a&xs= zVOE+|X0=&k)|z$ZTC?74FdNajubG=n$-2Zn(i`E`dt4cQ--`GXd5eFXf03UoyePaY zOcUpbZ;6}5J%TdZ0 z#Ze|JbCqJ{dgVT)S@}kZP=~2!;+p%Z>TGo#Qus=JOZ!oa&?EIIJz9^^WA!*a9@0Bj zPtw!%1^Np8I{kjVQGZ?kSl_A-Fk+0;4cnMv{Mjfs?lhh>o;Bv1#pZ5vpmn|Vh-KT8 z>{<3adyU;_Z+E7yj3 zo2@O@PnKYxhq&78AMHrTa8e*M-#fW3*MN88h;EFgYl+BXy)GKf#t2g|lg#tYx#pwh zZ{~&8^H!W)id;0=@7t-)9OoYASjgACkg73Wp0~yO!rMi8Y6)bEI|^;}Am7ZthrBHi zss&CwNfgBl@gA{L94%cYJtggtB>4*YWjRurt~4tDRJJLNo2qkD|!_e#B6yuFma_Tc0)ov+|)`EU87g#`4=e+yp;9&-0*OZri&mYaxzoI5*O{$%%9G+%|WxcanFxSMJ?QabtfGi(_smKb^mk zZ{s`pV}wHCMxhRUa;4ZKo+EWjJEhrjfqa8JMoCvPm24$f$yW-{UrLqbN|{owtX3+N zN~KDv#u!nn)G2F~dZhs}aEvxjf6qM5%CM@ejn*(b-JTDL!;x0_6+#8lJerTeh!n@i z^9lS|J`uN#PvtZC-TW_no^Y#psw7I&rL9u5e3vXB=8u)V%4&71=AeXC`YZZQeT=cx zXfWO}J~bk&~Zk}xF+Of6raME@YnK>@Qr*EQmFybTvbrqvopxYOz|bu2w75 z8nsrfQ(sdzs;l(d^*8jP#%0Ep#xq7X#>pG4Z>%x4Zs*ub?5pfm_WkyDdxn$c+~=I& zCc0DHTiwUqR`(6}2#@0ja2d#VvM^sn>Hjn$9&SFneSQWVaUT6?;fws`=p)# ze~~&dhYrN)f6=qX{ef`ZPy0rN9obVaAUr)(C9IKFitkto1d9G&10b%P?@@| z$#ya1<~uveiFRU~SSQip9iNko@io<1?NmE8POa1FbfK5-aU$I_kh8a3#j`PQ4&e5p zxT=^f-YT|;zlyeWg|te#Raz@GN*kpu($~_@(h>3r@(6jntjUw*>GH+$0=YSz9 zDL*JbiTV3w^yzoyPI;@mQ~p8blp~enmElT)GC@gEQk69H!3<^2AL@rqnAKV!nQh8? zrCsSzHYuG-m(s0lS9T~p$}Xix-34u6ugYl=TBH`GMQbtIcvCdf%?xvnnQ0C|&75s7 zf;`@i@o5&u!Rwso-H+Ve7)kE;Uh}q68ygL?a^K~hM2Ktx23u%q>nWW2n z9HDmUiN^Ql8mq}V*Qs^4c|TBGEdxjm;(EkWr2C{H@-TUg@+{hCq$;V&nDw`+r)smb zTgdkbUU(e7RDqwY&?ySov6@+)_?*Xe!baqYn|cp!fQe==YA0R`= zX`{4pnx=W$O=bxnidLrK~~X3zRnXQ+0=GXjNK?zFNN+J@;!P&%Dii(cB7+;RKBR zU)jgIqcA!?;k^-*$DUyMP84RLJl6|%3D0AF@TSlX-QY{1NBCJ7ARZx(2*}|Q%+^)n zU68m5(uL9jsYJRG617$uEl-lqgRYq`U#vW(yrN8j4%4B2j*(=r_9xA!b?D=bbB%oC zTH`W|Q-8Nwt#_@R_7RTX&2dYhslVraOf`*XU|{^?_`&>9sO`~MG0{x-FMbU2wnzA@ z_??(2{Y#oI$3steN13VqO?wM-=VurT7n(O%kJA|Ijtj~Seo0F6bp-z>z8bo7aE|KW zH(}=P;=B3n{0_{ZkwS%Vyf_{j+W8oZ^3lU?7vDfn8Yr!lZjkPl>ZE7Tf?K8CQlwlj zzaY<0vXljC30m)T$lQhcV*M6Ag4|8KjcmFXOOp}A#F+MD=$HR z+o4>q?$&1OH|h`R|InuyJB{7OP|R_T`2@7d?dD$dM6}DpZa1{-BfT`rmk)OrV80=P zSNRou4gWHKk}yh05q3f%ip3F_%{L;IbChMu(->_uu5F+Xk8#_K-)a@5R= zNUcrZpm*q-u?E_v@6f-6F8iw|bX|!Wa*a>ZBJIb-k_{reRA!d|$JVuvO%>?rd%-`#egVl%;so83Q+_qWk zt#+%!+GKS?TkN*BTRW_teJj!kJJOD_qcOI`+HrQgoq)A^qWxE=+4;>m(H-Hc?rirO z=)-q-k9tp09jDL~Rrb(%UueNWx2 zPS&PrpK6ytm%3Zus2^#bh%sRVR(=nlj4jp%Xr7-yPcd!3eXaeV{T%ebPwXMiNX+VI zIS)Hk?lyN1)`nc>Kw9c>KSTf3L?3$172=KJ{o>P@eG;X!rAr}=&q)cgC$GW^;w)(N zk;buD>78uE8)GoGiH2^tMzV2^k!H*?<``MVJ!r=$^A%IH?!~z9r8V3B*&YrJVukaN z)9AeAyyI+iK6JJ?pJPoC;hyBiyJKA4orJOaIrn4t8+W32u6I7x<8!bgUx@Y23TW&% zK~t;s8oXyQ>#q0S!ASnO*W>M`IxiT=(HUAC#)V-RVPve=viQsSQs}f5d=>vUzK(yC zZ-8d|CjT~8aUbzp`JRBTeUY$AxJ_up`1mhjqtGpk5hsdsF*h&6m{>0!Cykaql}5;h zyhvUuuf!^#Mt)Rokk_Gg{vo%?Z_Dq?AIjYrslSzflm|dtIZinRYwGcepjgTjWhV5} zCCb&x16UEPgC6n^XdxTWb32tS$`{a;_h6NHg!(6}xJRj{s}odBO@hvRp}Is}0c}~p zx@?kmwYFNjMY~&jQftya!yNddb_CYpqHgHt>x=XT{aNfpY{ZHu(im+R#$fX_^Gx)S zdFG$ZE6nT7d(Dkz7c{)D%5lf0Ga4On47H^RLwz%2e6tl*9o&J}JIUPeuyBqn2pS|z?Lc8jwx zCdVl?m_f&CisnL(e;%Vvt-b|gN&@668!Lp*jTr1>Ey7OA+vd+$SEX3R)?ci3)~D97 zwu!aXt@aCcw;k=+POeksJn9_bO4!f(oBO8Q1^slcx72&W+YEW(Dj_S#PaM`fm-6@W zZ}1-@R~qL23ZY5ZAe;z2ZMJx`_^SAgcmy=+YUv5-Xy~icN0%71AcSSw6ItCZ_L%#5d-Gt3p{W9BIA$1JrT#GV6hKWZm9e?t$5 zaeeMAx7J;c{&y_K=IgN@-Ra?c{J?F1p((Dy3}#Euq7PPJC7KU? z_GcwYy%K9FO{>6sxl5DuBJ9I_id9Myb`>@o5g0X-&1L47=17bO%h0-CT2q}0=JHq?IN6-xhiN}g#Fn3;rF<=o!ftApK8pYQzdu|p{X)VFFEIowA z+8}nJCAS}-q57m`DFwPjz0@GB$1X&N)FbV}Udh3GT+tX!Vli{XqrJysMBy=e=-5Z{ zq4iUYRE#U>MrcgV^s>ENFW)QhxSn9YkL08Hdi3uIAySAIa)mOsR$UJ{+YTLLFGk)- zFi7oi3_2eeiRtx|SiKQ2a%RmZA{DzC~|SEobja@A6Gxmt!bbp<4#5wmogx?XLE ze%6WA46S2gwKy$aOVARvWG!9G&@#1Bj3X6VjaIAGY4utwWOWnPgF7&y?9rkFJ9;wq z5Pf=v-h-8FoI4g8eH!)(s83g5&$Sk7Ar{)V51L3C zbfYq%R#=Puh9@&C#;4 zuT`uq*UHg$RcJkGyGE@U?YAB+*r|1+1$SwCu|FGyz1cXd@e*}exAkN_6>GgYdNy{2 zi}mFgkt(onRHN5nKeQ1mz*elJI`9Uf8`|40tfwN3DD=QM^gbEAE*U*89rJiLMzLaO zZRJJ*uf~bE3gk%W7lCP ztPyL$R%kvQb|+?t9rj*3!imD(Mx2uXJxIokkqpfw9lOBUPQFv@EXS-?fjOiGb6Y*e z>SoMv>zxkldUQKGu+y^_vt1PSdEzkVC1PJT*-drR-8twf`FK~cob{I)%!KuBquY%6 zu*2wEW7i=WD~)u14tiKVU(7E*pr18D^J&F?UI*4mJNRAv zUW}AcLW~fH{o+K-Gq#W{qzdWid)Y$1P%JDL$}t;O2{qUWt%r8hjGe#rSgUkmJl!Ge zLVt`BW5l>XkCZW{ChzN&`PkD~j(%B%eT+Jcs*PeZ`saGF10!oUb};thO;Hr~F5<9n zk$6y#Eym7Od7#(UKqIKf7~71VyB>YF^MKwPgSR3Hf$`RslQHI|W7N&YO0`&CE|+75 ztb(3ahmp4tJ-St1FLxZGUneMu7=dk!!Kv6)nuDF){C<1+-b42B^geod>;IE}p4d+x zZ|t*&C+_RxbI`*Jj7s$EX7uV!fgT;PuP56F_2t!gtJTQ*Z;u&`e(SR`p-UE6rB)gG zYJFHg?Lz-Nbl+Sa=$+NrWo*IDbi2LD?y`IA7$@Eti(STiyzwYQ-#fV1MWVO$?rD{H zC(?u-w%v{NqPUeJmC)F{kpGWJkO z+AHwBq9HJg?NB&1Ixugg;(b*?V6LiHTcA;NV}6Q)Ccy`0q=91Q4^GGE2F4F_DxEfN}gtafpUo_V2Jl-fI+qw2? zykDxuzTjHC83~uX?U21aHs?fQzdJS{gFM~{`yh#FP6lQKnhUC(robv^2ll+zSR7J4nx 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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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/97] =?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 af54cbcc82814834973c9a9e96efd923242cfdfb Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Thu, 30 Mar 2023 23:16:25 +0800 Subject: [PATCH 57/97] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bd7e074..a1a9cd7 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,9 @@ vcpkg 2023-03-04 : 新增消息附件下载 -2023-03-21 : 新增hook语音 +2023-03-21 : 新增hook语音 + +2023-03-30 : 新增获取语音文件(推荐使用这个非hook接口) #### 功能预览: 0.检查是否登录 @@ -186,6 +188,7 @@ vcpkg 54.朋友圈下一页 55.获取联系人或者群名称 56.获取消息附件(图片,视频,文件) +57.获取语音文件(silk3格式) #### 感谢 https://github.com/ljc545w/ComWeChatRobot From c453b8fb30d066a0239d7d31b6d3e7b8c01ce457 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Thu, 30 Mar 2023 23:18:46 +0800 Subject: [PATCH 58/97] =?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 a1a9cd7..4a0a1d2 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ source: 简单的命令行远程注入源码。 个人常用的方法,请参考https://github.com/ttttupup/wxhelper/wiki 使用上的问题,可查询https://github.com/ttttupup/wxhelper/discussions 数据库解密,请参考https://github.com/ttttupup/wxhelper/wiki - +个人精力有限,只维护最新版本,旧版本的bug会在新版本中修复,不维护旧版本。 #### 编译环境 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 59/97] =?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 60/97] =?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 61/97] =?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 From 5ba4f72d5c1846269451d30e51af0642ee5a1139 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Sat, 8 Apr 2023 14:40:58 +0800 Subject: [PATCH 62/97] ocr --- src/http_handler.cc | 11 +++-- src/misc_mgr.cc | 2 +- src/send_message_mgr.cc | 90 +++++++++++++++++++++++++++++++++-------- src/send_message_mgr.h | 1 + src/wechat_function.h | 6 +-- 5 files changed, 83 insertions(+), 27 deletions(-) diff --git a/src/http_handler.cc b/src/http_handler.cc index 1b94fdc..91d3e15 100644 --- a/src/http_handler.cc +++ b/src/http_handler.cc @@ -131,7 +131,6 @@ string Dispatch(struct mg_connection *c, struct mg_http_message *hm) { 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"); @@ -502,11 +501,11 @@ string Dispatch(struct mg_connection *c, struct mg_http_message *hm) { 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(); + 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: { diff --git a/src/misc_mgr.cc b/src/misc_mgr.cc index 3aa02b9..8462307 100644 --- a/src/misc_mgr.cc +++ b/src/misc_mgr.cc @@ -90,7 +90,7 @@ int MiscMgr::DoOCRTask(wchar_t *img_path, std::string &result) { for (unsigned int i = 0; i < num - 1; i++) { DWORD content = *(DWORD *)header; result += Utils::WstringToUTF8(READ_WSTRING(content, 0x14)); - + result += "\r\n"; header = content; } } diff --git a/src/send_message_mgr.cc b/src/send_message_mgr.cc index 7a9b38e..0731e86 100644 --- a/src/send_message_mgr.cc +++ b/src/send_message_mgr.cc @@ -14,10 +14,9 @@ int SendMessageMgr::SendText(wchar_t* wxid, wchar_t* msg) { 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; + DWORD send_message_mgr_addr = base_addr_ + WX_SEND_MESSAGE_MGR_OFFSET; + DWORD send_text_msg_addr = base_addr_ + WX_SEND_TEXT_OFFSET; + DWORD free_chat_msg_addr = base_addr_ + WX_FREE_CHAT_MSG_OFFSET; char chat_msg[0x2D8] = {0}; __asm { PUSHAD @@ -44,6 +43,66 @@ int SendMessageMgr::SendText(wchar_t* wxid, wchar_t* msg) { int SendMessageMgr::SendAtText(wchar_t* chat_room_id, wchar_t** wxids, int len, wchar_t* msg) { int success = -1; + WeChatString * at_users = new WeChatString[len+1]; + std::wstring at_msg = L""; + int number =0; + for (int i = 0; i < len; i++) { + std::wstring nickname; + if (!lstrcmpiW((wchar_t *)wxids[i], (wchar_t *)L"notify@all")) { + nickname = L"所有人"; + } else { + // nickname = GlobalContext::GetInstance().contact_mgr->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; + } + std::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 send_message_mgr_addr = base_addr_ + WX_SEND_MESSAGE_MGR_OFFSET; + DWORD send_text_msg_addr = base_addr_ + WX_SEND_TEXT_OFFSET; + DWORD free_chat_msg_addr = base_addr_ + 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 + 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 + } + LOG_IF((success == -1), ERROR) << "SendText fail"; return success; } int SendMessageMgr::SendImage(wchar_t* wxid, wchar_t* image_path) { @@ -51,11 +110,10 @@ int SendMessageMgr::SendImage(wchar_t* wxid, wchar_t* image_path) { 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 send_message_mgr_addr = base_addr_ + WX_SEND_MESSAGE_MGR_OFFSET; + DWORD init_chat_msg_addr = base_addr_ + WX_INIT_CHAT_MSG_OFFSET; + DWORD send_image_msg_addr = base_addr_ + WX_SEND_IMAGE_OFFSET; + DWORD free_msg_addr = base_addr_ + WX_FREE_CHAT_MSG_OFFSET; DWORD temp = 0; WeChatString null_obj = {0}; __asm { @@ -88,11 +146,10 @@ int SendMessageMgr::SendFile(wchar_t* wxid, wchar_t* file_path) { 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 app_msg_mgr_addr = base_addr_ + WX_APP_MSG_MGR_OFFSET; + DWORD init_chat_msg_addr = base_addr_ + WX_INIT_CHAT_MSG_OFFSET; + DWORD send_file_addr = base_addr_ + WX_SEND_FILE_OFFSET; + DWORD free_msg_addr = base_addr_ + WX_FREE_CHAT_MSG_OFFSET; DWORD temp = 0; WeChatString null_obj = {0}; __asm { @@ -150,9 +207,8 @@ int SendMessageMgr::ForwardMsg(wchar_t* wxid, unsigned long long msgid) { 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; + DWORD forward_msg_addr = base_addr_ + WX_FORWARD_MSG_OFFSET; + DWORD init_chat_msg_addr = base_addr_ + WX_INIT_CHAT_MSG_OFFSET; __asm { PUSHAD PUSHFD diff --git a/src/send_message_mgr.h b/src/send_message_mgr.h index 7e34db5..3ebf7f7 100644 --- a/src/send_message_mgr.h +++ b/src/send_message_mgr.h @@ -2,6 +2,7 @@ #define WXHELPER_SEND_MESSAGE_MGR_H_ #include "base_mgr.h" namespace wxhelper { +class GlobalContext ; class SendMessageMgr:public BaseMgr { public: explicit SendMessageMgr(DWORD base); diff --git a/src/wechat_function.h b/src/wechat_function.h index 59ee100..a8719eb 100644 --- a/src/wechat_function.h +++ b/src/wechat_function.h @@ -133,9 +133,9 @@ //ocr -#define WX_INIT_OBJ_OFFSET 0x7a98f0 -#define WX_OCR_MANAGER_OFFSET 0x7ae470 -#define WX_DO_OCR_TASK_OFFSET 0x13230c0 +#define WX_INIT_OBJ_OFFSET 0x80a800 +#define WX_OCR_MANAGER_OFFSET 0x80f270 +#define WX_DO_OCR_TASK_OFFSET 0x13da3e0 //storage From 8279303614b9ce88cfdea0988e49ee2d23e31e57 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Sat, 8 Apr 2023 14:53:04 +0800 Subject: [PATCH 63/97] =?UTF-8?q?@=E6=B6=88=E6=81=AF=E5=92=8Cocr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/send_message_mgr.cc | 4 +++- src/send_message_mgr.h | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/send_message_mgr.cc b/src/send_message_mgr.cc index 0731e86..ec71383 100644 --- a/src/send_message_mgr.cc +++ b/src/send_message_mgr.cc @@ -5,6 +5,7 @@ #include "wechat_function.h" #include "db.h" +#include "contact_mgr.h" namespace wxhelper { SendMessageMgr::SendMessageMgr(DWORD base):BaseMgr(base) {} @@ -51,7 +52,8 @@ int SendMessageMgr::SendAtText(wchar_t* chat_room_id, wchar_t** wxids, int len, if (!lstrcmpiW((wchar_t *)wxids[i], (wchar_t *)L"notify@all")) { nickname = L"所有人"; } else { - // nickname = GlobalContext::GetInstance().contact_mgr->GetContactOrChatRoomNickname(wxids[i]); + ContactMgr contact{base_addr_}; + nickname = contact.GetContactOrChatRoomNickname(wxids[i]); } if (nickname.length() == 0) { continue; diff --git a/src/send_message_mgr.h b/src/send_message_mgr.h index 3ebf7f7..7e34db5 100644 --- a/src/send_message_mgr.h +++ b/src/send_message_mgr.h @@ -2,7 +2,6 @@ #define WXHELPER_SEND_MESSAGE_MGR_H_ #include "base_mgr.h" namespace wxhelper { -class GlobalContext ; class SendMessageMgr:public BaseMgr { public: explicit SendMessageMgr(DWORD base); From dffdbfb9d7af3cd09f6674823671a5f130ddca98 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Sat, 8 Apr 2023 14:55:08 +0800 Subject: [PATCH 64/97] =?UTF-8?q?=E6=96=87=E6=A1=A3=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9d31abc..ed58fe6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ #### 功能预览: 0.检查是否登录 1.获取登录微信信息 -2.发送文本 +2.发送文本 +3.发送@文本 5.发送图片 6.发送文件 9.hook消息 @@ -31,7 +32,7 @@ 46.联系人列表 47.获取群详情 48.获取解密图片 - +49.图片提取文字ocr 50.拍一拍 51.群消息置顶消息 52.群消息取消置顶 From 233b9f11f4241bc935b37211253cee8a9859b87f Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Sat, 8 Apr 2023 15:25:22 +0800 Subject: [PATCH 65/97] =?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 | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4a0a1d2..1a59ba1 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ dll在注入成功时,创建了一个默认端口为19088的http服务端, ``` #### 使用说明: -支持的版本3.8.0.41,3.8.1.26,3.9.0.28。 +支持的版本3.8.0.41、3.8.1.26、3.9.0.28、3.9.2.23 。 源码和主要实现在相应的分支内。 src:主要的dll代码 tool:简单的注入工具,一个是控制台,一个是图形界面。 @@ -63,6 +63,21 @@ cmake vcpkg #### 编译构建 先准备好编译环境。 +``` +cd wxhelper +mkdir build +cd build +cmake -DCMAKE_C_COMPILER=cl.exe \ +-DCMAKE_CXX_COMPILER=cl.exe \ +-DCMAKE_BUILD_TYPE=Debug \ +-DCMAKE_INSTALL_PREFIX=C:/other/codeSource/windows/wxhelper/out/install/x86-debug \ +-DCMAKE_TOOLCHAIN_FILE:FILEPATH=C:/vcpkg/scripts/buildsystems/vcpkg.cmake \ +-SC:/wxhelper \ +-BC:/wxhelper/build/x86-debug\ +-G Ninja + +cmake --build .. +``` 以下是在vscode中操作,vs中的操作类似。 1.安装vcpkg,cmake,vscode @@ -100,7 +115,7 @@ vcpkg } ``` -4.vscode中右键configure all projects,在Terminal中点击Run Task,如没有先配置build任务,然后运行即可 +4.执行cmake build vscode中右键configure all projects,在Terminal中点击Run Task,如没有先配置build任务,然后运行即可 5.命令行注入工具,注入命令 ``` javascript @@ -147,7 +162,9 @@ vcpkg 2023-03-21 : 新增hook语音 -2023-03-30 : 新增获取语音文件(推荐使用这个非hook接口) +2023-03-30 : 新增获取语音文件(推荐使用这个非hook接口) + +2023-04-08 : 3.9.2.23版本功能更新 #### 功能预览: 0.检查是否登录 @@ -164,7 +181,8 @@ vcpkg 14.取消hook语音 17.删除好友 19.通过手机或qq查找微信 -20.通过wxid添加好友 +20.通过wxid添加好友 +23.通过好友申请 25.获取群成员 26.获取群成员昵称 27.删除群成员 From 0942a691edd0ce14ccd4e641b10c27bcf00ef76c Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Thu, 13 Apr 2023 15:26:06 +0800 Subject: [PATCH 66/97] =?UTF-8?q?3.9.2.23=20=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0ead279..47da484 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ *.app CMakePresets.json .vscode +out \ No newline at end of file From 620d4d38db9ce82e0e3c97629aee4be09f37538b Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Thu, 13 Apr 2023 15:29:07 +0800 Subject: [PATCH 67/97] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/3.9.2.23.md | 1629 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1629 insertions(+) create mode 100644 doc/3.9.2.23.md diff --git a/doc/3.9.2.23.md b/doc/3.9.2.23.md new file mode 100644 index 0000000..7a1ce01 --- /dev/null +++ b/doc/3.9.2.23.md @@ -0,0 +1,1629 @@ + +## 3.9.2.23版本,预览功能没有的接口,不能使用,文档仅供参考。 +#### 功能预览: +0.检查是否登录 +1.获取登录微信信息 +2.发送文本 +3.发送@文本 +5.发送图片 +6.发送文件 +9.hook消息 +10.取消hook消息 +11.hook图片 +12.取消hook图片 +13.hook语音(不推荐) +14.取消hook语音 + +19.通过手机或qq查找微信 +20.通过wxid添加好友 +23.通过好友申请 +25.获取群成员 +26.获取群成员昵称 +27.删除群成员 +28.增加群成员 +31.修改群昵称 +32.获取数据库句柄 +34.查询数据库 +35.hook日志 +36.关闭hook日志 +40.转发消息 +44.退出登录 + +46.联系人列表 +47.获取群详情 +48.获取解密图片 +49.图片提取文字ocr +50.拍一拍 +51.群消息置顶消息 +52.群消息取消置顶 +53.朋友圈首页 +54.朋友圈下一页 +55.获取联系人或者群名称 +56.获取消息附件(图片,视频,文件) +57.获取消息语音文件 +### 接口文档: + + +#### 0.检查微信登录** +###### 接口功能 +> 检查微信是否登录 + +###### 接口地址 +> [/api/?type=0](/api/?type=0) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1 成功, 0失败| +|result|string|成功提示| +|data|string|响应内容| + +###### 接口示例 +入参: +``` javascript +``` +响应: +``` javascript +{ + "code": 1, + "result": "ok" +} +``` + +#### 1.获取登录用户信息** +###### 接口功能 +> 获取登录用户信息 + +###### 接口地址 +> [/api/?type=1](/api/?type=1) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1 成功, 0失败| +|result|string|成功提示| +|data|object|响应内容| +|account|string|账号| +|headImage|string|头像| +|city|string|城市| +|country|string|国家| +|currentDataPath|string|当前数据目录,登录的账号目录| +|dataSavePath|string|微信保存目录| +|mobile|string|手机| +|name|string|昵称| +|province|string|省| +|wxid|string|wxid| +|signature|string|个人签名| +|dbKey|string|数据库的SQLCipher的加密key,可以使用该key配合decrypt.py解密数据库 + +###### 接口示例 +入参: +``` javascript +``` +响应: +``` 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","dbKey":"aaa2222"},"result":"OK"} +``` + + + + +#### 2.发送文本消息** +###### 接口功能 +> 发送文本消息 + +###### 接口地址 +> [/api/?type=2](/api/?type=2) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 接收人wxid | +|msg|true |string|消息文本内容| + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,不为0成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 + +入参: +``` javascript +{ + "wxid": "filehelper", + "msg": "1112222" +} +``` +响应: +``` javascript +{"code":345686720,"result":"OK"} +``` + + + +#### 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.发送图片消息** +###### 接口功能 +> 发送图片消息 + +###### 接口地址 +> [/api/?type=5](/api/?type=5) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 接收人wxid | +|imagePath|true |string|图片路径| + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,不为0成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "wxid": "filehelper", + "imagePath": "C:/Users/123.png" +} +``` +响应: +``` javascript +{"code":345686724,"result":"OK"} +``` + + + +#### 6.发送文件消息** +###### 接口功能 +> 发送文件 + +###### 接口地址 +> [/api/?type=6](/api/?type=6) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 接收人wxid | +|filePath|true |string|文件路径| + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "wxid": "filehelper", + "filePath": "C:/Users/123.txt" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 9.hook消息** +###### 接口功能 +> hook接收文本消息,图片消息,群消息.该接口将hook的消息通过tcp回传给本地的端口 + +###### 接口地址 +> [/api/?type=9](/api/?type=9) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|port |true |string| 本地服务端端口,用来接收消息内容 | +|ip |true |string| 服务端ip地址,用来接收消息内容,可以是任意ip,即tcp客户端连接的服务端的ip (3.8.1.26版本)| + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "port": "19099" + "ip":"127.0.0.1" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 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图片** +###### 接口功能 +> hook图片原始内容,不推荐该接口,可以使用图片查询接口 + +###### 接口地址 +> [/api/?type=11](/api/?type=11) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|imgDir |true |string| 图片保存的目录 | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "imgDir":"C:\\other" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 12.取消hook图片** +###### 接口功能 +> 取消hook图片 + +###### 接口地址 +> [/api/?type=12](/api/?type=12) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` 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.删除好友** +###### 接口功能 +> 删除好友,该接口不够完善,删除后,只会在通讯录里删除,如果点击聊天记录,又会重新加回来,删除的不彻底。 + +###### 接口地址 +> [/api/?type=17](/api/?type=17) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 好友wxid | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,0成功, -1失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "wxid":"wxid_o" +} +``` +响应: +``` javascript +{"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" + } +} +``` + + + +#### 20.通过wxid添加好友** +###### 接口功能 +> 添加好友 + +###### 接口地址 +> [/api/?type=20](/api/?type=20) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|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| + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "wxid":"wxid_o1112222" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 25.获取群成员** +###### 接口功能 +> 获取群成员 + +###### 接口地址 +> [/api/?type=25](/api/?type=25) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| +|data|object|返回内容| +|admin|string|群主id| +|chatRoomId|string|群id| +|members|string|群成员id以^分隔| + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"123@chatroom" +} +``` +响应: +``` javascript +{"code":1,"data":{"admin":"wxid","chatRoomId":"123@chatroom","members":"wxid_123^Gwxid_456^Gwxid_45677"},"result":"OK"} +``` + + +#### 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.删除群成员** +###### 接口功能 +> 删除群成员 + +###### 接口地址 +> [/api/?type=27](/api/?type=27) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | +|memberIds |true |string| 成员id,以,分割 | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"34932563384@chatroom", + "memberIds":"wxid_oyb662qhop4422" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + + +#### 28.增加群成员** +###### 接口功能 +> 增加群成员 + +###### 接口地址 +> [/api/?type=28](/api/?type=28) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | +|memberIds |true |string| 成员id,以,分割 | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"34932563384@chatroom", + "memberIds":"wxid_oyb662qhop4422" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 31.修改自身群昵称** +###### 接口功能 +> 修改群名片 + +###### 接口地址 +> [/api/?type=31](/api/?type=31) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | +|wxid |true |string| 自己的id,只能修改自己的群名片 | +|nickName |true |string| 修改的昵称 | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"34932563384@chatroom", + "wxid":"wxid_272211111121112", + "nickName":"昵称test" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + + +#### 32.获取数据库句柄** +###### 接口功能 +> 获取sqlite3数据库句柄 + +###### 接口地址 +> [/api/?type=32](/api/?type=32) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| +|data|array|返回数据| +|databaseName|string|数据库名称| +|handle|int|数据库句柄| +|tables|array|表信息| +|name|string|表名| +|rootpage|string|rootpage| +|sql|string|sql| +|tableName|string|tableName| + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{ + "data": [ + { + "databaseName": "MicroMsg.db", + "handle": 119561688, + "tables": [ + { + "name": "Contact", + "rootpage": "2", + "sql": "CREATE TABLE Contact(UserName TEXT PRIMARY KEY ,Alias TEXT,EncryptUserName TEXT,DelFlag INTEGER DEFAULT 0,Type INTEGER DEFAULT 0,VerifyFlag INTEGER DEFAULT 0,Reserved1 INTEGER DEFAULT 0,Reserved2 INTEGER DEFAULT 0,Reserved3 TEXT,Reserved4 TEXT,Remark TEXT,NickName TEXT,LabelIDList TEXT,DomainList TEXT,ChatRoomType int,PYInitial TEXT,QuanPin TEXT,RemarkPYInitial TEXT,RemarkQuanPin TEXT,BigHeadImgUrl TEXT,SmallHeadImgUrl TEXT,HeadImgMd5 TEXT,ChatRoomNotify INTEGER DEFAULT 0,Reserved5 INTEGER DEFAULT 0,Reserved6 TEXT,Reserved7 TEXT,ExtraBuf BLOB,Reserved8 INTEGER DEFAULT 0,Reserved9 INTEGER DEFAULT 0,Reserved10 TEXT,Reserved11 TEXT)", + "tableName": "Contact" + } + ] + } + ], + "result": "OK" +} +``` + + + +#### 34.查询数据库** +###### 接口功能 +> 查询数据库 + +###### 接口地址 +> [/api/?type=34](/api/?type=34) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|dbHandle |true |int| 句柄 | +|sql |true |string| sql语句 | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| +|data|array|返回数据| + + + +###### 接口示例 +入参: +``` javascript +{ + "dbHandle": 219277920, + "sql":"select * from MSG where MsgSvrID=8985035417589024392" +} +``` +响应: +``` javascript +{"code":1,"data":[["localId","TalkerId","MsgSvrID","Type","SubType","IsSender","CreateTime","Sequence","StatusEx","FlagEx","Status","MsgServerSeq","MsgSequence","StrTalker","StrContent","DisplayContent","Reserved0","Reserved1","Reserved2","Reserved3","Reserved4","Reserved5","Reserved6","CompressContent","BytesExtra","BytesTrans"],["6346","24","8985035417589024392","1","0","0","1670897832","1670897832000","0","0","2","1","778715089","wxid_1222","112","","0","2","","","","","","","CgQIEBAAGkEIBxI9PG1zZ3NvdXJjZT4KCTxzaWduYXR1cmU+djFfSFFyeVAwZTE8L3NpZ25hdHVyZT4KPC9tc2dzb3VyY2U+ChokCAISIDU5NjI1NjUxNWE0YzU2ZDQxZDJlOWMyYmIxMjFhNmZl",""]],"result":"OK"} +``` + + + +#### 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.转发消息** +###### 接口功能 +> 直接转发消息 + +###### 接口地址 +> [/api/?type=40](/api/?type=40) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 消息接收人wxid | +|msgid |true |number| 消息id,hook消息接口中返回的消息id | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,非0成功| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "wxid": "filehelper", + "msgid":7215505498606506901 +} +``` +响应: +``` javascript +{"code":4344,"result":"OK"} +``` + + +#### 44.退出登录** +###### 接口功能 +> 退出登录微信,相当于直接退出微信,跟手动退出比,少了重新打开登录的一步,dll注入后也会随微信关闭而关闭。调用后不能再继续操作dll。 + +###### 接口地址 +> [/api/?type=44](/api/?type=44) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,非0成功| +|result|string|成功提示| + + + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{"code":4344,"result":"OK"} +``` + + +#### 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.联系人列表** +###### 接口功能 +> 联系人列表 + +###### 接口地址 +> [/api/?type=46](/api/?type=46) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| +|data|array|返回内容| +|customAccount|string|自定义账号| +|delFlag|int|删除标志| +|type|int|好友类型| +|userName|string|用户名称| +|verifyFlag|int|验证| +|wxid|string|wxid| + + + +###### 接口示例 +入参: +``` javascript +{ + "wxid": "filehelper", + "msgid":7215505498606506901 +} +``` +响应: +``` javascript +{"code":1,"data":[{"customAccount":"custom","delFlag":0,"type":8388611,"userName":"昵称","verifyFlag":0,"wxid":"wxid_123pcqm22"}]} +``` + + +#### 47.群详情** +###### 接口功能 +> 获取群详情 + +###### 接口地址 +> [/api/?type=47](/api/?type=47) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| +|data|object|返回内容| +|admin|string|群主id| +|chatRoomId|int|群id| +|notice|int|通知| +|xml|string|xml| + + + +###### 接口示例 +入参: +``` javascript +{ + "wxid": "filehelper", + "msgid":7215505498606506901 +} +``` +响应: +``` javascript +{"code":1,"data":{"admin":"123","chatRoomId":"123@chatroom","notice":"1222","xml":""},"result":"OK"} +``` + + + +#### 48.获取解密图片** +###### 接口功能 +> 获取解密图片 + +###### 接口地址 +> [/api/?type=48](/api/?type=48) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|imagePath |true |string| 图片路径 | +|savePath |true |string| 保存路径 | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "imagePath":"C:\\3a610d7bc1cf5a15d12225a64b8962.dat", + "savePath":"C:\\other" +} + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + + +#### 49.提取文字** +###### 接口功能 +> 提取图片中的文字 + +###### 接口地址 +> [/api/?type=49](/api/?type=49) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|imagePath |true |string| 图片路径 | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,0成功, -1失败,1 2 则是缓存或者正在进行中需再调用一次| +|result|string|成功提示| +|text|string|提取的相应文字| + + +#### 50.拍一拍** +###### 接口功能 +> 群里拍一拍用户 + +###### 接口地址 +> [/api/?type=50](/api/?type=50) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 微信群聊id | +|wxid |true |string| 要拍的用户wxid,如果使用用户自定义的微信号,则不会显示群内昵称 | +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"123331@chatroom", + "wxid":"wxid_123456" +} + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 51.群内消息置顶** +###### 接口功能 +> 在群聊里置顶某条消息,可以置顶文字和图片消息,其他消息未测试,部分低版本移动端置顶消息点击后会直接取消,高版本会一直置顶,其他未测试。 + +###### 接口地址 +> [/api/?type=51](/api/?type=51) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 置顶消息的发送人wxid | +|msgid |true |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 |true |string| 微信群聊id | +|msgid |true |string| 消息id | +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,0成功, -1失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"2136311004@chatroom", + "msgid":3374951233278903120 +} + +``` +响应: +``` 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 |true |string| 朋友圈的snsId | + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "snsId":"14056334227327177401" + +} + +``` +响应: +``` javascript +{"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"} +``` + + + +#### 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"} +``` + + +#### 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 + +https://github.com/NationalSecurityAgency/ghidra + +https://github.com/x64dbg/x64dbg From 40940c0b7a59c5e6b1e2c0d9c3c6d8fe90759f92 Mon Sep 17 00:00:00 2001 From: ttttupup <31303661+ttttupup@users.noreply.github.com> Date: Mon, 17 Apr 2023 16:35:59 +0800 Subject: [PATCH 68/97] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 6105789fccde0acf44397528290dc6992fc53f08 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Mon, 24 Apr 2023 20:37:41 +0800 Subject: [PATCH 69/97] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=AD=97=E6=AE=B5=E6=9C=AA=E7=9F=A5=E7=9A=84=E7=BC=96?= =?UTF-8?q?=E7=A0=81=E6=96=B9=E5=BC=8F=EF=BC=8C=E8=BD=AC=E6=8D=A2=E6=88=90?= =?UTF-8?q?base64=20close=20#51?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db.cc | 13 +++++++++++-- src/utils.cc | 27 ++++++++++++++++++++------- src/utils.h | 2 ++ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/db.cc b/src/db.cc index 1737688..1e3fe77 100644 --- a/src/db.cc +++ b/src/db.cc @@ -5,6 +5,7 @@ #include "easylogging++.h" #include "wechat_function.h" +#include "utils.h" using namespace std; namespace wxhelper { @@ -117,8 +118,16 @@ int DB::Select(DWORD db_hanle, const char *sql, 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); + bool is_utf8 = Utils::IsTextUtf8(it[i].content, it[i].content_len); + if (is_utf8) { + 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); + } + } else { string b64_str = base64_encode((BYTE *)it[i].content, it[i].content_len); diff --git a/src/utils.cc b/src/utils.cc index e2dc855..07fd094 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -1,7 +1,6 @@ -#include "pch.h" -#include "utils.h" - +#include "utils.h" +#include "pch.h" namespace wxhelper { std::wstring Utils::UTF8ToWstring(const std::string &str) { @@ -19,7 +18,7 @@ std::wstring Utils::AnsiToWstring(const std::string &input, DWORD locale) { MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, &temp[0], wchar_len); return std::wstring(&temp[0]); } - + return std::wstring(); } @@ -102,8 +101,6 @@ void Utils::Hex2Bytes(const std::string &hex, BYTE *bytes) { } } - - bool Utils::IsDigit(std::string str) { if (str.length() == 0) { return false; @@ -119,7 +116,8 @@ bool Utils::IsDigit(std::string str) { 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)) { + if (hFind != INVALID_HANDLE_VALUE && + (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { FindClose(hFind); return true; } @@ -174,4 +172,19 @@ std::string Utils::WCharToUTF8(wchar_t *wstr) { return std::string(); } +bool Utils::IsTextUtf8(const char *str, int len) { + int needed = MultiByteToWideChar(CP_UTF8, 0, str, len, NULL, 0); + wchar_t *wide_str = new wchar_t[needed]; + MultiByteToWideChar(CP_UTF8, 0, str, len, wide_str, needed); + + char *new_str = new char[len + 1]; + WideCharToMultiByte(CP_ACP, 0, wide_str, -1, new_str, len + 1, NULL, NULL); + + bool isUtf8 = strcmp(str, new_str) == 0; + + delete[] wide_str; + delete[] new_str; + + return isUtf8; +} } // namespace wxhelper \ No newline at end of file diff --git a/src/utils.h b/src/utils.h index b5e50b1..331b2fa 100644 --- a/src/utils.h +++ b/src/utils.h @@ -47,6 +47,8 @@ class Utils { static std::string WCharToUTF8(wchar_t *wstr); + static bool IsTextUtf8(const char *str, int len); + template static std::vector split(T1 str, T2 letter) { std::vector arr; From f9a7924658914144070ba2334971ec2db9737593 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Tue, 25 Apr 2023 08:48:16 +0800 Subject: [PATCH 70/97] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dutf8=E7=9A=84?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E5=92=8Cjson=E8=BF=94=E5=9B=9E=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db.cc | 4 ++-- src/http_handler.cc | 2 +- src/utils.cc | 46 ++++++++++++++++++++++++++++++++++----------- src/utils.h | 2 +- 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/db.cc b/src/db.cc index 1e3fe77..4c64ef7 100644 --- a/src/db.cc +++ b/src/db.cc @@ -123,9 +123,9 @@ int DB::Select(DWORD db_hanle, const char *sql, string content(it[i].content); item.push_back(content); } else { - string b64_str = + string base64_str = base64_encode((BYTE *)it[i].content, it[i].content_len); - item.push_back(b64_str); + item.push_back(base64_str); } } else { diff --git a/src/http_handler.cc b/src/http_handler.cc index 91d3e15..8450ca6 100644 --- a/src/http_handler.cc +++ b/src/http_handler.cc @@ -588,7 +588,7 @@ void HttpHandler::HandlerRequest(struct mg_connection *c, void *ev_data) { ret = res.dump(); } if (ret != "") { - mg_http_reply(c, 200, "", ret.c_str(), 0, 0); + mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\n", ret.c_str()); } } else { mg_http_reply(c, 500, NULL, "%s", "Invalid URI"); diff --git a/src/utils.cc b/src/utils.cc index 07fd094..d92bd2b 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -172,19 +172,43 @@ std::string Utils::WCharToUTF8(wchar_t *wstr) { return std::string(); } -bool Utils::IsTextUtf8(const char *str, int len) { - int needed = MultiByteToWideChar(CP_UTF8, 0, str, len, NULL, 0); - wchar_t *wide_str = new wchar_t[needed]; - MultiByteToWideChar(CP_UTF8, 0, str, len, wide_str, needed); +bool Utils::IsTextUtf8(const char *str,int length) { + char endian = 1; + bool littlen_endian = (*(char *)&endian == 1); - char *new_str = new char[len + 1]; - WideCharToMultiByte(CP_ACP, 0, wide_str, -1, new_str, len + 1, NULL, NULL); + size_t i; + int bytes_num; + unsigned char chr; - bool isUtf8 = strcmp(str, new_str) == 0; + i = 0; + bytes_num = 0; + while (i < length) { + if (littlen_endian) { + chr = *(str + i); + } else { // Big Endian + chr = (*(str + i) << 8) | *(str + i + 1); + i++; + } - delete[] wide_str; - delete[] new_str; - - return isUtf8; + if (bytes_num == 0) { + if ((chr & 0x80) != 0) { + while ((chr & 0x80) != 0) { + chr <<= 1; + bytes_num++; + } + if ((bytes_num < 2) || (bytes_num > 6)) { + return false; + } + bytes_num--; + } + } else { + if ((chr & 0xC0) != 0x80) { + return false; + } + bytes_num--; + } + i++; + } + return (bytes_num == 0); } } // namespace wxhelper \ No newline at end of file diff --git a/src/utils.h b/src/utils.h index 331b2fa..284c324 100644 --- a/src/utils.h +++ b/src/utils.h @@ -47,7 +47,7 @@ class Utils { static std::string WCharToUTF8(wchar_t *wstr); - static bool IsTextUtf8(const char *str, int len); + static bool IsTextUtf8(const char * str,int length) ; template static std::vector split(T1 str, T2 letter) { From 34eb14edb2ce39d8328f92bd20557a1a24809c1c Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 26 Apr 2023 09:57:39 +0800 Subject: [PATCH 71/97] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.cc | 15 +++++++++++++-- src/config.h | 24 +++++++++++++----------- src/global_context.cc | 5 +++-- src/log.cc | 2 +- src/log.h | 2 +- 5 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/config.cc b/src/config.cc index 2f86227..171ff88 100644 --- a/src/config.cc +++ b/src/config.cc @@ -1,6 +1,17 @@ -#include "config.h" +#include "pch.h" +#include "config.h" + namespace wxhelper { Config::Config(/* args */) {} Config::~Config() {} -} // namespace wxhelper \ No newline at end of file + + +void Config::Initialize(){ + port_ = GetPrivateProfileInt("config", "Port", 19088, "./config.ini"); +} +int Config::GetPort(){ + return port_; +} + + } // namespace wxhelper \ No newline at end of file diff --git a/src/config.h b/src/config.h index 5df279e..ffde254 100644 --- a/src/config.h +++ b/src/config.h @@ -1,15 +1,17 @@ -#ifndef WXHELPER_CONFIG_H_ +#ifndef WXHELPER_CONFIG_H_ #define WXHELPER_CONFIG_H_ -namespace wxhelper{ +namespace wxhelper { - class Config - { - private: - /* data */ - public: - Config(/* args */); - ~Config(); - }; -} +class Config { + public: + Config(/* args */); + ~Config(); + void Initialize(); + int GetPort(); + + private: + int port_; +}; +} // namespace wxhelper #endif \ No newline at end of file diff --git a/src/global_context.cc b/src/global_context.cc index 909973d..c711671 100644 --- a/src/global_context.cc +++ b/src/global_context.cc @@ -11,14 +11,15 @@ void GlobalContext::initialize(HMODULE module) { module_ = module; DWORD base = Utils::GetWeChatWinBase(); config.emplace(); + config->Initialize(); log.emplace(); - log->initialize(); + log->Initialize(); hide_module.emplace(); #ifndef _DEBUG hide_module->Hide(module_); #endif - HttpServer::GetInstance().Init(19088); + HttpServer::GetInstance().Init(config->GetPort()); HttpServer::GetInstance().HttpStart(); DB::GetInstance().init(base); contact_mgr.emplace(ContactMgr{base}); diff --git a/src/log.cc b/src/log.cc index 77841a8..c955235 100644 --- a/src/log.cc +++ b/src/log.cc @@ -7,7 +7,7 @@ Log::Log(/* args */) {} Log::~Log() {} -void Log::initialize() { +void Log::Initialize() { el::Configurations conf; // 启用日志 diff --git a/src/log.h b/src/log.h index ebca51a..9718263 100644 --- a/src/log.h +++ b/src/log.h @@ -8,7 +8,7 @@ namespace wxhelper{ public: Log(/* args */); ~Log(); - void initialize(); + void Initialize(); }; From c0b55da0156b2e141030e684d48f995b0de5aff6 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Wed, 26 Apr 2023 10:58:00 +0800 Subject: [PATCH 72/97] =?UTF-8?q?fix:=20=E6=B3=A8=E5=85=A5=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E7=AE=80=E5=8D=95=E6=94=AF=E6=8C=81pid=E6=B3=A8?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/ConsoleApplication.cc | 198 ++++++++++++++++++++++++++++++++++- 1 file changed, 197 insertions(+), 1 deletion(-) diff --git a/source/ConsoleApplication.cc b/source/ConsoleApplication.cc index f746b94..c86dc6d 100644 --- a/source/ConsoleApplication.cc +++ b/source/ConsoleApplication.cc @@ -771,6 +771,128 @@ error: return result; } +int InjectDllAndStartHttpByPid(unsigned int pid, 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; + 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 }; + + ulDllLength = (wcslen(szDllPath) + 1) * sizeof(wchar_t); + hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pid); + 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()){ @@ -825,6 +947,58 @@ int InjectDll(wchar_t* szPName, wchar_t* szDllPath) return result; } +int InjectDllByPid(unsigned int pid, wchar_t* szDllPath) +{ + if(!EnableDebugPrivilege()){ + return 0; + } + int result = 0; + HANDLE hRemoteThread; + LPTHREAD_START_ROUTINE lpSysLibAddr; + HINSTANCE__* hKernelModule; + LPVOID lpRemoteDllBase; + HANDLE hProcess; + size_t ulDllLength; + + ulDllLength = (wcslen(szDllPath) + 1) * sizeof(wchar_t); + hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pid); + 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("pid : %d ", pid); + 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; @@ -878,8 +1052,9 @@ int main(int argc, char** argv) int port = 0; ULONG pid = 0; + unsigned int injectPid =0; - while ((param = getopt(argc, argv, "i:p:u:d:m:P:h")) != -1) + while ((param = getopt(argc, argv, "i:p:u:d:m:P:I:h")) != -1) { switch (param) { @@ -916,6 +1091,9 @@ int main(int argc, char** argv) case 'P': port = std::atoi(optarg); break; + case 'I': + injectPid = std::atoi(optarg); + break; default: abort(); break; @@ -925,6 +1103,24 @@ int main(int argc, char** argv) if (pid) { FindHandles(pid, (LPSTR)"_WeChat_App_Instance_Identity_Mutex_Name", TRUE, TRUE); } + if (injectPid != 0 && cDllPath[0] != 0) + { + if(cDllPath[0] != '\0') + { + if (port == 0) { + std::wstring wsPath = Utf8ToUnicode(cDllPath); + int ret = InjectDllByPid(injectPid, (wchar_t*)wsPath.c_str()); + printf(" 注入结果:%i \n", ret); + } + else + { + std::wstring wsPath = Utf8ToUnicode(cDllPath); + int ret = InjectDllAndStartHttpByPid(injectPid, (wchar_t*)wsPath.c_str(), port); + printf(" 注入结果:%i \n", ret); + } + } + } + if (cInjectprogram[0] != 0 && cDllPath[0] != 0) { From 4c9a292c77686c8d739adb597537717f01d5bad2 Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Thu, 27 Apr 2023 08:51:22 +0800 Subject: [PATCH 73/97] clean --- README.md | 13 +- source/ConsoleApplication.cpp | 956 ------- src/account_mgr.cc | 219 -- src/account_mgr.h | 19 - src/api_route.h | 78 - src/base_mgr.cc | 13 - src/base_mgr.h | 13 - src/chat_room_mgr.cc | 375 --- src/chat_room_mgr.h | 28 - src/config.cc | 17 - src/config.h | 17 - src/contact_mgr.cc | 219 -- src/contact_mgr.h | 21 - src/db.cc | 549 ---- src/db.h | 38 - src/dllMain.cc | 32 - src/easylogging++.cc | 3120 ---------------------- src/easylogging++.h | 4576 --------------------------------- src/global_context.cc | 39 - src/global_context.h | 43 - src/handler.h | 10 - src/hide_module.cc | 64 - src/hide_module.h | 48 - src/hooks.cc | 533 ---- src/hooks.h | 23 - src/http_handler.cc | 598 ----- src/http_handler.h | 17 - src/http_server.cc | 100 - src/http_server.h | 35 - src/log.cc | 37 - src/log.h | 18 - src/misc_mgr.cc | 436 ---- src/misc_mgr.h | 24 - src/new_sqlite3.h | 195 -- src/pch.h | 22 - src/send_message_mgr.cc | 236 -- src/send_message_mgr.h | 18 - src/singleton.h | 21 - src/sns_mgr.cc | 52 - src/sns_mgr.h | 14 - src/utils.cc | 214 -- src/utils.h | 79 - src/wechat_function.h | 772 ------ 43 files changed, 12 insertions(+), 13939 deletions(-) delete mode 100644 source/ConsoleApplication.cpp delete mode 100644 src/account_mgr.cc delete mode 100644 src/account_mgr.h delete mode 100644 src/api_route.h delete mode 100644 src/base_mgr.cc delete mode 100644 src/base_mgr.h delete mode 100644 src/chat_room_mgr.cc delete mode 100644 src/chat_room_mgr.h delete mode 100644 src/config.cc delete mode 100644 src/config.h delete mode 100644 src/contact_mgr.cc delete mode 100644 src/contact_mgr.h delete mode 100644 src/db.cc delete mode 100644 src/db.h delete mode 100644 src/dllMain.cc delete mode 100644 src/easylogging++.cc delete mode 100644 src/easylogging++.h delete mode 100644 src/global_context.cc delete mode 100644 src/global_context.h delete mode 100644 src/handler.h delete mode 100644 src/hide_module.cc delete mode 100644 src/hide_module.h delete mode 100644 src/hooks.cc delete mode 100644 src/hooks.h delete mode 100644 src/http_handler.cc delete mode 100644 src/http_handler.h delete mode 100644 src/http_server.cc delete mode 100644 src/http_server.h delete mode 100644 src/log.cc delete mode 100644 src/log.h delete mode 100644 src/misc_mgr.cc delete mode 100644 src/misc_mgr.h delete mode 100644 src/new_sqlite3.h delete mode 100644 src/pch.h delete mode 100644 src/send_message_mgr.cc delete mode 100644 src/send_message_mgr.h delete mode 100644 src/singleton.h delete mode 100644 src/sns_mgr.cc delete mode 100644 src/sns_mgr.h delete mode 100644 src/utils.cc delete mode 100644 src/utils.h delete mode 100644 src/wechat_function.h diff --git a/README.md b/README.md index 1a59ba1..46e2940 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ cmake --build .. // -u 卸载程序名 -d 卸载dll名称 // -m pid 关闭微信互斥体,多开微信 // -P port 指定http端口,需要使用 specify-port 分支的生成的dll + // -I 注入程序的pid //注入 ConsoleInject.exe -i demo.exe -p E:\testInject.dll //卸载 @@ -130,7 +131,17 @@ cmake --build .. //多开 ConsoleInject.exe -m 1222 // 注入并指定http端口 - ConsoleInject.exe -i demo.exe -p E:\testInject.dll -P 18888 + ConsoleInject.exe -i demo.exe -p E:\testInject.dll -P 18888 + // 注入指定pid并关闭多开限制 + ConsoleInject.exe -I 15048 -p E:\testInject.dll -m 15048 + +``` + +6.如果想改变端口,可以在微信目录下创建config.ini配置文件,修改端口即可。不创建则默认端口19088。 +``` shell +[config] +port=19099 + ``` #### 更新说明 diff --git a/source/ConsoleApplication.cpp b/source/ConsoleApplication.cpp deleted file mode 100644 index d06f638..0000000 --- a/source/ConsoleApplication.cpp +++ /dev/null @@ -1,956 +0,0 @@ -// 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; -} - - -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; - -} - -BOOL RemoteLibraryFunction(HANDLE hProcess, LPCSTR lpModuleName, LPCSTR lpProcName, LPVOID lpParameters, SIZE_T dwParamSize, PVOID* ppReturn) -{ - LPVOID lpRemoteParams = NULL; - - LPVOID lpFunctionAddress = GetProcAddress(GetModuleHandleA(lpModuleName), lpProcName); - if (!lpFunctionAddress) lpFunctionAddress = GetProcAddress(LoadLibraryA(lpModuleName), lpProcName); - if (!lpFunctionAddress) goto ErrorHandler; - - if (lpParameters) - { - lpRemoteParams = VirtualAllocEx(hProcess, NULL, dwParamSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); - if (!lpRemoteParams) goto ErrorHandler; - - SIZE_T dwBytesWritten = 0; - BOOL result = WriteProcessMemory(hProcess, lpRemoteParams, lpParameters, dwParamSize, &dwBytesWritten); - if (!result || dwBytesWritten < 1) goto ErrorHandler; - } - - HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpFunctionAddress, lpRemoteParams, NULL, NULL); - if (!hThread) goto ErrorHandler; - - DWORD dwOut = 0; - while (GetExitCodeThread(hThread, &dwOut)) { - if (dwOut != STILL_ACTIVE) { - *ppReturn = (PVOID)dwOut; - break; - } - } - - return TRUE; - -ErrorHandler: - if (lpRemoteParams) VirtualFreeEx(hProcess, lpRemoteParams, dwParamSize, MEM_RELEASE); - return FALSE; -} - -int InjectDllAndStartHttp(wchar_t* szPName, wchar_t* szDllPath, DWORD port) -{ - 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 = L"wxhelper.dll"; - size_t dllNameLen = wcslen(dllName) * 2 + 2; - char* funcName = "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) -{ - 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"); - 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, "_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/src/account_mgr.cc b/src/account_mgr.cc deleted file mode 100644 index e8579ee..0000000 --- a/src/account_mgr.cc +++ /dev/null @@ -1,219 +0,0 @@ -#include "pch.h" -#include "account_mgr.h" -#include "easylogging++.h" - - -#include "wechat_function.h" - -using namespace std; -namespace wxhelper { - AccountMgr::AccountMgr(DWORD base):BaseMgr(base){ - - } - AccountMgr::~AccountMgr(){ - - } -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 - CALL accout_service_addr - MOV service_addr,EAX - POPAD - } - if (service_addr) { - if (*(DWORD *)(service_addr + 0x44) == 0 || - *(DWORD *)(service_addr + 0x44 + 0x10) == 0) { - out.wxid = string(); - } else { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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)); - } - } - - 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 = Utils::Bytes2Hex((BYTE *)byte_addr, len); - } - } - - 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 (data_save_path.ptr) { - 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 = Utils::WstringToUTF8( - wstring(current_data_path.ptr, current_data_path.length)); - } else { - out.current_data_path = string(); - } - return 1; -} - -int AccountMgr::CheckLogin() { - int success = -1; - DWORD accout_service_addr = base_addr_ + WX_ACCOUNT_SERVICE_OFFSET; - DWORD service_addr = NULL; - __asm { - PUSHAD - CALL accout_service_addr - MOV service_addr,EAX - POPAD - } - if (service_addr) { - success = *(DWORD *)(service_addr + 0x4C8); - } - return success; -} - -int AccountMgr::Logout() { - int success = -1; - if (!CheckLogin()) { - return success; - } - DWORD account_service_addr = base_addr_ + WX_ACCOUNT_SERVICE_OFFSET; - DWORD logout_addr = base_addr_ + WX_LOGOUT_OFFSET; - __asm { - PUSHAD - CALL account_service_addr - PUSH 0x0 - MOV ECX,EAX - CALL logout_addr - MOV success,EAX - POPAD - } - return success; -} - -} // namespace wxhelper \ No newline at end of file diff --git a/src/account_mgr.h b/src/account_mgr.h deleted file mode 100644 index 67da58d..0000000 --- a/src/account_mgr.h +++ /dev/null @@ -1,19 +0,0 @@ -#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_route.h b/src/api_route.h deleted file mode 100644 index 224b7fe..0000000 --- a/src/api_route.h +++ /dev/null @@ -1,78 +0,0 @@ -#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 deleted file mode 100644 index 4131e0e..0000000 --- a/src/base_mgr.cc +++ /dev/null @@ -1,13 +0,0 @@ -#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 deleted file mode 100644 index 86f4608..0000000 --- a/src/base_mgr.h +++ /dev/null @@ -1,13 +0,0 @@ -#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_mgr.cc b/src/chat_room_mgr.cc deleted file mode 100644 index a0c6a12..0000000 --- a/src/chat_room_mgr.cc +++ /dev/null @@ -1,375 +0,0 @@ -#include "pch.h" -#include "chat_room_mgr.h" - -#include "db.h" - -using namespace std; - -namespace wxhelper { - -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 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 - LEA ECX,chat_room_info - CALL create_chat_room_info_addr - CALL get_chat_room_mgr_addr - PUSH 0x0 - LEA ECX,chat_room_info - PUSH ECX - LEA ECX,chat_room - PUSH ECX - MOV ECX,EAX - CALL get_chat_room_detail_addr - MOV success,EAX - POPAD - } - 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; - - 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; - - 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; - - __asm { - PUSHAD - LEA ECX,chat_room_info - CALL free_chat_room_info_addr - POPAD - } - return success; -} - -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; - DWORD members_ptr = (DWORD)&list->start; - for (int i = 0; i < len; i++) { - WeChatString pwxid(wxids[i]); - members.push_back(pwxid); - } - 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 - SUB ESP,0x14 - MOV ESI,EAX - MOV ECX,ESP - LEA EDI,chat_room - PUSH EDI - CALL init_chat_msg_addr - MOV ECX,ESI - MOV EAX,dword ptr[members_ptr] - PUSH EAX - CALL del_member_addr - MOV success,EAX - POPAD - } - return success; -} - -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; - DWORD members_ptr = (DWORD)&list->start; - for (int i = 0; i < len; i++) { - WeChatString pwxid(wxids[i]); - members.push_back(pwxid); - } - 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 - CALL get_chat_room_mgr_addr - SUB ESP,0x8 - MOV temp,EAX - MOV ECX,ESP - MOV dword ptr [ECX],0x0 - MOV dword ptr [ECX + 4],0x0 - TEST ESI,ESI - SUB ESP,0x14 - MOV ECX,ESP - LEA EAX,chat_room - PUSH EAX - CALL init_chat_msg_addr - MOV ECX,temp - MOV EAX,dword ptr[members_ptr] - PUSH EAX - CALL add_member_addr - MOV success,EAX - POPFD - POPAD - } - return success; -} - -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; - char buffer[0x1D4] = {0}; - 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 - CALL create_chat_room_addr - CALL get_chat_room_mgr_addr - LEA EAX, buffer - PUSH EAX - PUSH chat_room_ptr - CALL get_member_addr - MOVZX EAX,AL - MOV success,EAX - POPAD - } - 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.admin = new wchar_t[wcslen(admin) + 1]; - wmemcpy(out.admin, admin, wcslen(admin) + 1); - - __asm { - LEA ECX,buffer - CALL free_chat_room_addr - } - return success; -} - -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 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 - 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; -} - -int ChatRoomMgr::SetTopMsg(wchar_t* wxid, ULONG64 msg_id) { - int success = -1; - 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; - } - __asm{ - PUSHAD - PUSHFD - 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 - CALL get_chat_room_mgr_addr - 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 - POPFD - POPAD - } - - return success; -} - -int ChatRoomMgr::RemoveTopMsg(wchar_t* chat_room_id, ULONG64 msg_id) { - int success = -1; - WeChatString chat_room(chat_room_id); - DWORD get_chat_room_mgr_addr = base_addr_ + WX_CHAT_ROOM_MGR_OFFSET; - DWORD new_chat_msg_addr = base_addr_ + WX_NEW_CHAT_MSG_OFFSET; - DWORD init_chat_msg_addr = base_addr_ + WX_INIT_CHAT_MSG_OFFSET; - DWORD remove_top_msg_addr = base_addr_ + WX_REMOVE_TOP_MSG_OFFSET; - - __asm { - PUSHAD - CALL get_chat_room_mgr_addr - MOV EDI,dword ptr [msg_id] - LEA EAX,chat_room - MOV ESI,dword ptr [msg_id + 0x4] - SUB ESP,0x14 - MOV ECX,ESP - PUSH EAX - CALL init_chat_msg_addr - PUSH ESI - PUSH EDI - CALL remove_top_msg_addr - MOV success,EAX - POPAD - } - - return success; -} - -std::wstring ChatRoomMgr::GetChatRoomMemberNickname(wchar_t* chat_room_id, - wchar_t* wxid) { - WeChatString chat_room(chat_room_id); - WeChatString member_id(wxid); - WeChatString nickname(NULL); - DWORD get_chat_room_mgr_addr = base_addr_ + WX_CHAT_ROOM_MGR_OFFSET; - DWORD get_nickname_addr = base_addr_ + WX_GET_MEMBER_NICKNAME_OFFSET; - 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; - __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; -} -} // namespace wxhelper \ No newline at end of file diff --git a/src/chat_room_mgr.h b/src/chat_room_mgr.h deleted file mode 100644 index ce6ef19..0000000 --- a/src/chat_room_mgr.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef WXHELPER_CHAT_ROOM_MGR_H_ -#define WXHELPER_CHAT_ROOM_MGR_H_ -#include "wechat_function.h" -#include "base_mgr.h" -namespace wxhelper { -class ChatRoomMgr:public BaseMgr { - public: - explicit ChatRoomMgr(DWORD base); - ~ChatRoomMgr(); - 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); -}; -} // namespace wxhelper -#endif \ No newline at end of file diff --git a/src/config.cc b/src/config.cc deleted file mode 100644 index 171ff88..0000000 --- a/src/config.cc +++ /dev/null @@ -1,17 +0,0 @@ -#include "pch.h" -#include "config.h" - -namespace wxhelper { -Config::Config(/* args */) {} - -Config::~Config() {} - - -void Config::Initialize(){ - port_ = GetPrivateProfileInt("config", "Port", 19088, "./config.ini"); -} -int Config::GetPort(){ - return port_; -} - - } // namespace wxhelper \ No newline at end of file diff --git a/src/config.h b/src/config.h deleted file mode 100644 index ffde254..0000000 --- a/src/config.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef WXHELPER_CONFIG_H_ -#define WXHELPER_CONFIG_H_ - -namespace wxhelper { - -class Config { - public: - Config(/* args */); - ~Config(); - void Initialize(); - int GetPort(); - - private: - int port_; -}; -} // namespace wxhelper -#endif \ No newline at end of file diff --git a/src/contact_mgr.cc b/src/contact_mgr.cc deleted file mode 100644 index 9525e41..0000000 --- a/src/contact_mgr.cc +++ /dev/null @@ -1,219 +0,0 @@ -#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 instance =0; - Unkown null_obj={0,0,0,0,0,0xF}; - __asm{ - PUSHAD - PUSHFD - CALL contact_mgr_addr - 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 - 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 - } - 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 deleted file mode 100644 index 677d564..0000000 --- a/src/contact_mgr.h +++ /dev/null @@ -1,21 +0,0 @@ -#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); - int VerifyApply(wchar_t *v3, wchar_t *v4); -}; -} // namespace wxhelper - -#endif; \ No newline at end of file diff --git a/src/db.cc b/src/db.cc deleted file mode 100644 index 4c64ef7..0000000 --- a/src/db.cc +++ /dev/null @@ -1,549 +0,0 @@ -#include "pch.h" -#include "db.h" - -#include "base64.h" -#include "easylogging++.h" - -#include "wechat_function.h" -#include "utils.h" -using namespace std; - -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) { - bool is_utf8 = Utils::IsTextUtf8(it[i].content, it[i].content_len); - if (is_utf8) { - string content(it[i].content); - item.push_back(content); - } else { - string base64_str = - base64_encode((BYTE *)it[i].content, it[i].content_len); - item.push_back(base64_str); - } - - } 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; - TableInfo tb = {0}; - if (argv[1]) { - tb.name = new char[strlen(argv[1]) + 1]; - memcpy(tb.name, argv[1], strlen(argv[1]) + 1); - } else { - tb.name = (char *)"NULL"; - } - if (argv[2]) { - tb.table_name = new char[strlen(argv[2]) + 1]; - memcpy(tb.table_name, argv[2], strlen(argv[2]) + 1); - } else { - tb.table_name = (char *)"NULL"; - } - if (argv[3]) { - tb.rootpage = new char[strlen(argv[3]) + 1]; - memcpy(tb.rootpage, argv[3], strlen(argv[3]) + 1); - } else { - tb.rootpage = (char *)"NULL"; - } - if (argv[4]) { - tb.sql = new char[strlen(argv[4]) + 1]; - memcpy(tb.sql, argv[4], strlen(argv[4]) + 1); - } else { - tb.sql = (char *)"NULL"; - } - tb.name_len = strlen(tb.name); - tb.table_name_len = strlen(tb.table_name); - tb.sql_len = strlen(tb.sql); - tb.rootpage_len = strlen(tb.rootpage); - pdata->tables.push_back(tb); - pdata->count = pdata->tables.size(); - return 0; -} - -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); - DWORD emotion_db_addr = *(DWORD *)(p_contact_addr + DB_EMOTION_OFFSET); - DWORD media_db_addr = *(DWORD *)(p_contact_addr + DB_MEDIA_OFFSET); - DWORD bizchat_msg_db_addr = - *(DWORD *)(p_contact_addr + DB_BIZCHAT_MSG_OFFSET); - DWORD function_msg_db_addr = - *(DWORD *)(p_contact_addr + DB_FUNCTION_MSG_OFFSET); - - // microMsg.db - DatabaseInfo micro_msg_db{0}; - micro_msg_db.db_name = (wchar_t *)(*( - DWORD *)(p_contact_addr + DB_MICRO_MSG_OFFSET + DB_NAME_OFFSET)); - micro_msg_db.db_name_len = - *(DWORD *)(p_contact_addr + DB_MICRO_MSG_OFFSET + DB_NAME_OFFSET + 0x4); - micro_msg_db.handle = micro_msg_db_addr; - ExecuteSQL(micro_msg_db_addr, - "select * from sqlite_master where type=\"table\";", - (DWORD)GetDbInfo, µ_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; - - // chatMsg.db - DatabaseInfo chat_msg_db{0}; - chat_msg_db.db_name = (wchar_t *)(*( - DWORD *)(p_contact_addr + DB_CHAT_MSG_OFFSET + DB_NAME_OFFSET)); - chat_msg_db.db_name_len = - *(DWORD *)(p_contact_addr + DB_CHAT_MSG_OFFSET + DB_NAME_OFFSET + 0x4); - chat_msg_db.handle = chat_msg_db_addr; - ExecuteSQL(chat_msg_db_addr, - "select * from sqlite_master where type=\"table\";", - (DWORD)GetDbInfo, &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; - - // misc.db - DatabaseInfo misc_db{0}; - misc_db.db_name = - (wchar_t *)(*(DWORD *)(p_contact_addr + DB_MISC_OFFSET + DB_NAME_OFFSET)); - misc_db.db_name_len = - *(DWORD *)(p_contact_addr + DB_MISC_OFFSET + DB_NAME_OFFSET + 0x4); - 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); - wstring misc_name = wstring(( - wchar_t *)(*(DWORD *)(p_contact_addr + DB_MISC_OFFSET + DB_NAME_OFFSET))); - dbmap_[misc_name] = misc_db; - - // emotion.db - DatabaseInfo emotion_db{0}; - emotion_db.db_name = (wchar_t *)(*( - DWORD *)(p_contact_addr + DB_EMOTION_OFFSET + DB_NAME_OFFSET)); - emotion_db.db_name_len = - *(DWORD *)(p_contact_addr + DB_EMOTION_OFFSET + DB_NAME_OFFSET + 0x4); - emotion_db.handle = emotion_db_addr; - ExecuteSQL(emotion_db_addr, - "select * from sqlite_master where type=\"table\";", - (DWORD)GetDbInfo, &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; - - // media.db - DatabaseInfo media_db{0}; - media_db.db_name = (wchar_t *)(*(DWORD *)(p_contact_addr + DB_MEDIA_OFFSET + - DB_NAME_OFFSET)); - media_db.db_name_len = - *(DWORD *)(p_contact_addr + DB_MEDIA_OFFSET + DB_NAME_OFFSET + 0x4); - 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); - wstring media_name = wstring((wchar_t *)(*( - DWORD *)(p_contact_addr + DB_MEDIA_OFFSET + DB_NAME_OFFSET))); - dbmap_[media_name] = media_db; - - // functionMsg.db - DatabaseInfo function_msg_db{0}; - function_msg_db.db_name = (wchar_t *)(*( - DWORD *)(p_contact_addr + DB_FUNCTION_MSG_OFFSET + DB_NAME_OFFSET)); - function_msg_db.db_name_len = *( - DWORD *)(p_contact_addr + DB_FUNCTION_MSG_OFFSET + DB_NAME_OFFSET + 0x4); - function_msg_db.handle = function_msg_db_addr; - ExecuteSQL(function_msg_db_addr, - "select * from sqlite_master where type=\"table\";", - (DWORD)GetDbInfo, &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; - - if (bizchat_msg_db_addr) { - // functionMsg.db maybe null - DatabaseInfo bizchat_msg_db{0}; - bizchat_msg_db.db_name = (wchar_t *)(*( - DWORD *)(p_contact_addr + DB_FUNCTION_MSG_OFFSET + DB_NAME_OFFSET)); - bizchat_msg_db.db_name_len = - *(DWORD *)(p_contact_addr + DB_FUNCTION_MSG_OFFSET + DB_NAME_OFFSET + - 0x4); - bizchat_msg_db.handle = bizchat_msg_db_addr; - ExecuteSQL(bizchat_msg_db_addr, - "select * from sqlite_master where type=\"table\";", - (DWORD)GetDbInfo, &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; - } - // 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_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 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; - 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); - 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; - } - } - - // publicMsg.db - 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.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; - } - - // Favorite.db - 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_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; - } - - DatabaseInfo db_end = {0}; - dbs_.push_back(db_end); -#ifdef _DEBUG - 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]); - return ret_array; -} - -DWORD DB::GetDbHandleByDbName(wchar_t *dbname) { - if (dbmap_.size() == 0) { - GetDbHandles(); - } - if (dbmap_.find(dbname) != dbmap_.end()) { - return dbmap_[dbname].handle; - } - - return 0; -} - -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); - if (handle == 0) { - LOG(INFO) << "MSG db handle is null"; - return 0; - } - vector> result; - int ret = Select(handle, (const char *)sql, result); - if (result.size() == 0) continue; - dbIndex = dbmap_[dbname].extrainfo; - return stoi(result[1][0]); - } - return 0; -} - -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); - 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) { - LOG(INFO) << "MSG db handle is null"; - return {}; - } - vector> result; - int ret = Select(handle, (const char *)sql, result); - if (result.size() == 0) continue; - return result[1]; - } - return {}; -} - -std::string DB::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); - if (handle == 0) { - LOG(INFO) << "Media db handle is null"; - return ""; - } - vector> result; - int ret = Select(handle, (const char *)sql, result); - if (result.size() == 0) continue; - return result[1][0]; - } - return ""; -} -} // namespace wxhelper \ No newline at end of file diff --git a/src/db.h b/src/db.h deleted file mode 100644 index f8927fe..0000000 --- a/src/db.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef WXHELPER_DB_H_ -#define WXHELPER_DB_H_ -#include -#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/dllMain.cc b/src/dllMain.cc deleted file mode 100644 index 6a54435..0000000 --- a/src/dllMain.cc +++ /dev/null @@ -1,32 +0,0 @@ -#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: { - DisableThreadLibraryCalls(hModule); - GlobalContext::GetInstance().initialize(hModule); - break; - } - case DLL_THREAD_ATTACH: { - break; - } - case DLL_THREAD_DETACH: { - break; - } - case DLL_PROCESS_DETACH: { - GlobalContext::GetInstance().finally(); - break; - } - } - return TRUE; -} diff --git a/src/easylogging++.cc b/src/easylogging++.cc deleted file mode 100644 index 4a8d927..0000000 --- a/src/easylogging++.cc +++ /dev/null @@ -1,3120 +0,0 @@ -// -// Bismillah ar-Rahmaan ar-Raheem -// -// Easylogging++ v9.96.7 -// 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 -// - -#include "easylogging++.h" - -#if defined(AUTO_INITIALIZE_EASYLOGGINGPP) -INITIALIZE_EASYLOGGINGPP -#endif - -namespace el { - -// el::base -namespace base { -// el::base::consts -namespace consts { - -// Level log values - These are values that are replaced in place of %level format specifier -// Extra spaces after format specifiers are only for readability purposes in log files -static const base::type::char_t* kInfoLevelLogValue = ELPP_LITERAL("INFO"); -static const base::type::char_t* kDebugLevelLogValue = ELPP_LITERAL("DEBUG"); -static const base::type::char_t* kWarningLevelLogValue = ELPP_LITERAL("WARNING"); -static const base::type::char_t* kErrorLevelLogValue = ELPP_LITERAL("ERROR"); -static const base::type::char_t* kFatalLevelLogValue = ELPP_LITERAL("FATAL"); -static const base::type::char_t* kVerboseLevelLogValue = - ELPP_LITERAL("VERBOSE"); // will become VERBOSE-x where x = verbose level -static const base::type::char_t* kTraceLevelLogValue = ELPP_LITERAL("TRACE"); -static const base::type::char_t* kInfoLevelShortLogValue = ELPP_LITERAL("I"); -static const base::type::char_t* kDebugLevelShortLogValue = ELPP_LITERAL("D"); -static const base::type::char_t* kWarningLevelShortLogValue = ELPP_LITERAL("W"); -static const base::type::char_t* kErrorLevelShortLogValue = ELPP_LITERAL("E"); -static const base::type::char_t* kFatalLevelShortLogValue = ELPP_LITERAL("F"); -static const base::type::char_t* kVerboseLevelShortLogValue = ELPP_LITERAL("V"); -static const base::type::char_t* kTraceLevelShortLogValue = ELPP_LITERAL("T"); -// Format specifiers - These are used to define log format -static const base::type::char_t* kAppNameFormatSpecifier = ELPP_LITERAL("%app"); -static const base::type::char_t* kLoggerIdFormatSpecifier = ELPP_LITERAL("%logger"); -static const base::type::char_t* kThreadIdFormatSpecifier = ELPP_LITERAL("%thread"); -static const base::type::char_t* kSeverityLevelFormatSpecifier = ELPP_LITERAL("%level"); -static const base::type::char_t* kSeverityLevelShortFormatSpecifier = ELPP_LITERAL("%levshort"); -static const base::type::char_t* kDateTimeFormatSpecifier = ELPP_LITERAL("%datetime"); -static const base::type::char_t* kLogFileFormatSpecifier = ELPP_LITERAL("%file"); -static const base::type::char_t* kLogFileBaseFormatSpecifier = ELPP_LITERAL("%fbase"); -static const base::type::char_t* kLogLineFormatSpecifier = ELPP_LITERAL("%line"); -static const base::type::char_t* kLogLocationFormatSpecifier = ELPP_LITERAL("%loc"); -static const base::type::char_t* kLogFunctionFormatSpecifier = ELPP_LITERAL("%func"); -static const base::type::char_t* kCurrentUserFormatSpecifier = ELPP_LITERAL("%user"); -static const base::type::char_t* kCurrentHostFormatSpecifier = ELPP_LITERAL("%host"); -static const base::type::char_t* kMessageFormatSpecifier = ELPP_LITERAL("%msg"); -static const base::type::char_t* kVerboseLevelFormatSpecifier = ELPP_LITERAL("%vlevel"); -static const char* kDateTimeFormatSpecifierForFilename = "%datetime"; -// Date/time -static const char* kDays[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; -static const char* kDaysAbbrev[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; -static const char* kMonths[12] = { "January", "February", "March", "April", "May", "June", "July", "August", - "September", "October", "November", "December" - }; -static const char* kMonthsAbbrev[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; -static const char* kDefaultDateTimeFormat = "%Y-%M-%d %H:%m:%s,%g"; -static const char* kDefaultDateTimeFormatInFilename = "%Y-%M-%d_%H-%m"; -static const int kYearBase = 1900; -static const char* kAm = "AM"; -static const char* kPm = "PM"; -// Miscellaneous constants - -static const char* kNullPointer = "nullptr"; -#if ELPP_VARIADIC_TEMPLATES_SUPPORTED -#endif // ELPP_VARIADIC_TEMPLATES_SUPPORTED -static const base::type::VerboseLevel kMaxVerboseLevel = 9; -static const char* kUnknownUser = "unknown-user"; -static const char* kUnknownHost = "unknown-host"; - - -//---------------- DEFAULT LOG FILE ----------------------- - -#if defined(ELPP_NO_DEFAULT_LOG_FILE) -# if ELPP_OS_UNIX -static const char* kDefaultLogFile = "/dev/null"; -# elif ELPP_OS_WINDOWS -static const char* kDefaultLogFile = "nul"; -# endif // ELPP_OS_UNIX -#elif defined(ELPP_DEFAULT_LOG_FILE) -static const char* kDefaultLogFile = ELPP_DEFAULT_LOG_FILE; -#else -static const char* kDefaultLogFile = "myeasylog.log"; -#endif // defined(ELPP_NO_DEFAULT_LOG_FILE) - - -#if !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG) -static const char* kDefaultLogFileParam = "--default-log-file"; -#endif // !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG) -#if defined(ELPP_LOGGING_FLAGS_FROM_ARG) -static const char* kLoggingFlagsParam = "--logging-flags"; -#endif // defined(ELPP_LOGGING_FLAGS_FROM_ARG) -static const char* kValidLoggerIdSymbols = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._"; -static const char* kConfigurationComment = "##"; -static const char* kConfigurationLevel = "*"; -static const char* kConfigurationLoggerId = "--"; -} -// el::base::utils -namespace utils { - -/// @brief Aborts application due with user-defined status -static void abort(int status, const std::string& reason) { - // Both status and reason params are there for debugging with tools like gdb etc - ELPP_UNUSED(status); - ELPP_UNUSED(reason); -#if defined(ELPP_COMPILER_MSVC) && defined(_M_IX86) && defined(_DEBUG) - // Ignore msvc critical error dialog - break instead (on debug mode) - _asm int 3 -#else - ::abort(); -#endif // defined(ELPP_COMPILER_MSVC) && defined(_M_IX86) && defined(_DEBUG) -} - -} // namespace utils -} // namespace base - -// el - -// LevelHelper - -const char* LevelHelper::convertToString(Level level) { - // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet. - if (level == Level::Global) return "GLOBAL"; - if (level == Level::Debug) return "DEBUG"; - if (level == Level::Info) return "INFO"; - if (level == Level::Warning) return "WARNING"; - if (level == Level::Error) return "ERROR"; - if (level == Level::Fatal) return "FATAL"; - if (level == Level::Verbose) return "VERBOSE"; - if (level == Level::Trace) return "TRACE"; - return "UNKNOWN"; -} - -struct StringToLevelItem { - const char* levelString; - Level level; -}; - -static struct StringToLevelItem stringToLevelMap[] = { - { "global", Level::Global }, - { "debug", Level::Debug }, - { "info", Level::Info }, - { "warning", Level::Warning }, - { "error", Level::Error }, - { "fatal", Level::Fatal }, - { "verbose", Level::Verbose }, - { "trace", Level::Trace } -}; - -Level LevelHelper::convertFromString(const char* levelStr) { - for (auto& item : stringToLevelMap) { - if (base::utils::Str::cStringCaseEq(levelStr, item.levelString)) { - return item.level; - } - } - return Level::Unknown; -} - -void LevelHelper::forEachLevel(base::type::EnumType* startIndex, const std::function& 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 deleted file mode 100644 index 6e81edf..0000000 --- a/src/easylogging++.h +++ /dev/null @@ -1,4576 +0,0 @@ -// -// 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/global_context.cc b/src/global_context.cc deleted file mode 100644 index c711671..0000000 --- a/src/global_context.cc +++ /dev/null @@ -1,39 +0,0 @@ -#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(); - config->Initialize(); - log.emplace(); - log->Initialize(); - hide_module.emplace(); - #ifndef _DEBUG - hide_module->Hide(module_); - #endif - - HttpServer::GetInstance().Init(config->GetPort()); - 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 deleted file mode 100644 index 0a6a7fa..0000000 --- a/src/global_context.h +++ /dev/null @@ -1,43 +0,0 @@ -#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 deleted file mode 100644 index 985843a..0000000 --- a/src/handler.h +++ /dev/null @@ -1,10 +0,0 @@ -#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 deleted file mode 100644 index 81e1b0d..0000000 --- a/src/hide_module.cc +++ /dev/null @@ -1,64 +0,0 @@ -#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 deleted file mode 100644 index 1cc4c0f..0000000 --- a/src/hide_module.h +++ /dev/null @@ -1,48 +0,0 @@ -#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/hooks.cc b/src/hooks.cc deleted file mode 100644 index c5147f9..0000000 --- a/src/hooks.cc +++ /dev/null @@ -1,533 +0,0 @@ -#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 deleted file mode 100644 index 960712e..0000000 --- a/src/hooks.h +++ /dev/null @@ -1,23 +0,0 @@ -#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 deleted file mode 100644 index 8450ca6..0000000 --- a/src/http_handler.cc +++ /dev/null @@ -1,598 +0,0 @@ -#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: { - 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: { - 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: { - 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, "Content-Type: application/json\r\n", "%s\n", ret.c_str()); - } - } 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 deleted file mode 100644 index 828d678..0000000 --- a/src/http_handler.h +++ /dev/null @@ -1,17 +0,0 @@ -#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 deleted file mode 100644 index 0a839fd..0000000 --- a/src/http_server.cc +++ /dev/null @@ -1,100 +0,0 @@ -#include "pch.h" -#include "http_server.h" - -#include - -#include "api_route.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 - Utils::CreateConsole(); -#endif - running_ = true; - thread_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)StartHttpServer, NULL, - NULL, 0); - return true; -} - -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() { - 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 deleted file mode 100644 index b4b170c..0000000 --- a/src/http_server.h +++ /dev/null @@ -1,35 +0,0 @@ -#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_; - HANDLE thread_; -}; -} // namespace wxhelper - -#endif diff --git a/src/log.cc b/src/log.cc deleted file mode 100644 index c955235..0000000 --- a/src/log.cc +++ /dev/null @@ -1,37 +0,0 @@ -#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 deleted file mode 100644 index 9718263..0000000 --- a/src/log.h +++ /dev/null @@ -1,18 +0,0 @@ -#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 deleted file mode 100644 index 8462307..0000000 --- a/src/misc_mgr.cc +++ /dev/null @@ -1,436 +0,0 @@ -#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)); - result += "\r\n"; - 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/new_sqlite3.h b/src/new_sqlite3.h deleted file mode 100644 index 28323f5..0000000 --- a/src/new_sqlite3.h +++ /dev/null @@ -1,195 +0,0 @@ -#ifndef NEW_SQLITE3_H_ -#define NEW_SQLITE3_H_ -#include "Windows.h" -#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); - -#endif \ No newline at end of file diff --git a/src/pch.h b/src/pch.h deleted file mode 100644 index 7dc3789..0000000 --- a/src/pch.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef PCH_H -#define PCH_H - - -#define GLOG_NO_ABBREVIATED_SEVERITIES -#include "framework.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "Windows.h" -#include "utils.h" - -#endif // PCH_H - - diff --git a/src/send_message_mgr.cc b/src/send_message_mgr.cc deleted file mode 100644 index ec71383..0000000 --- a/src/send_message_mgr.cc +++ /dev/null @@ -1,236 +0,0 @@ -#include "pch.h" -#include "send_message_mgr.h" - -#include "easylogging++.h" - -#include "wechat_function.h" -#include "db.h" -#include "contact_mgr.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 send_message_mgr_addr = base_addr_ + WX_SEND_MESSAGE_MGR_OFFSET; - DWORD send_text_msg_addr = base_addr_ + WX_SEND_TEXT_OFFSET; - DWORD free_chat_msg_addr = base_addr_ + 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; - WeChatString * at_users = new WeChatString[len+1]; - std::wstring at_msg = L""; - int number =0; - for (int i = 0; i < len; i++) { - std::wstring nickname; - if (!lstrcmpiW((wchar_t *)wxids[i], (wchar_t *)L"notify@all")) { - nickname = L"所有人"; - } else { - ContactMgr contact{base_addr_}; - nickname = contact.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; - } - std::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 send_message_mgr_addr = base_addr_ + WX_SEND_MESSAGE_MGR_OFFSET; - DWORD send_text_msg_addr = base_addr_ + WX_SEND_TEXT_OFFSET; - DWORD free_chat_msg_addr = base_addr_ + 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 - 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 - } - LOG_IF((success == -1), ERROR) << "SendText fail"; - 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 send_message_mgr_addr = base_addr_ + WX_SEND_MESSAGE_MGR_OFFSET; - DWORD init_chat_msg_addr = base_addr_ + WX_INIT_CHAT_MSG_OFFSET; - DWORD send_image_msg_addr = base_addr_ + WX_SEND_IMAGE_OFFSET; - DWORD free_msg_addr = base_addr_ + 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 app_msg_mgr_addr = base_addr_ + WX_APP_MSG_MGR_OFFSET; - DWORD init_chat_msg_addr = base_addr_ + WX_INIT_CHAT_MSG_OFFSET; - DWORD send_file_addr = base_addr_ + WX_SEND_FILE_OFFSET; - DWORD free_msg_addr = base_addr_ + 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 forward_msg_addr = base_addr_ + WX_FORWARD_MSG_OFFSET; - DWORD init_chat_msg_addr = base_addr_ + 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 deleted file mode 100644 index 7e34db5..0000000 --- a/src/send_message_mgr.h +++ /dev/null @@ -1,18 +0,0 @@ -#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/singleton.h b/src/singleton.h deleted file mode 100644 index 826a09c..0000000 --- a/src/singleton.h +++ /dev/null @@ -1,21 +0,0 @@ -#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_mgr.cc b/src/sns_mgr.cc deleted file mode 100644 index 274e91d..0000000 --- a/src/sns_mgr.cc +++ /dev/null @@ -1,52 +0,0 @@ -#include "pch.h" -#include "sns_mgr.h" - - -#include "wechat_function.h" - - -namespace wxhelper { -SNSMgr::SNSMgr(DWORD base):BaseMgr(base) {} -SNSMgr::~SNSMgr() {} -int SNSMgr::GetFirstPage() { - int success = -1; - 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 { - 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 - } - - return success; -} -int SNSMgr::GetNextPage(ULONG64 sns_id) { - int success = -1; - 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 { - 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; -} -} // namespace wxhelper \ No newline at end of file diff --git a/src/sns_mgr.h b/src/sns_mgr.h deleted file mode 100644 index cb90059..0000000 --- a/src/sns_mgr.h +++ /dev/null @@ -1,14 +0,0 @@ -#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/utils.cc b/src/utils.cc deleted file mode 100644 index d92bd2b..0000000 --- a/src/utils.cc +++ /dev/null @@ -1,214 +0,0 @@ -#include "utils.h" - -#include "pch.h" - -namespace wxhelper { -std::wstring Utils::UTF8ToWstring(const std::string &str) { - return Utils::AnsiToWstring(str, CP_UTF8); -} - -std::string Utils::WstringToUTF8(const std::wstring &str) { - return Utils::WstringToAnsi(str, CP_UTF8); -} - -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(); -} - -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; - freopen_s(&retStream, "CONOUT$", "w", stdout); - 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 false; - } - return true; -} - -void Utils::CloseConsole() { - fclose(stdin); - fclose(stdout); - fclose(stderr); - FreeConsole(); -} - -std::string Utils::EncodeHexString(const std::string &str) { - const std::string hex_table = "0123456789abcdef"; - 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); - } - return sb; -} - -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) { - ret += BYTE(hex_table.find(hex_str.at(i)) << 4 | - hex_table.find(hex_str.at(i + 1))); - } - return ret; -} - -std::string Utils::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 Utils::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; - } -} - -bool Utils::IsDigit(std::string str) { - if (str.length() == 0) { - return false; - } - for (auto it : str) { - if (it < '0' || it > '9') { - return false; - } - } - return true; -} - -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(); -} - -bool Utils::IsTextUtf8(const char *str,int length) { - char endian = 1; - bool littlen_endian = (*(char *)&endian == 1); - - size_t i; - int bytes_num; - unsigned char chr; - - i = 0; - bytes_num = 0; - while (i < length) { - if (littlen_endian) { - chr = *(str + i); - } else { // Big Endian - chr = (*(str + i) << 8) | *(str + i + 1); - i++; - } - - if (bytes_num == 0) { - if ((chr & 0x80) != 0) { - while ((chr & 0x80) != 0) { - chr <<= 1; - bytes_num++; - } - if ((bytes_num < 2) || (bytes_num > 6)) { - return false; - } - bytes_num--; - } - } else { - if ((chr & 0xC0) != 0x80) { - return false; - } - bytes_num--; - } - i++; - } - return (bytes_num == 0); -} -} // namespace wxhelper \ No newline at end of file diff --git a/src/utils.h b/src/utils.h deleted file mode 100644 index 284c324..0000000 --- a/src/utils.h +++ /dev/null @@ -1,79 +0,0 @@ -#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); - - static bool IsTextUtf8(const char * str,int length) ; - - 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_function.h b/src/wechat_function.h deleted file mode 100644 index a8719eb..0000000 --- a/src/wechat_function.h +++ /dev/null @@ -1,772 +0,0 @@ -#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 -#define WX_VERIFY_OK_OFFSET 0xa18bd0 -#define WX_NEW_ADD_FRIEND_HELPER_OFFSET 0xa17d50 -#define WX_FREE_ADD_FRIEND_HELPER_OFFSET 0xa17e70 - -// 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 0x80a800 -#define WX_OCR_MANAGER_OFFSET 0x80f270 -#define WX_DO_OCR_TASK_OFFSET 0x13da3e0 - - -//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 From 3b4a4aa3da06684817ec9cb97eb6f5c22b3489fb Mon Sep 17 00:00:00 2001 From: ttttupup <31303661+ttttupup@users.noreply.github.com> Date: Thu, 11 May 2023 16:57:54 +0800 Subject: [PATCH 74/97] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 46e2940..91e37df 100644 --- a/README.md +++ b/README.md @@ -224,3 +224,6 @@ https://github.com/ljc545w/ComWeChatRobot https://github.com/NationalSecurityAgency/ghidra https://github.com/x64dbg/x64dbg + +#### 讨论组 +https://t.me/+LmvAauweyUpjYzJl From 2ceece90f6c55d366c27dd0b8461db8cdd67505a Mon Sep 17 00:00:00 2001 From: sglmsn <36943585+sglmsn@users.noreply.github.com> Date: Thu, 25 May 2023 10:29:07 +0800 Subject: [PATCH 75/97] Update 3.9.2.23.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 接受好友请求 --- doc/3.9.2.23.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/3.9.2.23.md b/doc/3.9.2.23.md index 7a1ce01..68d8a6b 100644 --- a/doc/3.9.2.23.md +++ b/doc/3.9.2.23.md @@ -635,6 +635,7 @@ |---|---|---|---| |v3 |true |string| 添加好友消息内容里的encryptusername| |v4 |true |string| 添加好友消息内容里的ticket| +|permission |true |string| 0| ###### 返回字段 |返回字段|字段类型|说明 | @@ -647,7 +648,10 @@ 入参: ``` javascript { - "wxid":"wxid_o1112222" + + "v3":"v3", + "v4":"v4", + "permission":"0" } ``` 响应: From 472c53b863542ddb2384ccb4e475334ff0ecc41b Mon Sep 17 00:00:00 2001 From: sglmsn <36943585+sglmsn@users.noreply.github.com> Date: Thu, 25 May 2023 10:57:10 +0800 Subject: [PATCH 76/97] =?UTF-8?q?=E9=80=9A=E8=BF=87=E5=A5=BD=E5=8F=8B?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E6=8E=A5=E5=8F=A3=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/3.9.2.23.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/3.9.2.23.md b/doc/3.9.2.23.md index 68d8a6b..ac1fe01 100644 --- a/doc/3.9.2.23.md +++ b/doc/3.9.2.23.md @@ -635,7 +635,7 @@ |---|---|---|---| |v3 |true |string| 添加好友消息内容里的encryptusername| |v4 |true |string| 添加好友消息内容里的ticket| -|permission |true |string| 0| +|permission |true |string|好友权限,0是无限制,1是不让他看我,2是不看他,3是1+2| ###### 返回字段 |返回字段|字段类型|说明 | From fa27a73355c96e6d7a9b4400287bd2b01578abb8 Mon Sep 17 00:00:00 2001 From: ttttupup <31303661+ttttupup@users.noreply.github.com> Date: Fri, 26 May 2023 21:32:31 +0800 Subject: [PATCH 77/97] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 91e37df..39db5ae 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # wxhelper -wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26, 3.9.0.28, 3.9.2.23版本。 +wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26, 3.9.0.28, 3.9.2.23,3.9.2.26版本。 #### 免责声明: 本仓库发布的内容,仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关! From 81ea48b53478a96fa830dd9740721f9f8a38d4b2 Mon Sep 17 00:00:00 2001 From: ttttupup <31303661+ttttupup@users.noreply.github.com> Date: Mon, 29 May 2023 23:01:18 +0800 Subject: [PATCH 78/97] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 39db5ae..596b6f3 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,11 @@ port=19099 55.获取联系人或者群名称 56.获取消息附件(图片,视频,文件) 57.获取语音文件(silk3格式) +58.登录二维码 +59.邀请入群 +60.获取群/群成员详情 +61.撤回消息 + #### 感谢 https://github.com/ljc545w/ComWeChatRobot From 3f66d7b6d0eba19c0cf6109012156b31ba54a21b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Wed, 31 May 2023 11:20:49 +0800 Subject: [PATCH 79/97] =?UTF-8?q?java=E5=AE=A2=E6=88=B7=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- java_client/.gitignore | 33 +++ java_client/README.md | 15 ++ java_client/pom.xml | 184 +++++++++++++++ .../com/example/wxhk/WxhkApplication.java | 22 ++ .../com/example/wxhk/constant/WxMsgType.java | 35 +++ .../wxhk/controller/WxMsgController.java | 14 ++ .../example/wxhk/model/PrivateChatMsg.java | 50 ++++ .../com/example/wxhk/msg/WxMsgHandle.java | 214 ++++++++++++++++++ .../com/example/wxhk/tcp/vertx/ArrHandle.java | 54 +++++ .../example/wxhk/tcp/vertx/InitWeChat.java | 163 +++++++++++++ .../com/example/wxhk/tcp/vertx/VertxTcp.java | 79 +++++++ .../com/example/wxhk/util/HttpAsyncUtil.java | 76 +++++++ .../com/example/wxhk/util/HttpSendUtil.java | 57 +++++ .../com/example/wxhk/util/HttpSyncUtil.java | 34 +++ .../src/main/resources/application.properties | 4 + java_client/src/main/resources/exec/c.exe | Bin 0 -> 24064 bytes .../src/main/resources/exec/wxhelper.dll | Bin 0 -> 385024 bytes .../src/main/resources/logback-spring.xml | 171 ++++++++++++++ java_client/src/main/resources/spy.properties | 10 + .../example/wxhk/WxhkApplicationTests.java | 13 ++ .../example/wxhk/tcp/HttpAsyncUtilTest.java | 44 ++++ .../java/com/example/wxhk/tcp/XmlTest.java | 72 ++++++ 22 files changed, 1344 insertions(+) create mode 100644 java_client/.gitignore create mode 100644 java_client/README.md create mode 100644 java_client/pom.xml create mode 100644 java_client/src/main/java/com/example/wxhk/WxhkApplication.java create mode 100644 java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java create mode 100644 java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java create mode 100644 java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java create mode 100644 java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java create mode 100644 java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java create mode 100644 java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java create mode 100644 java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java create mode 100644 java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java create mode 100644 java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java create mode 100644 java_client/src/main/resources/application.properties create mode 100644 java_client/src/main/resources/exec/c.exe create mode 100644 java_client/src/main/resources/exec/wxhelper.dll create mode 100644 java_client/src/main/resources/logback-spring.xml create mode 100644 java_client/src/main/resources/spy.properties create mode 100644 java_client/src/test/java/com/example/wxhk/WxhkApplicationTests.java create mode 100644 java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java create mode 100644 java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java diff --git a/java_client/.gitignore b/java_client/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/java_client/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/java_client/README.md b/java_client/README.md new file mode 100644 index 0000000..9c712ab --- /dev/null +++ b/java_client/README.md @@ -0,0 +1,15 @@ +环境为jdk17 +执行之后会在当前项目所处磁盘根路径生成一个exec文件夹,然后会把src/main/resources/exec下的文件放在那避免因为路径问题出错 + +项目启动之后,会生成一个tcp服务端,用来接受hook信息,然后把接收的信息放在队列中,之后用一个线程去循环处理消息. +具体实现可以看 +```com.example.wxhk.tcp.vertx```包下的三个文件 + +com.example.wxhk.tcp.vertx.VertxTcp 这个是tcp服务端,接受信息 + +com.example.wxhk.tcp.vertx.InitWeChat 微信环境初始化 + +com.example.wxhk.tcp.vertx.ArrHandle 循环消息处理 + + +启动项目需要去修改配置文件的微信路径 diff --git a/java_client/pom.xml b/java_client/pom.xml new file mode 100644 index 0000000..3a98e6f --- /dev/null +++ b/java_client/pom.xml @@ -0,0 +1,184 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.0 + + + com.example + wxhk + 0.0.1-SNAPSHOT + wxhk + wxhk + + 17 + + + + org.springframework.boot + spring-boot-starter-mail + + + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + + + + + + io.netty + netty-all + 4.1.92.Final + + + com.squareup.okhttp3 + okhttp + 4.11.0 + + + + io.vertx + vertx-core + 4.4.2 + + + io.vertx + vertx-web + 4.4.2 + + + io.vertx + vertx-web-client + 4.4.2 + + + io.vertx + vertx-mysql-client + 4.4.2 + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.dromara.hutool + hutool-all + 6.0.0.M3 + + + com.fasterxml.jackson.core + jackson-databind + 2.15.1 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + + + + + + + + **/com/example/wxhk/** + + + + + + + true + lib/ + false + + com.example.wxhk.WxhkApplication + + + + resources/ + + + + ${project.build.directory}/pack/ + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + copy-dependencies + package + + copy-dependencies + + + + ${project.build.directory}/pack/lib + + + + + + maven-resources-plugin + + + + copy-resources + package + + copy-resources + + + + + src/main/resources + + + + ${project.build.directory}/pack/resources + + + + + + + + diff --git a/java_client/src/main/java/com/example/wxhk/WxhkApplication.java b/java_client/src/main/java/com/example/wxhk/WxhkApplication.java new file mode 100644 index 0000000..da0ccbd --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/WxhkApplication.java @@ -0,0 +1,22 @@ +package com.example.wxhk; + +import io.vertx.core.Vertx; +import io.vertx.core.VertxOptions; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class WxhkApplication { + public static final Vertx vertx; + + static { + vertx = Vertx.vertx(new VertxOptions().setWorkerPoolSize(5).setEventLoopPoolSize(5)); + } + + //ConsoleInject.exe -i WeChat.exe -p D:\wxhelper.dll + //ConsoleApplication.exe -I 4568 -p C:\wxhelper.dll -m 17484 -P 1888 + public static void main(String[] args) { + SpringApplication.run(WxhkApplication.class, args); + } + +} diff --git a/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java b/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java new file mode 100644 index 0000000..59b9570 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java @@ -0,0 +1,35 @@ +package com.example.wxhk.constant; + +/** + * 接受到的微信消息类型 + * + * @author wt + * @date 2023/05/26 + */ +public enum WxMsgType { + + /** + * + */ + 私聊信息(1), + 好友请求(37), + 收到名片(42), + 表情(47), + 转账和收款(49), + 收到转账之后(51), + /** + * 扫码触发,会触发2次, 有一次有编号,一次没有,还有登陆之后也有,很多情况都会调用这个 + */ + 扫码触发(10002), + + ; + Integer type; + + public Integer getType() { + return type; + } + + WxMsgType(Integer type) { + this.type = type; + } +} diff --git a/java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java b/java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java new file mode 100644 index 0000000..05e4ede --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java @@ -0,0 +1,14 @@ +package com.example.wxhk.controller; + + +import org.dromara.hutool.log.Log; + +public class WxMsgController { + + protected static final Log log = Log.get(); + + + void init(){ + + } +} diff --git a/java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java b/java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java new file mode 100644 index 0000000..ceb6da0 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java @@ -0,0 +1,50 @@ +package com.example.wxhk.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * 私聊 + * + * @author wt + * @date 2023/05/26 + */ +@Data +@Accessors(chain = true) +@JsonIgnoreProperties(ignoreUnknown = true) +public class PrivateChatMsg implements Serializable { + + /** + * 内容 + */ + + private String content; + /** + * 当是群聊的时候 为群id,否则为微信id + */ + private String fromGroup; + /** + * 微信id + */ + private String fromUser; + private Integer isSendMsg; + /** + * 1通过手机发送 + */ + private Integer isSendByPhone; + private Long msgId; + private Integer pid; + private String sign; + private String signature; + private String time; + private Integer timestamp; + + String path; + /** + * 类型 + */ + private Integer type; +} diff --git a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java new file mode 100644 index 0000000..a31fec0 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java @@ -0,0 +1,214 @@ +package com.example.wxhk.msg; + +import com.example.wxhk.constant.WxMsgType; +import com.example.wxhk.model.PrivateChatMsg; +import com.example.wxhk.tcp.vertx.InitWeChat; +import com.example.wxhk.util.HttpAsyncUtil; +import com.example.wxhk.util.HttpSendUtil; +import com.example.wxhk.util.HttpSyncUtil; +import io.vertx.core.json.JsonObject; +import org.dromara.hutool.core.util.XmlUtil; +import org.dromara.hutool.log.Log; +import org.springframework.stereotype.Component; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.math.BigDecimal; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class WxMsgHandle { + public static final ConcurrentHashMap map = new ConcurrentHashMap<>(32); + + public static ConcurrentHashMap cache = new ConcurrentHashMap<>(); + protected static final Log log=Log.get(); + + + + + public WxMsgHandle() { + add(chatMsg -> { + return 1; + },WxMsgType.私聊信息);// 好友请求 + add(chatMsg -> { + HttpSendUtil.通过好友请求(chatMsg); + return 1; + },WxMsgType.好友请求);// 好友请求 + add(chatMsg -> { + boolean f = 解析扫码支付第二段(chatMsg); + if(f){ + f=解析收款信息1段(chatMsg); + if(f){ + 解析收款信息2段(chatMsg); + } + } + return null; + },WxMsgType.转账和收款); + add(chatMsg -> { + boolean f = 解析扫码支付第一段(chatMsg); + return null; + },WxMsgType.扫码触发); + + + + } + + /** + * 解析扫码支付第一段,得到交易id和微信id + * + * @param chatMsg + * @return boolean 返回true 则继续解析,否则解析成功,不需要解析了 + */ + public static boolean 解析扫码支付第一段(PrivateChatMsg chatMsg) { + try { + Document document = XmlUtil.parseXml(chatMsg.getContent()); + Element documentElement = document.getDocumentElement(); + String localName = documentElement.getLocalName(); + if("sysmsg".equals(localName)){ + String type = documentElement.getAttribute("type"); + if("paymsg".equals(type)){ + NodeList outtradeno = documentElement.getElementsByTagName("outtradeno"); + if (outtradeno.getLength()>0) { + String textContent = outtradeno.item(0).getTextContent(); + String textContent1 = documentElement.getElementsByTagName("username").item(0).getTextContent(); + cache.put(textContent,textContent1); + return false; + } + } + } + } catch (Exception e) { + log.error(e); + } + return true; + } + + + /** + * 解析扫码支付第二段 + * + * @param chatMsg 聊天味精 + * @return boolean true 则 继续解析, false则解析成功,不需要再解析了 + */ + public static boolean 解析扫码支付第二段(PrivateChatMsg chatMsg) { + try { + Document document = XmlUtil.parseXml(chatMsg.getContent()); + Element documentElement = document.getDocumentElement(); + String localName = documentElement.getLocalName(); + if("msg".equals(localName)){ + NodeList outtradeno = documentElement.getElementsByTagName("weapp_path"); + if(outtradeno.getLength()>1){ + String textContent = outtradeno.item(1).getTextContent(); + Set> entries = cache.entrySet(); + Iterator> iterator = entries.iterator(); + while (iterator.hasNext()){ + Map.Entry next = iterator.next(); + if (textContent.contains(next.getKey())) { + // 得到了交易信息 + NodeList word = documentElement.getElementsByTagName("word"); + String monery = word.item(1).getTextContent(); + String remark = word.item(3).getTextContent(); + if(monery.startsWith("¥")){ + String substring = monery.substring(1); + BigDecimal decimal = new BigDecimal(substring); + log.info("扫码收款:{},付款人:{},付款备注:{}",decimal.stripTrailingZeros().toPlainString(),next.getValue(),remark); + iterator.remove(); + return false; + } + } + } + + + } + } + } catch (Exception e) { + log.error(e); + } + return true; + } + public static boolean 解析收款信息2段(PrivateChatMsg chatMsg) { + try { + Document document = XmlUtil.parseXml(chatMsg.getContent()); + Element documentElement = document.getDocumentElement(); + String localName = documentElement.getLocalName(); + if("msg".equals(localName)){ + if (documentElement.getElementsByTagName("transcationid").getLength()>0) { + String remark = documentElement.getElementsByTagName("pay_memo").item(0).getTextContent(); + String monery = documentElement.getElementsByTagName("feedesc").item(0).getTextContent(); + String receiver_username = documentElement.getElementsByTagName("receiver_username").item(0).getTextContent(); + if(InitWeChat.WXID_MAP.contains(receiver_username)){ + // 如果是自己转出去的,则不需要解析了 + return false; + } + + if(monery.startsWith("¥")){ + String substring = monery.substring(1); + BigDecimal decimal = new BigDecimal(substring); + log.info("收款:{},付款人:{},付款备注:{}",decimal.stripTrailingZeros().toPlainString(),receiver_username,remark); + return false; + } + } + } + } catch (Exception e) { + log.error(e); + } + return true; + } + + + /** + * 解析收款信息1段 + * 会自动进行收款 + * @param chatMsg + * @return boolean true则 继续解析,false则不需要解析了 + */ + public static boolean 解析收款信息1段(PrivateChatMsg chatMsg){ + try { + String content = chatMsg.getContent(); + Document document = XmlUtil.parseXml(content); + Node paysubtype = document.getElementsByTagName("paysubtype").item(0); + if("1".equals(paysubtype.getTextContent().trim())){ + // 手机发出去的 + String textContent = document.getElementsByTagName("receiver_username").item(0).getTextContent(); + if(!InitWeChat.WXID_MAP.contains(textContent)){ + // 如果不是机器人收款,则认为不需要解析了,大概率是机器人自己发出去的 + return false; + } + Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0); + Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0); + HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, new JsonObject().put("wxid",chatMsg.getFromUser()) + .put("transcationId",transcationid.getTextContent()) + .put("transferId",transferid.getTextContent())); + return false; + } + + } catch (Exception e) { + log.error(e); + } + return true; + } + + + public interface Handle{ + Object handle(PrivateChatMsg chatMsg); + } + + + + public void add(Handle handle, WxMsgType...type){ + for (WxMsgType integer : type) { + map.put(integer.getType(), handle); + } + } + + public static void exec(PrivateChatMsg chatMsg){ + Handle handle = map.get(chatMsg.getType()); + if (handle != null) { + handle.handle(chatMsg); + } + } +} diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java new file mode 100644 index 0000000..39add74 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java @@ -0,0 +1,54 @@ +package com.example.wxhk.tcp.vertx; + +import com.example.wxhk.model.PrivateChatMsg; +import com.example.wxhk.msg.WxMsgHandle; +import com.example.wxhk.util.HttpSendUtil; +import io.vertx.core.json.JsonObject; +import jakarta.annotation.PostConstruct; +import org.dromara.hutool.core.thread.NamedThreadFactory; +import org.dromara.hutool.log.Log; +import org.springframework.stereotype.Component; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * 消息处理 + * @author wt + * @date 2023/05/31 + */ +@Component +public class ArrHandle { + + protected static final Log log = Log.get(); + public static final ThreadPoolExecutor sub = new ThreadPoolExecutor(1, 10, 30, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new NamedThreadFactory("sub", false)); + + + @PostConstruct + public void exec(){ + for (int i = 0; i < sub.getCorePoolSize(); i++) { + sub.submit(() -> { + while (!Thread.currentThread().isInterrupted()){ + try { + JsonObject take = VertxTcp.LINKED_BLOCKING_QUEUE.take(); + log.info("{}",take.encode()); + + PrivateChatMsg privateChatMsg = take.mapTo(PrivateChatMsg.class); + if("weixin".equals(privateChatMsg.getFromUser())){ + String s = HttpSendUtil.获取当前登陆微信id(); + InitWeChat.WXID_MAP.add(s); + continue; + } + WxMsgHandle.exec(privateChatMsg); + } catch (Exception e) { + log.error(e); + } + } + log.error("退出线程了"); + }); + } + + } + +} diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java new file mode 100644 index 0000000..649eaf3 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java @@ -0,0 +1,163 @@ +package com.example.wxhk.tcp.vertx; + +import com.example.wxhk.util.HttpAsyncUtil; +import com.example.wxhk.util.HttpSyncUtil; +import io.vertx.core.impl.ConcurrentHashSet; +import io.vertx.core.json.JsonObject; +import org.dromara.hutool.core.io.file.FileUtil; +import org.dromara.hutool.core.net.NetUtil; +import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.core.thread.ThreadUtil; +import org.dromara.hutool.log.Log; +import org.dromara.hutool.setting.Setting; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; + +/** + * 微信注入环境初始化和相关方法 + * + * @author wt + * @date 2023/05/16 + */ +@Order(-1) +@Component +public class InitWeChat implements CommandLineRunner { + + public final static Log log = Log.get(); + + public static String wxPath; + + public static Integer wxPort; + public static Integer vertxPort; + /** + * wxhelper.dll 所在路径 + */ + public static File DLL_PATH; + + public static final ConcurrentHashSet WXID_MAP=new ConcurrentHashSet<>(); + + + public static void 注入dll(String wxPid) throws IOException { + String format = StrUtil.format("cmd /C c.exe -I {} -p {}\\wxhelper.dll -m {}", wxPid, DLL_PATH.getAbsolutePath(), wxPid); + Process exec = Runtime.getRuntime().exec(format, null, DLL_PATH); + log.info("注入结果:{}", new String(exec.getInputStream().readAllBytes(), "gbk")); + } + + @NotNull + private static File 环境初始化() { + File target = new File(new File("").getAbsolutePath().split("\\\\")[0] + "\\exec\\"); + try { + File wxPathFile = new File(wxPath); + File config = new File(wxPathFile.getParentFile(), "config.ini"); + Setting setting = new Setting(config.getAbsolutePath()); + setting.getGroupedMap().put("config", "port", String.valueOf(wxPort)); + setting.store(); + ClassPathResource classPathResource = new ClassPathResource("exec"); + File file = classPathResource.getFile(); + target.mkdir(); + for (File listFile : file.listFiles()) { + FileUtil.copy(listFile, target, true); + } + } catch (Exception e) { + log.error(e, "环境初始化失败,请检查"); + } + return target; + } + + /** + * 返回最后一个微信的pid + * + * @return {@link String} + * @throws IOException ioexception + */ + public static String createWx() throws IOException { + Runtime.getRuntime().exec("cmd /C \"" + wxPath + "\""); + return getWxPid(); + } + + @NotNull + private static String getWxPid() throws IOException { + String line = null; + try { + Process exec = Runtime.getRuntime().exec("cmd /C tasklist /FI \"IMAGENAME eq WeChat.exe\" "); + byte[] bytes = exec.getInputStream().readAllBytes(); + line = new String(bytes, "gbk"); + String[] split = line.split("\n"); + if (!line.contains("WeChat.exe")) { + return createWx(); + } + String[] split1 = split[split.length - 1].replaceAll("\\s{2,}", " ").split(" "); + return split1[1]; + } catch (IOException e) { + log.error("获取端口错误:{}", line); + throw e; + } + } + + public static Integer getWxPort() { + return wxPort; + } + + @Value("${wx.port}") + public void setWxPort(Integer wxPort) { + InitWeChat.wxPort = wxPort; + } + + public static String getWxPath() { + return wxPath; + } + + @Value("${wx.path}") + public void setWxPath(String wxPath) { + InitWeChat.wxPath = wxPath; + } + + public static Integer getVertxPort() { + return vertxPort; + } + + @Value("${vertx.port}") + public void setVertxPort(Integer vertxPort) { + InitWeChat.vertxPort = vertxPort; + } + + @Override + public void run(String... args) throws Exception { + //tasklist /FI "IMAGENAME eq WeChat.exe" /m + boolean usableLocalPort = NetUtil.isUsableLocalPort(wxPort); + if (usableLocalPort) { + DLL_PATH = 环境初始化(); + String wxPid = getWxPid(); + 注入dll(wxPid); + } + ThreadUtil.execute(() -> { + while (!Thread.currentThread().isInterrupted()){ + JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.检查微信登陆, new JsonObject()); + if(exec.getInteger("code").equals(1)){ + JsonObject dl = HttpSyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject()); + JsonObject jsonObject = dl.getJsonObject("data"); + String wx = jsonObject.getString("wxid"); + WXID_MAP.add(wx); + if (log.isDebugEnabled()) { + log.debug("检测到微信登陆:{}",wx); + } + break; + } + ThreadUtil.safeSleep(500); + } + + }); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + HttpSyncUtil.exec(HttpAsyncUtil.Type.关闭hook, new JsonObject()); + })); + //netstat -aon|findstr "端口号" + // c.exe -I 4568 -p D:\exec\wxhelper.dll -m 4568 + } +} diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java new file mode 100644 index 0000000..aad3656 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java @@ -0,0 +1,79 @@ +package com.example.wxhk.tcp.vertx; + +import com.example.wxhk.WxhkApplication; +import com.example.wxhk.util.HttpAsyncUtil; +import io.vertx.core.AbstractVerticle; +import io.vertx.core.DeploymentOptions; +import io.vertx.core.Future; +import io.vertx.core.Promise; +import io.vertx.core.json.JsonObject; +import io.vertx.core.net.NetServer; +import io.vertx.core.net.NetServerOptions; +import io.vertx.core.parsetools.JsonParser; +import org.dromara.hutool.log.Log; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.concurrent.LinkedBlockingQueue; + +/** + * 接受微信hook信息 + * @author wt + * @date 2023/05/26 + */ +@Component +@Order() +public class VertxTcp extends AbstractVerticle implements CommandLineRunner { + protected static final Log log = Log.get(); + NetServer netServer; + public final static LinkedBlockingQueue LINKED_BLOCKING_QUEUE = new LinkedBlockingQueue<>(); + + + + @Override + public void start(Promise startPromise) throws Exception { + netServer = vertx.createNetServer(new NetServerOptions() + .setPort(InitWeChat.getVertxPort()) + .setIdleTimeout(0) + .setLogActivity(false) + ); + netServer.connectHandler(socket -> { + JsonParser parser = JsonParser.newParser(); + parser.objectValueMode(); + parser.handler(event -> { + switch (event.type()) { + case START_OBJECT -> { + } + case END_OBJECT -> { + } + case START_ARRAY -> { + } + case END_ARRAY -> { + } + case VALUE -> { + LINKED_BLOCKING_QUEUE.add(event.objectValue()); + } + } + }); + socket.handler(parser); + }); + + Future listen = netServer.listen(); + listen.onComplete(event -> { + boolean succeeded = event.succeeded(); + if (succeeded) { + HttpAsyncUtil.exec(HttpAsyncUtil.Type.开启hook, new JsonObject().put("port", "8080").put("ip", "127.0.0.1")); + startPromise.complete(); + }else{ + startPromise.fail(event.cause()); + } + + }); + } + + @Override + public void run(String... args) throws Exception { + WxhkApplication.vertx.deployVerticle(this,new DeploymentOptions().setWorkerPoolSize(6)); + } +} diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java new file mode 100644 index 0000000..49d9015 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java @@ -0,0 +1,76 @@ +package com.example.wxhk.util; + +import com.example.wxhk.WxhkApplication; +import com.example.wxhk.tcp.vertx.InitWeChat; +import io.vertx.core.AsyncResult; +import io.vertx.core.Future; +import io.vertx.core.Handler; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.client.HttpResponse; +import io.vertx.ext.web.client.WebClient; +import io.vertx.ext.web.client.WebClientOptions; +import org.dromara.hutool.log.Log; + +/** + * http异步请求 + * + * @author wt + * @date 2023/05/25 + */ +public class HttpAsyncUtil { + protected static final Log log = Log.get(); + public static final WebClient client = WebClient.create(WxhkApplication.vertx,new WebClientOptions().setDefaultHost("localhost").setDefaultPort(InitWeChat.wxPort) + .setConnectTimeout(10000).setMaxPoolSize(10).setPoolEventLoopSize(10)); + + public static Future> exec(Type type, JsonObject object) { + return client.post(InitWeChat.wxPort, "localhost", "/api/?type=" + type.getType()) + .sendJsonObject(object) + .onSuccess(event -> + { + if (log.isDebugEnabled()) { + log.debug("type:{},{}",type.getType(), event.bodyAsJsonObject()); + } + } + ); + } + + public static Future> exec(Type type, JsonObject object, Handler>> handler) { + return client.post(InitWeChat.wxPort, "localhost", "/api/?type=" + type.getType()) + .sendJsonObject(object) + .onComplete(handler) + ; + + + } + + public enum Type { + 检查微信登陆("0"), + 获取登录信息("1"), + 发送文本("2"), + 发送at文本("3"), + 发送图片("5"), + 发送文件("6"), + 开启hook("9"), + 关闭hook("10"), + 通过好友申请("23"), + 获取群成员("25"), + 获取群成员昵称("26"), + 删除群成员("27"), + 确认收款("45"), + 联系人列表("46"), + 查询微信信息("55"), + + + ; + String type; + + public String getType() { + return type; + } + + Type(String type) { + this.type = type; + } + } +} diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java new file mode 100644 index 0000000..8a815ea --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java @@ -0,0 +1,57 @@ +package com.example.wxhk.util; + +import com.example.wxhk.model.PrivateChatMsg; +import com.example.wxhk.tcp.vertx.InitWeChat; +import io.vertx.core.json.JsonObject; +import org.dromara.hutool.core.util.XmlUtil; +import org.dromara.hutool.log.Log; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +/** + * 常见方法 + * @author wt + * @date 2023/05/29 + */ +public class HttpSendUtil { + + protected static final Log log = Log.get(); + public static JsonObject 通过好友请求(PrivateChatMsg msg){ + Document document = XmlUtil.parseXml(msg.getContent()); + String encryptusername = document.getDocumentElement().getAttribute("encryptusername"); + String ticket = document.getDocumentElement().getAttribute("ticket"); + return HttpSyncUtil.exec(HttpAsyncUtil.Type.通过好友申请, new JsonObject().put("v3", encryptusername).put("v4", ticket).put("permission", "0")); + } + public static JsonObject 确认收款(PrivateChatMsg msg){ + try { + String content = msg.getContent(); + Document document = XmlUtil.parseXml(content); + Node paysubtype = document.getElementsByTagName("paysubtype").item(0); + if("1".equals(paysubtype.getTextContent().trim())){ + // 手机发出去的 + String textContent = document.getElementsByTagName("receiver_username").item(0).getTextContent(); + if(!InitWeChat.WXID_MAP.contains(textContent)){ + return new JsonObject().put("spick",true); + } + Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0); + Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0); + return HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, new JsonObject().put("wxid",msg.getFromUser()) + .put("transcationId",transcationid.getTextContent()) + .put("transferId",transferid.getTextContent())); + + } + // 如果是确认接受收款,则跳过 + return new JsonObject(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + public static String 获取当前登陆微信id(){ + JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject()); + return exec.getJsonObject("data").getString("wxid"); + } + +} diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java new file mode 100644 index 0000000..d5a2a63 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java @@ -0,0 +1,34 @@ +package com.example.wxhk.util; + +import com.example.wxhk.tcp.vertx.InitWeChat; +import io.vertx.core.json.JsonObject; +import org.dromara.hutool.http.client.ClientConfig; +import org.dromara.hutool.http.client.Request; +import org.dromara.hutool.http.client.engine.ClientEngine; +import org.dromara.hutool.http.client.engine.ClientEngineFactory; +import org.dromara.hutool.http.meta.Method; +import org.dromara.hutool.log.Log; + +/** + * http同步请求 + * + * @author wt + * @date 2023/05/25 + */ +public class HttpSyncUtil { + static final ClientEngine engine; + protected static final Log log = Log.get(); + static { + ClientConfig clientConfig = ClientConfig.of() + .setTimeout(30 * 1000); + engine = ClientEngineFactory.createEngine(clientConfig); + + } + public static JsonObject exec(HttpAsyncUtil.Type type,JsonObject obj){ + String post = engine.send(Request.of("http://localhost:" + InitWeChat.wxPort + "/api/?type=" + type.getType()).method(Method.POST).body(obj.encode())).bodyStr(); + if (log.isDebugEnabled()) { + log.debug("type:{},{}",type.getType(),post); + } + return new JsonObject(post); + } +} diff --git a/java_client/src/main/resources/application.properties b/java_client/src/main/resources/application.properties new file mode 100644 index 0000000..199c37a --- /dev/null +++ b/java_client/src/main/resources/application.properties @@ -0,0 +1,4 @@ +wx.path=D:\\Program Files (x86)\\Tencent\\WeChat\\[3.9.2.23]\\WeChat.exe +wx.port=19088 +spring.profiles.active=local +vertx.port=8080 \ No newline at end of file diff --git a/java_client/src/main/resources/exec/c.exe b/java_client/src/main/resources/exec/c.exe new file mode 100644 index 0000000000000000000000000000000000000000..68787d2a018e282bf3c66f2e1c5bedc80bafefd8 GIT binary patch literal 24064 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 OdZB;+c>eE5;C}(gpByIu literal 0 HcmV?d00001 diff --git a/java_client/src/main/resources/exec/wxhelper.dll b/java_client/src/main/resources/exec/wxhelper.dll new file mode 100644 index 0000000000000000000000000000000000000000..168a32b95f98fc269861dc7f0f6309b904ab77ae GIT binary patch literal 385024 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 \\t \\t \\t\\t \\t\\t \\t\\t\\t \\t\\t\\t \\t\\t \\t\\t<![CDATA[收款到账通知]]>\\n\\n1685325848\\n\\n\\n1\\n1\\n\\n\\n0\\n\\n\\n1\\n1\\n1\\n0\\n\\n0\\n\\n\\n\\n0\\n\\n\\n\\n\\n \\t\\t1\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n1\\n\\n\\n\\n\\n1\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n1\\n967\\n0\\n\\n0\\n0\\n\\n0\\n\\n\\n\\n0\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n1\\n967\\n0\\n\\n0\\n0\\n\\n0\\n\\n\\n\\n0\\n\\n\\n1\\n\\n0\\n \\t 0 \\t \\t \\t \\t 01\",\"fromGroup\":\"gh_f0a92aa7146c\",\"fromUser\":\"gh_f0a92aa7146c\",\"isSendMsg\":0,\"msgId\":4286533159027281478,\"pid\":14268,\"sign\":\"65bd099003179e79b95fbbab461d1f6c\",\"signature\":\"\\n\\t3\\n\\t\\n\\t\\t2\\n\\t\\t\\n\\t\\n\\tv1_xf1t7z4t\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-05-29 10:04:09\",\"timestamp\":1685325849,\"type\":49}"; + + WxMsgHandle.解析扫码支付第二段(new JsonObject(smg).mapTo(PrivateChatMsg.class)); + } +} From 37b1f727b8719d0e708a79e78558078293db7b5c Mon Sep 17 00:00:00 2001 From: wxs <18771603711@sina.cn> Date: Wed, 31 May 2023 16:35:51 +0800 Subject: [PATCH 80/97] =?UTF-8?q?java=20=E6=9C=8D=E5=8A=A1=E7=AB=AF?= =?UTF-8?q?=E5=92=8C=E5=AE=A2=E6=88=B7=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- weChatHook-java/.idea/.gitignore | 8 + weChatHook-java/.idea/compiler.xml | 13 + .../inspectionProfiles/Project_Default.xml | 36 ++ weChatHook-java/.idea/jarRepositories.xml | 20 + weChatHook-java/.idea/misc.xml | 14 + weChatHook-java/pom.xml | 42 ++ .../com/example/client/WeChatHookClient.java | 609 ++++++++++++++++++ .../service/WeChatHookNettyServer.java | 164 +++++ .../src/main/resources/ConsoleInject.exe | Bin 0 -> 16896 bytes .../src/main/resources/injector.dll | Bin 0 -> 43008 bytes .../src/main/resources/wxhelper.dll | Bin 0 -> 383488 bytes 11 files changed, 906 insertions(+) create mode 100644 weChatHook-java/.idea/.gitignore create mode 100644 weChatHook-java/.idea/compiler.xml create mode 100644 weChatHook-java/.idea/inspectionProfiles/Project_Default.xml create mode 100644 weChatHook-java/.idea/jarRepositories.xml create mode 100644 weChatHook-java/.idea/misc.xml create mode 100644 weChatHook-java/pom.xml create mode 100644 weChatHook-java/src/main/java/com/example/client/WeChatHookClient.java create mode 100644 weChatHook-java/src/main/java/com/example/service/WeChatHookNettyServer.java create mode 100644 weChatHook-java/src/main/resources/ConsoleInject.exe create mode 100644 weChatHook-java/src/main/resources/injector.dll create mode 100644 weChatHook-java/src/main/resources/wxhelper.dll diff --git a/weChatHook-java/.idea/.gitignore b/weChatHook-java/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/weChatHook-java/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/weChatHook-java/.idea/compiler.xml b/weChatHook-java/.idea/compiler.xml new file mode 100644 index 0000000..0264bce --- /dev/null +++ b/weChatHook-java/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/weChatHook-java/.idea/inspectionProfiles/Project_Default.xml b/weChatHook-java/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..6560a98 --- /dev/null +++ b/weChatHook-java/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,36 @@ + + + + \ No newline at end of file diff --git a/weChatHook-java/.idea/jarRepositories.xml b/weChatHook-java/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/weChatHook-java/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/weChatHook-java/.idea/misc.xml b/weChatHook-java/.idea/misc.xml new file mode 100644 index 0000000..132404b --- /dev/null +++ b/weChatHook-java/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/weChatHook-java/pom.xml b/weChatHook-java/pom.xml new file mode 100644 index 0000000..abda013 --- /dev/null +++ b/weChatHook-java/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + org.example + weChatHook-java + 1.0-SNAPSHOT + + + 8 + 8 + + + + + org.jsoup + jsoup + 1.14.3 + + + + com.alibaba + fastjson + 1.2.83 + + + + org.projectlombok + lombok + 1.18.24 + + + + + io.netty + netty-all + 4.1.51.Final + + + \ No newline at end of file diff --git a/weChatHook-java/src/main/java/com/example/client/WeChatHookClient.java b/weChatHook-java/src/main/java/com/example/client/WeChatHookClient.java new file mode 100644 index 0000000..dc187d7 --- /dev/null +++ b/weChatHook-java/src/main/java/com/example/client/WeChatHookClient.java @@ -0,0 +1,609 @@ +package com.example.client; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import lombok.SneakyThrows; +import org.jsoup.Connection; +import org.jsoup.Jsoup; + +import java.util.HashMap; +import java.util.Map; + +/** + * @PACKAGE_NAME: com.example.client + * @NAME: WeChatHookClient + * @AUTHOR: wxs + * @DATE: 2023/5/31 15:08 + * @PROJECT_NAME: WeChatHook-java + **/ +public class WeChatHookClient { + + private static final String apiPath = "http://127.0.0.1:19088/api/"; + + public static void main(String[] args) { + System.out.println(check_login()); + } + + /** + * 检查是否登录 + * + * @return + */ + public static JSONObject check_login() { + String url = apiPath + "?type=0"; + JSONObject response = post(url, null); + return response; + } + + /** + * 登录用户信息 + * + * @return + */ + public static JSONObject user_info() { + String url = apiPath + "?type=8"; + JSONObject response = post(url, null); + System.out.println(response); + return response; + } + + /** + * 发送文本 + * + * @param wxid + * @param msg + * @return + */ + public static JSONObject send_text(String wxid, String msg) { + String url = apiPath + "?type=2"; + Map map = new HashMap<>(); + map.put("wxid", wxid); + map.put("msg", msg); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 发送@消息 + * + * @param chatRoomId + * @param wxids notify@all + * @param msg + * @return + */ + public static JSONObject send_at(String chatRoomId, String wxids, String msg) { + String url = apiPath + "?type=3"; + Map map = new HashMap<>(); + map.put("chatRoomId", chatRoomId); + map.put("wxids", wxids); + map.put("msg", msg); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 发送图片 + * + * @param wxid + * @param imagePath C:/Users/ww/Downloads/素材图片 (4).jpg + * @return + */ + public static JSONObject send_img(String wxid, String imagePath) { + String url = apiPath + "?type=5"; + Map map = new HashMap<>(); + map.put("wxid", wxid); + map.put("imagePath", imagePath); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 发送文件 + * + * @param wxid + * @param filePath C:/test.txt + * @return + */ + public static JSONObject send_file(String wxid, String filePath) { + String url = apiPath + "?type=6"; + Map map = new HashMap<>(); + map.put("wxid", wxid); + map.put("filePath", filePath); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * hook 消息 + * + * @param ip + * @param port + * @return + */ + public static JSONObject hook_msg(String ip, String port) { + String url = apiPath + "?type=9"; + Map map = new HashMap<>(); + map.put("ip", ip); + map.put("port", port); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 取消消息hook + * + * @return + */ + public static JSONObject unhook_msg() { + String url = apiPath + "?type=10"; + JSONObject response = post(url, null); + System.out.println(response); + return response; + } + + /** + * hook 图片 + * + * @return + */ + public static JSONObject hook_img(String imgDir) { + String url = apiPath + "?type=11"; + Map map = new HashMap<>(); + map.put("imgDir", imgDir); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 取消hook 图片 + * + * @param imgDir C:\img + * @return + */ + public static JSONObject unhook_img(String imgDir) { + String url = apiPath + "?type=12"; + Map map = new HashMap<>(); + map.put("imgDir", imgDir); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * hook 语音 + * + * @return + */ + public static JSONObject hook_voice(Long msgId) { + String url = apiPath + "?type=56"; + Map map = new HashMap<>(); + map.put("msgId", msgId); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 取消hook 语音 + * + * @return + */ + public static JSONObject unhook_voice() { + String url = apiPath + "?type=14"; + JSONObject response = post(url, null); + System.out.println(response); + return response; + } + + /** + * 删除好友 + * + * @param wxid + * @return + */ + public static JSONObject del_friend(String wxid) { + String url = apiPath + "?type=17"; + Map map = new HashMap<>(); + map.put("wxid", wxid); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 网络搜素用户 + * + * @return + */ + public static JSONObject search_friend(String keyword) { + String url = apiPath + "?type=19"; + Map map = new HashMap<>(); + map.put("keyword", keyword); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 添加好友 + * + * @param wxid + * @return + */ + public static JSONObject add_friend(String wxid) { + String url = apiPath + "?type=20"; + Map map = new HashMap<>(); + map.put("wxid", wxid); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 群成员 + * + * @param chatRoomId + * @return + */ + public static JSONObject fetch_chat_room_members(String chatRoomId) { + String url = apiPath + "?type=25"; + Map map = new HashMap<>(); + map.put("chatRoomId", chatRoomId); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 群成员昵称 + * + * @param chatRoomId + * @param memberId + * @return + */ + public static JSONObject get_member_nickname(String chatRoomId, String memberId) { + String url = apiPath + "?type=26"; + Map map = new HashMap<>(); + map.put("chatRoomId", chatRoomId); + map.put("memberId", memberId); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 删除群成员 + * + * @param chatRoomId + * @param memberIds + * @return + */ + public static JSONObject del_member(String chatRoomId, String memberIds) { + String url = apiPath + "?type=27"; + Map map = new HashMap<>(); + map.put("chatRoomId", chatRoomId); + map.put("memberIds", memberIds); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 增加群成员 + * + * @param chatRoomId + * @param memberIds + * @return + */ + public static JSONObject add_member(String chatRoomId, String memberIds) { + String url = apiPath + "?type=28"; + Map map = new HashMap<>(); + map.put("chatRoomId", chatRoomId); + map.put("memberIds", memberIds); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 修改群昵称 + * + * @param chatRoomId + * @param wxid + * @param nickName + * @return + */ + public static JSONObject modify_room_name(String chatRoomId, String wxid, String nickName) { + String url = apiPath + "?type=31"; + Map map = new HashMap<>(); + map.put("chatRoomId", chatRoomId); + map.put("wxid", wxid); + map.put("nickName", nickName); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 获取sqlite3的操作句柄 + * + * @return + */ + public static JSONObject get_db_handlers() { + String url = apiPath + "?type=32"; + JSONObject response = post(url, null); + System.out.println(response); + return response; + } + + /** + * 查询数据库 + * + * @param dbHandle + * @param sql + * @return + */ + public static JSONObject query_db_by_sql(String dbHandle, String sql) { + String url = apiPath + "?type=34"; + Map map = new HashMap<>(); + map.put("dbHandle", dbHandle); + map.put("sql", sql); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * hook 日志 + * + * @return + */ + public static JSONObject hook_log() { + String url = apiPath + "?type=36"; + JSONObject response = post(url, null); + System.out.println(response); + return response; + } + + /** + * 取消hook日志 + * + * @return + */ + public static JSONObject unhook_log() { + String url = apiPath + "?type=37"; + JSONObject response = post(url, null); + System.out.println(response); + return response; + } + + /** + * 转发消息 + * + * @param wxid + * @param msgid + * @return + */ + public static JSONObject forward(String wxid, String msgid) { + String url = apiPath + "?type=40"; + Map map = new HashMap<>(); + map.put("wxid", wxid); + map.put("msgid", msgid); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 退出登录 + * + * @return + */ + public static JSONObject logout() { + String url = apiPath + "?type=44"; + JSONObject response = post(url, null); + System.out.println(response); + return response; + } + + /** + * 确认收款 + * + * @param wxid + * @param transcationId + * @param transferId + * @return + */ + public static JSONObject confirm_receipt(String wxid, String transcationId, String transferId) { + String url = apiPath + "?type=45"; + Map map = new HashMap<>(); + map.put("wxid", wxid); + map.put("transcationId", transcationId); + map.put("transferId", transferId); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 好友列表 + * + * @return + */ + public static JSONObject contact_list() { + String url = apiPath + "?type=46"; + JSONObject response = post(url, null); + System.out.println(response); + return response; + } + + /** + * 群详情 + * + * @param chatRoomId + * @return + */ + public static JSONObject room_detail(String chatRoomId) { + String url = apiPath + "?type=47"; + Map map = new HashMap<>(); + map.put("chatRoomId", chatRoomId); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * ocr提取文字 + * + * @param imagePath + * @return + */ + public static JSONObject ocr(String imagePath) { + String url = apiPath + "?type=49"; + Map map = new HashMap<>(); + map.put("imagePath", imagePath); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 拍一拍 + * + * @param chatRoomId + * @param wxid + * @return + */ + public static JSONObject pat(String chatRoomId, String wxid) { + String url = apiPath + "?type=50"; + Map map = new HashMap<>(); + map.put("chatRoomId", chatRoomId); + map.put("wxid", wxid); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 消息置顶 + * + * @param chatRoomId + * @param msgid + * @return + */ + public static JSONObject top_msg(String chatRoomId, Long msgid) { + String url = apiPath + "?type=51"; + Map map = new HashMap<>(); + map.put("chatRoomId", chatRoomId); + map.put("msgid", msgid); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 取消置顶 + * + * @param chatRoomId + * @param msgid + * @return + */ + public static JSONObject close_top_msg(String chatRoomId, Long msgid) { + String url = apiPath + "?type=52"; + Map map = new HashMap<>(); + map.put("chatRoomId", chatRoomId); + map.put("msgid", msgid); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 朋友圈首页 + * + * @return + */ + public static JSONObject sns_first() { + String url = apiPath + "?type=53"; + JSONObject response = post(url, null); + System.out.println(response); + return response; + } + + /** + * 朋友圈下一页 + * + * @param snsId + * @return + */ + public static JSONObject sns_next(String snsId) { + String url = apiPath + "?type=54"; + Map map = new HashMap<>(); + map.put("snsId", snsId); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 查询联系人或群名称 + * + * @param wxid 微信id + * @return + */ + public static JSONObject query_nickname(String wxid) { + String url = apiPath + "?type=55"; + Map map = new HashMap<>(); + map.put("id", wxid); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 下载消息附件 + * + * @param msgId + * @return + */ + public static JSONObject download_msg_attach(Long msgId) { + String url = apiPath + "?type=56"; + Map map = new HashMap<>(); + map.put("msgId", msgId); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + /** + * 获取群/群成员信息 + * + * @param wxid + * @return + */ + public static JSONObject get_member_info(String wxid) { + String url = apiPath + "?type=57"; + Map map = new HashMap<>(); + map.put("wxid", wxid); + JSONObject response = post(url, JSON.toJSONString(map)); + System.out.println(response); + return response; + } + + @SneakyThrows + public static JSONObject post(String url, String json) { + String body = Jsoup.connect(url) + .method(Connection.Method.POST) + .header("Content-Type", "application/json") + .requestBody(json) + .execute().body(); + return JSON.parseObject(body); + } + +} diff --git a/weChatHook-java/src/main/java/com/example/service/WeChatHookNettyServer.java b/weChatHook-java/src/main/java/com/example/service/WeChatHookNettyServer.java new file mode 100644 index 0000000..00e1459 --- /dev/null +++ b/weChatHook-java/src/main/java/com/example/service/WeChatHookNettyServer.java @@ -0,0 +1,164 @@ +package com.example.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.example.client.WeChatHookClient; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.DelimiterBasedFrameDecoder; +import io.netty.handler.codec.Delimiters; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import io.netty.util.CharsetUtil; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; + +/** + * @PACKAGE_NAME: com.example.service + * @NAME: WeChatHookNettyServer + * @AUTHOR: wxs + * @DATE: 2023/5/31 15:07 + * @PROJECT_NAME: WeChatHook-java + **/ +public class WeChatHookNettyServer { + + /** + * 直接启动main方法 + * @param args + */ + public static void main(String[] args) { + Integer hookPort = 19099; + //1、注入 + inject(); + + //2、开启hook + try { + JSONObject result = WeChatHookClient.hook_msg("127.0.0.1", hookPort.toString()); + } catch (Exception e) { + System.out.println("hook 失败,请检查微信是否登录"); + return; + } + //3、启动服务 + start(hookPort); + } + + /** + * 执行注入命令 + */ + public static void inject() { + ClassLoader classLoader = WeChatHookNettyServer.class.getClassLoader(); + //获取ConsoleInject 文件路径 + String ConsoleInject = classLoader.getResource("ConsoleInject.exe").getPath().replaceFirst("/", ""); + //获取 wxhelper.dll 文件路径 + String wxhelper = classLoader.getResource("wxhelper.dll").getPath().replaceFirst("/", ""); + + //ConsoleInject.exe -i WeChat.exe -p C:\Users\DELL\Desktop\injector\wxhelper.dll + String command = ConsoleInject + " -i WeChat.exe -p " + wxhelper; + + //重试3次 + int retryCount = 3; + do { + retryCount--; + try { + //检查登录状态 + JSONObject jsonObject = WeChatHookClient.check_login(); + //如果已登录不需要注入 + if (jsonObject.getInteger("code").equals(1)) { + return; + } + } catch (Exception e) { + System.out.println(e.getMessage() + "请确认微信已登录"); + + } + //执行注入命令 + excuteShell(command); + + } while (retryCount > 0); + } + + /** + * 启动服务 + * + * @param port + */ + public static void start(Integer port) { + NioEventLoopGroup bossGroup = new NioEventLoopGroup(); + NioEventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + ServerBootstrap serverBootstrap = new ServerBootstrap(); + serverBootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .localAddress(new InetSocketAddress(port)) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ch.pipeline().addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); + ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8)); + ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8)); + ch.pipeline().addLast(new ReceiveMsgHandler()); + } + }) + .option(ChannelOption.SO_BACKLOG, 128) + .childOption(ChannelOption.SO_KEEPALIVE, true); + + Channel channel = serverBootstrap.bind().sync().channel(); + System.out.println("服务启动成功 端口号 " + port); + channel.closeFuture().sync(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } + + private static class ReceiveMsgHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, String msg) { + JSON.parseObject(msg).forEach((k, v) -> { + System.out.println(k + " = " + v); + }); + System.out.println("----------end----------"); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + cause.printStackTrace(); + ctx.close(); + } + + } + + /** + * 执行shell 命令 + * + * @param command + */ + public static void excuteShell(String command) { + try { + Process process = Runtime.getRuntime().exec(command); + + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + int exitCode = process.waitFor(); + System.out.println("Exit Code: " + exitCode); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + +} diff --git a/weChatHook-java/src/main/resources/ConsoleInject.exe b/weChatHook-java/src/main/resources/ConsoleInject.exe new file mode 100644 index 0000000000000000000000000000000000000000..ed0fe3fe623074082106f5ff61ccb4e188ba4e23 GIT binary patch literal 16896 zcmeHu4|tT-mG_-wCXA3UK@%GlWYD3+LU8^iB$*5hBojah1VR!}U<}DS%*13S&b%)K zODJ?QD{o&$`L??2KI?v#V*BW}?uT6~(b760Y7#}k)+!2X`Kql$ldh?FbIv{YoOADe=S|IR2UsR!OpBCEGS-8V9v9F5Or~LM`s`<> zvnQ{3c}|aJ)ys3%`}|>JM<}>0)YNWlZVCi~qH(KW3`qf_KVYm{Q)g@swg?Mza;BP6 zqz7hR`|bT*VmzIqy7%JWL_0f6>b;kjclAEZ%Z}bXyxh?nqf)E(KX|p&E2F)?ONu{_ zG9#;tkITpsIbKGV5-0qOtnIwL&fn}KUg??Dx*1!g(XlIzu4+!h&aw>6G|lum%m@f2 zJYfvwv{EN?ApZXB%Tg! z(wxlKT3jshT*2mX=*NM-ci4_aI>>5W%=WzO9;<8c6#@{8zV@HVr1EAft4(}$7Q@Kw z{^)UlNW@mwngN1l7gI)}f0S~!+^*D&*QVI?W>*2ZFBUrwRIf6_>&TU^_eOs%=6jX38D2+ihGdBJY9nzifYRHx_>k_p zcw|5W8o4TcP<fbHciw<&l=Y9a>oNmhV3jm8GlZNhx zpfKfaRX?%iy^+xp!#x)GrQxYP75OffG(}A)NT8uy?&HiInxGnUJTI zae1*;_{U{CM3N>7NhU6fGJ23igGj0<%Ik-?WN%NFDJsH>lqyrUGONH022O??3$aew zWXg-47ORwj#1mM7S};l`XS3uRWEWyltJX)+$)`mqYh_rNnKXJdnlwb;ff*g7b!teS zROzK?SD6Z;aYOgtVMt_`DVG_#6;#2X77Ub;hD$7D$U~TI==MDb)hF|fTr5NPMglzv zP;K%T%Bkod4c!%hG_>l|M%!OyGLjx*D9Jc0QUj+GD`z2EDf_H4<;GT-T!z`*+%>&Q zuGcZc5Pcafvej$6N-b=4ZH6KG3_vhem@L`r>$dp0#{q$~H2JzlFmZ7vVwS%7ob_E%pyf5f6M(9 zZAbgu_bx(Ve8DCSn5g%2GVs!_RhH5S9Lo^Bhz{`J-Xxl`Q&n5s1ApJoy~K7r@%K4U z89XbESY@hEfQ)&Zpc&NGjhhF&wRfl|^08miuk$4!=}=MR;|^+=t!Nk{7bQvG;OnHu zRgsG=)QD3fYn@MTbMdG%im64O)7B<(KgRU-n>Jt>5uAFUWd^|@kuD`sGJhDj2Y#IA zV%b-^*q>&&7(qrc@f` zt`XTiD%YQvr7_0`hI=1?aE93~<~V7%_is>F-d0|mKQR9-xn|h$=I*lS;O>R7yn8bH zV)lEK3(C~kI<1lw+pd+DjmfLWBJXRYA*BY9eN?GGuSjD`*U0=ofbvjD^z`ncnEmf# z+Iy6biN2B>b7__7G<3z7+>jbNZd_&`&ImGNTae_8yVFY_P;{DlGR_GjrmY8xG}Ojmx+Z+yH-8vM&zz1+9?G3JUVE&KQ31n^jGH+H^ULYVq5aOkqhNY zO}1P!YH;_>oAQ8 zfT>E?8Ku`zKO$YJFh{?bA!kJflH}TOX2m_GXb(U)MTY2gdeY&F3@`NBJspim*C-RG zMlKXR4cB9$?rB<&%wDKisTq|s4`#!$m5-JBJlTDQ#yo<}i^R%Ow0nsVMvA8B&k@+s z;f-`^nUudaDHSC1JGgJ_DfR?u&{ej$>^>VAOxlui&AW1a0(RiVs?Amg)cA}wb&Ass zN0w#4G5|z?83r18$|<>PSjigY9eKMexV_Eg0^tb8oH-yQl|q$`g}*DA}k zj!G@mxLoh3)Js#n%JOWlqcU5Z((mR6Wg1YI8|lk+$?joD6qU&N>JzrXNY_~=hNFX0 zu&n01cq7+R(!Muo@pVoPcXR%jOh%r|RT}b?7h^CCetsG+?}`3uEDmF@UPxwnV%l5e z>b#yMXs1_H75Zs%iJ^P21@nzlnZ8t>?v0c)Ov2FpOY}j#D`U$tom;=Pe0)wYM#+Je{&*cHm1vMrYp(|b$k-AzXU{lLgFF1-zs^eeopi z{rW?iL0&S8i{(MET>+Go$)rqVis48>B%V~W@$Py-HB(vzq z^$MIb@^QJL`*~h*VBNVZmo^%rZ@}AVZVp5AII6a|Z4k$2_gN(iF6=EcNb3;yq~&l! z!;w+4`q|KVBb2^xvdA}ck#nKY9#W{hTmKB%vuBI9Omn-z&2>s1Q?XZh@k)lHX){7Jl zE~o+FN8pK&7d@@Ugf}TB+?DiITwW{cUWn1DT_W|RWM+T2Jl z0s|ta8XJPJ##CSs5EzY}YQ;@Zn;yzK_XMVw@(Y{Fy|84GRqg-71nvhWvHx@!f+I?|VViZ2bWKy& z4tv+oEfd-7o6KhJL^hMSr~&C*WDJT+V*S%0lJBRV#B-?QGeXy`2wjytbZIV32wmhN zPyp*`M5a0jnRU4#`T_u`YRe5rz9JRTHQ?%G7Wk$|Y%v_^fz z=<&!nJ#ikFMj$*%^`s2dJudGXSBOW}Afb${rmwjy z{i6uWf6WXGjq!dRy$_(LUq`ob3T*v4y4R~Unk|Jv!wel=-urcrrqBU+kOO|00`xg@ z7?34R-#HvZasAaK0@D~vG~jJ#ZQ>uOT7{}IlK3g=ts2>!_!GtR=d!nS$HnmJUnkmd zpzwYCHM}$k=zx6=9TAR2MfJ8Gggp6sveT6{0l6n(2QHP1Li!|`xkkuw` z`1jxj`j4u&F@8oF_sv>boaT>9IS#SY=cYb@OCp0z+=kIZKxteJ#W$Fcf#G#JF$knUS=f?HBLllZ?RL&6Us zl@VLAY_WJ5lVvzXl6`(Sj zxB?BuoESq3mwa=g{`cg0*;uKCP!F_sXZLAImq?%=w`)z}>qKZ{N`B>qp<=o%Y7~ZT zcWrGV4780K`xGIOcpuy`+`6ahJq7}gqbEHIJ9N!Ri;TN7uo12XX*C|k3BMt+6=+H6 zY~l`-#}l;YpA(@eI0N1?=HFq)#3a00X+gzzg_gyW`K74VCi6F;*xU!ay~8GqYW+$w ze*-{Jwyrf}9mt{K!LfMgHg^t&y-`V0@a1A>) zbfF<}A9oMY*mAG{{C~CrCaBbEhqR89Vy-Q&yf|WbVmeHF#2VkycUZH9xJJ74Oq%k7 zZWVAfiMz4ti4cVEj!Rl=d~;tyP#aTQS>iTSHx8XUtl{v*M2=-v_I`=Y%Nd+`;n} z1l9nZ`r;A>Rt?eV@hC@~dhV&(h z-UHhe+KApHuh1sYP+J*jfqt1T8(i^jr?2NIq4)Gqekr=tjSnw4UA%l#Yi8oRScHD9 ziEbh>9sua~j0W|Up&Z?h+o~MfNBINYl>gHn%746z@}G7h_wIk-5njX|y3FVwa#nsLms4zI0 z>mM|t-b3N+n3g3@U?F6)vO}AAiPsW$Km*4kb33t=tgo)&XZzs!KcUR=VfdOxWieia zO;t{$w$C>rFK8VfhR$JoBHROnOWB{py>troN%LK-C0R_f!|xfnavR?7Bh5dHUwQZs zKyV$~^R=>&NJ_Y#jimwtDc?QPa)`!p9UwAd(|!zdz?o9c!hsS$w>9{#zX4ky`9@+C z%NTiXCc+wpBWJ%KCjN?tLc%$=}Uiv}X(EV)yA_H2r7sEg4WqNTZVX<8CF593%a}pP4xqgHK zdd)~{)|MMzaJ(QjrpW760x&X_z#sdJq9Zb3RKW;FDj3^I=E)mRIZjE}gU&N&0SB=g zk2#J>IqH(R9(@?ew)X~{#9b9b5HA9F>Xck;ZA=kN0TEYBa}`5B&{;`s>A z<2*mg^B$fb<~hSDB02XFUVoJ5Kje9W=TGwdexC2=`54cigg##q|{~4(o?fX$?0XAI8*y~7fr0*kr3u!yj2BZokdS<#n z8)MER;U_fqHcEPSyLiD~S*n&jsq(25{5G_GNZXOBkiLaffwU0m2Bd#Snz{_~BiWIv zkbaG^FCu*(>3*a=z*&JZ10_ARF7QWv71CDJ>G_t67wq}MR0Yjn-SA0$0qSpzYqN0= zT>$7}a3>R3hy{?hF&ne8g{%d&HjcR}*wnJhzcthp+DWg3*zHxzmTxw;w6)>ms8*p_ zG=`<-W+5D=zFR+sPIX8SQgoIETHKv}alJ1jG_}w;KR&wYK$4JJ#(jXZ22c^bLC9ug zMQkqWMo22KyMXKGwJ;z}yuS(UZGg9ov>NSfE9$fsL8S1db-3JkqECH9qXlCG)B|XV zoEljo&G2&ToL=S6ldt)|mHv47|5EEDsnhz9Fk2eejCpQD-Ouhui)>=Dra$5I#m3JC z4^`7?t)*q~gYR~%T^qQ6nlte=Vnt~Dyg##^f2jt@qd$x4Cwlqx>>DA4D*vCiw!f6l z7q_^y?ml16zf>RT6~TT)&h|wg#B*1vdbFgMZxbHPgw_{sRDu3yJq6?VrN2$h03yYZ z@021fP)DWv=o&uYT@N?;V*Xb&mFPuyB87si(j6E59Hi0*> z-iMN4-`lxigy4ouKy6m zu2=CM1Z)NI=K`6?bXfWu>xF_UBz z2fN06j7SAYt-!epsSD{|q<=*EF4AL2hmnYK2Ft{n>R1+=!Y26QLZMS&+XOM#A*KpX zTQIO~eBBcawCzj-LPA&&8L*o|+o%viAu9X<>@OXh3ST4QO3w>)k1*p7f^|zjB`&Nj zU?!|v`l($8{|2mc94rLbC}7%gu)hVY8?gQ3U_F3kV+Ci9gS`dVL%_=$2m2FX_XCza z4mPtAv;h08=ga%or|)00vl>HwQ9yL3+fa?r9t`bd?Anawf>kvv}-zqKzd}7RpQDd3bb+fgtiWw zwJy-q5%vW|n36VyWwWlvUrFGuw(t{3wLcUFNM@C7!LYERDbUg;u=g@+f-O>;V6&#W z*%Kr!ky+Z(0&@;C_L8o_9}=aewxwtAyXm0PoA9Z-bg3sgi zx54uvg21W>B6^xSIy~ae4gvQq&=f=|EQH#dL?6Wmb}geRc>UX?kl@+jZxK9*9-zC3 zdHeyt=x=KC-<|3JKYu6~Xon>+HlKMQLK;UMIUb=@9S>33S=lFbkAOR#fV(%HjUgl| z>s2Ye^fyj8=|IK~53~{5*37sTsI`L&^%cmfj=dT+b@USE2?^W$VVbkZ*BwE!xv7m5 z;DM!rg9pvFtxe5$QVdVcRz>BQ0{%FM>nR}Y@NgkCstkP9NT!krx(Eh1QL$+&Oc;M$ z^9lb{53L`!nAzZlHC3e``ypcq1>3wG9$0oOBjxx5=w?kF{-XA9(GGv02qTK{mtUg4 zT_|d^6j_Q?Q<;RpP2+R=$1TO@V1>Ewk4Km+3E9c#^{X2EJPe=E)G?v|30-|a^$fg( zNjXAukxJMr?u|*V0Z1BVD6iD+!^UwL-`n zgvAG%g~}i_D}>l}pN@(vTiEPzP&I}C$c718w-XlDz8;IRGz?({sxfco%1bU%w+c46 zf?g+m6B6;rx=jeBCHbs9>`U1HTn*4Kw@+O2Y!#gmHJC;^(UB@$|DQX%FD3u$G@#-4 z_5xf{CXS=up7{~`Q*eCr!tY&lujwMBNl(R+&h|Frb|DnT$-_C%vd}!wC}_NH)A zyT3UU3th23O zv)*hiw=T8bVqI(9X!Te_)=ukT>$BDY>nqkPYzEs*o7J|$)?jG3sCy*Hp}@sHoUlalGQSia$6nID?CK zEPj3Q+lv!RMwT$6de`1$ZZSV;?lqq%rk{Hx=rL#PN;Y_IrH z#nFoAD_*I1r((F`I;Y85gPoco=B>wM7pznzab|Jixi`IPgh^O$qM z`HJ&3=UdLR&fhwRogX+yofnE?jI~Om}j6KayuLS{7K$mKBy& gmYIu;iwYO*S@htdA36>@jyq1`z0H@;|33}c162v&TChc zTda0fv#n`~&D5Z(HZ?XjIaG_yDx0%WWo=Xy-dLt;XsR(!OGp?!!B2W;;{H>o8y|lz z5NyehJ>Q3STk<2%zt5jrp8p)rkK!JC{%;)bvFCQ;xf1z*< zi6k_9VpxGxh?fD7%JN6*W*M^~w{Mq>)ev%@j9pCvAM%M|g`n4RpNwgT^Wg zurI5wYp6a56h?sPtq-E`NCDAHO|7n70=p!~bL+}cw+3Yql1g?ud;89yQ42Nf~ zZvtX5Hvm+s0M4c%c-KXy{LgtiK%Y~YV;DPC{$qeSSpYjJHj%(T2`ne@fg0c}w0-KQ z^g3Z8K$8YwbP9l52T+g+5JR~;rvV(NSUrsS)VA3GuM*G~0GzxCz)qxIq_hXG1$dZZ zZ&2k=Q_M|e-=J8|RDj2(1N@4LsVQw_GQj18(oV0FDI=F+jiUh`yd2Y@^p& z%ILok;6Lg0l4}8Wrvm(tYMV{q1A6`QWdJ{**L4K`KvXUzsr{D9Dyi&0W&-?@a*tmP zpr_aY$~b%_KtIJ4=>R&4%|-7zb(ms>7Xu_wtd2_Dp9Zig5uk(8c2XUOt^wFaY&=eS zyOL16Bv=!n#8Ly>D3(nGb`ZTP0`E{-9Hntm7Z8;n5b`d{=*<9FLi9>U18k(U^#nTL z1W&z03jGPSZNl{ciNwY$ROQn|KuNhbj|8ZsjAklvh(H!)EG8+|{ADZA>Ho7?tMk(^#}{};&paN+Lwb9HLaU$`f%mAt+bkm2<``s^j4 z`Zi;vBVIcIpsl^o&gJIMwARw$*4+t&u6~m@io&q5D4P6fPCc z2&l@J?2wX0B%|C~l&b@}*Ko`zp6UnXz44ufV_aJC$XnXQgo>?#VVkEsq1#iKaH8Fx z+F6(&#`r63H+qZrmeeV|6~{{Jm{bksfW&{BRQn}^YOmw9_aqNWE<^HmS*h@v(VILl z7NYtD9L>K8iV=zjG_Q#>@!GbRqzn~Z4P>ZjPwiTon##w?lxLtxcxqOG_%Xyu!lj7s z^~dKR-tNaSAifoG1#k)wuXkE&Sk3Y>>uqM%RA1w0X83O@Z(3?@JeLWCY5!jz<@U8| zgk)bYcEE(5RYDGalOAkhhe?yI@yt0=({buvG!d3=tW${npZRNY?f21t!hRosO3z`x7oiQ7Dey1wOzsXK8^t+bo1Bw- z2VPMUS#>euM5KD$Tan~?k)UMp9%Og&azw*VLGThniRhGc-9Uf`u9OFwWqH1R_u&JVdW2a31t zC85C5=TQMzdg6Fm;j<&ar-oAlx)HB$ZZ>~cwmzJ<;`XmV&3yc7e->+{fO4c!>ki@% zvJ&3~*RH*CkD;H0*Ke$27N2iC2Al%P<=?}F((7vNm6UN6S|FUMlUryX#Ue))G)DA7 zRE1K3g}gwwtF@as`7lL!{$Xkc^5DYWLn+XZijyxBin~jMir!LRa*;%2ED)hDt#HP| zR!LQK1FIKGog-m!VQFozA&7j{x?ng(*BcAZKD43aNqg+&s^kt459w>4A-}1eYkaA%W7iew(gLz#c;eM)J=ur_!4o;U#M<{#~|4spO%I7hd2K(*M3pJyt+{8$WZgz z1~iXBGOxiT)=DJehUj!TGQL}#`qX4YJ~bJfPffF9pPH=Ir>3d2PfeY{rzWrDQI9$YG4TlyEX)5kh(}dQirb#uoK@v-}!fEep zCd`$CQz2$YJu}2p>Ui$cDGP5(3Z>F`iPk3LMzGl@?%^a*^rrcP z3b)5!EK`O#xb`k(#QQ3J?o%)$=Xf}G89Demwl9v7Wa7&o(|qW2F0-*=BqD@^LjGdn z{lItaO^U?KNuX6JxIXpqDW5N*D1VXci5AF)ephRM?D89h;(p%*?+aIBJgZQaa#yw- zh5&ccqjj~y;z#T=_+&kDK$f_(7&EO&82K?%=ou&0LJ)~NJ9-jDk3S|k2-0&ryB##^ z5jG+`jxdy)p()_6?ySY~Ynjs0eZ5Rsvzd(2=-Mkn`g}E%X6VxvQEPQp}O?Bya$i08!66*>^kG<9k@))gsc*GcDeUE zV|psY2SlGwj6;%m7{C(m0yBy`G0%Gw8ITJm`!PM!#5KIoLHA)4Qd#6}z`}`(QnYh^ z6cTxeih&0P9?oJ}%GcICV-j~7J~E0cKmc;w1F4>L?{~;CK;JB0k2*YtZlR*vrDKjP znhS{7*(OBJp<@EF;4}AM?HpPLh&(QGR{<#zKUZ)LIG^bwM*vV5$?5x z;=USB0!iX0Ut;t_)$)x~Bz>+KqR+l_=+mVq#@QXDM^f-+-VRZ=g`S-TIx!+yQY*Lu z))-FWl~N`3#yCj5D@cReP6G5&ObZ~XgUSVplWIs6#V1R|zW|q1Q3n%;K*dk^yTWf% zsMlXGq{9*7&X0V)JT99!mj1AzbJ| zH^HKSRumQ^ypQDFXo&${T+jb%OT8$)8nHQ zvFkDPaoSkrB|!>dHN;R2UDzo6sx^RqTW=vuEwr@;=F5bI@$&?I3WiRP`vr7z7si9t zkHIe7?>eursP4hmt9+hO{4*cmz#Jl4m5FyEZ4A$rR+~=59 zYAo?N72+SMtL{}U+#@9SA*-D>YD_6rVO0 zoOE7L93T5e&O!T>yHYfXJNFpd)4+peENo$Nr?Eu5pVSKcy~;H|ybi=Lca80NzG~kH zcFR5)JMa8;um(uK6$)YZU5bL$`<|shro%OZc4eesBE8Q@=z7)O5>eYy^7U-PkpMX&pDR z0CrF!+c{5;5#FZ>E3D8F>}mdCYoHM4b7-L#3Cs61X++paZ5CdY7`1euBwPhCSQ%pN zP1oKOSHH|DI}kMS-Du>OdFH{E^sjk`{bBxpl;%6iiqQPOS8P*pU**JV8m{WT z0oYB4F3eTs4t7BX^pZ$dr9_Hl&S1PRxCw?_)qD&OMp}x-Vo17y>XE#a=CxbIejJc} z8%?$sVV)hfq(png>8Ri59P4u?Vbmf!aw>eya$lw`j_S!XD7X1h2 zF9{0>CeJ5$c{#xhBSHNfz}mZ$TlgcPo+uGt@Z7K1cfdWd!2 z=|Bqc-Of{R9-T}(7dVgZ=PA3onUEl-lsR$zmX&LG8-LETCnuT+*O5(~yqrQzR;4kPD<9J6c`_DNi)WS-)9tejlCWu9cR#h}$yC_ML)Df&l|o>ACNb#=&as{^BBPI0_F z88a`WVWld_+dT67TY%j2jwIqY|MNQ#anKei0(rZ7lQbPT&8L+P&hLH6>&RJDoWYC{ zd}s{);7!_u*3cd`;_HDR#l8GwYy&SapsDB$lX~yt zDtO+J!l#95x{K~m3Vg5e?}t3RpZ5G%%*Qx^irQKS2-!IbGQAOzo)p@QexC|CFGLd( zF2v2cUERtHIQtMYcBN1Okp6zylT3a7D>5g^%c8o)f`6VbF8U*lI`Y97 z$Zt-Uf)>ixrQ^itpPR1mgIUWFv#`t^?rZiGbPyW==aW1j`bAx2^C1V zZp6~ANwLL!o?=m`=!1&=MmtGTa!#@6xQZKXe9m?8wjV&iLP9<#axI*HF**!D&7O^i7x9h)+g7+aL< z;f?fE^rKO2TDtbn5mF#T*Dk4veTmP+=r6#Go#$XGKb&BP;f<2OU(M#R==*+!a``I6 zf+Ku6L9C>UAH9WMTY!^pV(Ga8XkXLOm-y6Z2pn7DjE*k`at!@WSR&-~ZuLlTXww{VpurW2Z zRD2j@u|I92M*+wXeol|#ory`ZSNT)9_Q)UdRmSK4c~myFXUAM1jtL2gUv`p z;-8Fh+R%2 zje`slgWYurT~#BD@~Eimlj?4m^6j&(Ti1$3rzaoDNJU51mV zehX~DaB#BWn8$#H>j9YCu~P9xx->}#-$z}oN10Q(&+rlL@1KS6?lOGT(}I%`p=d(e zaTH$VK<=2bBBG*Orlz{c3NJzgYbS@2oLB7vo$>I-MG&+0Px@&qhaf59&WKUg{b! z!FU{g#M8+W935<$_WvJx-LC`Mq2Kr#e;LM21exRgA$~KNBt|e?zVbP*#&o$v)JTNj zL=;#c(I7>#kt8?Jb4|}gn1NC16KT8}Fb-5K(~A2EwO>N*Kc^>=Sm5<5Hj|mdCAnG$ zu+HT5Uo7$7Gp^?nUO$aDMyb2)I>3!)SjwPRLY#@V0RIk}#t<{S0lZsc;2378=M15I z`zkOK5If%6dZ!FGf3Axa*GhF*4qzQbH{y<<*BeD0=NvbfS7Q^(<6TAY{*?qbwh(;U zLGZ<9z}ma--@qT|}Wj}1R}ufwC0*Fg(5p;+$m-i;)0h1_%h zdWsn2LeYT7y9V((tk}I3n@YtexU6t@coV+UQ_yL6lsW1ZEhegPyqMmCz`J_welq zX+EzY0no)1$wZ~8QVl33KBH*+JUB@z<&(C7*z0nosb5=maC(#_P5j1&rbTL+Fs*Qm zSx)1~*yrfJ^U~d8C~>8%MXKTJ&76Y$$#U=_norXfMZk(f5+A;O=rQCi2he~vUA4WT z+j%ou=W0>LI!iGywXH}}#oafKj;;sVZcDm?;+XRew6!E%N%43IhZQ_1b6zR7@5ji8 zaf~)dgd#cofvAQRLINJ8_*oZSWr8NY;yn`Z+Zks;hlG3rbHY$-4jia_sqjGzp8NF1 zhh<9p<@1C*?DUQn-Uw{ty7tN2&QhN3!>U`t_LTxODqotvftRoO)w#=4-Z{#HKQ6oy z(aKFxPR=R@6D}i7w*qHQbfR1L7)7Yq@7_*SgPMB zT4BS(ERV>3QQRo*fcTJ)K|^*RyzW5$x?ujuXJ;V(+BM-=0cr91c|=B_e0i`ui~++( zlJp*dOH_dG(kr^5Pe&p#CM3{{@6d3(2k?7omQip&t?|#S`Xx=7&P=A+R3B=ZOpE-! zoM|3s$y})7=;#hLcl30)xwM2$sKum!A_Xe||ZJ(TkAL zVEVa1`gBNZ6nil>lK7M4yiIzKpb?-QlpoxGKgkb6Xe`|v!b%l_JCjqaTt77=)78d@@3A6J|EtOkFj5g49d6lCSl7kbUw98-%R~G(S0c>?2Drp zndm^Aj(OF2I4|ftHq{6C*(;w+vNW2u4N!bM!Hj0jN z=%|OTfsNhgFPP}w3^7aJvT06*_=ekgrOsN_xI zM^adtWCnRF7>>8wF|m+%yCS^GJ0#NZs`zrvNBEiyX#`%AzrXE&A4WOfK@!(b3g$B* z{a__vp|545;oYQ&Efnou_tNb4SZJ=BVy6qVL zaQ@rjKkDDKN0K5A|4_J-Lj{T%B}EIbB0^s_plkCfP_OT4s>Dz8P3cY8NGu3_IG>ZQ zY`p2O2bUw)xn88=GN&MTsfN#X$(*YWoe~bYK95Pf(+*OzrJ8E9rx$tUMTzbzq}94w z&Mt6_es%%OuhydwUJ-Wv{h+J=qQpCAAlEzNLcJXJpZGHk-Zrm+@I9-G$$B^6EyepO zzE+A4P<)*fXYdM}*Gq9Z#WzTC1;sZ>@py_qD#ewE*KXd57v4o`QB4w(LY?4~-PCZ> z`Df1YA=M@YtQPl3UjYgbARY`~`=q1yYpGaZDEkhq^SdX(?L8h>TtfB^KkN09l<5+K z1ygMXj|YfEayWrFF5Z;X#nKM1QGBX{+CBlkputiI>w(~aS~tolZrbJdG0rlJR>}e8 zI^5;`X)q^Sk~5hiO3?$faR~)q3v?)xMuPa>kRUDE70(JjLpZIVvLw&98quH7olyP6J6 z;!!pF@ zN#uyXfDpG(0Bf9%No>+ zD|2C(L~<-g>*jYYh*Y!6i_33iKIfUinD0OzLTfMKSiJNFf-p+BKf!N339bEfYvN%- z1)d@t2maB7e6WC=cwEGVr5#11y@mmV6KHg(4Y+b~JyufKO+DjG!hTfFtIHFxB@??V z=V;=cF3w2*Fcmm|t=W%jfxhHb5H4iAnq=Hcm=O~4x1u1NJE66k2x=qKMBx&_HYFm> zaLj)RR&dhjv8fII$~WG=e%D8_QoWTJ;_=NfR1M=G5#I&`aDhhl8HGfz;fP<@x5lIO zp*CX|pTw98?8ysHE1?!o})sgtjX>yXZ zy2iv5J>K}}$USovp33;ZA-PbP+>P(_@Yh|(nSk$Vx1GM;-01Y5Ud9TA3EdgiM)T|o4sxVJ z$9YPl!(4yC!J&JWcNHI5Q~Vxk=vBC4%a)_Wjp^v{G>JEtlOd=Uyw;Q>#gjzHbG9A<+UIzCF0-`CG$^??B zq1YLQVz87<=1ja2s0J?lwbf!pL{U;!W zG0?}irj?1SUzXUH9mMXor{bVcaR8g$uJ=c{x|yc}r@?z&tsNs0-M<2wt936HJG9`I z>c#kns}kJy_T!QZMYt{xy$}28b9Jj6OC>~XWCsydLlD&%R1fM2ICHSSE>s+BJKl{F zuJ@UzxZP9nLR)J)y*n@G-#@%WqKTPLpwR`q(VC9HxB45}(Y3yBE(!ggcb>q4nf#GU zKLs*pU%rU-si)ve_dfL`4wW1xaX6O46b@A!PT-Kfr0-L&=5Q5-c8a65+%4!3ex z&*3%>ALVcphZ{It&*3@_*K*j*p@qY{Ib6hH4TtnaH=mkrO#0Mxq0XnKyPG~W-Bn(i$5)N~)or>5HnJ~iD*@TuwQf=^9H^FB3gulm$9prS<_((vw6(_GM} zru7y)E#w(--0&T|58aO~cj?Jz%Ubb`FvY2H=)rBRiXAZwn>dI(O%a|$cSPZ>$q&GH zQ~&NX^pczGG>Ej}iYxBq`0@1+6@MJd=Z~YS_~Qso`a2B=$-{IS4$$@FPD2;1ggXs; z=}Xg{hK@Fl`2v0S0e?C~&M|i=IOcXG$K0j@X5y0z>HNagm(RzzfwtpR5MK{ii?gYo zn`vA1eJYHUdnjc#hBP%14WMW-({DRYgpqbEb zVj-RHT?dXB&7&_-2}C6jl|WPiQ3*sP5S2hw0#OM>B@mTBR02^6L?sZFKvV)z3H*2SzT?W z-zk$mH+%`bSvIrTPj5zJjiJTrD7VO-}j+JW8@QS{+tXJ$@)cD$Q9qCy;2@JI$)5T9u*9$g8KH z)5EXpHJfeqD^zyVGSp$Jwl&%9DwC?#*;wtcHZ|gB_v-7XsZ{)@_`r|T?nJ^0VA#xd zXT5{y5el&sR;tEaU2n4anN~G5nuAEyO^wUUjhr+!D7;W}lNDFNY^un@CRL5Kw$^Mz zp<#$jOQ5g2eeKT-kg_{ejwaM^Zvg*PEq;kkaZx-mFup5=M`zwwP?HYL#9!$*Q_qmD)0E)-^c^qi0%7jY~-8oGyL@ zk$w=-&({iA3OWL5*95_#GZ50P(3{nC2Y=oP5i0%xC6ebhu4ur>rz1UpaWJ6ONe(44+JX9DJMbzQ%UR6*bh@PuTR?)b$ zv1xgus?-U6HJDX1EoSshgU!}ti=bT!ztqSa@JJ2t7%DcCS@5i8OA$A~BiUFJ!ZenE zug0iY1#nI9W#qk7DAfd5gR*wkj5HHlf%j5Y4k{{E3>-WDnYf?h9X=qmVV)8nQ&Dy} zPG*4$`AjExp_;E^YSgP@QxVjtBk~hy`z&j{xzW_XoopbjxT(eohcwrp%pjksZyqUKc?4j`$Cd3qI9${A)(ItpK-QiV{CK%}Mi1RSqlzHs{i#%B7xWWYyq_eidhJV&@^3={~`^y_mF$vKfdKcd$M z1~e0CYzVzU9X)6YAw>E+UiA09C}lDFMcS_AmZfrI;-e6u~XrT!Y-bW*`L# zm$4h!EVOSPn}N0(Adz|Cbves~G^n1rc&h==Bq=M}LH^B-Pz(MSKtj|KC-|y|936;} zcWXipNpl)nv;gU5p3}_nX9jB?8ll-9)#gM!4(P#(_rNEl75p~pAe{wp&XsN~ypCi8 zQP7XSn(6UEZ(;XJ4G+7|yU-RT&8UC821HVN~7e9ZDaNq$MJJ2c- z3FOh=8Kdtik6kZgV{e!8qA)6fs05-C7%YL(vO+lhin8D2JT&`v{hhCrt^Z2`xjln! z!7{ti)et+l8ay*!OfS%MIrkMB?h>svMhzomsh`O(k0B><6enbmKmKXk|F`*%Fd z$NM4#3ql-D8Q#UOLaadCg+L3?z|?CRjy>3He(X_*SVdzfe(p-6IZ+Ax3nV~)Nc*XA ze<&odi z3J^*WY7mwowC{&+ME*?(XAue!9z)tg2=^eYM5slWgOG`kg1``ZyJYNjg#SR;fbdg< zE~Go~T!iprgzxI3+$UpKBNQUgk00HJunwUQ{C|Y-bw4WbmWm)p(00n$7YIs(&p`8I zgrf*u2rnThfpY?RZzH^f@Cd?B5o!=p5PpmxNBB0MyLX4m{}k=fcF0(HSlUr)BhqU2 zgz&ZzZ@Xk{M_5`ec*R%whh-&dQ;}l%zT4EL@*ACwW~YO1B$YXAIBJ@KpG;)@aHGi7 zSW|CiH8Izl9dmFv#gA|qdpB;L$?Cx2c^R~NS%aYpSh2X1*J%IOFpfb(ixrPb!ZfjK`xG~o1a{D^rrtHbO^DCXzejJ+Pfqw6C; z`#)5lbfhZriKCjQD2>YbPZKfz`8gzg&{WsRn0Tpe&QHcjrYLX$qrYO_1$ zvD8q?TyqN&*vVL_iXaL*6-<#(ZZMS2HO$eZ^O+$_jtOg9dDBue4%f!B88vlIyMw

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}#5xIRg$5Ym_Kyr4F@dP_TnKNtgtczywT!A|Jg)jL|BF8Nf;) zVKTyDdu-au{o7VsdrR-7SKCWlz}TW@63B!PAyH~$jT#m0j!A9!s02jj{jI&veB}eM zz5l+?`#dj?X7)K}pSAa1d+qhL*IuXSt~!&&WHKrEKN2yS>hVke+~V)K|McPU$nn1) zY1%&Q)hp`FvtGU8j`I6fWG%0J=-$e2emm=1-+b`FhkRN8c28EN|G}*L9?Y6CXI|E~ zA6k0PH5XkpJlg<$QE7R@ty9x}5dGghXY&sh;yUg2l<((@-_-BB#joZ2#o{;p`$hQu zkthB8v+#TC)btWSq$SX}h0yd_b#qF=>8sQZTO`Ul~YWnG`hmS= +5~>tlmWIb}yo`!eH}-2> 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

TKfnt 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Ð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 Date: Thu, 1 Jun 2023 08:57:09 +0800 Subject: [PATCH 81/97] Update readme.md --- tool/injector/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/injector/readme.md b/tool/injector/readme.md index e505953..d2fe096 100644 --- a/tool/injector/readme.md +++ b/tool/injector/readme.md @@ -1 +1 @@ -## 可以使用3.9.0.28 分支下的注入工具,或者自己编译一下 source目录下的注入程序。 \ No newline at end of file +## 可以使用对应分支下的注入工具,或者自己编译一下 source目录下的注入程序。 From 391011b69610395d50e9e9e1fe2ddf3885cbaf1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Thu, 1 Jun 2023 09:57:03 +0800 Subject: [PATCH 82/97] =?UTF-8?q?=E5=B8=B8=E8=A7=81=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=92=8C=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- java_client/README.md | 1 + .../com/example/wxhk/constant/WxMsgType.java | 10 +- .../wxhk/controller/WxMsgController.java | 4 +- .../java/com/example/wxhk/infe/SendMsg.java | 11 +++ .../example/wxhk/model/PrivateChatMsg.java | 3 +- .../wxhk/model/request/AddFriends.java | 20 ++++ .../wxhk/model/request/ConfirmThePayment.java | 27 +++++ .../wxhk/model/request/FindWeChat.java | 19 ++++ .../wxhk/model/request/ForwardMessages.java | 23 +++++ .../wxhk/model/request/GetGroupMembers.java | 16 +++ .../GetsTheNicknameOfAGroupMember.java | 23 +++++ .../request/IncreaseGroupMembership.java | 23 +++++ .../wxhk/model/request/SendAtText.java | 25 +++++ .../example/wxhk/model/request/SendFile.java | 22 +++++ .../example/wxhk/model/request/SendImg.java | 22 +++++ .../example/wxhk/model/request/SendMsg.java | 52 ++++++++++ .../example/wxhk/model/request/SendText.java | 18 ++++ .../wxhk/model/request/ThroughFriends.java | 27 +++++ .../com/example/wxhk/msg/WxMsgHandle.java | 98 ++++++++++--------- .../com/example/wxhk/tcp/vertx/ArrHandle.java | 59 ++++++----- .../example/wxhk/tcp/vertx/InitWeChat.java | 12 +-- .../com/example/wxhk/tcp/vertx/VertxTcp.java | 15 ++- .../com/example/wxhk/util/HttpAsyncUtil.java | 14 +-- .../com/example/wxhk/util/HttpSendUtil.java | 53 +++++++--- .../com/example/wxhk/util/HttpSyncUtil.java | 8 +- 25 files changed, 489 insertions(+), 116 deletions(-) create mode 100644 java_client/src/main/java/com/example/wxhk/infe/SendMsg.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/AddFriends.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/ConfirmThePayment.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/FindWeChat.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/ForwardMessages.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/GetGroupMembers.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/GetsTheNicknameOfAGroupMember.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/IncreaseGroupMembership.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/SendAtText.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/SendFile.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/SendImg.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/SendMsg.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/SendText.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/ThroughFriends.java diff --git a/java_client/README.md b/java_client/README.md index 9c712ab..d41a462 100644 --- a/java_client/README.md +++ b/java_client/README.md @@ -1,5 +1,6 @@ 环境为jdk17 执行之后会在当前项目所处磁盘根路径生成一个exec文件夹,然后会把src/main/resources/exec下的文件放在那避免因为路径问题出错 +java_client/src/main/resources/exec/c.exe 为注入器,只不过把名字改短了,更新的话换成最新版,改个名字就行, wxhelper.dll同理 项目启动之后,会生成一个tcp服务端,用来接受hook信息,然后把接收的信息放在队列中,之后用一个线程去循环处理消息. 具体实现可以看 diff --git a/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java b/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java index 59b9570..d46ffde 100644 --- a/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java +++ b/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java @@ -16,7 +16,7 @@ public enum WxMsgType { 收到名片(42), 表情(47), 转账和收款(49), - 收到转账之后(51), + 收到转账之后或者文件助手等信息(51), /** * 扫码触发,会触发2次, 有一次有编号,一次没有,还有登陆之后也有,很多情况都会调用这个 */ @@ -25,11 +25,11 @@ public enum WxMsgType { ; Integer type; - public Integer getType() { - return type; - } - WxMsgType(Integer type) { this.type = type; } + + public Integer getType() { + return type; + } } diff --git a/java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java b/java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java index 05e4ede..78b2e5d 100644 --- a/java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java +++ b/java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java @@ -5,10 +5,10 @@ import org.dromara.hutool.log.Log; public class WxMsgController { - protected static final Log log = Log.get(); + protected static final Log log = Log.get(); - void init(){ + void init() { } } diff --git a/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java b/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java new file mode 100644 index 0000000..1ab7d3a --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java @@ -0,0 +1,11 @@ +package com.example.wxhk.infe; + +/** + * http接口请求的基础接口 + * + * @author wt + * @date 2023/06/01 + */ +public interface SendMsg extends java.io.Serializable{ + +} diff --git a/java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java b/java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java index ceb6da0..25424b7 100644 --- a/java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java +++ b/java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java @@ -17,6 +17,7 @@ import java.io.Serializable; @JsonIgnoreProperties(ignoreUnknown = true) public class PrivateChatMsg implements Serializable { + String path; /** * 内容 */ @@ -41,8 +42,6 @@ public class PrivateChatMsg implements Serializable { private String signature; private String time; private Integer timestamp; - - String path; /** * 类型 */ diff --git a/java_client/src/main/java/com/example/wxhk/model/request/AddFriends.java b/java_client/src/main/java/com/example/wxhk/model/request/AddFriends.java new file mode 100644 index 0000000..cc20d11 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/AddFriends.java @@ -0,0 +1,20 @@ +package com.example.wxhk.model.request; + +import com.example.wxhk.infe.SendMsg; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 添加wxid 好友 + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class AddFriends implements SendMsg { + String wxid; + /** + * 验证信息 + */ + String msg; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/ConfirmThePayment.java b/java_client/src/main/java/com/example/wxhk/model/request/ConfirmThePayment.java new file mode 100644 index 0000000..26e0f0b --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/ConfirmThePayment.java @@ -0,0 +1,27 @@ +package com.example.wxhk.model.request; + +import com.example.wxhk.infe.SendMsg; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 确认收款 + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class ConfirmThePayment implements SendMsg { + /** + * 转账人微信id,从hook的消息中获取 + */ + String wxid; + /** + * 从hook的消息中获取对应的字段内容 + */ + String transcationId; + /** + * 从hook的消息中获取对应的字段内容。 + */ + String transferId; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/FindWeChat.java b/java_client/src/main/java/com/example/wxhk/model/request/FindWeChat.java new file mode 100644 index 0000000..dc3d4a0 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/FindWeChat.java @@ -0,0 +1,19 @@ +package com.example.wxhk.model.request; + +import com.example.wxhk.infe.SendMsg; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 通过手机或者qq查找微信 + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class FindWeChat implements SendMsg { + /** + * 通过 手机或qq查询信息 + */ + String keyword; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/ForwardMessages.java b/java_client/src/main/java/com/example/wxhk/model/request/ForwardMessages.java new file mode 100644 index 0000000..723e928 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/ForwardMessages.java @@ -0,0 +1,23 @@ +package com.example.wxhk.model.request; + +import com.example.wxhk.infe.SendMsg; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 转发消息 + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class ForwardMessages implements SendMsg { + /** + * 消息接收人wxid + */ + String wxid; + /** + * 消息id + */ + String msgid; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/GetGroupMembers.java b/java_client/src/main/java/com/example/wxhk/model/request/GetGroupMembers.java new file mode 100644 index 0000000..7f48379 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/GetGroupMembers.java @@ -0,0 +1,16 @@ +package com.example.wxhk.model.request; + +import com.example.wxhk.infe.SendMsg; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 获取群成员 + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class GetGroupMembers implements SendMsg { + String chatRoomId; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/GetsTheNicknameOfAGroupMember.java b/java_client/src/main/java/com/example/wxhk/model/request/GetsTheNicknameOfAGroupMember.java new file mode 100644 index 0000000..60e4e22 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/GetsTheNicknameOfAGroupMember.java @@ -0,0 +1,23 @@ +package com.example.wxhk.model.request; + +import com.example.wxhk.infe.SendMsg; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 获取群成员昵称 + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class GetsTheNicknameOfAGroupMember implements SendMsg { + /** + * 聊天室id + */ + String chatRoomId; + /** + * 成员id + */ + String memberId; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/IncreaseGroupMembership.java b/java_client/src/main/java/com/example/wxhk/model/request/IncreaseGroupMembership.java new file mode 100644 index 0000000..a43a1e4 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/IncreaseGroupMembership.java @@ -0,0 +1,23 @@ +package com.example.wxhk.model.request; + +import com.example.wxhk.infe.SendMsg; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 增加群成员 + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class IncreaseGroupMembership implements SendMsg { + /** + * 聊天室id + */ + String chatRoomId; + /** + * 成员id,以,分割 + */ + String memberIds; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendAtText.java b/java_client/src/main/java/com/example/wxhk/model/request/SendAtText.java new file mode 100644 index 0000000..28b93b0 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendAtText.java @@ -0,0 +1,25 @@ +package com.example.wxhk.model.request; + +import com.example.wxhk.infe.SendMsg; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 发送at文本 + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class SendAtText implements SendMsg { + /** + * 聊天室id,群聊用 + */ + String chatRoomId; + /** + * 群聊的时候用at多个用逗号隔开,@所有人则是notify@all + */ + String wxids; + + String msg; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendFile.java b/java_client/src/main/java/com/example/wxhk/model/request/SendFile.java new file mode 100644 index 0000000..ee5e409 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendFile.java @@ -0,0 +1,22 @@ +package com.example.wxhk.model.request; + +import com.example.wxhk.infe.SendMsg; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 发送文件 + * + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class SendFile implements SendMsg { + String wxid; + /** + * 发送文件路径 + * "filePath": "C:/Users/123.txt" + */ + String filePath; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendImg.java b/java_client/src/main/java/com/example/wxhk/model/request/SendImg.java new file mode 100644 index 0000000..a019d02 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendImg.java @@ -0,0 +1,22 @@ +package com.example.wxhk.model.request; + +import com.example.wxhk.infe.SendMsg; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 发送图片 + * + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class SendImg implements SendMsg { + String wxid; + /** + * 发送图片接口 + * "imagePath": "C:/Users/123.png" + */ + String imagePath; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendMsg.java b/java_client/src/main/java/com/example/wxhk/model/request/SendMsg.java new file mode 100644 index 0000000..e86b213 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendMsg.java @@ -0,0 +1,52 @@ +package com.example.wxhk.model.request; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * http请求参数 + * + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class SendMsg { + /** + * wxid + */ + String wxid; + /** + * 消息内容 + */ + String msg; + + /** + * 聊天室id,群聊用 + */ + String chatRoomId; + /** + * 成员id + */ + String memberId; + + /** + * 群聊的时候用at多个用逗号隔开,@所有人则是notify@all + */ + String wxids; + /** + * 发送图片接口 + * "imagePath": "C:/Users/123.png" + */ + String imagePath; + /** + * 发送文件路径 + * "filePath": "C:/Users/123.txt" + */ + String filePath; + /** + * 通过 手机或qq查询信息 + */ + String keyword; + +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendText.java b/java_client/src/main/java/com/example/wxhk/model/request/SendText.java new file mode 100644 index 0000000..f15056e --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendText.java @@ -0,0 +1,18 @@ +package com.example.wxhk.model.request; + +import com.example.wxhk.infe.SendMsg; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 发送文本 + * + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class SendText implements SendMsg { + String wxid; + String msg; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/ThroughFriends.java b/java_client/src/main/java/com/example/wxhk/model/request/ThroughFriends.java new file mode 100644 index 0000000..2134b40 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/ThroughFriends.java @@ -0,0 +1,27 @@ +package com.example.wxhk.model.request; + +import com.example.wxhk.infe.SendMsg; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 通过好友请求 + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class ThroughFriends implements SendMsg { + /** + * 添加好友消息内容里的encryptusername + */ + String v3; + /** + * 添加好友消息内容里的ticket + */ + String v4; + /** + * 好友权限,0是无限制,1是不让他看我,2是不看他,3是1+2 + */ + String permission; +} diff --git a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java index a31fec0..0f99519 100644 --- a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java +++ b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java @@ -18,42 +18,49 @@ import org.w3c.dom.NodeList; import java.math.BigDecimal; import java.util.Iterator; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @Component public class WxMsgHandle { - public static final ConcurrentHashMap map = new ConcurrentHashMap<>(32); - + public static final ConcurrentHashMap map = new ConcurrentHashMap<>(32); + protected static final Log log = Log.get(); public static ConcurrentHashMap cache = new ConcurrentHashMap<>(); - protected static final Log log=Log.get(); - - public WxMsgHandle() { add(chatMsg -> { + if(Objects.equals(chatMsg.getIsSendMsg(), 1) && Objects.equals(chatMsg.getIsSendByPhone(), 1)){ + log.info("手机端对:{}发出:{}",chatMsg.getFromUser(),chatMsg.getContent()); + return 1; + } return 1; - },WxMsgType.私聊信息);// 好友请求 + }, WxMsgType.私聊信息); + add(chatMsg -> { + if("filehelper".equals(chatMsg.getFromUser())){ + log.info("文件助手:{},",chatMsg.getContent()); + } + return 1; + }, WxMsgType.收到转账之后或者文件助手等信息); add(chatMsg -> { HttpSendUtil.通过好友请求(chatMsg); return 1; - },WxMsgType.好友请求);// 好友请求 + }, WxMsgType.好友请求);// 好友请求 add(chatMsg -> { boolean f = 解析扫码支付第二段(chatMsg); - if(f){ - f=解析收款信息1段(chatMsg); - if(f){ + if (f) { + f = 解析收款信息1段(chatMsg); + if (f) { 解析收款信息2段(chatMsg); } } return null; - },WxMsgType.转账和收款); + }, WxMsgType.转账和收款); add(chatMsg -> { boolean f = 解析扫码支付第一段(chatMsg); return null; - },WxMsgType.扫码触发); - + }, WxMsgType.扫码触发); } @@ -69,14 +76,14 @@ public class WxMsgHandle { Document document = XmlUtil.parseXml(chatMsg.getContent()); Element documentElement = document.getDocumentElement(); String localName = documentElement.getLocalName(); - if("sysmsg".equals(localName)){ + if ("sysmsg".equals(localName)) { String type = documentElement.getAttribute("type"); - if("paymsg".equals(type)){ + if ("paymsg".equals(type)) { NodeList outtradeno = documentElement.getElementsByTagName("outtradeno"); - if (outtradeno.getLength()>0) { + if (outtradeno.getLength() > 0) { String textContent = outtradeno.item(0).getTextContent(); String textContent1 = documentElement.getElementsByTagName("username").item(0).getTextContent(); - cache.put(textContent,textContent1); + cache.put(textContent, textContent1); return false; } } @@ -99,23 +106,23 @@ public class WxMsgHandle { Document document = XmlUtil.parseXml(chatMsg.getContent()); Element documentElement = document.getDocumentElement(); String localName = documentElement.getLocalName(); - if("msg".equals(localName)){ + if ("msg".equals(localName)) { NodeList outtradeno = documentElement.getElementsByTagName("weapp_path"); - if(outtradeno.getLength()>1){ + if (outtradeno.getLength() > 1) { String textContent = outtradeno.item(1).getTextContent(); Set> entries = cache.entrySet(); Iterator> iterator = entries.iterator(); - while (iterator.hasNext()){ + while (iterator.hasNext()) { Map.Entry next = iterator.next(); if (textContent.contains(next.getKey())) { // 得到了交易信息 NodeList word = documentElement.getElementsByTagName("word"); String monery = word.item(1).getTextContent(); String remark = word.item(3).getTextContent(); - if(monery.startsWith("¥")){ + if (monery.startsWith("¥")) { String substring = monery.substring(1); BigDecimal decimal = new BigDecimal(substring); - log.info("扫码收款:{},付款人:{},付款备注:{}",decimal.stripTrailingZeros().toPlainString(),next.getValue(),remark); + log.info("扫码收款:{},付款人:{},付款备注:{}", decimal.stripTrailingZeros().toPlainString(), next.getValue(), remark); iterator.remove(); return false; } @@ -130,25 +137,26 @@ public class WxMsgHandle { } return true; } + public static boolean 解析收款信息2段(PrivateChatMsg chatMsg) { try { Document document = XmlUtil.parseXml(chatMsg.getContent()); Element documentElement = document.getDocumentElement(); String localName = documentElement.getLocalName(); - if("msg".equals(localName)){ - if (documentElement.getElementsByTagName("transcationid").getLength()>0) { + if ("msg".equals(localName)) { + if (documentElement.getElementsByTagName("transcationid").getLength() > 0) { String remark = documentElement.getElementsByTagName("pay_memo").item(0).getTextContent(); String monery = documentElement.getElementsByTagName("feedesc").item(0).getTextContent(); String receiver_username = documentElement.getElementsByTagName("receiver_username").item(0).getTextContent(); - if(InitWeChat.WXID_MAP.contains(receiver_username)){ + if (InitWeChat.WXID_MAP.contains(receiver_username)) { // 如果是自己转出去的,则不需要解析了 return false; } - if(monery.startsWith("¥")){ + if (monery.startsWith("¥")) { String substring = monery.substring(1); BigDecimal decimal = new BigDecimal(substring); - log.info("收款:{},付款人:{},付款备注:{}",decimal.stripTrailingZeros().toPlainString(),receiver_username,remark); + log.info("收款:{},付款人:{},付款备注:{}", decimal.stripTrailingZeros().toPlainString(), receiver_username, remark); return false; } } @@ -163,52 +171,50 @@ public class WxMsgHandle { /** * 解析收款信息1段 * 会自动进行收款 + * * @param chatMsg * @return boolean true则 继续解析,false则不需要解析了 */ - public static boolean 解析收款信息1段(PrivateChatMsg chatMsg){ + public static boolean 解析收款信息1段(PrivateChatMsg chatMsg) { try { String content = chatMsg.getContent(); Document document = XmlUtil.parseXml(content); Node paysubtype = document.getElementsByTagName("paysubtype").item(0); - if("1".equals(paysubtype.getTextContent().trim())){ + if ("1".equals(paysubtype.getTextContent().trim())) { // 手机发出去的 String textContent = document.getElementsByTagName("receiver_username").item(0).getTextContent(); - if(!InitWeChat.WXID_MAP.contains(textContent)){ + if (!InitWeChat.WXID_MAP.contains(textContent)) { // 如果不是机器人收款,则认为不需要解析了,大概率是机器人自己发出去的 return false; } Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0); Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0); - HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, new JsonObject().put("wxid",chatMsg.getFromUser()) - .put("transcationId",transcationid.getTextContent()) - .put("transferId",transferid.getTextContent())); + HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, new JsonObject().put("wxid", chatMsg.getFromUser()) + .put("transcationId", transcationid.getTextContent()) + .put("transferId", transferid.getTextContent())); return false; } - } catch (Exception e) { - log.error(e); + } catch (Exception e) { + log.error(e); } return true; } - - public interface Handle{ - Object handle(PrivateChatMsg chatMsg); + public static void exec(PrivateChatMsg chatMsg) { + Handle handle = map.get(chatMsg.getType()); + if (handle != null) { + handle.handle(chatMsg); + } } - - - public void add(Handle handle, WxMsgType...type){ + public void add(Handle handle, WxMsgType... type) { for (WxMsgType integer : type) { map.put(integer.getType(), handle); } } - public static void exec(PrivateChatMsg chatMsg){ - Handle handle = map.get(chatMsg.getType()); - if (handle != null) { - handle.handle(chatMsg); - } + public interface Handle { + Object handle(PrivateChatMsg chatMsg); } } diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java index 39add74..d9fa2f6 100644 --- a/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java @@ -15,40 +15,51 @@ import java.util.concurrent.TimeUnit; /** * 消息处理 + * * @author wt * @date 2023/05/31 */ @Component public class ArrHandle { - protected static final Log log = Log.get(); - public static final ThreadPoolExecutor sub = new ThreadPoolExecutor(1, 10, 30, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new NamedThreadFactory("sub", false)); + public static final ThreadPoolExecutor sub = new ThreadPoolExecutor(1, 10, 30, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new NamedThreadFactory("sub", false)); + public static final ThreadLocal chatMsgThreadLocal = new InheritableThreadLocal<>(); + protected static final Log log = Log.get(); + /** + * 得到当前正在处理的消息 + * + * @return {@link PrivateChatMsg} + */ + public static PrivateChatMsg getPriMsg() { + return chatMsgThreadLocal.get(); + } - @PostConstruct - public void exec(){ - for (int i = 0; i < sub.getCorePoolSize(); i++) { - sub.submit(() -> { - while (!Thread.currentThread().isInterrupted()){ - try { - JsonObject take = VertxTcp.LINKED_BLOCKING_QUEUE.take(); - log.info("{}",take.encode()); - - PrivateChatMsg privateChatMsg = take.mapTo(PrivateChatMsg.class); - if("weixin".equals(privateChatMsg.getFromUser())){ - String s = HttpSendUtil.获取当前登陆微信id(); - InitWeChat.WXID_MAP.add(s); - continue; - } - WxMsgHandle.exec(privateChatMsg); - } catch (Exception e) { - log.error(e); + @PostConstruct + public void exec() { + for (int i = 0; i < sub.getCorePoolSize(); i++) { + sub.submit(() -> { + while (!Thread.currentThread().isInterrupted()) { + try { + JsonObject take = VertxTcp.LINKED_BLOCKING_QUEUE.take(); + log.info("{}", take.encode()); + PrivateChatMsg privateChatMsg = take.mapTo(PrivateChatMsg.class); + chatMsgThreadLocal.set(privateChatMsg); + if ("weixin".equals(privateChatMsg.getFromUser())) { + String s = HttpSendUtil.获取当前登陆微信id(); + InitWeChat.WXID_MAP.add(s); + continue; } + WxMsgHandle.exec(privateChatMsg); + chatMsgThreadLocal.remove(); + } catch (Exception e) { + log.error(e); } - log.error("退出线程了"); - }); - } - + } + log.error("退出线程了"); + }); } + } + } diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java index 649eaf3..9c5eab5 100644 --- a/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java @@ -31,9 +31,8 @@ import java.io.IOException; public class InitWeChat implements CommandLineRunner { public final static Log log = Log.get(); - + public static final ConcurrentHashSet WXID_MAP = new ConcurrentHashSet<>(); public static String wxPath; - public static Integer wxPort; public static Integer vertxPort; /** @@ -41,9 +40,6 @@ public class InitWeChat implements CommandLineRunner { */ public static File DLL_PATH; - public static final ConcurrentHashSet WXID_MAP=new ConcurrentHashSet<>(); - - public static void 注入dll(String wxPid) throws IOException { String format = StrUtil.format("cmd /C c.exe -I {} -p {}\\wxhelper.dll -m {}", wxPid, DLL_PATH.getAbsolutePath(), wxPid); Process exec = Runtime.getRuntime().exec(format, null, DLL_PATH); @@ -138,15 +134,15 @@ public class InitWeChat implements CommandLineRunner { 注入dll(wxPid); } ThreadUtil.execute(() -> { - while (!Thread.currentThread().isInterrupted()){ + while (!Thread.currentThread().isInterrupted()) { JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.检查微信登陆, new JsonObject()); - if(exec.getInteger("code").equals(1)){ + if (exec.getInteger("code").equals(1)) { JsonObject dl = HttpSyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject()); JsonObject jsonObject = dl.getJsonObject("data"); String wx = jsonObject.getString("wxid"); WXID_MAP.add(wx); if (log.isDebugEnabled()) { - log.debug("检测到微信登陆:{}",wx); + log.debug("检测到微信登陆:{}", wx); } break; } diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java index aad3656..4a9bd8b 100644 --- a/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java @@ -19,21 +19,20 @@ import java.util.concurrent.LinkedBlockingQueue; /** * 接受微信hook信息 + * * @author wt * @date 2023/05/26 */ @Component @Order() public class VertxTcp extends AbstractVerticle implements CommandLineRunner { - protected static final Log log = Log.get(); - NetServer netServer; public final static LinkedBlockingQueue LINKED_BLOCKING_QUEUE = new LinkedBlockingQueue<>(); - - + protected static final Log log = Log.get(); + NetServer netServer; @Override public void start(Promise startPromise) throws Exception { - netServer = vertx.createNetServer(new NetServerOptions() + netServer = vertx.createNetServer(new NetServerOptions() .setPort(InitWeChat.getVertxPort()) .setIdleTimeout(0) .setLogActivity(false) @@ -63,9 +62,9 @@ public class VertxTcp extends AbstractVerticle implements CommandLineRunner { listen.onComplete(event -> { boolean succeeded = event.succeeded(); if (succeeded) { - HttpAsyncUtil.exec(HttpAsyncUtil.Type.开启hook, new JsonObject().put("port", "8080").put("ip", "127.0.0.1")); + HttpAsyncUtil.exec(HttpAsyncUtil.Type.开启hook, new JsonObject().put("port", InitWeChat.getVertxPort().toString()).put("ip", "127.0.0.1")); startPromise.complete(); - }else{ + } else { startPromise.fail(event.cause()); } @@ -74,6 +73,6 @@ public class VertxTcp extends AbstractVerticle implements CommandLineRunner { @Override public void run(String... args) throws Exception { - WxhkApplication.vertx.deployVerticle(this,new DeploymentOptions().setWorkerPoolSize(6)); + WxhkApplication.vertx.deployVerticle(this, new DeploymentOptions().setWorkerPoolSize(6)); } } diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java index 49d9015..b8d2ba0 100644 --- a/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java +++ b/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java @@ -19,9 +19,9 @@ import org.dromara.hutool.log.Log; * @date 2023/05/25 */ public class HttpAsyncUtil { - protected static final Log log = Log.get(); - public static final WebClient client = WebClient.create(WxhkApplication.vertx,new WebClientOptions().setDefaultHost("localhost").setDefaultPort(InitWeChat.wxPort) + public static final WebClient client = WebClient.create(WxhkApplication.vertx, new WebClientOptions().setDefaultHost("localhost").setDefaultPort(InitWeChat.wxPort) .setConnectTimeout(10000).setMaxPoolSize(10).setPoolEventLoopSize(10)); + protected static final Log log = Log.get(); public static Future> exec(Type type, JsonObject object) { return client.post(InitWeChat.wxPort, "localhost", "/api/?type=" + type.getType()) @@ -29,7 +29,7 @@ public class HttpAsyncUtil { .onSuccess(event -> { if (log.isDebugEnabled()) { - log.debug("type:{},{}",type.getType(), event.bodyAsJsonObject()); + log.debug("type:{},{}", type.getType(), event.bodyAsJsonObject()); } } ); @@ -65,12 +65,12 @@ public class HttpAsyncUtil { ; String type; - public String getType() { - return type; - } - Type(String type) { this.type = type; } + + public String getType() { + return type; + } } } diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java index 8a815ea..6381a84 100644 --- a/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java +++ b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java @@ -1,6 +1,8 @@ package com.example.wxhk.util; import com.example.wxhk.model.PrivateChatMsg; +import com.example.wxhk.model.request.SendMsg; +import com.example.wxhk.tcp.vertx.ArrHandle; import com.example.wxhk.tcp.vertx.InitWeChat; import io.vertx.core.json.JsonObject; import org.dromara.hutool.core.util.XmlUtil; @@ -10,46 +12,75 @@ import org.w3c.dom.Node; /** * 常见方法 + * * @author wt * @date 2023/05/29 */ public class HttpSendUtil { - protected static final Log log = Log.get(); - public static JsonObject 通过好友请求(PrivateChatMsg msg){ + protected static final Log log = Log.get(); + + public static JsonObject 通过好友请求(PrivateChatMsg msg) { Document document = XmlUtil.parseXml(msg.getContent()); String encryptusername = document.getDocumentElement().getAttribute("encryptusername"); String ticket = document.getDocumentElement().getAttribute("ticket"); return HttpSyncUtil.exec(HttpAsyncUtil.Type.通过好友申请, new JsonObject().put("v3", encryptusername).put("v4", ticket).put("permission", "0")); } - public static JsonObject 确认收款(PrivateChatMsg msg){ + + public static JsonObject 确认收款(PrivateChatMsg msg) { try { String content = msg.getContent(); Document document = XmlUtil.parseXml(content); Node paysubtype = document.getElementsByTagName("paysubtype").item(0); - if("1".equals(paysubtype.getTextContent().trim())){ + if ("1".equals(paysubtype.getTextContent().trim())) { // 手机发出去的 String textContent = document.getElementsByTagName("receiver_username").item(0).getTextContent(); - if(!InitWeChat.WXID_MAP.contains(textContent)){ - return new JsonObject().put("spick",true); + if (!InitWeChat.WXID_MAP.contains(textContent)) { + return new JsonObject().put("spick", true); } Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0); Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0); - return HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, new JsonObject().put("wxid",msg.getFromUser()) - .put("transcationId",transcationid.getTextContent()) - .put("transferId",transferid.getTextContent())); + return HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, new JsonObject().put("wxid", msg.getFromUser()) + .put("transcationId", transcationid.getTextContent()) + .put("transferId", transferid.getTextContent())); } // 如果是确认接受收款,则跳过 return new JsonObject(); - } catch (Exception e) { + } catch (Exception e) { throw new RuntimeException(e); } } - public static String 获取当前登陆微信id(){ + public static JsonObject 发送文本(String wxid,String msg){ + return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送文本, JsonObject.mapFrom(new SendMsg().setMsg(msg).setWxid(wxid))); + } + public static JsonObject 发送文本(String msg){ + return 发送文本(ArrHandle.getPriMsg().getFromUser(),msg); + } + public static JsonObject 发送at文本(String chatRoomId,String wxids,String msg){ + return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送at文本, JsonObject.mapFrom(new SendMsg().setMsg(msg).setWxids(wxids).setChatRoomId(chatRoomId))); + } + public static JsonObject 发送at文本(String wxids,String msg){ + return 发送at文本(ArrHandle.getPriMsg().getFromGroup(),wxids,msg); + } + public static JsonObject 发送图片(String wxid,String msg){ + return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送图片, JsonObject.mapFrom(new SendMsg().setImagePath(msg).setWxid(wxid))); + } + public static JsonObject 发送图片(String msg){ + return 发送图片(ArrHandle.getPriMsg().getFromUser(),msg); + } + public static JsonObject 发送文件(String wxid,String msg){ + return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送文件, JsonObject.mapFrom(new SendMsg().setFilePath(msg).setWxid(wxid))); + } + public static JsonObject 发送文件(String msg){ + return 发送文件(ArrHandle.getPriMsg().getFromUser(),msg); + } + + + public static String 获取当前登陆微信id() { JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject()); return exec.getJsonObject("data").getString("wxid"); } diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java index d5a2a63..21686ac 100644 --- a/java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java +++ b/java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java @@ -16,18 +16,20 @@ import org.dromara.hutool.log.Log; * @date 2023/05/25 */ public class HttpSyncUtil { + protected static final Log log = Log.get(); static final ClientEngine engine; - protected static final Log log = Log.get(); + static { ClientConfig clientConfig = ClientConfig.of() .setTimeout(30 * 1000); engine = ClientEngineFactory.createEngine(clientConfig); } - public static JsonObject exec(HttpAsyncUtil.Type type,JsonObject obj){ + + public static JsonObject exec(HttpAsyncUtil.Type type, JsonObject obj) { String post = engine.send(Request.of("http://localhost:" + InitWeChat.wxPort + "/api/?type=" + type.getType()).method(Method.POST).body(obj.encode())).bodyStr(); if (log.isDebugEnabled()) { - log.debug("type:{},{}",type.getType(),post); + log.debug("type:{},{}", type.getType(), post); } return new JsonObject(post); } From 51498cde8ac9dd41e9647f51929ae505a1ecd348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Thu, 1 Jun 2023 10:22:56 +0800 Subject: [PATCH 83/97] =?UTF-8?q?=E5=B8=B8=E8=A7=81=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=92=8C=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/wxhk/infe/SendMsg.java | 8 ++- .../wxhk/model/request/AddFriends.java | 2 +- .../wxhk/model/request/ConfirmThePayment.java | 2 +- .../wxhk/model/request/FindWeChat.java | 2 +- .../wxhk/model/request/ForwardMessages.java | 2 +- .../wxhk/model/request/GetGroupMembers.java | 2 +- .../GetsTheNicknameOfAGroupMember.java | 2 +- .../request/IncreaseGroupMembership.java | 2 +- .../example/wxhk/model/request/OpenHook.java | 18 +++++ .../wxhk/model/request/SendAtText.java | 2 +- .../example/wxhk/model/request/SendFile.java | 2 +- .../example/wxhk/model/request/SendImg.java | 2 +- .../example/wxhk/model/request/SendText.java | 2 +- .../wxhk/model/request/ThroughFriends.java | 2 +- .../com/example/wxhk/util/HttpAsyncUtil.java | 1 + .../com/example/wxhk/util/HttpSendUtil.java | 72 +++++++++++++++---- .../example/wxhk/tcp/HttpAsyncUtilTest.java | 6 ++ 17 files changed, 103 insertions(+), 26 deletions(-) create mode 100644 java_client/src/main/java/com/example/wxhk/model/request/OpenHook.java diff --git a/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java b/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java index 1ab7d3a..4050bd6 100644 --- a/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java +++ b/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java @@ -1,11 +1,17 @@ package com.example.wxhk.infe; +import io.vertx.core.json.JsonObject; + /** * http接口请求的基础接口 * * @author wt * @date 2023/06/01 */ -public interface SendMsg extends java.io.Serializable{ +public interface SendMsg extends java.io.Serializable{ + + default JsonObject toJson(){ + return JsonObject.mapFrom(this); + } } diff --git a/java_client/src/main/java/com/example/wxhk/model/request/AddFriends.java b/java_client/src/main/java/com/example/wxhk/model/request/AddFriends.java index cc20d11..42178aa 100644 --- a/java_client/src/main/java/com/example/wxhk/model/request/AddFriends.java +++ b/java_client/src/main/java/com/example/wxhk/model/request/AddFriends.java @@ -11,7 +11,7 @@ import lombok.experimental.Accessors; */ @Data @Accessors(chain = true) -public class AddFriends implements SendMsg { +public class AddFriends implements SendMsg { String wxid; /** * 验证信息 diff --git a/java_client/src/main/java/com/example/wxhk/model/request/ConfirmThePayment.java b/java_client/src/main/java/com/example/wxhk/model/request/ConfirmThePayment.java index 26e0f0b..d17d7ff 100644 --- a/java_client/src/main/java/com/example/wxhk/model/request/ConfirmThePayment.java +++ b/java_client/src/main/java/com/example/wxhk/model/request/ConfirmThePayment.java @@ -11,7 +11,7 @@ import lombok.experimental.Accessors; */ @Data @Accessors(chain = true) -public class ConfirmThePayment implements SendMsg { +public class ConfirmThePayment implements SendMsg { /** * 转账人微信id,从hook的消息中获取 */ diff --git a/java_client/src/main/java/com/example/wxhk/model/request/FindWeChat.java b/java_client/src/main/java/com/example/wxhk/model/request/FindWeChat.java index dc3d4a0..68a80a9 100644 --- a/java_client/src/main/java/com/example/wxhk/model/request/FindWeChat.java +++ b/java_client/src/main/java/com/example/wxhk/model/request/FindWeChat.java @@ -11,7 +11,7 @@ import lombok.experimental.Accessors; */ @Data @Accessors(chain = true) -public class FindWeChat implements SendMsg { +public class FindWeChat implements SendMsg { /** * 通过 手机或qq查询信息 */ diff --git a/java_client/src/main/java/com/example/wxhk/model/request/ForwardMessages.java b/java_client/src/main/java/com/example/wxhk/model/request/ForwardMessages.java index 723e928..fed4d28 100644 --- a/java_client/src/main/java/com/example/wxhk/model/request/ForwardMessages.java +++ b/java_client/src/main/java/com/example/wxhk/model/request/ForwardMessages.java @@ -11,7 +11,7 @@ import lombok.experimental.Accessors; */ @Data @Accessors(chain = true) -public class ForwardMessages implements SendMsg { +public class ForwardMessages implements SendMsg { /** * 消息接收人wxid */ diff --git a/java_client/src/main/java/com/example/wxhk/model/request/GetGroupMembers.java b/java_client/src/main/java/com/example/wxhk/model/request/GetGroupMembers.java index 7f48379..036fa9d 100644 --- a/java_client/src/main/java/com/example/wxhk/model/request/GetGroupMembers.java +++ b/java_client/src/main/java/com/example/wxhk/model/request/GetGroupMembers.java @@ -11,6 +11,6 @@ import lombok.experimental.Accessors; */ @Data @Accessors(chain = true) -public class GetGroupMembers implements SendMsg { +public class GetGroupMembers implements SendMsg { String chatRoomId; } diff --git a/java_client/src/main/java/com/example/wxhk/model/request/GetsTheNicknameOfAGroupMember.java b/java_client/src/main/java/com/example/wxhk/model/request/GetsTheNicknameOfAGroupMember.java index 60e4e22..5d68590 100644 --- a/java_client/src/main/java/com/example/wxhk/model/request/GetsTheNicknameOfAGroupMember.java +++ b/java_client/src/main/java/com/example/wxhk/model/request/GetsTheNicknameOfAGroupMember.java @@ -11,7 +11,7 @@ import lombok.experimental.Accessors; */ @Data @Accessors(chain = true) -public class GetsTheNicknameOfAGroupMember implements SendMsg { +public class GetsTheNicknameOfAGroupMember implements SendMsg { /** * 聊天室id */ diff --git a/java_client/src/main/java/com/example/wxhk/model/request/IncreaseGroupMembership.java b/java_client/src/main/java/com/example/wxhk/model/request/IncreaseGroupMembership.java index a43a1e4..a80cee2 100644 --- a/java_client/src/main/java/com/example/wxhk/model/request/IncreaseGroupMembership.java +++ b/java_client/src/main/java/com/example/wxhk/model/request/IncreaseGroupMembership.java @@ -11,7 +11,7 @@ import lombok.experimental.Accessors; */ @Data @Accessors(chain = true) -public class IncreaseGroupMembership implements SendMsg { +public class IncreaseGroupMembership implements SendMsg { /** * 聊天室id */ diff --git a/java_client/src/main/java/com/example/wxhk/model/request/OpenHook.java b/java_client/src/main/java/com/example/wxhk/model/request/OpenHook.java new file mode 100644 index 0000000..12929d0 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/OpenHook.java @@ -0,0 +1,18 @@ +package com.example.wxhk.model.request; + +import com.example.wxhk.infe.SendMsg; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 开启hook + * + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +public class OpenHook implements SendMsg { + String port; + String ip; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendAtText.java b/java_client/src/main/java/com/example/wxhk/model/request/SendAtText.java index 28b93b0..d158bc3 100644 --- a/java_client/src/main/java/com/example/wxhk/model/request/SendAtText.java +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendAtText.java @@ -11,7 +11,7 @@ import lombok.experimental.Accessors; */ @Data @Accessors(chain = true) -public class SendAtText implements SendMsg { +public class SendAtText implements SendMsg { /** * 聊天室id,群聊用 */ diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendFile.java b/java_client/src/main/java/com/example/wxhk/model/request/SendFile.java index ee5e409..54fcfb9 100644 --- a/java_client/src/main/java/com/example/wxhk/model/request/SendFile.java +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendFile.java @@ -12,7 +12,7 @@ import lombok.experimental.Accessors; */ @Data @Accessors(chain = true) -public class SendFile implements SendMsg { +public class SendFile implements SendMsg { String wxid; /** * 发送文件路径 diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendImg.java b/java_client/src/main/java/com/example/wxhk/model/request/SendImg.java index a019d02..cd62977 100644 --- a/java_client/src/main/java/com/example/wxhk/model/request/SendImg.java +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendImg.java @@ -12,7 +12,7 @@ import lombok.experimental.Accessors; */ @Data @Accessors(chain = true) -public class SendImg implements SendMsg { +public class SendImg implements SendMsg { String wxid; /** * 发送图片接口 diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendText.java b/java_client/src/main/java/com/example/wxhk/model/request/SendText.java index f15056e..a749c3d 100644 --- a/java_client/src/main/java/com/example/wxhk/model/request/SendText.java +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendText.java @@ -12,7 +12,7 @@ import lombok.experimental.Accessors; */ @Data @Accessors(chain = true) -public class SendText implements SendMsg { +public class SendText implements SendMsg { String wxid; String msg; } diff --git a/java_client/src/main/java/com/example/wxhk/model/request/ThroughFriends.java b/java_client/src/main/java/com/example/wxhk/model/request/ThroughFriends.java index 2134b40..4e68332 100644 --- a/java_client/src/main/java/com/example/wxhk/model/request/ThroughFriends.java +++ b/java_client/src/main/java/com/example/wxhk/model/request/ThroughFriends.java @@ -11,7 +11,7 @@ import lombok.experimental.Accessors; */ @Data @Accessors(chain = true) -public class ThroughFriends implements SendMsg { +public class ThroughFriends implements SendMsg { /** * 添加好友消息内容里的encryptusername */ diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java index b8d2ba0..db408bf 100644 --- a/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java +++ b/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java @@ -53,6 +53,7 @@ public class HttpAsyncUtil { 发送文件("6"), 开启hook("9"), 关闭hook("10"), + 添加好友("20"), 通过好友申请("23"), 获取群成员("25"), 获取群成员昵称("26"), diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java index 6381a84..a599c60 100644 --- a/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java +++ b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java @@ -1,7 +1,7 @@ package com.example.wxhk.util; import com.example.wxhk.model.PrivateChatMsg; -import com.example.wxhk.model.request.SendMsg; +import com.example.wxhk.model.request.*; import com.example.wxhk.tcp.vertx.ArrHandle; import com.example.wxhk.tcp.vertx.InitWeChat; import io.vertx.core.json.JsonObject; @@ -54,29 +54,40 @@ public class HttpSendUtil { } - public static JsonObject 发送文本(String wxid,String msg){ + public static JsonObject 发送文本(String wxid, String msg) { return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送文本, JsonObject.mapFrom(new SendMsg().setMsg(msg).setWxid(wxid))); } - public static JsonObject 发送文本(String msg){ - return 发送文本(ArrHandle.getPriMsg().getFromUser(),msg); + + public static JsonObject 发送文本(String msg) { + return 发送文本(ArrHandle.getPriMsg().getFromUser(), msg); } - public static JsonObject 发送at文本(String chatRoomId,String wxids,String msg){ + + public static JsonObject 发送at文本(String chatRoomId, String wxids, String msg) { return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送at文本, JsonObject.mapFrom(new SendMsg().setMsg(msg).setWxids(wxids).setChatRoomId(chatRoomId))); } - public static JsonObject 发送at文本(String wxids,String msg){ - return 发送at文本(ArrHandle.getPriMsg().getFromGroup(),wxids,msg); + + public static JsonObject 发送at文本(String wxids, String msg) { + return 发送at文本(ArrHandle.getPriMsg().getFromGroup(), wxids, msg); } - public static JsonObject 发送图片(String wxid,String msg){ + + public static JsonObject 发送图片(String wxid, String msg) { return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送图片, JsonObject.mapFrom(new SendMsg().setImagePath(msg).setWxid(wxid))); } - public static JsonObject 发送图片(String msg){ - return 发送图片(ArrHandle.getPriMsg().getFromUser(),msg); + + public static JsonObject 发送图片(String msg) { + return 发送图片(ArrHandle.getPriMsg().getFromUser(), msg); } - public static JsonObject 发送文件(String wxid,String msg){ + + public static JsonObject 发送文件(String wxid, String msg) { return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送文件, JsonObject.mapFrom(new SendMsg().setFilePath(msg).setWxid(wxid))); } - public static JsonObject 发送文件(String msg){ - return 发送文件(ArrHandle.getPriMsg().getFromUser(),msg); + + public static JsonObject 发送文件(String msg) { + return 发送文件(ArrHandle.getPriMsg().getFromUser(), msg); + } + + public static JsonObject 添加好友(AddFriends p) { + return HttpSyncUtil.exec(HttpAsyncUtil.Type.添加好友, p.toJson()); } @@ -85,4 +96,39 @@ public class HttpSendUtil { return exec.getJsonObject("data").getString("wxid"); } + public static JsonObject 联系人列表(){ + JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.联系人列表, new JsonObject()); + return exec.getJsonObject("data"); + } + public static JsonObject 开启hook(OpenHook hook){ + JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.开启hook,hook.toJson()); + return exec.getJsonObject("data"); + } + + @Deprecated + public static com.example.wxhk.infe.SendMsg of(HttpAsyncUtil.Type type) { + switch (type) { + + case 检查微信登陆 -> { + + } + case 获取登录信息 -> { + } + case 发送文本 -> { + return new SendText(); + } + case 发送at文本 -> { + return new SendAtText(); + } + case 发送图片 -> { + return new SendImg(); + } + case 发送文件 -> { + return new SendFile(); + } + + } + return new SendText(); + } + } diff --git a/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java b/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java index 6bf1ddf..1f317a0 100644 --- a/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java +++ b/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java @@ -40,5 +40,11 @@ class HttpAsyncUtilTest { } ThreadUtil.sync(this); + } + @Test + void exec2() { + + + } } \ No newline at end of file From 74831d9d8b218bd40c1478dbe0d21bc354c1f239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Thu, 1 Jun 2023 10:27:56 +0800 Subject: [PATCH 84/97] =?UTF-8?q?=E5=B8=B8=E8=A7=81=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=92=8C=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- java_client/src/main/java/com/example/wxhk/infe/SendMsg.java | 3 +++ .../src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java b/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java index 4050bd6..8de6496 100644 --- a/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java +++ b/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java @@ -8,10 +8,13 @@ import io.vertx.core.json.JsonObject; * @author wt * @date 2023/06/01 */ +@FunctionalInterface public interface SendMsg extends java.io.Serializable{ default JsonObject toJson(){ return JsonObject.mapFrom(this); } + + T of(); } diff --git a/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java b/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java index 1f317a0..a1c5f5e 100644 --- a/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java +++ b/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java @@ -45,6 +45,5 @@ class HttpAsyncUtilTest { void exec2() { - } } \ No newline at end of file From e0588792956cc4595266b6cc7092e2c1fc635e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Thu, 1 Jun 2023 11:13:49 +0800 Subject: [PATCH 85/97] =?UTF-8?q?=E7=BE=A4=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/example/wxhk/infe/Resp.java | 11 ++++ .../java/com/example/wxhk/infe/SendMsg.java | 3 -- .../wxhk/model/response/ContactList.java | 50 +++++++++++++++++++ .../wxhk/model/response/GroupMembers.java | 37 ++++++++++++++ .../com/example/wxhk/util/HttpSendUtil.java | 18 +++++-- .../example/wxhk/tcp/HttpAsyncUtilTest.java | 1 - .../example/wxhk/util/HttpSendUtilTest.java | 41 +++++++++++++++ 7 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 java_client/src/main/java/com/example/wxhk/infe/Resp.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/response/ContactList.java create mode 100644 java_client/src/main/java/com/example/wxhk/model/response/GroupMembers.java create mode 100644 java_client/src/test/java/com/example/wxhk/util/HttpSendUtilTest.java diff --git a/java_client/src/main/java/com/example/wxhk/infe/Resp.java b/java_client/src/main/java/com/example/wxhk/infe/Resp.java new file mode 100644 index 0000000..1c3fd04 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/infe/Resp.java @@ -0,0 +1,11 @@ +package com.example.wxhk.infe; + +/** + * http 响应 + * @author wt + * @date 2023/06/01 + */ +public interface Resp extends java.io.Serializable{ + + +} diff --git a/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java b/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java index 8de6496..4050bd6 100644 --- a/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java +++ b/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java @@ -8,13 +8,10 @@ import io.vertx.core.json.JsonObject; * @author wt * @date 2023/06/01 */ -@FunctionalInterface public interface SendMsg extends java.io.Serializable{ default JsonObject toJson(){ return JsonObject.mapFrom(this); } - - T of(); } diff --git a/java_client/src/main/java/com/example/wxhk/model/response/ContactList.java b/java_client/src/main/java/com/example/wxhk/model/response/ContactList.java new file mode 100644 index 0000000..fcd1770 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/response/ContactList.java @@ -0,0 +1,50 @@ +package com.example.wxhk.model.response; + +import com.example.wxhk.infe.Resp; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.List; + +/** + * 联系人列表 + * @author wt + * @date 2023/06/01 + */ +@Data +@Accessors(chain = true) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ContactList implements Resp { + + /** + * code : 1 + * data : [{"customAccount":"","delFlag":0,"type":1,"userName":"朋友推荐消息","verifyFlag":0,"wxid":"fmessage"},{"customAccount":"tencent_cloud","delFlag":0,"type":3,"userName":"腾讯云助手","verifyFlag":24,"wxid":"gh_a73e2407e0f8"},{"customAccount":"","delFlag":0,"type":1,"userName":"语音记事本","verifyFlag":0,"wxid":"medianote"},{"customAccount":"","delFlag":0,"type":1,"userName":"漂流瓶","verifyFlag":0,"wxid":"floatbottle"},{"customAccount":"jys-wt","delFlag":0,"type":8651011,"userName":"时光似水戏流年","verifyFlag":0,"wxid":"wxid_gf1fogt5a0pq22"},{"customAccount":"wxzhifu","delFlag":0,"type":3,"userName":"微信支付","verifyFlag":24,"wxid":"gh_3dfda90e39d6"},{"customAccount":"dhkzfr","delFlag":0,"type":3,"userName":"阿芙(代发)","verifyFlag":0,"wxid":"wxid_kh16lri40gzj22"},{"customAccount":"","delFlag":0,"type":3,"userName":"文件传输助手","verifyFlag":0,"wxid":"filehelper"},{"customAccount":"","delFlag":0,"type":3,"userName":"fff","verifyFlag":0,"wxid":"24964676359@chatroom"},{"customAccount":"","delFlag":0,"type":2,"userName":"最美阿芙","verifyFlag":0,"wxid":"23793178249@chatroom"},{"customAccount":"afu943344","delFlag":0,"type":2,"userName":"A-阿芙4号-LOL永劫云顶出租-代发","verifyFlag":0,"wxid":"wxid_1gxthknqbmwv22"},{"customAccount":"","delFlag":0,"type":3,"userName":"微信收款助手","verifyFlag":24,"wxid":"gh_f0a92aa7146c"},{"customAccount":"","delFlag":0,"type":0,"userName":"","verifyFlag":0,"wxid":"25984984710827869@openim"}] + * result : OK + */ + + private Integer code; + private String result; + private List data; + + @Data + @Accessors(chain = true) + public static class DataBean implements Serializable { + /** + * customAccount : + * delFlag : 0 + * type : 1 + * userName : 朋友推荐消息 + * verifyFlag : 0 + * wxid : fmessage + */ + + private String customAccount; + private Integer delFlag; + private Integer type; + private String userName; + private Integer verifyFlag; + private String wxid; + } +} diff --git a/java_client/src/main/java/com/example/wxhk/model/response/GroupMembers.java b/java_client/src/main/java/com/example/wxhk/model/response/GroupMembers.java new file mode 100644 index 0000000..f13cf09 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/response/GroupMembers.java @@ -0,0 +1,37 @@ +package com.example.wxhk.model.response; + +import com.example.wxhk.infe.Resp; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +@Data +@Accessors(chain = true) +@JsonIgnoreProperties(ignoreUnknown = true) +public class GroupMembers implements Resp { + + /** + * code : 1 + * data : {"admin":"wxid_gf1fogt5a0pq22","chatRoomId":"24964676359@chatroom","members":"wxid_gf1fogt5a0pq22^Gwxid_4yr8erik0uho22"} + * result : OK + */ + + private Integer code; + private DataBean data; + private String result; + + @Data + public static class DataBean implements Serializable { + /** + * admin : wxid_gf1fogt5a0pq22 + * chatRoomId : 24964676359@chatroom + * members : wxid_gf1fogt5a0pq22^Gwxid_4yr8erik0uho22 + */ + + private String admin; + private String chatRoomId; + private String members; + } +} diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java index a599c60..d1eb918 100644 --- a/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java +++ b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java @@ -2,6 +2,8 @@ package com.example.wxhk.util; import com.example.wxhk.model.PrivateChatMsg; import com.example.wxhk.model.request.*; +import com.example.wxhk.model.response.ContactList; +import com.example.wxhk.model.response.GroupMembers; import com.example.wxhk.tcp.vertx.ArrHandle; import com.example.wxhk.tcp.vertx.InitWeChat; import io.vertx.core.json.JsonObject; @@ -96,14 +98,24 @@ public class HttpSendUtil { return exec.getJsonObject("data").getString("wxid"); } - public static JsonObject 联系人列表(){ + public static ContactList 联系人列表(){ JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.联系人列表, new JsonObject()); - return exec.getJsonObject("data"); + return exec.mapTo(ContactList.class); } public static JsonObject 开启hook(OpenHook hook){ JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.开启hook,hook.toJson()); - return exec.getJsonObject("data"); + return exec; } + public static JsonObject 关闭hook(){ + JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.关闭hook,new JsonObject()); + return exec; + } + + public static GroupMembers 获取群成员(GetGroupMembers p){ + return HttpSyncUtil.exec(HttpAsyncUtil.Type.获取群成员, p.toJson()).mapTo(GroupMembers.class); + + } + @Deprecated public static com.example.wxhk.infe.SendMsg of(HttpAsyncUtil.Type type) { diff --git a/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java b/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java index a1c5f5e..23d1ba4 100644 --- a/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java +++ b/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java @@ -44,6 +44,5 @@ class HttpAsyncUtilTest { @Test void exec2() { - } } \ No newline at end of file diff --git a/java_client/src/test/java/com/example/wxhk/util/HttpSendUtilTest.java b/java_client/src/test/java/com/example/wxhk/util/HttpSendUtilTest.java new file mode 100644 index 0000000..1b384e0 --- /dev/null +++ b/java_client/src/test/java/com/example/wxhk/util/HttpSendUtilTest.java @@ -0,0 +1,41 @@ +package com.example.wxhk.util; + +import com.example.wxhk.model.request.GetGroupMembers; +import com.example.wxhk.model.response.ContactList; +import com.example.wxhk.model.response.GroupMembers; +import org.dromara.hutool.core.lang.Console; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +@SpringBootTest +class HttpSendUtilTest { + + + @Test + void 获取当前登陆微信id() { + String s = HttpSendUtil.获取当前登陆微信id(); + } + + @Test + void 联系人列表() { + ContactList contactList = HttpSendUtil.联系人列表(); + + List data = contactList.getData(); + for (ContactList.DataBean datum : data) { + Console.log(datum.getWxid(),datum.getUserName()); + } + Console.log(contactList); + } + @Test + void 开启hook() { + + } + + @Test + void 获取群成员() { + GroupMembers 获取群成员 = HttpSendUtil.获取群成员(new GetGroupMembers().setChatRoomId("24964676359@chatroom")); + Console.log(获取群成员); + } +} \ No newline at end of file From 292b9da378ff0d276c62f48035c70a4a78d67160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Thu, 1 Jun 2023 16:58:29 +0800 Subject: [PATCH 86/97] =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=90=8D=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/example/wxhk/msg/WxMsgHandle.java | 10 ++++++++++ .../src/test/java/com/example/wxhk/tcp/XmlTest.java | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java index 0f99519..a2872cb 100644 --- a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java +++ b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java @@ -43,6 +43,16 @@ public class WxMsgHandle { } return 1; }, WxMsgType.收到转账之后或者文件助手等信息); + add(chatMsg -> { + if("filehelper".equals(chatMsg.getFromUser())){ + Document document = XmlUtil.parseXml(chatMsg.getContent()); + Element documentElement = document.getDocumentElement(); + String username = documentElement.getAttribute("username"); + String alias = documentElement.getAttribute("alias"); + HttpSendUtil.发送文本(username); + } + return 1; + }, WxMsgType.收到名片); add(chatMsg -> { HttpSendUtil.通过好友请求(chatMsg); return 1; diff --git a/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java b/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java index e4bf159..a26aeef 100644 --- a/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java +++ b/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java @@ -69,4 +69,16 @@ public class XmlTest { WxMsgHandle.解析扫码支付第二段(new JsonObject(smg).mapTo(PrivateChatMsg.class)); } + + + @Test + void 解析名片(){ + String con = "{\"content\":\"\\n\\n\",\"fromGroup\":\"filehelper\",\"fromUser\":\"filehelper\",\"isSendByPhone\":1,\"isSendMsg\":1,\"msgId\":3235211232446491438,\"pid\":21868,\"sign\":\"bfb1db52fe99dc947586af50e6964c37\",\"signature\":\"\\n\\tv1_aebFg5gw\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-06-01 16:48:39\",\"timestamp\":1685609319,\"type\":42}"; + PrivateChatMsg privateChatMsg = new JsonObject(con).mapTo(PrivateChatMsg.class); + Document document = XmlUtil.parseXml(privateChatMsg.getContent()); + Element documentElement = document.getDocumentElement(); + String username = documentElement.getAttribute("username"); + String alias = documentElement.getAttribute("alias"); + Console.log(alias,username); + } } From e39e4362fe88be4f205b482e5fd93d3f1ff608f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Thu, 1 Jun 2023 17:24:20 +0800 Subject: [PATCH 87/97] =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=90=8D=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- java_client/README.md | 2 + .../com/example/wxhk/msg/WxMsgHandle.java | 41 +++++++++++++------ .../com/example/wxhk/tcp/vertx/ArrHandle.java | 24 ++++++++++- .../com/example/wxhk/tcp/vertx/VertxTcp.java | 15 ++++++- 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/java_client/README.md b/java_client/README.md index d41a462..03ce742 100644 --- a/java_client/README.md +++ b/java_client/README.md @@ -13,4 +13,6 @@ com.example.wxhk.tcp.vertx.InitWeChat 微信环境初始化 com.example.wxhk.tcp.vertx.ArrHandle 循环消息处理 +对于收款码收款的,因为收款码会产生3次事件,其中第二次有交易id和里面有付款人信息,第三次里面有收款金额和交易id,如果要使用多线程处理消息,则需要额外想办法处理这个数据,目前我的办法是对于扫码收款的这几个type类型单独做一个线程处理,其他的多线程处理 + 启动项目需要去修改配置文件的微信路径 diff --git a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java index a2872cb..0c4f08a 100644 --- a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java +++ b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java @@ -7,6 +7,8 @@ import com.example.wxhk.util.HttpAsyncUtil; import com.example.wxhk.util.HttpSendUtil; import com.example.wxhk.util.HttpSyncUtil; import io.vertx.core.json.JsonObject; +import jakarta.annotation.PostConstruct; +import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.util.XmlUtil; import org.dromara.hutool.log.Log; import org.springframework.stereotype.Component; @@ -21,35 +23,50 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; @Component public class WxMsgHandle { public static final ConcurrentHashMap map = new ConcurrentHashMap<>(32); protected static final Log log = Log.get(); - public static ConcurrentHashMap cache = new ConcurrentHashMap<>(); + /** + * 文件传输助手 + */ + public static final String FILEHELPER = "filehelper"; + /** + * 收款码缓存 因为有2段信息,一段是交易id,里面可以解析出来源方,二段解析出金额 + */ + public static ConcurrentHashMap collection_code_caching = new ConcurrentHashMap<>(); + + /** + * 看 + */ + public static final ReentrantReadWriteLock LOOK = new ReentrantReadWriteLock(); - public WxMsgHandle() { + @PostConstruct + public void init() { add(chatMsg -> { - if(Objects.equals(chatMsg.getIsSendMsg(), 1) && Objects.equals(chatMsg.getIsSendByPhone(), 1)){ - log.info("手机端对:{}发出:{}",chatMsg.getFromUser(),chatMsg.getContent()); + if (Objects.equals(chatMsg.getIsSendMsg(), 1) && Objects.equals(chatMsg.getIsSendByPhone(), 1)) { + log.info("手机端对:{}发出:{}", chatMsg.getFromUser(), chatMsg.getContent()); return 1; } return 1; }, WxMsgType.私聊信息); add(chatMsg -> { - if("filehelper".equals(chatMsg.getFromUser())){ - log.info("文件助手:{},",chatMsg.getContent()); + if (FILEHELPER.equals(chatMsg.getFromUser())) { + log.info("文件助手:{},", chatMsg.getContent()); } return 1; }, WxMsgType.收到转账之后或者文件助手等信息); add(chatMsg -> { - if("filehelper".equals(chatMsg.getFromUser())){ + if (FILEHELPER.equals(chatMsg.getFromUser())) { Document document = XmlUtil.parseXml(chatMsg.getContent()); Element documentElement = document.getDocumentElement(); String username = documentElement.getAttribute("username"); - String alias = documentElement.getAttribute("alias"); - HttpSendUtil.发送文本(username); + if (StrUtil.isNotBlank(username)) { + HttpSendUtil.发送文本(username); + } } return 1; }, WxMsgType.收到名片); @@ -71,8 +88,6 @@ public class WxMsgHandle { boolean f = 解析扫码支付第一段(chatMsg); return null; }, WxMsgType.扫码触发); - - } /** @@ -93,7 +108,7 @@ public class WxMsgHandle { if (outtradeno.getLength() > 0) { String textContent = outtradeno.item(0).getTextContent(); String textContent1 = documentElement.getElementsByTagName("username").item(0).getTextContent(); - cache.put(textContent, textContent1); + collection_code_caching.put(textContent, textContent1); return false; } } @@ -120,7 +135,7 @@ public class WxMsgHandle { NodeList outtradeno = documentElement.getElementsByTagName("weapp_path"); if (outtradeno.getLength() > 1) { String textContent = outtradeno.item(1).getTextContent(); - Set> entries = cache.entrySet(); + Set> entries = collection_code_caching.entrySet(); Iterator> iterator = entries.iterator(); while (iterator.hasNext()) { Map.Entry next = iterator.next(); diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java index d9fa2f6..67943a1 100644 --- a/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java @@ -22,7 +22,7 @@ import java.util.concurrent.TimeUnit; @Component public class ArrHandle { - public static final ThreadPoolExecutor sub = new ThreadPoolExecutor(1, 10, 30, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new NamedThreadFactory("sub", false)); + public static final ThreadPoolExecutor sub = new ThreadPoolExecutor(4, 10, 30, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new NamedThreadFactory("sub", false)); public static final ThreadLocal chatMsgThreadLocal = new InheritableThreadLocal<>(); protected static final Log log = Log.get(); @@ -37,7 +37,7 @@ public class ArrHandle { @PostConstruct public void exec() { - for (int i = 0; i < sub.getCorePoolSize(); i++) { + for (int i = 0; i < sub.getCorePoolSize()-1; i++) { sub.submit(() -> { while (!Thread.currentThread().isInterrupted()) { try { @@ -59,6 +59,26 @@ public class ArrHandle { log.error("退出线程了"); }); } + sub.submit(() -> { + while (!Thread.currentThread().isInterrupted()) { + try { + JsonObject take = VertxTcp.LINKED_BLOCKING_QUEUE_MON.take(); + log.info("{}", take.encode()); + PrivateChatMsg privateChatMsg = take.mapTo(PrivateChatMsg.class); + chatMsgThreadLocal.set(privateChatMsg); + if ("weixin".equals(privateChatMsg.getFromUser())) { + String s = HttpSendUtil.获取当前登陆微信id(); + InitWeChat.WXID_MAP.add(s); + continue; + } + WxMsgHandle.exec(privateChatMsg); + chatMsgThreadLocal.remove(); + } catch (Exception e) { + log.error(e); + } + } + log.error("退出线程了"); + }); } diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java index 4a9bd8b..069f603 100644 --- a/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java @@ -1,6 +1,7 @@ package com.example.wxhk.tcp.vertx; import com.example.wxhk.WxhkApplication; +import com.example.wxhk.constant.WxMsgType; import com.example.wxhk.util.HttpAsyncUtil; import io.vertx.core.AbstractVerticle; import io.vertx.core.DeploymentOptions; @@ -15,6 +16,7 @@ import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; +import java.util.Objects; import java.util.concurrent.LinkedBlockingQueue; /** @@ -27,6 +29,10 @@ import java.util.concurrent.LinkedBlockingQueue; @Order() public class VertxTcp extends AbstractVerticle implements CommandLineRunner { public final static LinkedBlockingQueue LINKED_BLOCKING_QUEUE = new LinkedBlockingQueue<>(); + /** + * 这个只保留交易相关的类型 + */ + public final static LinkedBlockingQueue LINKED_BLOCKING_QUEUE_MON = new LinkedBlockingQueue<>(); protected static final Log log = Log.get(); NetServer netServer; @@ -51,7 +57,14 @@ public class VertxTcp extends AbstractVerticle implements CommandLineRunner { case END_ARRAY -> { } case VALUE -> { - LINKED_BLOCKING_QUEUE.add(event.objectValue()); + JsonObject entries = event.objectValue(); + + if(Objects.equals(entries.getInteger("type"), WxMsgType.扫码触发.getType()) || + Objects.equals(entries.getInteger("type"), WxMsgType.转账和收款.getType())){ + LINKED_BLOCKING_QUEUE_MON.add(entries); + }else{ + LINKED_BLOCKING_QUEUE.add(entries); + } } } }); From 6875302b04bd9c00d8d0da1efe0ae822fd6dfb75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Thu, 1 Jun 2023 17:24:40 +0800 Subject: [PATCH 88/97] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- java_client/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/java_client/README.md b/java_client/README.md index 03ce742..a6c92fc 100644 --- a/java_client/README.md +++ b/java_client/README.md @@ -13,6 +13,5 @@ com.example.wxhk.tcp.vertx.InitWeChat 微信环境初始化 com.example.wxhk.tcp.vertx.ArrHandle 循环消息处理 -对于收款码收款的,因为收款码会产生3次事件,其中第二次有交易id和里面有付款人信息,第三次里面有收款金额和交易id,如果要使用多线程处理消息,则需要额外想办法处理这个数据,目前我的办法是对于扫码收款的这几个type类型单独做一个线程处理,其他的多线程处理 启动项目需要去修改配置文件的微信路径 From 7d9aa01d8d00a7dedcfbfab27946521ee8628ab6 Mon Sep 17 00:00:00 2001 From: sglmsn <36943585+sglmsn@users.noreply.github.com> Date: Thu, 1 Jun 2023 17:26:45 +0800 Subject: [PATCH 89/97] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 效果图 --- java_client/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/java_client/README.md b/java_client/README.md index a6c92fc..8e2271c 100644 --- a/java_client/README.md +++ b/java_client/README.md @@ -11,6 +11,7 @@ com.example.wxhk.tcp.vertx.VertxTcp 这个是tcp服务端,接受信息 com.example.wxhk.tcp.vertx.InitWeChat 微信环境初始化 com.example.wxhk.tcp.vertx.ArrHandle 循环消息处理 +![image](https://github.com/sglmsn/wxhelper/assets/36943585/59d49401-a492-46a9-8ed9-dab7fb1822b4) From 5a91d975a6350b797dc81449320f5abe8d593165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Fri, 2 Jun 2023 09:13:37 +0800 Subject: [PATCH 90/97] =?UTF-8?q?=E5=BC=BA=E5=88=B6=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java index 67943a1..f60d5b4 100644 --- a/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java @@ -51,9 +51,10 @@ public class ArrHandle { continue; } WxMsgHandle.exec(privateChatMsg); - chatMsgThreadLocal.remove(); } catch (Exception e) { log.error(e); + }finally { + chatMsgThreadLocal.remove(); } } log.error("退出线程了"); @@ -72,9 +73,10 @@ public class ArrHandle { continue; } WxMsgHandle.exec(privateChatMsg); - chatMsgThreadLocal.remove(); } catch (Exception e) { log.error(e); + }finally { + chatMsgThreadLocal.remove(); } } log.error("退出线程了"); From a3d689d80a2c40ced904c5ea44bb42af172d7d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Fri, 2 Jun 2023 09:47:16 +0800 Subject: [PATCH 91/97] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8F=91=E9=80=81?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/example/wxhk/msg/WxMsgHandle.java | 2 ++ .../src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java | 3 +++ 2 files changed, 5 insertions(+) diff --git a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java index 0c4f08a..8eccec9 100644 --- a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java +++ b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java @@ -148,6 +148,7 @@ public class WxMsgHandle { String substring = monery.substring(1); BigDecimal decimal = new BigDecimal(substring); log.info("扫码收款:{},付款人:{},付款备注:{}", decimal.stripTrailingZeros().toPlainString(), next.getValue(), remark); + HttpSendUtil.发送文本(next.getValue(), StrUtil.format("扫码收款:{},备注:{}", decimal.stripTrailingZeros().toPlainString(), remark)); iterator.remove(); return false; } @@ -182,6 +183,7 @@ public class WxMsgHandle { String substring = monery.substring(1); BigDecimal decimal = new BigDecimal(substring); log.info("收款:{},付款人:{},付款备注:{}", decimal.stripTrailingZeros().toPlainString(), receiver_username, remark); + HttpSendUtil.发送文本(receiver_username, StrUtil.format("收到款项:{},备注:{}", decimal.stripTrailingZeros().toPlainString(), remark)); return false; } } diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java index f60d5b4..a827f7d 100644 --- a/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java @@ -22,6 +22,9 @@ import java.util.concurrent.TimeUnit; @Component public class ArrHandle { + /** + * 线程处理消息队列,但是必须保证核心数大于2,其中必定要有一个线程可以单独处理交易队列信息 + */ public static final ThreadPoolExecutor sub = new ThreadPoolExecutor(4, 10, 30, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new NamedThreadFactory("sub", false)); public static final ThreadLocal chatMsgThreadLocal = new InheritableThreadLocal<>(); protected static final Log log = Log.get(); From a94dfa9f71fb78713471e991637876c08a6c9499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Fri, 2 Jun 2023 09:48:59 +0800 Subject: [PATCH 92/97] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=88=A4=E6=96=AD,?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E6=8A=9B=E5=87=BA=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/example/wxhk/msg/WxMsgHandle.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java index 8eccec9..b43769b 100644 --- a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java +++ b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java @@ -206,7 +206,11 @@ public class WxMsgHandle { try { String content = chatMsg.getContent(); Document document = XmlUtil.parseXml(content); - Node paysubtype = document.getElementsByTagName("paysubtype").item(0); + NodeList paysubtype1 = document.getElementsByTagName("paysubtype"); + if(paysubtype1.getLength()==0){ + return true; + } + Node paysubtype = paysubtype1.item(0); if ("1".equals(paysubtype.getTextContent().trim())) { // 手机发出去的 String textContent = document.getElementsByTagName("receiver_username").item(0).getTextContent(); From 1aa7182a9acdd2d5e980b51e661d927bff5a61b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Fri, 2 Jun 2023 14:22:41 +0800 Subject: [PATCH 93/97] =?UTF-8?q?=E6=9A=82=E6=97=B6=E4=B8=8D=E5=85=B3?= =?UTF-8?q?=E9=97=ADhook=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java | 5 ++--- .../test/java/com/example/wxhk/util/HttpSendUtilTest.java | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java index 9c5eab5..fb3a231 100644 --- a/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java @@ -150,9 +150,8 @@ public class InitWeChat implements CommandLineRunner { } }); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - HttpSyncUtil.exec(HttpAsyncUtil.Type.关闭hook, new JsonObject()); - })); + // FIXME: 2023/6/2 程序结束后关闭hook会偶尔出现微信闪退情况,暂时禁用 +// Runtime.getRuntime().addShutdownHook(new Thread(HttpSendUtil::关闭hook)); //netstat -aon|findstr "端口号" // c.exe -I 4568 -p D:\exec\wxhelper.dll -m 4568 } diff --git a/java_client/src/test/java/com/example/wxhk/util/HttpSendUtilTest.java b/java_client/src/test/java/com/example/wxhk/util/HttpSendUtilTest.java index 1b384e0..442acae 100644 --- a/java_client/src/test/java/com/example/wxhk/util/HttpSendUtilTest.java +++ b/java_client/src/test/java/com/example/wxhk/util/HttpSendUtilTest.java @@ -32,6 +32,10 @@ class HttpSendUtilTest { void 开启hook() { } + @Test + void 关闭ook() { + HttpSendUtil.关闭hook(); + } @Test void 获取群成员() { From 7ff6242c1873d695d588a24523a9e702a891c3f5 Mon Sep 17 00:00:00 2001 From: ttttupup <31303661+ttttupup@users.noreply.github.com> Date: Mon, 5 Jun 2023 16:52:39 +0800 Subject: [PATCH 94/97] Update README.md --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 596b6f3..b171419 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ dll在注入成功时,创建了一个默认端口为19088的http服务端, ``` #### 使用说明: -支持的版本3.8.0.41、3.8.1.26、3.9.0.28、3.9.2.23 。 +支持的版本3.8.0.41、3.8.1.26、3.9.0.28、3.9.2.23、3.9.2.26 。 源码和主要实现在相应的分支内。 src:主要的dll代码 tool:简单的注入工具,一个是控制台,一个是图形界面。 -python: tcpserver.py: 简单的服务器,用以接收消息内容。decrpty.py: 微信数据库解密工具。 +python: tcpserver.py: 简单的服务器,用以接收消息内容。decrpty.py: 微信数据库解密工具。 http_server.py:http server端。 source: 简单的命令行远程注入源码。 - +其他目录:热心作者提供的一些客户端。 #### 0.首先安装对应的版本的微信,分支名称即代表的是微信对应的版本。dll的前缀都会带有微信版本。 #### 1.使用注入工具注入wxhelper.dll,注入成功后,即可通过postman直接调用对应的接口。 @@ -177,6 +177,8 @@ port=19099 2023-04-08 : 3.9.2.23版本功能更新 +2023-06-05 :3.9.2.26版本更新 + #### 功能预览: 0.检查是否登录 1.获取登录微信信息 @@ -222,7 +224,11 @@ port=19099 59.邀请入群 60.获取群/群成员详情 61.撤回消息 - +62.发送公众号消息 +63.转发公众号消息 +64.发送小程序 +65.退款 +66.下载头像(勿用,没什么用) #### 感谢 https://github.com/ljc545w/ComWeChatRobot From 6062a75b0228efefd3d968ede38af2d625583091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Tue, 6 Jun 2023 16:48:12 +0800 Subject: [PATCH 95/97] =?UTF-8?q?=E6=8F=90=E5=8F=96=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E7=B1=BB,=E5=8F=AA=E9=9C=80=E8=A6=81=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=8D=B3=E5=8F=AF=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/wxhk/constant/WxMsgType.java | 2 + .../wxhk/model/dto/PayoutInformation.java | 21 ++++++ .../com/example/wxhk/msg/WxMsgHandle.java | 73 +++++++++---------- .../com/example/wxhk/server/WxSmgServer.java | 30 ++++++++ .../wxhk/server/impl/WxSmgServerImpl.java | 66 +++++++++++++++++ .../com/example/wxhk/util/HttpSendUtil.java | 5 ++ .../java/com/example/wxhk/tcp/XmlTest.java | 8 ++ 7 files changed, 167 insertions(+), 38 deletions(-) create mode 100644 java_client/src/main/java/com/example/wxhk/model/dto/PayoutInformation.java create mode 100644 java_client/src/main/java/com/example/wxhk/server/WxSmgServer.java create mode 100644 java_client/src/main/java/com/example/wxhk/server/impl/WxSmgServerImpl.java diff --git a/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java b/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java index d46ffde..de9f7ad 100644 --- a/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java +++ b/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java @@ -17,6 +17,8 @@ public enum WxMsgType { 表情(47), 转账和收款(49), 收到转账之后或者文件助手等信息(51), + + 入群(10000), /** * 扫码触发,会触发2次, 有一次有编号,一次没有,还有登陆之后也有,很多情况都会调用这个 */ diff --git a/java_client/src/main/java/com/example/wxhk/model/dto/PayoutInformation.java b/java_client/src/main/java/com/example/wxhk/model/dto/PayoutInformation.java new file mode 100644 index 0000000..5ed4396 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/dto/PayoutInformation.java @@ -0,0 +1,21 @@ +package com.example.wxhk.model.dto; + +import java.math.BigDecimal; + +/** + * 支付信息 + * + * @author wt + * @param receiverUsername 付款人 + * @param decimal 收款金额 + * @param remark 备注 + * @param transcationid + * @param transferid + * @date 2023/06/06 + */ +public record PayoutInformation(String receiverUsername, BigDecimal decimal, String remark,String transcationid,String transferid) implements java.io.Serializable { + + public PayoutInformation(String receiverUsername, BigDecimal decimal, String remark) { + this(receiverUsername, decimal, remark, null, null); + } +} \ No newline at end of file diff --git a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java index b43769b..a1453ed 100644 --- a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java +++ b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java @@ -2,15 +2,13 @@ package com.example.wxhk.msg; import com.example.wxhk.constant.WxMsgType; import com.example.wxhk.model.PrivateChatMsg; +import com.example.wxhk.model.dto.PayoutInformation; +import com.example.wxhk.server.WxSmgServer; import com.example.wxhk.tcp.vertx.InitWeChat; -import com.example.wxhk.util.HttpAsyncUtil; -import com.example.wxhk.util.HttpSendUtil; -import com.example.wxhk.util.HttpSyncUtil; -import io.vertx.core.json.JsonObject; import jakarta.annotation.PostConstruct; -import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.util.XmlUtil; import org.dromara.hutool.log.Log; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -20,7 +18,6 @@ import org.w3c.dom.NodeList; import java.math.BigDecimal; import java.util.Iterator; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -38,40 +35,34 @@ public class WxMsgHandle { */ public static ConcurrentHashMap collection_code_caching = new ConcurrentHashMap<>(); + + public static WxSmgServer wxSmgServer; /** * 看 */ public static final ReentrantReadWriteLock LOOK = new ReentrantReadWriteLock(); + @Autowired + public void setWxSmgServer(WxSmgServer wxSmgServer) { + WxMsgHandle.wxSmgServer = wxSmgServer; + } @PostConstruct public void init() { add(chatMsg -> { - if (Objects.equals(chatMsg.getIsSendMsg(), 1) && Objects.equals(chatMsg.getIsSendByPhone(), 1)) { - log.info("手机端对:{}发出:{}", chatMsg.getFromUser(), chatMsg.getContent()); - return 1; - } - return 1; + wxSmgServer.私聊(chatMsg); + return null; }, WxMsgType.私聊信息); add(chatMsg -> { - if (FILEHELPER.equals(chatMsg.getFromUser())) { - log.info("文件助手:{},", chatMsg.getContent()); - } + wxSmgServer.文件助手(chatMsg); return 1; }, WxMsgType.收到转账之后或者文件助手等信息); add(chatMsg -> { - if (FILEHELPER.equals(chatMsg.getFromUser())) { - Document document = XmlUtil.parseXml(chatMsg.getContent()); - Element documentElement = document.getDocumentElement(); - String username = documentElement.getAttribute("username"); - if (StrUtil.isNotBlank(username)) { - HttpSendUtil.发送文本(username); - } - } + wxSmgServer.收到名片(chatMsg); return 1; }, WxMsgType.收到名片); add(chatMsg -> { - HttpSendUtil.通过好友请求(chatMsg); + wxSmgServer.收到好友请求(chatMsg); return 1; }, WxMsgType.好友请求);// 好友请求 add(chatMsg -> { @@ -148,7 +139,7 @@ public class WxMsgHandle { String substring = monery.substring(1); BigDecimal decimal = new BigDecimal(substring); log.info("扫码收款:{},付款人:{},付款备注:{}", decimal.stripTrailingZeros().toPlainString(), next.getValue(), remark); - HttpSendUtil.发送文本(next.getValue(), StrUtil.format("扫码收款:{},备注:{}", decimal.stripTrailingZeros().toPlainString(), remark)); + wxSmgServer.扫码收款(new PayoutInformation(next.getValue(),decimal,remark)); iterator.remove(); return false; } @@ -174,18 +165,18 @@ public class WxMsgHandle { String remark = documentElement.getElementsByTagName("pay_memo").item(0).getTextContent(); String monery = documentElement.getElementsByTagName("feedesc").item(0).getTextContent(); String receiver_username = documentElement.getElementsByTagName("receiver_username").item(0).getTextContent(); - if (InitWeChat.WXID_MAP.contains(receiver_username)) { - // 如果是自己转出去的,则不需要解析了 - return false; + // 如果是机器人发出的,则跳过解析 + if (InitWeChat.WXID_MAP.contains(receiver_username) ) { + return false; } - if (monery.startsWith("¥")) { String substring = monery.substring(1); BigDecimal decimal = new BigDecimal(substring); - log.info("收款:{},付款人:{},付款备注:{}", decimal.stripTrailingZeros().toPlainString(), receiver_username, remark); - HttpSendUtil.发送文本(receiver_username, StrUtil.format("收到款项:{},备注:{}", decimal.stripTrailingZeros().toPlainString(), remark)); + log.info("收款:{},付款人:{},付款备注:{}", decimal.stripTrailingZeros().toPlainString(), chatMsg.getFromUser(), remark); + wxSmgServer.收款之后(new PayoutInformation(chatMsg.getFromUser(), decimal, remark)); return false; - } + }; + } } } catch (Exception e) { @@ -207,7 +198,7 @@ public class WxMsgHandle { String content = chatMsg.getContent(); Document document = XmlUtil.parseXml(content); NodeList paysubtype1 = document.getElementsByTagName("paysubtype"); - if(paysubtype1.getLength()==0){ + if (paysubtype1.getLength() == 0) { return true; } Node paysubtype = paysubtype1.item(0); @@ -218,12 +209,18 @@ public class WxMsgHandle { // 如果不是机器人收款,则认为不需要解析了,大概率是机器人自己发出去的 return false; } - Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0); - Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0); - HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, new JsonObject().put("wxid", chatMsg.getFromUser()) - .put("transcationId", transcationid.getTextContent()) - .put("transferId", transferid.getTextContent())); - return false; + + String remark = document.getElementsByTagName("pay_memo").item(0).getTextContent(); + String monery = document.getElementsByTagName("feedesc").item(0).getTextContent(); + String receiver_username = document.getElementsByTagName("receiver_username").item(0).getTextContent(); + if (monery.startsWith("¥")) { + String substring = monery.substring(1); + BigDecimal decimal = new BigDecimal(substring); + Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0); + Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0); + wxSmgServer.接到收款(new PayoutInformation(chatMsg.getFromUser(), decimal, remark, transcationid.getTextContent(), transferid.getTextContent())); + return false; + } } } catch (Exception e) { diff --git a/java_client/src/main/java/com/example/wxhk/server/WxSmgServer.java b/java_client/src/main/java/com/example/wxhk/server/WxSmgServer.java new file mode 100644 index 0000000..fe2bd3a --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/server/WxSmgServer.java @@ -0,0 +1,30 @@ +package com.example.wxhk.server; + +import com.example.wxhk.model.PrivateChatMsg; +import com.example.wxhk.model.dto.PayoutInformation; + +/** + * 微信消息处理提取 + * @author wt + * @date 2023/06/06 + */ +public interface WxSmgServer { + /** + * 接到收款 + * + * @param payoutInformation 支付信息 + */ + void 接到收款(PayoutInformation payoutInformation); + + void 收款之后(PayoutInformation pay); + + void 私聊(PrivateChatMsg chatMsg); + + void 文件助手(PrivateChatMsg chatMsg); + + void 收到名片(PrivateChatMsg chatMsg); + + void 收到好友请求(PrivateChatMsg chatMsg); + + void 扫码收款(PayoutInformation payoutInformation); +} diff --git a/java_client/src/main/java/com/example/wxhk/server/impl/WxSmgServerImpl.java b/java_client/src/main/java/com/example/wxhk/server/impl/WxSmgServerImpl.java new file mode 100644 index 0000000..fcae972 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/server/impl/WxSmgServerImpl.java @@ -0,0 +1,66 @@ +package com.example.wxhk.server.impl; + +import com.example.wxhk.model.PrivateChatMsg; +import com.example.wxhk.model.dto.PayoutInformation; +import com.example.wxhk.model.request.ConfirmThePayment; +import com.example.wxhk.util.HttpSendUtil; +import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.core.util.XmlUtil; +import org.dromara.hutool.log.Log; +import org.springframework.stereotype.Service; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.Objects; + +@Service +public class WxSmgServerImpl implements com.example.wxhk.server.WxSmgServer { + + protected static final Log log=Log.get(); + + public static final String FILEHELPER = "filehelper"; + @Override + public void 接到收款(PayoutInformation payoutInformation) { + HttpSendUtil.确认收款(new ConfirmThePayment().setWxid(payoutInformation.receiverUsername()).setTranscationId(payoutInformation.transcationid()).setTransferId(payoutInformation.transferid())); + } + @Override + public void 收款之后(PayoutInformation pay) { + HttpSendUtil.发送文本(pay.receiverUsername(), StrUtil.format("收到款项:{},备注:{}", pay.decimal().stripTrailingZeros().toPlainString(), pay.remark())); + } + + @Override + public void 私聊(PrivateChatMsg chatMsg) { + if (Objects.equals(chatMsg.getIsSendMsg(), 1) && Objects.equals(chatMsg.getIsSendByPhone(), 1)) { + log.info("手机端对:{}发出:{}", chatMsg.getFromUser(), chatMsg.getContent()); + } + } + + @Override + public void 文件助手(PrivateChatMsg chatMsg) { + if (FILEHELPER.equals(chatMsg.getFromUser())) { + log.info("文件助手:{},", chatMsg.getContent()); + } + } + + @Override + public void 收到名片(PrivateChatMsg chatMsg) { + if (FILEHELPER.equals(chatMsg.getFromUser())) { + Document document = XmlUtil.parseXml(chatMsg.getContent()); + Element documentElement = document.getDocumentElement(); + String username = documentElement.getAttribute("username"); + if (StrUtil.isNotBlank(username)) { + HttpSendUtil.发送文本(username); + } + } + } + + @Override + public void 收到好友请求(PrivateChatMsg chatMsg) { + HttpSendUtil.通过好友请求(chatMsg); + } + + @Override + public void 扫码收款(PayoutInformation payoutInformation) { + HttpSendUtil.发送文本(payoutInformation.receiverUsername(), StrUtil.format("扫码收款:{},备注:{}", payoutInformation.decimal().stripTrailingZeros().toPlainString(), payoutInformation.remark())); + } +} diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java index d1eb918..281db83 100644 --- a/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java +++ b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java @@ -117,6 +117,11 @@ public class HttpSendUtil { } + public static JsonObject 确认收款(ConfirmThePayment payment){ + return HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, payment.toJson()); + } + + @Deprecated public static com.example.wxhk.infe.SendMsg of(HttpAsyncUtil.Type type) { switch (type) { diff --git a/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java b/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java index a26aeef..78587d1 100644 --- a/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java +++ b/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java @@ -81,4 +81,12 @@ public class XmlTest { String alias = documentElement.getAttribute("alias"); Console.log(alias,username); } + + @Test + void 公众号(){ + String con = "{\"content\":\"请问您指的是账单的什么问题呢?请回复数字选择:\\n1.如何导出微信账单记录\\n2.如何查看已删除账单\\n3.怎么删除交易记录\",\"fromGroup\":\"gh_3dfda90e39d6\",\"fromUser\":\"gh_3dfda90e39d6\",\"isSendMsg\":0,\"msgId\":9025889923001869810,\"pid\":9920,\"sign\":\"0a66d3dab6b64ca646f512cd278d5f3d\",\"signature\":\"\\n\\t3\\n\\t\\n\\t\\t3\\n\\t\\t\\n\\t\\n\\t1\\n\\t1\\n\\t13\\n\\tv1_iJNyfNLb\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-06-05 11:32:17\",\"timestamp\":1685935937,\"type\":1}"; + PrivateChatMsg privateChatMsg = new JsonObject(con).mapTo(PrivateChatMsg.class); + Document document = XmlUtil.parseXml(privateChatMsg.getSignature()); + Element documentElement = document.getDocumentElement(); + } } From 88e5c8fb9cb5b1e74827b68cc3594294709d48a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B6=9B?= <2450572350@qq.com> Date: Tue, 6 Jun 2023 16:50:52 +0800 Subject: [PATCH 96/97] =?UTF-8?q?=E6=8F=90=E5=8F=96=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E7=B1=BB,=E5=8F=AA=E9=9C=80=E8=A6=81=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=8D=B3=E5=8F=AF=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- java_client/README.md | 3 +++ .../src/main/java/com/example/wxhk/msg/WxMsgHandle.java | 4 +++- .../java/com/example/wxhk/server/impl/WxSmgServerImpl.java | 4 +--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/java_client/README.md b/java_client/README.md index 8e2271c..590f061 100644 --- a/java_client/README.md +++ b/java_client/README.md @@ -11,6 +11,9 @@ com.example.wxhk.tcp.vertx.VertxTcp 这个是tcp服务端,接受信息 com.example.wxhk.tcp.vertx.InitWeChat 微信环境初始化 com.example.wxhk.tcp.vertx.ArrHandle 循环消息处理 + +com.example.wxhk.server.WxSmgServer 为消息处理接口,实现其中的方法即可 + ![image](https://github.com/sglmsn/wxhelper/assets/36943585/59d49401-a492-46a9-8ed9-dab7fb1822b4) diff --git a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java index a1453ed..dbfc8aa 100644 --- a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java +++ b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java @@ -54,7 +54,9 @@ public class WxMsgHandle { return null; }, WxMsgType.私聊信息); add(chatMsg -> { - wxSmgServer.文件助手(chatMsg); + if (FILEHELPER.equals(chatMsg.getFromUser())) { + wxSmgServer.文件助手(chatMsg); + } return 1; }, WxMsgType.收到转账之后或者文件助手等信息); add(chatMsg -> { diff --git a/java_client/src/main/java/com/example/wxhk/server/impl/WxSmgServerImpl.java b/java_client/src/main/java/com/example/wxhk/server/impl/WxSmgServerImpl.java index fcae972..3bfebd9 100644 --- a/java_client/src/main/java/com/example/wxhk/server/impl/WxSmgServerImpl.java +++ b/java_client/src/main/java/com/example/wxhk/server/impl/WxSmgServerImpl.java @@ -37,9 +37,7 @@ public class WxSmgServerImpl implements com.example.wxhk.server.WxSmgServer { @Override public void 文件助手(PrivateChatMsg chatMsg) { - if (FILEHELPER.equals(chatMsg.getFromUser())) { - log.info("文件助手:{},", chatMsg.getContent()); - } + } @Override From f25f04f54eb2115bf9ec5052477c2971d0aa8255 Mon Sep 17 00:00:00 2001 From: wxs <18771603711@sina.cn> Date: Wed, 7 Jun 2023 16:34:56 +0800 Subject: [PATCH 97/97] =?UTF-8?q?--user=3Dwxs=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E8=BD=AC=E5=8F=91=E6=B6=88=E6=81=AF=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E5=8F=AF=E6=89=93=E5=8C=85=E6=88=90jar=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=EF=BC=8C=E8=AF=A6=E6=83=85=E8=A7=81README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- weChatHook-java/README.md | 29 +++ weChatHook-java/pom.xml | 37 +++- .../com/example/client/WeChatHookClient.java | 10 + .../service/WeChatHookNettyServer.java | 176 ++++++++++++++---- 4 files changed, 217 insertions(+), 35 deletions(-) create mode 100644 weChatHook-java/README.md diff --git a/weChatHook-java/README.md b/weChatHook-java/README.md new file mode 100644 index 0000000..be80b0f --- /dev/null +++ b/weChatHook-java/README.md @@ -0,0 +1,29 @@ +#maven打包 +```aidl +进入weChatHook-java项目根目录执行如下,命令进行打包 + +mvn package + +打包完成后在target目录下找到 +weChatHook-java-1.0-jar-with-dependencies.jar +文件就可以直接启动了 +``` +#启动命令 +####命令参数说明 +###port:监听的端口 默认端口19077 +###hookApi:消息转发的接口 为空不转发 +```aidl +java -jar .\weChatHook-java-1.0-jar-with-dependencies.jar --port=9999 --hookApi=http://localhost:29099/api/demo/msg +``` +#java接收hook消息示例 +```aidl +@RequestMapping("/api/demo") +public class DemoController { + @PostMapping("/msg") + public void getMsg(String msg){ + JSONObject jsonObject = JSON.parseObject(msg); + jsonObject.forEach((k,v)->{ + System.out.println(k+":"+v); + }); +} +``` \ No newline at end of file diff --git a/weChatHook-java/pom.xml b/weChatHook-java/pom.xml index abda013..5fa9c97 100644 --- a/weChatHook-java/pom.xml +++ b/weChatHook-java/pom.xml @@ -6,7 +6,7 @@ org.example weChatHook-java - 1.0-SNAPSHOT + 1.0 8 @@ -38,5 +38,40 @@ netty-all 4.1.51.Final + + junit + junit + 4.13.2 + test + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + true + com.example.service.WeChatHookNettyServer + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + \ No newline at end of file diff --git a/weChatHook-java/src/main/java/com/example/client/WeChatHookClient.java b/weChatHook-java/src/main/java/com/example/client/WeChatHookClient.java index dc187d7..df214cb 100644 --- a/weChatHook-java/src/main/java/com/example/client/WeChatHookClient.java +++ b/weChatHook-java/src/main/java/com/example/client/WeChatHookClient.java @@ -606,4 +606,14 @@ public class WeChatHookClient { return JSON.parseObject(body); } + @SneakyThrows + public static JSONObject hook(String url, String json) { + String body = Jsoup.connect(url) + .data("msg",json) + .method(Connection.Method.POST) + .timeout(1000) + .execute().body(); + return JSON.parseObject(body); + } + } diff --git a/weChatHook-java/src/main/java/com/example/service/WeChatHookNettyServer.java b/weChatHook-java/src/main/java/com/example/service/WeChatHookNettyServer.java index 00e1459..600ed01 100644 --- a/weChatHook-java/src/main/java/com/example/service/WeChatHookNettyServer.java +++ b/weChatHook-java/src/main/java/com/example/service/WeChatHookNettyServer.java @@ -17,11 +17,11 @@ import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.util.CharsetUtil; +import io.netty.util.internal.StringUtil; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; +import java.io.*; import java.net.InetSocketAddress; +import java.util.*; /** * @PACKAGE_NAME: com.example.service @@ -34,56 +34,125 @@ public class WeChatHookNettyServer { /** * 直接启动main方法 + * * @param args */ public static void main(String[] args) { - Integer hookPort = 19099; + + Integer serverPort = 19077; + + String hookApi = null; + + for (String arg : args) { + System.out.println(arg); + if (arg.startsWith("--port")) { + serverPort = Integer.valueOf(arg.split("=")[1]); + } + if (arg.startsWith("--hookApi")) { + hookApi = arg.split("=")[1]; + } + } + //1、注入 inject(); //2、开启hook try { - JSONObject result = WeChatHookClient.hook_msg("127.0.0.1", hookPort.toString()); + JSONObject result = WeChatHookClient.hook_msg("127.0.0.1", serverPort.toString()); } catch (Exception e) { System.out.println("hook 失败,请检查微信是否登录"); return; } //3、启动服务 - start(hookPort); + start(serverPort, hookApi); } /** * 执行注入命令 */ public static void inject() { - ClassLoader classLoader = WeChatHookNettyServer.class.getClassLoader(); - //获取ConsoleInject 文件路径 - String ConsoleInject = classLoader.getResource("ConsoleInject.exe").getPath().replaceFirst("/", ""); - //获取 wxhelper.dll 文件路径 - String wxhelper = classLoader.getResource("wxhelper.dll").getPath().replaceFirst("/", ""); + // + File consoleInjectTemp = null; + File wxhelperTemp = null; + try { + consoleInjectTemp = createTempFile("ConsoleInject", ".exe"); + wxhelperTemp = createTempFile("wxhelper", ".dll"); - //ConsoleInject.exe -i WeChat.exe -p C:\Users\DELL\Desktop\injector\wxhelper.dll - String command = ConsoleInject + " -i WeChat.exe -p " + wxhelper; + String ConsoleInject = consoleInjectTemp.getAbsolutePath(); - //重试3次 - int retryCount = 3; - do { - retryCount--; - try { - //检查登录状态 - JSONObject jsonObject = WeChatHookClient.check_login(); - //如果已登录不需要注入 - if (jsonObject.getInteger("code").equals(1)) { - return; + String wxhelper = wxhelperTemp.getAbsolutePath(); + + //ConsoleInject.exe -i WeChat.exe -p C:\Users\DELL\Desktop\injector\wxhelper.dll + String command = ConsoleInject + " -i WeChat.exe -p " + wxhelper; + + //重试3次 + int retryCount = 3; + do { + retryCount--; + try { + //检查登录状态 + JSONObject jsonObject = WeChatHookClient.check_login(); + //如果已登录不需要注入 + if (jsonObject.getInteger("code").equals(1)) { + return; + } + } catch (Exception e) { + System.out.println(e.getMessage() + "请确认微信已登录"); } - } catch (Exception e) { - System.out.println(e.getMessage() + "请确认微信已登录"); + //执行注入命令 + excuteShell(command); + } while (retryCount >= 0); + + } catch (Exception e) { + e.printStackTrace(); + return; + } finally { + ////如果不为空删除临时文件 + if (Objects.nonNull(consoleInjectTemp)) { + consoleInjectTemp.delete(); } - //执行注入命令 - excuteShell(command); + //如果不为空删除临时文件 + if (Objects.nonNull(wxhelperTemp)) { + wxhelperTemp.delete(); + } + } - } while (retryCount > 0); + } + + /** + * 创建临时文件 + * + * @param fileName + * @param suffix + * @return + */ + private static File createTempFile(String fileName, String suffix) { + InputStream inputStream = WeChatHookNettyServer.class.getResourceAsStream("/" + fileName + suffix); + + FileOutputStream outputStream = null; + try { + File tempFile = File.createTempFile(fileName, suffix); + outputStream = new FileOutputStream(tempFile); + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + return tempFile; + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (Objects.nonNull(outputStream)) { + try { + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + return null; } /** @@ -91,7 +160,7 @@ public class WeChatHookNettyServer { * * @param port */ - public static void start(Integer port) { + public static void start(Integer port, String hookApi) { NioEventLoopGroup bossGroup = new NioEventLoopGroup(); NioEventLoopGroup workerGroup = new NioEventLoopGroup(); try { @@ -102,10 +171,10 @@ public class WeChatHookNettyServer { .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { - ch.pipeline().addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); + ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024 * 100, Delimiters.lineDelimiter())); ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8)); ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8)); - ch.pipeline().addLast(new ReceiveMsgHandler()); + ch.pipeline().addLast(new ReceiveMsgHandler(hookApi)); } }) .option(ChannelOption.SO_BACKLOG, 128) @@ -124,12 +193,52 @@ public class WeChatHookNettyServer { private static class ReceiveMsgHandler extends SimpleChannelInboundHandler { + private String hookApi; + + public ReceiveMsgHandler(String hookApi) { + this.hookApi = hookApi; + } + @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { - JSON.parseObject(msg).forEach((k, v) -> { + JSONObject jsonObject = JSON.parseObject(msg); + + jsonObject.forEach((k, v) -> { System.out.println(k + " = " + v); }); - System.out.println("----------end----------"); + + String fromGroup = jsonObject.getString("fromGroup"); + String fromUser = jsonObject.getString("fromUser"); + String from; + if (fromGroup.equals(fromUser)) { + JSONObject fromGroupJson = WeChatHookClient.query_nickname(jsonObject.getString("fromGroup")); + String groupNname = fromGroupJson.getString("name"); + from = "消息来自:" + groupNname; + jsonObject.put("fromUserName", groupNname); + } else { + JSONObject fromGroupJson = WeChatHookClient.query_nickname(jsonObject.getString("fromGroup")); + String groupNname = fromGroupJson.getString("name"); + + JSONObject fromUserJson = WeChatHookClient.query_nickname(jsonObject.getString("fromUser")); + String fromUserName = fromUserJson.getString("name"); + jsonObject.put("fromUserName", fromUserName); + + from = "消息来自:" + groupNname + "->" + fromUserName; + } + System.out.println("----------" + from + "----------"); + //消息转发 + if (StringUtil.isNullOrEmpty(hookApi)) { + return; + } + //检查api接口是否是通的 + //转发消息 + try { + WeChatHookClient.hook(hookApi, msg); + } catch (Exception e) { + //请检查hookApi服务是否正常 + System.err.println("--》消息转发失败,请检查hookApi服务是否正常"); + } + } @Override @@ -160,5 +269,4 @@ public class WeChatHookNettyServer { e.printStackTrace(); } } - }

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 aLA;&3%{rXaY{UD~6x{g#KmX6p!2ba8UdufI literal 0 HcmV?d00001 diff --git a/java_client/src/main/resources/logback-spring.xml b/java_client/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..5027603 --- /dev/null +++ b/java_client/src/main/resources/logback-spring.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + ${withLineNumber_debug} + utf-8 + + + + + + + + log_error.log + + + ${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz + 50MB + 100 + + + + true + + + + ${file_pattern} + utf-8 + + + + + error + ACCEPT + DENY + + + + + + + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + utf-8 + + + + + + + + ${LOG_PATH}/log_error.log + + + ${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz + 50MB + 100 + + + + true + + + + ${file_pattern} + utf-8 + + + + + error + ACCEPT + DENY + + + + + + + + ${LOG_PATH}/log_total.log + + + ${LOG_PATH}/total/%d{yyyy-MM}/log_total-%d{yyyy-MM-dd}.%i.log.gz + 50MB + 100 + + + true + + + ${file_pattern} + utf-8 + + + + + + ${LOG_PATH}/log_business.log + + + ${LOG_PATH}/business/%d{yyyy-MM}/log_business-%d{yyyy-MM-dd}.%i.log.gz + + 50MB + 100 + + + true + + + ${file_pattern} + utf-8 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java_client/src/main/resources/spy.properties b/java_client/src/main/resources/spy.properties new file mode 100644 index 0000000..d64f7b1 --- /dev/null +++ b/java_client/src/main/resources/spy.properties @@ -0,0 +1,10 @@ +logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat +# 使用Slf4J记录sql +appender=com.p6spy.engine.spy.appender.Slf4JLogger +# 是否开启慢SQL记录 +outagedetection=true +# 慢SQL记录标准,单位秒 +outagedetectioninterval=2 +#日期格式 +dateformat=HH:mm:ss +customLogMessageFormat=%(executionTime)|%(category)|connection%(connectionId)|%(sqlSingleLine) \ No newline at end of file diff --git a/java_client/src/test/java/com/example/wxhk/WxhkApplicationTests.java b/java_client/src/test/java/com/example/wxhk/WxhkApplicationTests.java new file mode 100644 index 0000000..c7a768f --- /dev/null +++ b/java_client/src/test/java/com/example/wxhk/WxhkApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.wxhk; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class WxhkApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java b/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java new file mode 100644 index 0000000..6bf1ddf --- /dev/null +++ b/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java @@ -0,0 +1,44 @@ +package com.example.wxhk.tcp; + +import com.example.wxhk.util.HttpAsyncUtil; +import io.vertx.core.Future; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.client.HttpResponse; +import org.dromara.hutool.core.lang.Console; +import org.dromara.hutool.core.thread.ThreadUtil; +import org.dromara.hutool.log.Log; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class HttpAsyncUtilTest { + + protected static final Log log = Log.get(); + + + @Test + void exec() { + Future> exec = HttpAsyncUtil.exec(HttpAsyncUtil.Type.联系人列表, new JsonObject()); + exec.onSuccess(event -> { + Console.log(event.bodyAsJsonObject()); + }); + } + @Test + void exec1() { + + for(int i=0;i<10000;i++){ + int finalI = i; + HttpAsyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject(), event -> { + if (event.succeeded()) { + log.info("i:{},{}", finalI,event.result().bodyAsJsonObject()); + }else{ + event.cause().printStackTrace(); + } + }); + log.info("发出请求:{}",i); + } + + ThreadUtil.sync(this); + } +} \ No newline at end of file diff --git a/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java b/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java new file mode 100644 index 0000000..e4bf159 --- /dev/null +++ b/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java @@ -0,0 +1,72 @@ +package com.example.wxhk.tcp; + +import com.example.wxhk.model.PrivateChatMsg; +import com.example.wxhk.msg.WxMsgHandle; +import io.vertx.core.json.JsonObject; +import org.dromara.hutool.core.lang.Console; +import org.dromara.hutool.core.util.XmlUtil; +import org.dromara.hutool.http.HttpUtil; +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class XmlTest { + @Test + void t(){ + String con = "{\"content\":\"\",\"fromGroup\":\"fmessage\",\"fromUser\":\"fmessage\",\"isSendMsg\":0,\"msgId\":4949224622157906392,\"pid\":1860,\"sign\":\"ab8277a517ed906cf31a64843d4c61d5\",\"signature\":\"\\n\\tv1_ZLVKn3eO\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-05-25 10:50:39\",\"timestamp\":1684983039,\"type\":37}"; + + + JsonObject entries = new JsonObject(con); + String content = entries.getString("content"); + Document document = XmlUtil.parseXml(content); + String encryptusername = document.getDocumentElement().getAttribute("encryptusername"); + + String ticket = document.getDocumentElement().getAttribute("ticket"); + System.out.println(ticket); + + String post = HttpUtil.post("http://127.0.0.1:19088/api/?type=23", new JsonObject().put("v3", encryptusername).put("v4", ticket) + .put("type","8") + .put("permission","5") + .encode()); + System.out.println(post); + } + + + + @Test + void 接受转账(){ + String smg = "{\"content\":\"\\n\\n<![CDATA[微信转账]]>\\n\\n\\n2000\\n\\n\\n\\n\\n\\n\\n\\n1\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"fromGroup\":\"wxid_gf1fogt5a0pq22\",\"fromUser\":\"wxid_gf1fogt5a0pq22\",\"isSendMsg\":0,\"msgId\":3157044781598783480,\"path\":\"wxid_4yr8erik0uho22\\\\FileStorage\\\\Cache\\\\2023-05\\\\324af3cccfe197a57506357bff7c85f7\",\"pid\":14268,\"sign\":\"e588b9632126b915e17c5a960e4ef288\",\"signature\":\"\\n\\tv1_LzLnadY1\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-05-29 10:00:15\",\"timestamp\":1685325615,\"type\":49}"; + JsonObject entries = new JsonObject(smg); + String content = entries.getString("content"); + Document document = XmlUtil.parseXml(content); + Element documentElement = document.getDocumentElement(); + Node transcationid = documentElement.getElementsByTagName("transcationid").item(0); + Node transferid = documentElement.getElementsByTagName("transferid").item(0); + Console.log(transcationid); + + } + + @Test + void 扫码支付(){ + String smg = "{\"content\":\"\\n \\n 9\\n \\n \\n\\t\\t\\n\\t\\t\\n \\n\\t\\t\\n\\t\\t\\n \\n\\t\\t\\n 1685325848\\n 1\\n 1\\n \\n\\n\\n\\n 0\\n 1\\n10001071012023052901214894726608\\n \\n \\n\",\"fromGroup\":\"wxid_4yr8erik0uho22\",\"fromUser\":\"wxid_4yr8erik0uho22\",\"isSendByPhone\":1,\"isSendMsg\":1,\"msgId\":3869459965780413850,\"pid\":14268,\"sign\":\"2285732a2e5b17729e7eeb8e305add15\",\"signature\":\"\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-05-29 10:04:08\",\"timestamp\":1685325848,\"type\":10002}"; + JsonObject entries = new JsonObject(smg); + String content = entries.getString("content"); + Document document = XmlUtil.parseXml(content); + Element documentElement = document.getDocumentElement(); + String localName = documentElement.getLocalName(); + String type = documentElement.getAttribute("type"); + NodeList outtradeno = documentElement.getElementsByTagName("outtradeno"); + if (outtradeno.getLength()>0) { + Console.log(outtradeno.item(0).getTextContent()); + } + + } + @Test + void 扫码支付2duan(){ + String smg = "{\"content\":\" \\t<![CDATA[微信支付收款0.01元(朋友到店)]]> \\t \\t \\t5 \\t1 0 \\t \\t0 \\t \\t \\t \\t\\t0 \\t\\t \\t\\t \\t\\t \\t\\t \\t\\t \\t \\t \\t \\t \\t \\t\\t \\t\\t\\t \\t\\t\\t \\t\\t\\t\\t \\t\\t\\t\\t0 \\t\\t\\t\\t0 \\t\\t\\t\\t \\t\\t\\t \\t\\t\\t\\t \\t4 \\t<![CDATA[微信支付收款0.01元(朋友到店)]]> \\t \\t \\t \\t1685325848 \\t \\t \\t \\t0 \\t \\t \\t \\t \\t \\t\\n\\n\\n\\n\\n\\t 0 0 0 \\t \\t \\t1 \\t \\t \\t967 \\t0 0 0 \\t0 \\t \\t \\t\\t0\\t \\t 0 0 0 0