Compare commits

...

10 Commits
v1.0.7 ... main

Author SHA1 Message Date
HAL
260c7306ad 1. 修复FileStorage\Image文件没有导出的问题
2. 增加图片定位到聊天位置的功能
3. 导出界面增加提示
2025-04-16 00:00:48 +08:00
HAL
26399847ee update readme 2025-04-04 00:12:41 +08:00
HAL
640f27ce67 1. 修改程序ICON 2025-03-31 23:20:55 +08:00
HAL
6ddb45770f 1. UI统一使用“思源黑体”字体
2. 修复导出分享会话时可执行文件可能拷贝错误的情况
3. 修复微信存储路径为网盘时,解密会失败的情况
2025-03-31 23:01:13 +08:00
HAL
4d9043cccf 1. 实现最后浏览位置记录和书签功能
2. 实现会话导出分享功能
3. 增加语音消息和通话消息的筛选
4. 实现单聊会话对话人位置调换功能
5. 添加繁体和英语表情的解析
6. 修复相近时间的消息可能会出现顺序不对的问题
2025-03-08 16:32:59 +08:00
HAL
3cc400bcbd update readme 2025-03-08 15:33:54 +08:00
HAL
3d6d890212 1. 实现最后浏览位置记录和书签功能
2. 实现会话导出分享功能
3. 增加语音消息和通话消息的筛选
4. 实现单聊会话对话人位置调换功能
5. 添加繁体和英语表情的解析
6. 修复相近时间的消息可能会出现顺序不对的问题
2025-03-08 15:27:46 +08:00
git-jiadong
a8addf11eb
wechat QR code 2025-03-02 22:55:25 +08:00
HAL
81b873048b update readme 2025-01-16 22:48:17 +08:00
HAL
c13e680b3d 1. 支持转账、通话、链接消息的显示
2. 支持名片、视频号、QQ音乐、小程序、定位等消息的显示
3. 支持直播、游戏消息、语音通话、视频通话等消息的显示
4. 解密前判断对数据库是否有读权限
5. system消息html格式转文本
2025-01-13 23:42:02 +08:00
36 changed files with 1892 additions and 602 deletions

View File

@ -29,11 +29,12 @@ jobs:
submodules: recursive
- name: Build wails
uses: dAppServer/wails-build-action@v2.2
uses: dAppServer/wails-build-action@main
id: build
with:
build-name: ${{ matrix.build.name }}
sign: false
build-platform: ${{ matrix.build.platform }}
package: true
go-version: '1.21'
go-version: '1.21'
wails-version: "v2.9.1"

3
.gitignore vendored
View File

@ -30,4 +30,5 @@ env
User
config.json
wechatDataBackup.exe
app.log
app.log
app-*.log

View File

