Skip to content

Commit

Permalink
Add weixin 4.0 OCR support
Browse files Browse the repository at this point in the history
  • Loading branch information
lixungeng committed Dec 24, 2024
1 parent de82245 commit 6c5a990
Show file tree
Hide file tree
Showing 23 changed files with 11,138 additions and 6,362 deletions.
37 changes: 31 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,48 @@
# wcocr: demonstrate how to use WeChatOCR.exe

Great thanks to IEEE by his [Project IEEE/QQImpl](https://github.com/EEEEhex/qqimpl)] and [article](https://bbs.kanxue.com/thread-278161.htm).
This project is based on it.
This project is based on it and reduced the product size by using `protobuf-lite` instead of `protobuf`.

This project reduced the product size by using `protobuf-lite` instead of `protobuf`,
and provided a direct Python interface for calling in sync mode.
This project provided a direct Python interface for calling in sync mode as well as other languages support including but not limited with c++/java/c#.

To use this project, you need to prepare the `wechatocr.exe` and the wechat folder.
For example, `wechatocr.exe` might be:
# Prepare for usage

To work with this project, you need to prepare the wechat OCR binary and the wechat runtime folder.

For wechat 3.x, the wechat OCR binary is `wechatocr.exe`, it might be:

```
C:\Users\yourname\AppData\Roaming\Tencent\WeChat\XPlugin\Plugins\WeChatOCR\7061\extracted\WeChatOCR.exe
```
and the wechat folder might be:
and the wechat runtime folder might be:
```
C:\Program Files (x86)\Tencent\WeChat\[3.9.8.25]
```

**Wechat 4.0 is now supported!**

For wechat 4.0, the wechat OCR binary is `wxocr.dll`, it might be:

```
C:\Users\yourname\AppData\Roaming\Tencent\xwechat\XPlugin\plugins\WeChatOcr\8011\extracted\wxocr.dll
```

and the wechat runtime folder might be:

```
C:\Program Files\Tencent\Weixin\4.0.0.26
```

## Warning

Wechat 4.0 OCR binary is `wxocr.dll`, but this project built a dll named `wcocr.dll`

**Their names are similar, DO NOT confuse them.**



## C++ interface

You can use the following code to test it:
```cpp
CWeChatOCR ocr(wechatocr_path, wechat_path);
Expand Down
5 changes: 5 additions & 0 deletions compile-proto.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@echo off
cd /d %~dp0
cd src
..\spt\protoc.exe --cpp_out=lite:. -I ../pb ocr_common.proto ocr_wx3.proto ocr_wx4.proto
pause
2 changes: 1 addition & 1 deletion java/Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface SetResCallback extends Callback {
public static void main(String [] args) {
System.out.println("ocr begin...");
String ocr_exe = "C:\\Users\\xungeng\\AppData\\Roaming\\Tencent\\WeChat\\XPlugin\\Plugins\\WeChatOCR\\7079\\extracted\\WeChatOCR.exe";
String wechat_dir = "C:\\Program Files (x86)\\Tencent\\WeChat\\[3.9.10.19]";
String wechat_dir = "C:\\Program Files\\Tencent\\Weixin\\4.0.0.26";
String tstpng = "test.png";
AtomicReference<String> result = new AtomicReference<>();
WechatOCR.dll.wechat_ocr(new WString(ocr_exe), new WString(wechat_dir), tstpng, new WechatOCR.SetResCallback() {
Expand Down
32 changes: 32 additions & 0 deletions pb/ocr_common.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
syntax = "proto3";
package ocr_common;

message Point{
optional float x = 1;
optional float y = 2;
}

message Box{
optional Point topleft = 1;
optional Point topright = 2;
optional Point bottomright = 3;
optional Point bottomleft = 4;
}

message OCRResultChar{
optional Box char_box = 1;
optional string chars = 2;
}

message OCRResultLine {
Box line_box = 1;
string text = 2; //UTF8格式的字符串
float rate = 3; //单行的识别率
repeated OCRResultChar blocks = 4;
float left = 5; //识别矩形的left\top\right\bottom的坐标
float top = 6;
float right = 7;
float bottom = 8;
optional bool unknown_0 = 9; //未知
optional Box box10 = 10; //未知
}
65 changes: 0 additions & 65 deletions pb/ocr_protobuf.proto

This file was deleted.

36 changes: 36 additions & 0 deletions pb/ocr_wx3.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
syntax = "proto3";
package wx3;
import "ocr_common.proto";

message OcrInputBuffer {
// 看起来有两种传递图片的方式。
// 第一种是只传文件路径 pic_path = "C:/path/to/xxx.png"
// 第二种是用字节流传,猜测需要用到u2,u3,pic_data变量。暂时微信没有用到,可能是为将来保留
optional string pic_path = 1;
optional uint32 u2 = 2;
optional uint32 u3 = 3;
optional bytes pic_data = 4;
}

message OcrOutputBuffer {
repeated ocr_common.OCRResultLine lines = 1; //repeated 每行的结果
optional uint32 img_width = 2;
optional uint32 img_height = 3;
optional string unk4 = 4;
}

message OcrRespond {
optional int32 type = 1; // type=1像是初始化成功回调。如果是正常OCR请求,回答的type=0
optional uint64 task_id = 2;
optional int32 err_code = 3;
optional OcrOutputBuffer ocr_result = 4;
}

message OcrRequest {
int32 type = 1; //为0执行ocr,为1会直接返回init信息. 与OcrRespond.type意义相同
// 经过反复核查,在腾讯proto文件中,这个task_id确实是64位的。但在,在执行过程中,高32位会被丢弃,且第32位为1会出错。
// 也就是协议上是有64位的uint64,实际上只能有31位。必须是>0的整形数字,范围是[1,2147483647]
// 由于 task_id = 1会被用于初始化,所以最好取值为 [2, 0x7fffFFFF]
uint64 task_id = 2;
OcrInputBuffer input = 3;
}
44 changes: 44 additions & 0 deletions pb/ocr_wx4.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
syntax = "proto3";
package wx4;
import "ocr_common.proto";

message OCRSupportMessage {
optional bool supported = 1;
}

message ReqType {
optional bool t1 = 1;
optional bool t2 = 2;
optional bool t3 = 3;
}

message ParseOCRReqMessage {
optional uint64 task_id = 1;
optional string pic_path = 2;
optional uint32 xx3 = 3;
optional uint32 xx4 = 4;
optional bytes pic_data = 5;
optional ReqType rt = 6;
}

message OCRResultInfo {
repeated ocr_common.OCRResultLine lines = 3;
optional uint32 img_width = 4;
optional uint32 img_height = 5;
optional string cpu_report = 6;
optional uint64 time_used = 7;
}

message QRResultInfo {
}
message MMFGResultInfo {
}

message ParseOCRRespMessage {
optional uint64 task_id = 1;
optional int32 err_code = 2;
optional OCRResultInfo res = 3;
optional ReqType rt = 4;
optional bytes qrcode = 5; // ¶þάÂëʶ±ð
optional bytes mmfg = 6; // what is mmfg?
}
5 changes: 4 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ bool wechat_ocr(const wchar_t* ocr_exe, const wchar_t * wechat_dir, const char *
return false;
}
CWeChatOCR::result_t res;
wechat_ocr.doOCR(imgfn, &res);
if (!wechat_ocr.doOCR(imgfn, &res))
return false;
string json;
json += "{";
json += "\"errcode\":" + std::to_string(res.errcode) + ",";
Expand Down Expand Up @@ -95,10 +96,12 @@ HRESULT DllRegisterServer(void)

CWeChatOCR wechat_ocr(_wgetenv(L"WECHATOCR_EXE"), _wgetenv(L"WECHAT_DIR"));
if (!wechat_ocr.wait_connection(5000)) {
fprintf(stderr, "wechat_ocr.wait_connection failed\n");
return E_FAIL;
}
wechat_ocr.doOCR(getenv("TEST_PNG"), nullptr);
wechat_ocr.wait_done(-1);
fprintf(stderr, "debug play ocr DONE!\n");
return S_OK;
}
#endif
61 changes: 42 additions & 19 deletions src/mojocall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
#include "mojocall.h"
#include "mmmojo.h"

#ifdef _DEBUG
#define DBG_PRINT(fmt, ...) fprintf(stderr, "[DEBUG]" fmt, __VA_ARGS__)
#else
#define DBG_PRINT(fmt, ...)
#endif

CMojoCall::~CMojoCall()
{
Stop();
Expand Down Expand Up @@ -46,30 +52,38 @@ bool CMojoCall::Start(LPCWSTR exepath)
//设置回调函数
SetMMMojoEnvironmentCallbacks(env, MMMojoEnvironmentCallbackType::kMMUserData, this);
void (*ReadOnPush)(uint32_t request_id, const void* request_info, void* user_data) = [](uint32_t request_id, const void* request_info, void* user_data) {
DBG_PRINT("ReadOnPush: %u\n", request_id);
return ((CMojoCall*)user_data)->ReadOnPush(request_id, request_info);
};
};
void (*ReadOnPull)(uint32_t request_id, const void* request_info, void* user_data) = [](uint32_t request_id, const void* request_info, void* user_data) {
DBG_PRINT("ReadOnPull: %u\n", request_id);
return ((CMojoCall*)user_data)->ReadOnPull(request_id, request_info);
};
};
void (*ReadOnShared)(uint32_t request_id, const void* request_info, void* user_data) = [](uint32_t request_id, const void* request_info, void* user_data) {
DBG_PRINT("ReadOnShared: %u\n", request_id);
return ((CMojoCall*)user_data)->ReadOnShared(request_id, request_info);
};
};

void (*OnConnect)(bool is_connected, void* user_data) = [](bool is_connected, void* user_data) {
DBG_PRINT("OnConnect: %d\n", is_connected);
return ((CMojoCall*)user_data)->OnRemoteConnect(is_connected);
};
};
void (*OnDisConnect)(void* user_data) = [](void* user_data) {
DBG_PRINT("OnDisConnect\n");
return ((CMojoCall*)user_data)->OnRemoteDisConnect();
};
};
void (*OnProcessLaunched)(void* user_data) = [](void* user_data) {
DBG_PRINT("OnProcessLaunched\n");
return ((CMojoCall*)user_data)->OnRemoteProcessLaunched();
};
};
void (*OnProcessLaunchFailed)(int error_code, void* user_data) = [](int error_code, void* user_data) {
DBG_PRINT("OnProcessLaunchFailed: %d\n", error_code);
return ((CMojoCall*)user_data)->OnRemoteProcessLaunchFailed(error_code);
};
};
void (*OnError)(const void* errorbuf, int errorsize, void* user_data) = [](const void* errorbuf, int errorsize, void* user_data) {
DBG_PRINT("OnError: %.*s\n", errorsize, (const char*)errorbuf);
return ((CMojoCall*)user_data)->OnRemoteError(errorbuf, errorsize);
};
};

SetMMMojoEnvironmentCallbacks(env, MMMojoEnvironmentCallbackType::kMMReadPush, ReadOnPush);
SetMMMojoEnvironmentCallbacks(env, MMMojoEnvironmentCallbackType::kMMReadPull, ReadOnPull);
Expand Down Expand Up @@ -123,30 +137,39 @@ bool CMojoCall::SendPbSerializedData(const void* pb_data, size_t data_size, int

void CMojoCall::OnRemoteConnect(bool is_connected)
{
std::lock_guard<std::mutex> lock(m_mutex_conn);
m_connected = is_connected;
m_cv_conn.notify_all();
std::lock_guard<std::mutex> lock(m_mutex_state);
m_state = is_connected ? MJC_CONNECTED : MJC_FAILED;
m_cv_state.notify_all();
}

void CMojoCall::OnRemoteDisConnect() {
std::lock_guard<std::mutex> lock(m_mutex_conn);
m_connected = false;
m_cv_conn.notify_all();
std::lock_guard<std::mutex> lock(m_mutex_state);
m_state = MJC_FAILED;
m_cv_state.notify_all();
}

bool CMojoCall::wait_connection(int timeout)
{
if (timeout < 0) {
std::unique_lock<std::mutex> lock(m_mutex_conn);
m_cv_conn.wait(lock, [this] {return m_connected; });
std::unique_lock<std::mutex> lock(m_mutex_state);
m_cv_state.wait(lock, [this] {return m_state != MJC_PENDING; });
}
else
{
std::unique_lock<std::mutex> lock(m_mutex_conn);
if (!m_cv_conn.wait_for(lock, std::chrono::milliseconds(timeout), [this] {return m_connected; }))
std::unique_lock<std::mutex> lock(m_mutex_state);
if (!m_cv_state.wait_for(lock, std::chrono::milliseconds(timeout), [this] {return m_state != MJC_PENDING; }))
{
return false;
}
}
return m_connected;
return m_state >= MJC_CONNECTED;
}

void CMojoCall::OnRemoteProcessLaunchFailed(int error_code)
{
std::lock_guard<std::mutex> lock(m_mutex_state);
if (m_state == MJC_PENDING) {
m_state = MJC_FAILED;
m_cv_state.notify_all();
}
}
Loading

0 comments on commit 6c5a990

Please sign in to comment.