@ -1,5 +1,34 @@
<p align="center" style="text-align: center">
<img src="./res/logo_256.png" width="15%"><br/>
</p>
<p align="center">
<b>wechatDataBackup: PC微信聊天记录数据导出工具</b>
<br/>
<br/>
<a href="https://github.com/git-jiadong/wechatDataBackup/stargazers">
<img src="https://img.shields.io/github/stars/git-jiadong/wechatDataBackup" alt="GitHub Star"/>
</a>
<a href="https://github.com/git-jiadong/wechatDataBackup/releases">
<img src="https://img.shields.io/github/downloads/git-jiadong/wechatDataBackup/total" alt="downloads" />
</a>
<a href="https://github.com/git-jiadong/wechatDataBackup/releases">
<img src="https://img.shields.io/github/v/release/git-jiadong/wechatDataBackup" alt="releases version"/>
</a>
<a href="https://github.com/git-jiadong/wechatDataBackup/commits/main">
<img src="https://img.shields.io/github/last-commit/git-jiadong/wechatDataBackup" alt="last commit" />
</a>
<a href="https://github.com/git-jiadong/wechatDataBackup" >
<img src="https://img.shields.io/github/languages/top/git-jiadong/wechatDataBackup" alt="languages"/>
</a>
<a href="https://github.com/git-jiadong/wechatDataBackup" >
<img src="https://img.shields.io/github/repo-size/git-jiadong/wechatDataBackup" alt="repo size" />
</a>
<a href="https://github.com/git-jiadong/wechatDataBackup/blob/main/LICENSE">
<img src="https://img.shields.io/github/license/git-jiadong/wechatDataBackup" alt="license" />
</a>
</p>
# wechatDataBackup
PC微信聊天记录数据导出工具
* 基于wails开发 + React前端实现PC端微信聊天记录一键导出功能。
* 导出后数据可以做永久化保存,即使微信停止支持,聊天记录也可以随时查看。
@ -39,6 +68,16 @@ wails build
- [x] 支持链接消息
- [x] 支持语音消息
- [x] 支持文件消息
- [x] 支持名片消息
- [x] 支持定位消息
- [x] 支持视频/语音通话消息
- [x] 支持QQ音乐消息
- [x] 支持第三方视频软件分享消息
- [x] 支持分享表情集消息
- [x] 支持小程序消息
- [x] 支持视频号/直播消息
- [x] 支持转账消息
- [x] 支持腾讯游戏分享消息
- [x] 支持原始表情显示
- [x] 支持按类型检索
- [x] 支持日期检索
@ -49,6 +88,10 @@ wails build
- [x] 头像使用本地头像
- [ ] 支持更多消息类型显示
- [x] 图片查看器重绘
- [x] 支持会话导出分享
- [x] 支持自动定位到最后浏览位置
- [x] 支持书签功能
- [x] 支持单聊会话对话人位置调换功能
- [ ] 实现表情预先下载(实现完全离线查看)
- [ ] 聊天报告
- [ ] AI本地模型应用
@ -84,4 +127,7 @@ A: Win7电脑需要安装WebView2运行时才能正常使用。github release版
- 微信数据库解密和数据库的使用 [PyWxDump](https://github.com/xaoyaoo/PyWxDump/tree/master)
- silk语音消息解码 [silk-v3-decoder](https://github.com/kn007/silk-v3-decoder)
- PCM转MP3 [lame](https://github.com/viert/lame.git)
- Dat图片解码 [wechatDatDecode](https://github.com/liuggchen/wechatDatDecode)
- Dat图片解码 [wechatDatDecode](https://github.com/liuggchen/wechatDatDecode)
## 交流/讨论
![](./res/wechatQR.png)

172
app.go
View File

@ -23,7 +23,7 @@ const (
configDefaultUserKey = "userConfig.defaultUser"
configUsersKey = "userConfig.users"
configExportPathKey = "exportPath"
appVersion = "v1.0.6"
appVersion = "v1.2.4"
)
type FileLoader struct {
@ -183,14 +183,15 @@ func (a *App) startup(ctx context.Context) {
}
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
return false
}
func (a *App) shutdown(ctx context.Context) {
if a.provider != nil {
a.provider.WechatWechatDataProviderClose()
a.provider = nil
}
return false
log.Printf("App Version %s exit!", appVersion)
}
func (a *App) GetWeChatAllInfo() string {
@ -713,3 +714,166 @@ func (a *App) SaveFileDialog(file string, alisa string) string {
return ""
}
func (a *App) GetSessionLastTime(userName string) string {
if a.provider == nil || userName == "" {
lastTime := &wechat.WeChatLastTime{}
lastTimeString, _ := json.Marshal(lastTime)
return string(lastTimeString)
}
lastTime := a.provider.WeChatGetSessionLastTime(userName)
lastTimeString, _ := json.Marshal(lastTime)
return string(lastTimeString)
}
func (a *App) SetSessionLastTime(userName string, stamp int64, messageId string) string {
if a.provider == nil {
return ""
}
lastTime := &wechat.WeChatLastTime{
UserName: userName,
Timestamp: stamp,
MessageId: messageId,
}
err := a.provider.WeChatSetSessionLastTime(lastTime)
if err != nil {
log.Println("WeChatSetSessionLastTime failed:", err.Error())
return err.Error()
}
return ""
}
func (a *App) SetSessionBookMask(userName, tag, info string) string {
if a.provider == nil || userName == "" {
return "invaild params"
}
err := a.provider.WeChatSetSessionBookMask(userName, tag, info)
if err != nil {
log.Println("WeChatSetSessionBookMask failed:", err.Error())
return err.Error()
}
return ""
}
func (a *App) DelSessionBookMask(markId string) string {
if a.provider == nil || markId == "" {
return "invaild params"
}
err := a.provider.WeChatDelSessionBookMask(markId)
if err != nil {
log.Println("WeChatDelSessionBookMask failed:", err.Error())
return err.Error()
}
return ""
}
func (a *App) GetSessionBookMaskList(userName string) string {
if a.provider == nil || userName == "" {
return "invaild params"
}
markLIst, err := a.provider.WeChatGetSessionBookMaskList(userName)
if err != nil {
log.Println("WeChatGetSessionBookMaskList failed:", err.Error())
_list := &wechat.WeChatBookMarkList{}
_listString, _ := json.Marshal(_list)
return string(_listString)
}
markLIstString, _ := json.Marshal(markLIst)
return string(markLIstString)
}
func (a *App) SelectedDirDialog(title string) string {
dialogOptions := runtime.OpenDialogOptions{
Title: title,
}
selectedDir, err := runtime.OpenDirectoryDialog(a.ctx, dialogOptions)
if err != nil {
log.Println("OpenDirectoryDialog:", err)
return ""
}
if selectedDir == "" {
return ""
}
return selectedDir
}
func (a *App) ExportWeChatDataByUserName(userName, path string) string {
if a.provider == nil || userName == "" || path == "" {
return "invaild params" + userName
}
if !utils.PathIsCanWriteFile(path) {
log.Println("PathIsCanWriteFile: " + path)
return "PathIsCanWriteFile: " + path
}
exPath := path + "\\" + "wechatDataBackup_" + userName
if _, err := os.Stat(exPath); err != nil {
os.MkdirAll(exPath, os.ModePerm)
} else {
return "path exist:" + exPath
}
log.Println("ExportWeChatDataByUserName:", userName, exPath)
err := a.provider.WeChatExportDataByUserName(userName, exPath)
if err != nil {
log.Println("WeChatExportDataByUserName failed:", err)
return "WeChatExportDataByUserName failed:" + err.Error()
}
config := map[string]interface{}{
"exportpath": ".\\",
"userconfig": map[string]interface{}{
"defaultuser": a.defaultUser,
"users": []string{a.defaultUser},
},
}
configJson, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Println("MarshalIndent:", err)
return "MarshalIndent:" + err.Error()
}
configPath := exPath + "\\" + "config.json"
err = os.WriteFile(configPath, configJson, os.ModePerm)
if err != nil {
log.Println("WriteFile:", err)
return "WriteFile:" + err.Error()
}
exeSrcPath, err := os.Executable()
if err != nil {
log.Println("Executable:", exeSrcPath)
return "Executable:" + err.Error()
}
exeDstPath := exPath + "\\" + "wechatDataBackup.exe"
log.Printf("Copy [%s] -> [%s]\n", exeSrcPath, exeDstPath)
_, err = utils.CopyFile(exeSrcPath, exeDstPath)
if err != nil {
log.Println("CopyFile:", err)
return "CopyFile:" + err.Error()
}
return ""
return ""
}
func (a *App) GetAppIsShareData() bool {
if a.provider != nil {
return a.provider.IsShareData
}
return false
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -1,3 +1,36 @@
## v1.2.4
1. 修复FileStorage\Image文件没有导出的问题
2. 增加图片定位到聊天位置的功能
3. 导出界面增加提示
## v1.2.3
1. 修改程序ICON
## v1.2.2
1. UI统一使用“思源黑体”字体
2. 修复导出分享会话时可执行文件可能拷贝错误的情况
3. 修复微信存储路径为网盘时,解密会失败的情况
## v1.2.0
1. 实现最后浏览位置记录和书签功能
2. 实现会话导出分享功能
3. 增加语音消息和通话消息的筛选
4. 实现单聊会话对话人位置调换功能
5. 添加繁体和英语表情的解析
6. 修复相近时间的消息可能会出现顺序不对的问题
## v1.1.0
1. 支持转账、通话、链接消息的显示
2. 支持名片、视频号、QQ音乐、小程序、定位等消息的显示
3. 支持直播、游戏消息、语音通话、视频通话等消息的显示
4. 解密前判断对数据库是否有读权限
5. system消息html格式转文本
## v1.0.7
1. 修复出现空数据库时不显示,聊天显示不全的问题
2. 解决安卓平板扫码登陆的情况下解密失败的问题
3. 移除lame和silk代码改用mod的方式引入
## v1.0.6
1. 图片/视频查看重新实现,保持与微信一致
2. 增加软件信息页面

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
frontend/dist/assets/applet.ce6471b1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

533
frontend/dist/assets/index.8be36b27.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
frontend/dist/assets/logo.8df6944e.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
frontend/dist/assets/map.b91d2cda.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
frontend/dist/assets/share.3d7abf39.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

View File

@ -4,8 +4,8 @@
<meta charset="UTF-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>wechatDataBackup</title>
<script type="module" crossorigin src="/assets/index.d5e97187.js"></script>
<link rel="stylesheet" href="/assets/index.a11c4643.css">
<script type="module" crossorigin src="/assets/index.8be36b27.js"></script>
<link rel="stylesheet" href="/assets/index.00f6955e.css">
</head>
<body >
<div id="root"></div>

2
go.mod
View File

@ -13,6 +13,7 @@ require (
github.com/shirou/gopsutil/v3 v3.24.2
github.com/spf13/viper v1.18.2
github.com/wailsapp/wails/v2 v2.9.1
golang.org/x/net v0.25.0
golang.org/x/sys v0.20.0
google.golang.org/protobuf v1.31.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
@ -62,7 +63,6 @@ require (
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/text v0.15.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

View File

@ -51,6 +51,7 @@ func main() {
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
OnBeforeClose: app.beforeClose,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
},
@ -58,6 +59,6 @@ func main() {
})
if err != nil {
println("Error:", err.Error())
log.Println("Error:", err.Error())
}
}

View File

@ -1,6 +1,8 @@
package utils
import (
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"io"
@ -8,10 +10,12 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/pkg/browser"
"github.com/shirou/gopsutil/v3/disk"
"golang.org/x/net/html"
"golang.org/x/sys/windows/registry"
)
@ -150,3 +154,83 @@ func CopyFile(src, dst string) (int64, error) {
return bytesWritten, nil
}
func extractTextFromHTML(htmlStr string) string {
doc, err := html.Parse(strings.NewReader(htmlStr))
if err != nil {
fmt.Println("Error parsing HTML:", err)
return ""
}
var extractText func(*html.Node) string
extractText = func(n *html.Node) string {
if n.Type == html.TextNode {
return n.Data
}
var text string
for c := n.FirstChild; c != nil; c = c.NextSibling {
text += extractText(c)
}
return text
}
return extractText(doc)
}
func removeCustomTags(input string) string {
re := regexp.MustCompile(`<(_wc_custom_link_)[^>]*?>`)
return re.ReplaceAllString(input, `$2`)
}
func Html2Text(htmlStr string) string {
// if htmlStr == "" {
// return ""
// }
if len(htmlStr) == 0 || htmlStr[0] != '<' {
return htmlStr
}
text := extractTextFromHTML(htmlStr)
if strings.Contains(text, `<_wc_custom_link_`) {
text = "\U0001F9E7" + removeCustomTags(text)
}
return text
}
func HtmlMsgGetAttr(htmlStr, tag string) map[string]string {
doc, err := html.Parse(strings.NewReader(htmlStr))
if err != nil {
fmt.Println("Error parsing HTML:", err)
return nil
}
var attributes map[string]string
var findAttributes func(*html.Node)
findAttributes = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == tag {
attributes = make(map[string]string)
for _, attr := range n.Attr {
attributes[attr.Key] = attr.Val
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
findAttributes(c)
}
}
findAttributes(doc)
return attributes
}
func Hash256Sum(data []byte) string {
hash := md5.New()
hash.Write([]byte(data))
hashSum := hash.Sum(nil)
return hex.EncodeToString(hashSum)
}

View File

@ -310,6 +310,7 @@ func exportWeChatVideoAndFile(info WeChatInfo, expPath string, progress chan<- s
videoRootPath := info.FilePath + "\\FileStorage\\Video"
fileRootPath := info.FilePath + "\\FileStorage\\File"
cacheRootPath := info.FilePath + "\\FileStorage\\Cache"
rootPaths := []string{videoRootPath, fileRootPath, cacheRootPath}
handleNumber := int64(0)
@ -326,6 +327,9 @@ func exportWeChatVideoAndFile(info WeChatInfo, expPath string, progress chan<- s
go func() {
for _, rootPath := range rootPaths {
log.Println(rootPath)
if _, err := os.Stat(rootPath); err != nil {
continue
}
err := filepath.Walk(rootPath, func(path string, finfo os.FileInfo, err error) error {
if err != nil {
log.Printf("filepath.Walk%v\n", err)
@ -399,43 +403,51 @@ func exportWeChatVideoAndFile(info WeChatInfo, expPath string, progress chan<- s
func exportWeChatBat(info WeChatInfo, expPath string, progress chan<- string) {
progress <- "{\"status\":\"processing\", \"result\":\"export WeChat Dat start\", \"progress\": 21}"
datRootPath := info.FilePath + "\\FileStorage\\MsgAttach"
fileInfo, err := os.Stat(datRootPath)
if err != nil || !fileInfo.IsDir() {
progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%s error\"}", datRootPath)
return
}
imageRootPath := info.FilePath + "\\FileStorage\\Image"
rootPaths := []string{datRootPath, imageRootPath}
handleNumber := int64(0)
fileNumber := getPathFileNumber(datRootPath, ".dat")
fileNumber := int64(0)
for i := range rootPaths {
fileNumber += getPathFileNumber(rootPaths[i], ".dat")
}
log.Println("DatFileNumber ", fileNumber)
var wg sync.WaitGroup
var reportWg sync.WaitGroup
quitChan := make(chan struct{})
taskChan := make(chan [2]string, 100)
go func() {
err = filepath.Walk(datRootPath, func(path string, finfo os.FileInfo, err error) error {
if err != nil {
log.Printf("filepath.Walk%v\n", err)
return err
for i := range rootPaths {
if _, err := os.Stat(rootPaths[i]); err != nil {
continue
}
if !finfo.IsDir() && strings.HasSuffix(path, ".dat") {
expFile := expPath + path[len(info.FilePath):]
_, err := os.Stat(filepath.Dir(expFile))
err := filepath.Walk(rootPaths[i], func(path string, finfo os.FileInfo, err error) error {
if err != nil {
os.MkdirAll(filepath.Dir(expFile), 0644)
log.Printf("filepath.Walk%v\n", err)
return err
}
if !finfo.IsDir() && strings.HasSuffix(path, ".dat") {
expFile := expPath + path[len(info.FilePath):]
_, err := os.Stat(filepath.Dir(expFile))
if err != nil {
os.MkdirAll(filepath.Dir(expFile), 0644)
}
task := [2]string{path, expFile}
taskChan <- task
return nil
}
task := [2]string{path, expFile}
taskChan <- task
return nil
})
if err != nil {
log.Println("filepath.Walk:", err)
progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%v\"}", err)
}
return nil
})
if err != nil {
log.Println("filepath.Walk:", err)
progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%v\"}", err)
}
close(taskChan)
}()
@ -445,7 +457,7 @@ func exportWeChatBat(info WeChatInfo, expPath string, progress chan<- string) {
go func() {
defer wg.Done()
for task := range taskChan {
_, err = os.Stat(task[1])
_, err := os.Stat(task[1])
if err == nil {
atomic.AddInt64(&handleNumber, 1)
continue
@ -598,7 +610,7 @@ func GetWeChatInfo() (list *WeChatInfoList) {
for _, f := range files {
if strings.HasSuffix(f.Path, "\\Media.db") {
// fmt.Printf("opened %s\n", f.Path[4:])
filePath := f.Path[4:]
filePath := f.Path
parts := strings.Split(filePath, string(filepath.Separator))
if len(parts) < 4 {
log.Println("Error filePath " + filePath)
@ -697,6 +709,12 @@ func Is64BitProcess(pid uint32) (bool, error) {
}
func GetWeChatKey(info *WeChatInfo) string {
mediaDB := info.FilePath + "\\Msg\\Media.db"
if _, err := os.Stat(mediaDB); err != nil {
log.Printf("open db %s error: %v", mediaDB, err)
return ""
}
handle, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ, false, uint32(info.ProcessID))
if err != nil {
log.Println("Error opening process:", err)
@ -741,6 +759,7 @@ func hasDeviceSybmol(buffer []byte) int {
{'p', 'a', 'd', '-', 'a', 'n', 'd', 'r', 'o', 'i', 'd', 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00},
{'i', 'p', 'h', 'o', 'n', 'e', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00},
{'i', 'p', 'a', 'd', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00},
{'O', 'H', 'O', 'S', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00},
}
for _, syb := range sybmols {
if index := bytes.Index(buffer, syb); index != -1 {

File diff suppressed because it is too large Load Diff

BIN
res/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

BIN
res/logo_128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
res/logo_256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
res/wechatQR.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB