58 Commits
v0.4.1 ... dev

Author SHA1 Message Date
Suxue
3da9a923b4 1.优化批量导出时的体验。现在起记录为空的用户将不会被导出。
2.修复批量导出的部分问题。
2024-01-10 23:20:15 +08:00
Suxue
f07d93c461 修复超大文件解密问题 2024-01-09 17:17:54 +08:00
Suxue
6653bcd035 update 2024-01-09 16:16:46 +08:00
Suxue
85f77c468e log 2024-01-09 16:08:43 +08:00
Suxue
f16e0b020b 1.解密内存优化
2.记录分页支持
3.模版选择初步支持
2024-01-09 15:01:36 +08:00
Suxue
00b6596237 update 2024-01-06 11:22:31 +08:00
Suxue
3dfb81b990 新增部分消息类型容错
新增已删除联系人记录导出
2024-01-06 11:20:50 +08:00
Suxue
f3d73d2507 修复bug
dev分支预下载暂时不可用,拿来调试功能了
2023-12-25 23:41:18 +08:00
Suxue
b276815960 新增引用消息支持
新增词云分词异常时,错误提示
2023-12-23 00:05:14 +08:00
Suxue
c507e68080 合并0.9.6.2 2023-12-21 15:32:23 +08:00
Suxue
932b2d099f v0.9.6.2 Release!
1.初步支持转发消息导出。
2.新增HTML导出全局异常处理,新增LOG方便直接定位
2023-12-19 23:17:39 +08:00
Suxue
49c39b13cf v0.9.6.1 Release!
1.支持词云蒙版,现在可以自定义词云形状啦!
2.导出附件按用户id归集。
3.修复图片不存在导致导出线程退出的问题
2023-12-18 22:41:10 +08:00
Suxue
cc63fe94a6 词云蒙版支持 2023-12-16 12:26:13 +08:00
Suxue
ddfec75201 导出附件按用户id归集 2023-12-16 09:15:23 +08:00
Suxue
e174f6d00a 合并 2023-12-16 08:37:02 +08:00
Suxue
6234ad3084 v0.9.6.0 Release!
1.新增词云功能,导出时选择与他的词云即可。
2.修复公钥头推断失败BUG
3.修复创建工作区与已有工作区切换不成功BUG
2023-12-15 22:48:40 +08:00
Suxue
ce393253a8 新增词云
修复创建工作区与已有工作区之间切换的bug
导出文件名优化
2023-12-15 18:46:15 +08:00
Suxue
db79e51305 v0.9.5.0 Release!
1.修复查询微信时,可能出现假死的问题。
2.修复导出HTML时,Emoji异常导致导出线程退出的问题。
3.更换了图片解密算法,修复部分情况下不能正常导出图片的问题
4.新增公钥头推断Key
2023-12-14 23:25:23 +08:00
Suxue
cf7d75625d 新增公钥头推断查找 2023-12-14 23:11:55 +08:00
Suxue
cca9e0626d 合并 2023-12-14 21:54:44 +08:00
Suxue
f02a1164cf 1.修复导出html时,emoji xml导致线程退出的问题
2.修复图片导出问题
2023-12-14 21:50:07 +08:00
Suxue
5ae3e6ef5d v0.9.4.0 Release!
1.导出HTML新增了文件与表情的支持,需要导出表情,请先使用表情预下载功能,由于略微做了一些频率限制,预下载可能会较久,请耐心等待。
2.调整了软件本身的显示逻辑
3.旧版消息工具已经移动至管理界面,如需使用,工作区,右键,管理即可
2023-12-13 18:35:32 +08:00
Suxue
2399fa5f4a v0.9.3.0
1.修复一处内存泄露问题。
2.修复部分用户在创建工作区时遇到“未找到适用于完成此操作的图像处理组件。”的问题
3.支持Type=10000的系统消息。
4.新增表情预下载功能,为后期表情支持做准备。
2023-12-12 22:52:29 +08:00
Suxue
a49f9ec9de v0.9.3.0
1.修复一处内存泄露问题。
2.修复部分用户在创建工作区时遇到“未找到适用于完成此操作的图像处理组件。”的问题
3.支持Type=10000的系统消息。
4.新增表情预下载功能,为后期表情支持做准备。
2023-12-12 22:50:30 +08:00
Suxue
eaacb554ac Merge branch 'master' into dev 2023-12-11 21:55:07 +08:00
Suxue
c996a89303 v0.9.2.0版本!
1.异步优化,现在起创建工作区,导出聊天记录,都是独立线程操作了!体验更好!并且多了相关状态提示,体验更好。
2.新增批量导出模式,左侧工作区,右键->管理就见啦!
3.修复视频、图片导出时逻辑bug
2023-12-11 16:43:11 +08:00
Suxue
eb4d4fa3fa 新增批量导出 2023-12-11 16:18:42 +08:00
Suxue
9bf5d1c7d3 1.异步创建工作区,同时创建工作区支持状态显示了
2.异步导出聊天记录,html导出平滑优化
3.支持拖拽窗体
2023-12-11 13:50:27 +08:00
Suxue
24f2962475 修复视频文件不存在时,程序异常退出的问题 2023-12-09 21:17:06 +08:00
Suxue
5475b5414f Update README.md 2023-12-08 11:25:58 +08:00
Suxue
07f58afd98 fix style 2023-12-07 15:56:49 +08:00
Suxue
f8a06fa000 优化导出,新增txt格式导出 2023-12-07 15:55:50 +08:00
Suxue
375f17fe49 优化导出按钮
新增txt导出
2023-12-07 14:56:52 +08:00
Suxue
985505b2e7 导出改造
新增txt导出
2023-12-06 23:06:54 +08:00
Suxue
9ad5f6b311 Merge branch 'master' into dev 2023-12-06 22:07:42 +08:00
Suxue
25b006b992 修复欢迎页项目名称错误 2023-12-06 22:00:10 +08:00
Suxue
6c7614dcac merge master 2023-12-06 21:58:17 +08:00
Suxue
4cb96b4449 更新url 2023-12-06 20:38:05 +08:00
Suxue
3957f2462e 更新url 2023-12-06 20:37:19 +08:00
Suxue
1929ef64fe v0.9.0.0 release! 2023-12-06 20:25:35 +08:00
Suxue
74ad9297c2 清理旧版本文件和部分方法
修复联系人列表没有显示备注的问题
2023-12-06 19:50:58 +08:00
Suxue
ffbc855a0f 新增用户数和消息数统计
群聊导出支持用户名
2023-12-06 19:32:55 +08:00
Suxue
50c3b22c74 迁移解密至WXWorkspace
解密方式选择切换MVVM
完善新界面功能
2023-12-06 18:22:22 +08:00
Suxue
d1a0f3897f update readme 2023-12-06 15:34:20 +08:00
Suxue
4b5554cbe1 update readme 2023-12-06 15:27:57 +08:00
Suxue
585e6683cd 项目更名 2023-12-06 14:13:14 +08:00
Suxue
e83d3869cc 调整部分局部变量可见性
完善新版工作区创建
群聊数据改用标准方式获取
2023-12-06 12:05:34 +08:00
Suxue
191849e9c2 1.用户头像切换到本地缓存
2.修复多工作区切换,页面不变的问题
3.WXContact切换到ObservableCollection
4.群聊名称支持
2023-12-05 14:50:32 +08:00
Suxue
486a797c5f readme update 2023-12-04 15:56:17 +08:00
Suxue
33089f8fcf fix bug 2023-12-04 15:50:13 +08:00
Suxue
d4aa12cf07 WindowsAPI调用重构,改用SYSTEM_EXTENDED_HANDLE_INFORMATION解决 2023-12-04 15:46:55 +08:00
Suxue
85b0b1db52 features:
1.workspace page 引入mvvm
2.wxcontact修改为联合模型
2023-11-30 17:24:13 +08:00
Suxue
8305a58c39 dev init 2023-11-30 09:03:49 +08:00
Suxue
de7a18a28e fix bug 2023-11-28 22:01:18 +08:00
Suxue
31c28d8111 新增用户名推定key地址,请确保新建工作区时填写当前账号
现在用户搜索支持了昵称、备注模糊搜索
增加了3.9.8.15 key地址
2023-11-15 16:40:55 +08:00
Suxue
e3cfc67dbd 1.修复新版获取不到微信的问题
2.修复导出聊天记录时,视频被移动的问题
3.标题新增版本号
2023-11-02 14:07:35 +08:00
Suxue
ba8e9f7e18 Update README.md 2023-11-01 16:25:23 +08:00
Suxue
cd9095859e 1.替换handle64,使用windows api直接获取文件句柄
2.新增了分享链接的支持
2023-11-01 16:17:07 +08:00
62 changed files with 852008 additions and 1058 deletions

View File

@@ -1,9 +1,9 @@
<Window x:Class="WechatPCMsgBakTool.Analyse"
<Window x:Class="WechatBakTool.Analyse"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WechatPCMsgBakTool"
xmlns:local="clr-namespace:WechatBakTool"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="溯雪微信备份工具-分析" Height="450" Width="900">

View File

@@ -11,9 +11,9 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using WechatPCMsgBakTool.Model;
using WechatBakTool.Model;
namespace WechatPCMsgBakTool
namespace WechatBakTool
{
/// <summary>
/// Analyse.xaml 的交互逻辑
@@ -31,10 +31,14 @@ namespace WechatPCMsgBakTool
private void btn_analyse_Click(object sender, RoutedEventArgs e)
{
List<WXContact>? contacts = UserReader.GetWXContacts();
List<WXMsgGroup> list = UserReader.GetWXMsgGroup().OrderByDescending(x => x.MsgCount).ToList();
if(contacts == null)
var tmp = UserReader.GetWXContacts();
List<WXContact> contacts;
if (tmp == null)
contacts = new List<WXContact>();
else
contacts = tmp.ToList();
List<WXMsgGroup> list = UserReader.GetWXMsgGroup().OrderByDescending(x => x.MsgCount).ToList();
foreach (WXMsgGroup item in list)
{

View File

@@ -1,8 +1,8 @@
<Application x:Class="WechatPCMsgBakTool.App"
<Application x:Class="WechatBakTool.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WechatPCMsgBakTool"
StartupUri="Main.xaml">
xmlns:local="clr-namespace:WechatBakTool"
StartupUri="Main2.xaml">
<Application.Resources>
</Application.Resources>

View File

@@ -6,7 +6,7 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace WechatPCMsgBakTool
namespace WechatBakTool
{
/// <summary>
/// Interaction logic for App.xaml

18
Export/ExportInterface.cs Normal file
View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WechatBakTool.Model;
using WechatBakTool.ViewModel;
namespace WechatBakTool.Export
{
public interface IExport
{
void InitTemplate(WXContact session,string path);
bool SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel);
void SetEnd();
void Save(string path = "");
}
}

355
Export/HtmlExport.cs Normal file
View File

@@ -0,0 +1,355 @@
using K4os.Compression.LZ4.Encoders;
using K4os.Compression.LZ4;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WechatBakTool.Model;
using System.Xml;
using Newtonsoft.Json;
using WechatBakTool.ViewModel;
using System.Security.Policy;
using System.Windows;
using System.Xml.Linq;
namespace WechatBakTool.Export
{
public class HtmlExport : IExport
{
private string HtmlBody = "";
private WXSession? Session = null;
private string Path = "";
public void InitTemplate(WXSession session)
{
Session = session;
HtmlBody = "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>WechatBakTool</title><style>p{margin:0px;}.msg{padding-bottom:10px;}.nickname{font-size:10px;}.content{font-size:14px;}</style></head><body>";
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\"><b>与 {0}({1}) 的聊天记录</b></p>", Session.NickName, Session.UserName);
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\"><b>导出时间:{0}</b></p><hr/>", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
}
public void InitTemplate(WXContact contact, string p)
{
Path = p;
WXSession session = new WXSession();
session.NickName = contact.NickName;
session.UserName = contact.UserName;
InitTemplate(session);
}
public void Save(string path = "")
{
}
public void SetEnd()
{
HtmlBody += "</body></html>";
File.AppendAllText(Path, HtmlBody);
}
public bool SetMsg(WXUserReader reader, WXContact contact,WorkspaceViewModel viewModel)
{
if (Session == null)
throw new Exception("请初始化模版Not Use InitTemplate");
List<WXMsg>? msgList = reader.GetWXMsgs(contact.UserName);
if (msgList == null)
throw new Exception("获取消息失败,请确认数据库读取正常");
if(msgList.Count == 0)
{
viewModel.ExportCount = "没有消息,忽略";
return false;
}
msgList.Sort((x, y) => x.CreateTime.CompareTo(y.CreateTime));
bool err = false;
int msgCount = 0;
StreamWriter streamWriter = new StreamWriter(Path, true);
foreach (var msg in msgList)
{
try
{
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\">{0} <span style=\"padding-left:10px;\">{1}</span></p>", msg.IsSender ? "我" : msg.NickName, TimeStampToDateTime(msg.CreateTime).ToString("yyyy-MM-dd HH:mm:ss"));
if (msg.Type == 1)
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", msg.StrContent);
else if (msg.Type == 3)
{
string? path = reader.GetAttachment(WXMsgType.Image, msg);
if (path == null)
{
#if DEBUG
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Img Error Path=>", path));
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Img Error Msg=>", JsonConvert.SerializeObject(msg)));
#endif
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "图片转换出现错误或文件不存在");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><img src=\"{0}\" style=\"max-height:1000px;max-width:1000px;\"/></p></div>", path);
}
else if (msg.Type == 43)
{
string? path = reader.GetAttachment(WXMsgType.Video, msg);
if (path == null)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "视频不存在");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><video controls style=\"max-height:300px;max-width:300px;\"><source src=\"{0}\" type=\"video/mp4\" /></video></p></div>", path);
}
else if (msg.Type == 47)
{
string? path = reader.GetAttachment(WXMsgType.Emoji, msg);
if (path == null)
{
#if DEBUG
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Emoji Error Path=>", path));
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Emoji Error Msg=>", JsonConvert.SerializeObject(msg)));
#endif
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "表情未预下载或加密表情");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><img src=\"{0}\" style=\"max-height:300px;max-width:300px;\"/></p></div>", path);
}
else if (msg.Type == 49)
{
if (msg.SubType == 6 || msg.SubType == 40)
{
string? path = reader.GetAttachment(WXMsgType.File, msg);
if (path == null)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "文件不存在");
continue;
}
else
{
HtmlBody += string.Format("<p class=\"content\">{0}</p><p><a href=\"{1}\">点击访问</a></p></div>", "文件:" + path, path);
}
}
else if (msg.SubType == 19)
{
using (var decoder = LZ4Decoder.Create(true, 64))
{
byte[] target = new byte[10240];
int res = 0;
if (msg.CompressContent != null)
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
byte[] data = target.Skip(0).Take(res).ToArray();
string xml = Encoding.UTF8.GetString(data);
if (!string.IsNullOrEmpty(xml))
{
xml = xml.Replace("\n", "");
XmlDocument xmlObj = new XmlDocument();
xmlObj.LoadXml(xml);
if (xmlObj.DocumentElement != null)
{
string title = "";
string record = "";
string url = "";
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
if (findNode != null)
{
if (findNode.Count > 0)
{
title = findNode[0]!.InnerText;
}
}
HtmlBody += string.Format("<p class=\"content\">{0}</p>", title);
try
{
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/recorditem");
if (findNode != null)
{
if (findNode.Count > 0)
{
XmlDocument itemObj = new XmlDocument();
itemObj.LoadXml(findNode[0]!.InnerText);
XmlNodeList? itemNode = itemObj.DocumentElement.SelectNodes("/recordinfo/datalist/dataitem");
if (itemNode.Count > 0)
{
foreach (XmlNode node in itemNode)
{
string nodeMsg;
string name = node["sourcename"].InnerText;
if (node.Attributes["datatype"].InnerText == "1")
nodeMsg = node["datadesc1"].InnerText;
else if (node.Attributes["datatype"].InnerText == "2")
nodeMsg = "不支持的消息";
else
nodeMsg = node["datatitle"].InnerText;
HtmlBody += string.Format("<p class=\"content\">{0}{1}</p>", name, nodeMsg);
}
}
}
}
}
catch
{
HtmlBody += string.Format("<p class=\"content\">{0}</p>", "解析异常");
}
}
}
}
}
else if (msg.SubType == 57)
{
using (var decoder = LZ4Decoder.Create(true, 64))
{
byte[] target = new byte[10240];
int res = 0;
if (msg.CompressContent != null)
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
byte[] data = target.Skip(0).Take(res).ToArray();
string xml = Encoding.UTF8.GetString(data);
if (!string.IsNullOrEmpty(xml))
{
xml = xml.Replace("\n", "");
XmlDocument xmlObj = new XmlDocument();
xmlObj.LoadXml(xml);
if (xmlObj.DocumentElement != null)
{
string title = "";
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
if (findNode != null)
{
if (findNode.Count > 0)
{
title = findNode[0]!.InnerText;
}
}
HtmlBody += string.Format("<p class=\"content\">{0}</p>", title);
XmlNode? type = xmlObj.DocumentElement.SelectSingleNode("/msg/appmsg/refermsg/type");
if(type != null)
{
XmlNode? source = xmlObj.DocumentElement.SelectSingleNode("/msg/appmsg/refermsg/displayname");
XmlNode? text = xmlObj.DocumentElement.SelectSingleNode("/msg/appmsg/refermsg/content");
if(type.InnerText == "1" && source != null && text != null)
{
HtmlBody += string.Format("<p class=\"content\">[引用]{0}:{1}</p>", source.InnerText, text.InnerText);
}
else if(type.InnerText != "1" && source != null && text != null)
{
HtmlBody += string.Format("<p class=\"content\">[引用]{0}:非文本消息类型-{1}</p>", source.InnerText, type);
}
else
{
HtmlBody += string.Format("<p class=\"content\">未知的引用消息</p>");
}
}
}
}
}
}
else
{
using (var decoder = LZ4Decoder.Create(true, 64))
{
byte[] target = new byte[10240];
int res = 0;
if (msg.CompressContent != null)
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
byte[] data = target.Skip(0).Take(res).ToArray();
string xml = Encoding.UTF8.GetString(data);
if (!string.IsNullOrEmpty(xml))
{
xml = xml.Replace("\n", "");
XmlDocument xmlObj = new XmlDocument();
xmlObj.LoadXml(xml);
if (xmlObj.DocumentElement != null)
{
string title = "";
string appName = "";
string url = "";
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
if (findNode != null)
{
if (findNode.Count > 0)
{
title = findNode[0]!.InnerText;
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/sourcedisplayname");
if (findNode != null)
{
if (findNode.Count > 0)
{
appName = findNode[0]!.InnerText;
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/url");
if (findNode != null)
{
if (findNode.Count > 0)
{
url = findNode[0]!.InnerText;
}
}
HtmlBody += string.Format("<p class=\"content\">{0}|{1}</p><p><a href=\"{2}\">点击访问</a></p></div>", appName, title, url);
}
}
}
}
}
else if (msg.Type == 34)
{
string? path = reader.GetAttachment(WXMsgType.Audio, msg);
if (path == null)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "语音不存在");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><audio controls src=\"{0}\"></audio></p></div>", path);
}
else
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "暂未支持的消息");
}
}
catch(Exception ex)
{
err = true;
File.AppendAllText("Err.log", JsonConvert.SerializeObject(msg));
File.AppendAllText("Err.log", ex.ToString());
}
msgCount++;
if(msgCount % 50 == 0)
{
streamWriter.WriteLine(HtmlBody);
HtmlBody = "";
viewModel.ExportCount = msgCount.ToString();
}
}
if(msgCount % 50 != 0)
{
streamWriter.WriteLine(HtmlBody);
HtmlBody = "";
viewModel.ExportCount = msgCount.ToString();
if (err)
{
MessageBox.Show("本次导出发生了异常部分消息被跳过更新至最新版本后还有此问题请将Err.log反馈给开发谢谢。", "错误");
}
}
streamWriter.Close();
streamWriter.Dispose();
return true;
}
private static DateTime TimeStampToDateTime(long timeStamp, bool inMilli = false)
{
DateTimeOffset dateTimeOffset = inMilli ? DateTimeOffset.FromUnixTimeMilliseconds(timeStamp) : DateTimeOffset.FromUnixTimeSeconds(timeStamp);
return dateTimeOffset.LocalDateTime;
}
}
}

158
Export/TXTExport.cs Normal file
View File

@@ -0,0 +1,158 @@
using K4os.Compression.LZ4.Encoders;
using K4os.Compression.LZ4;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using WechatBakTool.Model;
using WechatBakTool.ViewModel;
namespace WechatBakTool.Export
{
public class TXTExport : IExport
{
private WXContact? Contact { get; set; } = null;
private string Path { get; set; } = "";
public void InitTemplate(WXContact contact,string p)
{
Contact = contact;
Path = p;
if (File.Exists(Path))
{
File.WriteAllText(Path, "");
}
File.AppendAllText(Path, string.Format("WechatBakTool\n"));
File.AppendAllText(Path, string.Format("与 {0} 的聊天记录\n", Contact.NickName));
File.AppendAllText(Path, string.Format("导出时间:{0}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
File.AppendAllText(Path, string.Format("=================================================================\n\n\n"));
}
void IExport.Save(string path)
{
}
void IExport.SetEnd()
{
}
public bool SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel)
{
if (Contact == null)
throw new Exception("请初始化模版Not Use InitTemplate");
List<WXMsg>? msgList = reader.GetWXMsgs(Contact.UserName);
if (msgList == null)
throw new Exception("获取消息失败,请确认数据库读取正常");
msgList.Sort((x, y) => x.CreateTime.CompareTo(y.CreateTime));
int msgCount = 0;
foreach (var msg in msgList)
{
string txtMsg = "";
switch (msg.Type)
{
case 1:
txtMsg = msg.StrContent;
break;
case 3:
txtMsg = "[图片]";
break;
case 34:
txtMsg = "[语音]";
break;
case 43:
txtMsg = "[视频]";
break;
case 49:
if (msg.SubType == 6 || msg.SubType == 19 || msg.SubType == 40)
{
txtMsg = "[文件]";
}
else
{
try
{
using (var decoder = LZ4Decoder.Create(true, 64))
{
byte[] target = new byte[10240];
int res = 0;
if (msg.CompressContent != null)
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
byte[] data = target.Skip(0).Take(res).ToArray();
string xml = Encoding.UTF8.GetString(data);
if (!string.IsNullOrEmpty(xml))
{
xml = xml.Replace("\n", "");
XmlDocument xmlObj = new XmlDocument();
xmlObj.LoadXml(xml);
if (xmlObj.DocumentElement != null)
{
string title = "";
string appName = "";
string url = "";
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
if (findNode != null)
{
if (findNode.Count > 0)
{
title = findNode[0]!.InnerText;
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/sourcedisplayname");
if (findNode != null)
{
if (findNode.Count > 0)
{
appName = findNode[0]!.InnerText;
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/url");
if (findNode != null)
{
if (findNode.Count > 0)
{
url = findNode[0]!.InnerText;
}
}
txtMsg = string.Format("{0},标题:{1},链接:{2}", appName, title, url);
}
else
{
txtMsg = "[分享链接出错了]";
}
}
else
{
txtMsg = "[分享链接出错了]";
}
}
}
catch
{
txtMsg = "[分享链接出错了]";
}
}
break;
}
string row = string.Format("{2} | {0}:{1}\n", msg.IsSender ? "我" : msg.NickName, txtMsg, TimeStampToDateTime(msg.CreateTime).ToString("yyyy-MM-dd HH:mm:ss"));
File.AppendAllText(Path, row);
msgCount++;
viewModel.ExportCount = msgCount.ToString();
}
return true;
}
private static DateTime TimeStampToDateTime(long timeStamp, bool inMilli = false)
{
DateTimeOffset dateTimeOffset = inMilli ? DateTimeOffset.FromUnixTimeMilliseconds(timeStamp) : DateTimeOffset.FromUnixTimeSeconds(timeStamp);
return dateTimeOffset.LocalDateTime;
}
}
}

View File

@@ -5,30 +5,30 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using WechatPCMsgBakTool.Model;
using System.Windows;
using WechatBakTool.Model;
using WechatBakTool.Pages;
using WechatBakTool.ViewModel;
namespace WechatPCMsgBakTool.Helpers
namespace WechatBakTool.Helpers
{
public class DecryptionHelper
{
const int IV_SIZE = 16;
const long IV_SIZE = 16;
const int HMAC_SHA1_SIZE = 20;
const int KEY_SIZE = 32;
const int AES_BLOCK_SIZE = 16;
const int DEFAULT_ITER = 64000;
const int DEFAULT_PAGESIZE = 4096; //4048数据 + 16IV + 20 HMAC + 12
const long DEFAULT_PAGESIZE = 4096; //4048数据 + 16IV + 20 HMAC + 12
const string SQLITE_HEADER = "SQLite format 3";
public static byte[]? GetWechatKey()
public static byte[]? GetWechatKey(string pid, int find_key_type, string account)
{
Process? process = ProcessHelper.GetProcess("WeChat");
if (process == null)
{
return null;
}
Process process = Process.GetProcessById(int.Parse(pid));
ProcessModule? module = ProcessHelper.FindProcessModule(process.Id, "WeChatWin.dll");
if (module == null)
{
@@ -40,63 +40,226 @@ namespace WechatPCMsgBakTool.Helpers
return null;
}
List<VersionInfo>? info = null;
string json = File.ReadAllText("version.json");
info = JsonConvert.DeserializeObject<List<VersionInfo>?>(json);
if (info == null)
return null;
if (info.Count == 0)
return null;
VersionInfo? cur = info.Find(x => x.Version == version);
if (cur == null)
return null;
//这里加的是版本偏移量,兼容不同版本把这个加给改了
long baseAddress = (long)module.BaseAddress + cur.BaseAddr;
byte[]? bytes = ProcessHelper.ReadMemoryDate(process.Handle, (IntPtr)baseAddress, 8);
if (bytes != null)
if (find_key_type == 1)
{
IntPtr baseAddress2 = (IntPtr)(((long)bytes[7] << 56) + ((long)bytes[6] << 48) + ((long)bytes[5] << 40) + ((long)bytes[4] << 32) + ((long)bytes[3] << 24) + ((long)bytes[2] << 16) + ((long)bytes[1] << 8) + (long)bytes[0]);
byte[]? twoGet = ProcessHelper.ReadMemoryDate(process.Handle, baseAddress2, 32);
if (twoGet != null)
List<VersionInfo>? info = null;
string json = File.ReadAllText("version.json");
info = JsonConvert.DeserializeObject<List<VersionInfo>?>(json);
if (info == null)
return null;
if (info.Count == 0)
return null;
VersionInfo? cur = info.Find(x => x.Version == version);
if (cur == null)
return null;
//这里加的是版本偏移量,兼容不同版本把这个加给改了
long baseAddress = (long)module.BaseAddress + cur.BaseAddr;
byte[]? bytes = ProcessHelper.ReadMemoryDate(process.Handle, (IntPtr)baseAddress, 8);
if (bytes != null)
{
string key = BytesToHex(twoGet);
return twoGet;
IntPtr baseAddress2 = (IntPtr)(((long)bytes[7] << 56) + ((long)bytes[6] << 48) + ((long)bytes[5] << 40) + ((long)bytes[4] << 32) + ((long)bytes[3] << 24) + ((long)bytes[2] << 16) + ((long)bytes[1] << 8) + (long)bytes[0]);
byte[]? twoGet = ProcessHelper.ReadMemoryDate(process.Handle, baseAddress2, 32);
if (twoGet != null)
{
string key = BytesToHex(twoGet);
return twoGet;
}
}
}
else if(find_key_type == 2)
{
List<int> read = ProcessHelper.FindProcessMemory(process.Handle, module, account);
if (read.Count >= 2)
{
byte[] buffer = new byte[8];
int key_offset = read[1] - 64;
if (NativeAPI.ReadProcessMemory(process.Handle, module.BaseAddress + key_offset, buffer, buffer.Length, out _))
{
ulong addr = BitConverter.ToUInt64(buffer, 0);
byte[] key_bytes = new byte[32];
if (NativeAPI.ReadProcessMemory(process.Handle, (IntPtr)addr, key_bytes, key_bytes.Length, out _))
{
return key_bytes;
}
}
}
}
else if (find_key_type == 3)
{
string searchString = "-----BEGIN PUBLIC KEY-----";
List<long> addr = NativeAPIHelper.SearchProcessAllMemory(process, searchString);
if (addr.Count > 0)
{
foreach (long a in addr)
{
byte[] buffer = new byte[module.ModuleMemorySize];
byte[] search = BitConverter.GetBytes(a);
Array.Resize(ref search, 8);
int read = 0;
List<int> offset = new List<int>();
if (NativeAPI.ReadProcessMemory(process.Handle, module.BaseAddress, buffer, buffer.Length, out read))
{
for (int i = 0; i < buffer.Length - 1; i++)
{
if (buffer[i] == search[0])
{
for (int s = 1; s < search.Length; s++)
{
if (buffer[i + s] != search[s])
break;
if (s == search.Length - 1)
{
long iii = (long)module.BaseAddress + i - 0xd8;
byte[] key = new byte[8];
if (NativeAPI.ReadProcessMemory(process.Handle, new IntPtr(iii), key, key.Length, out _))
{
ulong key_addr = BitConverter.ToUInt64(key, 0);
byte[] key_bytes = new byte[32];
NativeAPI.ReadProcessMemory(process.Handle, (IntPtr)key_addr, key_bytes, key_bytes.Length, out _);
string key1 = BitConverter.ToString(key_bytes, 0);
return key_bytes;
}
}
}
}
}
}
}
}
else
{
throw new Exception("搜索不到微信账号,请确认用户名是否正确,如错误请重新新建工作区,务必确认账号是否正确");
}
}
else if (find_key_type == 3)
{
string searchString = "-----BEGIN PUBLIC KEY-----";
}
return null;
}
public static byte[] DecryptDB(byte[] db_file_bytes, byte[] password_bytes)
public static string GetMD5(string text)
{
MD5 md5 = MD5.Create();
byte[] bs = Encoding.UTF8.GetBytes(text);
byte[] hs = md5.ComputeHash(bs);
StringBuilder sb = new StringBuilder();
foreach(byte b in hs)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
public static void DecryptDB(string file, string to_file, byte[] password_bytes)
{
//数据库头16字节是盐值
var salt = db_file_bytes.Take(16).ToArray();
byte[] salt_key = new byte[16];
FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
fileStream.Read(salt_key, 0, 16);
//HMAC验证时用的盐值需要亦或0x3a
byte[] hmac_salt = new byte[16];
for (int i = 0; i < salt.Length; i++)
for (int i = 0; i < salt_key.Length; i++)
{
hmac_salt[i] = (byte)(salt[i] ^ 0x3a);
hmac_salt[i] = (byte)(salt_key[i] ^ 0x3a);
}
//计算保留段长度
int reserved = IV_SIZE;
long reserved = IV_SIZE;
reserved += HMAC_SHA1_SIZE;
reserved = ((reserved % AES_BLOCK_SIZE) == 0) ? reserved : ((reserved / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE;
//密钥扩展分别对应AES解密密钥和HMAC验证密钥
byte[] key = new byte[KEY_SIZE];
byte[] hmac_key = new byte[KEY_SIZE];
OpenSSLInterop.PKCS5_PBKDF2_HMAC_SHA1(password_bytes, password_bytes.Length, salt, salt.Length, DEFAULT_ITER, key.Length, key);
OpenSSLInterop.PKCS5_PBKDF2_HMAC_SHA1(password_bytes, password_bytes.Length, salt_key, salt_key.Length, DEFAULT_ITER, key.Length, key);
OpenSSLInterop.PKCS5_PBKDF2_HMAC_SHA1(key, key.Length, hmac_salt, hmac_salt.Length, 2, hmac_key.Length, hmac_key);
int page_no = 0;
int offset = 16;
long page_no = 0;
long offset = 16;
Console.WriteLine("开始解密...");
var hmac_sha1 = HMAC.Create("HMACSHA1");
hmac_sha1!.Key = hmac_key;
List<byte> decrypted_file_bytes = new List<byte>();
while (page_no < db_file_bytes.Length / DEFAULT_PAGESIZE)
FileStream tofileStream = new FileStream(to_file, FileMode.OpenOrCreate, FileAccess.Write);
using (fileStream)
{
try
{
// 当前分页小于计算分页数
while (page_no < fileStream.Length / DEFAULT_PAGESIZE)
{
// 读内容
byte[] decryped_page_bytes = new byte[DEFAULT_PAGESIZE];
byte[] going_to_hashed = new byte[DEFAULT_PAGESIZE - reserved - offset + IV_SIZE + 4];
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + offset, SeekOrigin.Begin);
fileStream.Read(going_to_hashed, 0, Convert.ToInt32(DEFAULT_PAGESIZE - reserved - offset + IV_SIZE));
// 分页标志
var page_bytes = BitConverter.GetBytes(page_no + 1);
page_bytes.CopyTo(going_to_hashed, DEFAULT_PAGESIZE - reserved - offset + IV_SIZE);
var hash_mac_compute = hmac_sha1.ComputeHash(going_to_hashed, 0, going_to_hashed.Length);
// 取分页hash
byte[] hash_mac_cached = new byte[hash_mac_compute.Length];
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + DEFAULT_PAGESIZE - reserved + IV_SIZE, SeekOrigin.Begin);
fileStream.Read(hash_mac_cached, 0, hash_mac_compute.Length);
if (!hash_mac_compute.SequenceEqual(hash_mac_cached) && page_no == 0)
{
Console.WriteLine("Hash错误...");
return;
}
else
{
if (page_no == 0)
{
var header_bytes = Encoding.ASCII.GetBytes(SQLITE_HEADER);
header_bytes.CopyTo(decryped_page_bytes, 0);
}
// 加密内容
byte[] page_content = new byte[DEFAULT_PAGESIZE - reserved - offset];
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + offset, SeekOrigin.Begin);
fileStream.Read(page_content, 0, Convert.ToInt32(DEFAULT_PAGESIZE - reserved - offset));
// iv
byte[] iv = new byte[16];
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + (DEFAULT_PAGESIZE - reserved), SeekOrigin.Begin);
fileStream.Read(iv, 0, 16);
var decrypted_content = AESDecrypt(page_content, key, iv);
decrypted_content.CopyTo(decryped_page_bytes, offset);
// 保留
byte[] reserved_byte = new byte[reserved];
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + DEFAULT_PAGESIZE - reserved, SeekOrigin.Begin);
fileStream.Read(reserved_byte, 0, Convert.ToInt32(reserved));
reserved_byte.CopyTo(decryped_page_bytes, DEFAULT_PAGESIZE - reserved);
tofileStream.Write(decryped_page_bytes, 0, decryped_page_bytes.Length);
}
page_no++;
offset = 0;
}
}catch(Exception ex)
{
File.AppendAllText("err.log", "page=>" + page_no.ToString() + "\r\n");
File.AppendAllText("err.log", "size=>" + fileStream.Length.ToString() + "\r\n");
File.AppendAllText("err.log", "postion=>" + ((page_no * DEFAULT_PAGESIZE) + offset).ToString() + "\r\n");
File.AppendAllText("err.log", ex.ToString() + "\r\n");
}
}
/*
* 旧版解密
while (page_no < fileStream.Length / DEFAULT_PAGESIZE)
{
byte[] decryped_page_bytes = new byte[DEFAULT_PAGESIZE];
byte[] going_to_hashed = new byte[DEFAULT_PAGESIZE - reserved - offset + IV_SIZE + 4];
@@ -104,8 +267,7 @@ namespace WechatPCMsgBakTool.Helpers
var page_bytes = BitConverter.GetBytes(page_no + 1);
page_bytes.CopyTo(going_to_hashed, DEFAULT_PAGESIZE - reserved - offset + IV_SIZE);
//计算分页的Hash
var hash_mac_compute = hmac_sha1.ComputeHash(going_to_hashed, 0, going_to_hashed.Count());
//取出分页中存储的Hash
var hash_mac_compute = hmac_sha1.ComputeHash(going_to_hashed, 0, going_to_hashed.Length);
var hash_mac_cached = db_file_bytes.Skip((page_no * DEFAULT_PAGESIZE) + DEFAULT_PAGESIZE - reserved + IV_SIZE).Take(hash_mac_compute.Length).ToArray();
//对比两个Hash
if (!hash_mac_compute.SequenceEqual(hash_mac_cached))
@@ -134,8 +296,9 @@ namespace WechatPCMsgBakTool.Helpers
{
decrypted_file_bytes.Add(item);
}
}
return decrypted_file_bytes.ToArray();
}*/
tofileStream.Close();
tofileStream.Dispose();
}
public static byte[] AESDecrypt(byte[] content, byte[] key, byte[] iv)
{
@@ -154,99 +317,50 @@ namespace WechatPCMsgBakTool.Helpers
{
return BitConverter.ToString(bytes, 0).Replace("-", string.Empty).ToLower().ToUpper();
}
private readonly static List<byte[]> ImgHeader = new List<byte[]>()
{
new byte[] { 0xFF, 0xD8 },//JPG
new byte[] { 0x89, 0x50 },//PNG
new byte[] { 0x42, 0x4D },//BMP
new byte[] { 0x47, 0x49 },//GIF
new byte[] { 0x49, 0x49 },//TIF
new byte[] { 0x4D, 0x4D },//TIF
};
public static byte[] DecImage(string source)
{
//读取数据
byte[] fileBytes = File.ReadAllBytes(source);
//算差异转换
byte key = GetImgKey(fileBytes);
fileBytes = ConvertData(fileBytes, key);
return fileBytes;
foreach (byte[] b in ImgHeader)
{
byte t = (byte)(fileBytes[0] ^ b[0]);
byte[] decData = fileBytes.Select(b => (byte)(b ^ t)).ToArray();
if (b[1] != decData[1])
continue;
else
{
return decData;
}
}
return new byte[0];
}
public static string CheckFileType(byte[] data)
{
switch (data[0])
{
case 0XFF: //byte[] jpg = new byte[] { 0xFF, 0xD8, 0xFF };
{
if (data[1] == 0xD8 && data[2] == 0xFF)
{
return ".jpg";
}
break;
}
case 0x89: //byte[] png = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
{
if (data[1] == 0x50 && data[2] == 0x4E && data[7] == 0x0A)
{
return ".png";
}
break;
}
case 0x42: //byte[] bmp = new byte[] { 0x42, 0x4D };
{
if (data[1] == 0X4D)
{
return ".bmp";
}
break;
}
case 0x47: //byte[] gif = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39(0x37), 0x61 };
{
if (data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x38 && data[5] == 0x61)
{
return ".gif";
}
break;
}
case 0x49: // byte[] tif = new byte[] { 0x49, 0x49, 0x2A, 0x00 };
{
if (data[1] == 0x49 && data[2] == 0x2A && data[3] == 0x00)
{
return ".tif";
}
break;
}
case 0x4D: //byte[] tif = new byte[] { 0x4D, 0x4D, 0x2A, 0x00 };
{
if (data[1] == 0x4D && data[2] == 0x2A && data[3] == 0x00)
{
return ".tif";
}
break;
}
}
return ".dat";
}
private static byte GetImgKey(byte[] fileRaw)
{
byte[] raw = new byte[8];
for (int i = 0; i < 8; i++)
{
raw[i] = fileRaw[i];
}
for (byte key = 0x01; key < 0xFF; key++)
{
byte[] buf = new byte[8];
raw.CopyTo(buf, 0);
if (CheckFileType(ConvertData(buf, key)) != ".dat")
{
return key;
}
}
return 0x00;
}
private static byte[] ConvertData(byte[] data, byte key)
{
for (int i = 0; i < data.Length; i++)
{
data[i] ^= key;
}
return data;
if (data[0] == 0xFF && data[1] == 0xD8)
return ".jpg";
else if (data[0] == 0x89 && data[1] == 0x50)
return ".png";
else if (data[0] == 0x42 && data[1] == 0X4D)
return ".bmp";
else if (data[0] == 0x47 && data[1] == 0x49)
return ".gif";
else if (data[0] == 0x49 && data[1] == 0x49)
return ".tif";
else if (data[0] == 0x4D && data[1] == 0x4D)
return ".tif";
else
return ".dat";
}
public static string SaveDecImage(byte[] fileRaw,string source,string to_dir,string type)
{
@@ -260,6 +374,22 @@ namespace WechatPCMsgBakTool.Helpers
}
return saveFilePath;
}
public static void DecryUserData(byte[] key, string source, string to,CreateWorkViewModel viewModel)
{
string dbPath = source;
string decPath = to;
if (!Directory.Exists(decPath))
Directory.CreateDirectory(decPath);
string[] filePath = Directory.GetFiles(dbPath);
foreach (string file in filePath)
{
FileInfo info = new FileInfo(file);
viewModel.LabelStatus = "正在解密" + info.Name;
string to_file = Path.Combine(decPath, info.Name);
DecryptDB(file,to_file, key);
}
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace WechatBakTool.Helpers
{
public static class DevicePathMapper
{
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
private static extern uint QueryDosDevice([In] string lpDeviceName, [Out] StringBuilder lpTargetPath, [In] int ucchMax);
public static string FromDevicePath(string devicePath)
{
var drive = Array.Find(
DriveInfo.GetDrives(), d =>
devicePath.StartsWith(d.GetDevicePath() + "\\", StringComparison.InvariantCultureIgnoreCase)
);
return drive != null ?
devicePath.ReplaceFirst(drive.GetDevicePath(), drive.GetDriveLetter()) :
null;
}
private static string GetDevicePath(this DriveInfo driveInfo)
{
var devicePathBuilder = new StringBuilder(128);
return QueryDosDevice(driveInfo.GetDriveLetter(), devicePathBuilder, devicePathBuilder.Capacity + 1) != 0 ?
devicePathBuilder.ToString() :
null;
}
private static string GetDriveLetter(this DriveInfo driveInfo)
{
return driveInfo.Name.Substring(0, 2);
}
private static string ReplaceFirst(this string text, string search, string replace)
{
int pos = text.IndexOf(search);
if (pos < 0)
{
return text;
}
return text.Substring(0, pos) + replace + text.Substring(pos + search.Length);
}
}
}

239
Helpers/NativeAPI.cs Normal file
View File

@@ -0,0 +1,239 @@
using System;
using System.Runtime.InteropServices;
namespace WechatBakTool.Helpers
{
public class NativeAPI
{
// Constants
//=================================================
internal static uint NTSTATUS_STATUS_SUCCESS = 0x0;
internal static uint NTSTATUS_STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
internal static uint NTSTATUS_STATUS_ACCESS_DENIED = 0xC0000022;
internal static uint MEM_COMMIT = 0x1000;
internal static uint PAGE_READONLY = 0x02;
internal static uint PAGE_READWRITE = 0x04;
internal static uint PAGE_EXECUTE = 0x10;
internal static uint PAGE_EXECUTE_READ = 0x20;
// API Constants
internal static uint SystemExtendedHandleInformation = 0x40;
internal static uint DUPLICATE_SAME_ACCESS = 0x2;
// Structs
//=================================================
[StructLayout(LayoutKind.Sequential)]
internal struct OBJECT_NAME_INFORMATION
{
public UNICODE_STRING Name;
}
[StructLayout(LayoutKind.Sequential)]
internal struct OSVERSIONINFOEX
{
public uint OSVersionInfoSize;
public uint MajorVersion;
public uint MinorVersion;
public uint BuildNumber;
public uint PlatformId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string CSDVersion;
public ushort ServicePackMajor;
public ushort ServicePackMinor;
public ushort SuiteMask;
public byte ProductType;
public byte Reserved;
}
[StructLayout(LayoutKind.Sequential)]
internal struct UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public IntPtr Buffer;
}
[StructLayout(LayoutKind.Sequential)]
internal struct GENERIC_MAPPING
{
public uint GenericRead;
public uint GenericWrite;
public uint GenericExecute;
public uint GenericAll;
}
[StructLayout(LayoutKind.Sequential)]
internal struct OBJECT_TYPE_INFORMATION
{
public UNICODE_STRING TypeName;
public uint TotalNumberOfObjects;
public uint TotalNumberOfHandles;
public uint TotalPagedPoolUsage;
public uint TotalNonPagedPoolUsage;
public uint TotalNamePoolUsage;
public uint TotalHandleTableUsage;
public uint HighWaterNumberOfObjects;
public uint HighWaterNumberOfHandles;
public uint HighWaterPagedPoolUsage;
public uint HighWaterNonPagedPoolUsage;
public uint HighWaterNamePoolUsage;
public uint HighWaterHandleTableUsage;
public uint InvalidAttributes;
public GENERIC_MAPPING GenericMapping;
public uint ValidAccessMask;
public byte SecurityRequired;
public byte MaintainHandleCount;
public byte TypeIndex;
public byte ReservedByte;
public uint PoolType;
public uint DefaultPagedPoolCharge;
public uint DefaultNonPagedPoolCharge;
}
[StructLayout(LayoutKind.Sequential)]
internal struct OBJECT_ALL_TYPES_INFORMATION
{
public uint NumberOfObjectTypes;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SYSTEM_HANDLE_INFORMATION_EX
{
public IntPtr NumberOfHandles;
public IntPtr Reserved;
public SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX[] Handles;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
{
public IntPtr Object;
public IntPtr UniqueProcessId;
public IntPtr HandleValue;
public uint GrantedAccess;
public ushort CreatorBackTraceIndex;
public ushort ObjectTypeIndex;
public uint HandleAttributes;
public uint Reserved;
}
public struct MEMORY_BASIC_INFORMATION64
{
public IntPtr BaseAddress;
public IntPtr AllocationBase;
public uint AllocationProtect;
public uint __alignment1;
public ulong RegionSize;
public uint State;
public uint Protect;
public uint Type;
public uint __alignment2;
}
// Enums
//=================================================
internal enum OBJECT_INFORMATION_CLASS
{
ObjectBasicInformation = 0,
ObjectNameInformation = 1,
ObjectTypeInformation = 2,
ObjectAllTypesInformation = 3,
ObjectHandleInformation = 4
}
internal enum POOL_TYPE
{
NonPagedPool,
NonPagedPoolExecute = NonPagedPool,
PagedPool,
NonPagedPoolMustSucceed = NonPagedPool + 2,
DontUseThisType,
NonPagedPoolCacheAligned = NonPagedPool + 4,
PagedPoolCacheAligned,
NonPagedPoolCacheAlignedMustS = NonPagedPool + 6,
MaxPoolType,
NonPagedPoolBase = 0,
NonPagedPoolBaseMustSucceed = NonPagedPoolBase + 2,
NonPagedPoolBaseCacheAligned = NonPagedPoolBase + 4,
NonPagedPoolBaseCacheAlignedMustS = NonPagedPoolBase + 6,
NonPagedPoolSession = 32,
PagedPoolSession = NonPagedPoolSession + 1,
NonPagedPoolMustSucceedSession = PagedPoolSession + 1,
DontUseThisTypeSession = NonPagedPoolMustSucceedSession + 1,
NonPagedPoolCacheAlignedSession = DontUseThisTypeSession + 1,
PagedPoolCacheAlignedSession = NonPagedPoolCacheAlignedSession + 1,
NonPagedPoolCacheAlignedMustSSession = PagedPoolCacheAlignedSession + 1,
NonPagedPoolNx = 512,
NonPagedPoolNxCacheAligned = NonPagedPoolNx + 4,
NonPagedPoolSessionNx = NonPagedPoolNx + 32,
}
internal enum PROCESS_ACCESS_FLAGS : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000
}
// API
//=================================================
[DllImport("kernel32.dll")]
internal static extern bool CloseHandle(IntPtr hObject);
[DllImport("ntdll.dll")]
internal static extern uint RtlGetVersion(
ref OSVERSIONINFOEX VersionInformation);
[DllImport("ntdll.dll")]
internal static extern void RtlZeroMemory(
IntPtr Destination,
uint length);
[DllImport("ntdll.dll")]
internal static extern uint NtQueryObject(
IntPtr objectHandle,
OBJECT_INFORMATION_CLASS informationClass,
IntPtr informationPtr,
uint informationLength,
ref uint returnLength);
[DllImport("ntdll.dll")]
internal static extern uint NtQuerySystemInformation(
uint SystemInformationClass,
IntPtr SystemInformation,
uint SystemInformationLength,
ref uint ReturnLength);
[DllImport("kernel32.dll")]
internal static extern IntPtr OpenProcess(PROCESS_ACCESS_FLAGS dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll")]
internal static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION64 lpBuffer, uint dwLength);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead);
}
}

200
Helpers/NativeAPIHelper.cs Normal file
View File

@@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static WechatBakTool.Helpers.NativeAPI;
namespace WechatBakTool.Helpers
{
public class NativeAPIHelper
{
// Managed native buffer
internal static IntPtr AllocManagedMemory(uint iSize)
{
IntPtr pAlloc = Marshal.AllocHGlobal((int)iSize);
RtlZeroMemory(pAlloc, iSize);
return pAlloc;
}
// Free managed buffer
internal static bool FreeManagedMemory(IntPtr pAlloc)
{
Marshal.FreeHGlobal(pAlloc);
return true;
}
// Get an array of OBJECT_ALL_TYPES_INFORMATION, describing all object types
// Win8+ only
internal static string FindHandleName(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX systemHandleInformation, Process process)
{
IntPtr ipHandle = IntPtr.Zero;
IntPtr openProcessHandle = IntPtr.Zero;
IntPtr hObjectName = IntPtr.Zero;
try
{
PROCESS_ACCESS_FLAGS flags = PROCESS_ACCESS_FLAGS.DupHandle | PROCESS_ACCESS_FLAGS.VMRead;
openProcessHandle = OpenProcess(flags, false, process.Id);
// 通过 DuplicateHandle 访问句柄
if (!DuplicateHandle(openProcessHandle, systemHandleInformation.HandleValue, GetCurrentProcess(), out ipHandle, 0, false, DUPLICATE_SAME_ACCESS))
{
return "";
}
uint nLength = 0;
hObjectName = AllocManagedMemory(256 * 1024);
Task.Run(() =>
{
// 查询句柄名称
while (NtQueryObject(ipHandle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, hObjectName, nLength, ref nLength) == NTSTATUS_STATUS_INFO_LENGTH_MISMATCH)
{
FreeManagedMemory(hObjectName);
if (nLength == 0)
{
Console.WriteLine("Length returned at zero!");
}
hObjectName = AllocManagedMemory(nLength);
}
}).Wait(100);
OBJECT_NAME_INFORMATION? objObjectName = new OBJECT_NAME_INFORMATION();
objObjectName = Marshal.PtrToStructure(hObjectName, objObjectName.GetType()) as OBJECT_NAME_INFORMATION?;
if (objObjectName == null)
return "";
if (objObjectName.Value.Name.Buffer != IntPtr.Zero)
{
string? strObjectName = Marshal.PtrToStringUni(objObjectName.Value.Name.Buffer);
if (strObjectName != null)
return strObjectName;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
FreeManagedMemory(hObjectName);
CloseHandle(ipHandle);
CloseHandle(openProcessHandle);
}
return "";
}
internal static List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> GetHandleInfoForPID(uint ProcId)
{
// Create return object
List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> ltei = new List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX>();
// Create Buffer variable
IntPtr BuffPtr = IntPtr.Zero;
// Loop till success
uint LoopSize = 0;
while (true)
{
BuffPtr = AllocManagedMemory(LoopSize);
uint SystemInformationLength = 0;
uint CallRes = NtQuerySystemInformation(SystemExtendedHandleInformation, BuffPtr, LoopSize, ref SystemInformationLength);
if (CallRes == NTSTATUS_STATUS_INFO_LENGTH_MISMATCH)
{
FreeManagedMemory(BuffPtr);
LoopSize = Math.Max(LoopSize, SystemInformationLength);
}
else if (CallRes == NTSTATUS_STATUS_SUCCESS)
{
break;
}
else if (CallRes == NTSTATUS_STATUS_ACCESS_DENIED)
{
FreeManagedMemory(BuffPtr);
throw new AccessViolationException("[!] Failed to query SystemExtendedHandleInformation: Access Denied");
}
else
{
FreeManagedMemory(BuffPtr);
throw new InvalidOperationException("[!] Failed to query SystemExtendedHandleInformation.");
}
}
// Read handle count
Int32 HandleCount = Marshal.ReadInt32(BuffPtr);
// Move Buff ptr
BuffPtr = (IntPtr)(BuffPtr.ToInt64() + (IntPtr.Size * 2));
// Loop handles
for (int i = 0; i < HandleCount; i++)
{
ulong iCurrProcId = (ulong)Marshal.ReadIntPtr((IntPtr)(BuffPtr.ToInt64() + IntPtr.Size));
if (ProcId == iCurrProcId)
{
// Ptr -> SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX? tei = (SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX?)Marshal.PtrToStructure(BuffPtr, typeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX));
if (tei == null)
continue;
else
ltei.Add(tei.Value);
}
// Move Buffptr
BuffPtr = (IntPtr)(BuffPtr.ToInt64() + Marshal.SizeOf(typeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX)));
}
// Return list
return ltei;
}
public static List<long> SearchProcessAllMemory(Process process, string searchString)
{
IntPtr minAddress = IntPtr.Zero;
IntPtr maxAddress = IntPtr.MaxValue;
List<long> addrList = new List<long>();
while (minAddress.ToInt64() < maxAddress.ToInt64())
{
MEMORY_BASIC_INFORMATION64 memInfo;
int result = VirtualQueryEx(process.Handle, minAddress, out memInfo, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION64)));
if (result == 0)
{
break;
}
if (memInfo.State == MEM_COMMIT && (memInfo.Protect == PAGE_EXECUTE || memInfo.Protect == PAGE_EXECUTE_READ || memInfo.Protect == PAGE_EXECUTE_READ || memInfo.Protect == PAGE_READWRITE || memInfo.Protect == PAGE_READONLY))
{
byte[] buffer = new byte[(long)memInfo.RegionSize];
bool success = ReadProcessMemory(process.Handle, memInfo.BaseAddress, buffer, buffer.Length, out _);
if (success)
{
byte[] search = Encoding.ASCII.GetBytes(searchString);
for (int i = 0; i < buffer.Length - 8; i++)
{
if (buffer[i] == search[0])
{
for (int s = 1; s < search.Length; s++)
{
if (buffer[i + s] != search[s])
break;
if (s == search.Length - 1)
{
addrList.Add((long)memInfo.BaseAddress + i);
}
}
}
}
}
}
minAddress = new IntPtr(memInfo.BaseAddress.ToInt64() + (long)memInfo.RegionSize);
}
return addrList;
}
}
}

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace WechatPCMsgBakTool.Helpers
namespace WechatBakTool.Helpers
{
public class OpenSSLInterop
{

View File

@@ -1,37 +1,18 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace WechatPCMsgBakTool.Helpers
namespace WechatBakTool.Helpers
{
public class ProcessHelper
{
public static Process? GetProcess(string ProcessName)
{
Process[] processes = Process.GetProcessesByName(ProcessName);
if (processes.Length == 0)
return null;
else if(processes.Length > 1) {
SelectWechat selectWechat = new SelectWechat();
MessageBox.Show("检测到有多个微信,请选择本工作区对应的微信");
selectWechat.ShowDialog();
if (selectWechat.SelectProcess == null)
return null;
Process? p = processes.ToList().Find(x => x.Id.ToString() == selectWechat.SelectProcess.ProcessId);
if (p == null)
return null;
return p;
}
else
return processes[0];
}
public static ProcessModule? FindProcessModule(int ProcessId, string ModuleName)
{
Process process = Process.GetProcessById(ProcessId);
@@ -43,16 +24,53 @@ namespace WechatPCMsgBakTool.Helpers
return null;
}
public static List<int> FindProcessMemory(IntPtr processHandle, ProcessModule module, string content)
{
byte[] buffer = new byte[module.ModuleMemorySize];
byte[] search = Encoding.ASCII.GetBytes(content);
// 逐页读取数据
List<int> offset = new List<int>();
int readBytes;
bool success = NativeAPI.ReadProcessMemory(processHandle, module.BaseAddress, buffer, buffer.Length,out readBytes);
if (!success || readBytes == 0)
{
int error = Marshal.GetLastWin32Error();
Console.WriteLine($"ReadProcessMemory failed. GetLastError: {error}");
}
else
{
for (int i = 0; i < buffer.Length; i++)
{
if (buffer[i] == search[0])
{
for (int s = 1; s < search.Length; s++)
{
if (buffer[i + s] != search[s])
break;
if (s == search.Length - 1)
offset.Add(i);
}
}
}
}
return offset;
}
// 这里开始下面是对Windows API引用声明
public static byte[]? ReadMemoryDate(IntPtr hProcess, IntPtr lpBaseAddress, int nSize = 100)
{
byte[] array = new byte[nSize];
if (ReadProcessMemory(hProcess, lpBaseAddress, array, nSize, 0) == 0)
int readByte;
if (!NativeAPI.ReadProcessMemory(hProcess, lpBaseAddress, array, nSize, out readByte))
return null;
else
return array;
}
[DllImport("kernel32.dll")]
public static extern int ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, int lpNumberOfBytesRead);
}
}

View File

@@ -7,11 +7,11 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WechatPCMsgBakTool.Helpers
namespace WechatBakTool.Helpers
{
public class ToolsHelper
{
public static TaskFactory factory = new TaskFactory(new LimitedConcurrencyLevelTaskScheduler(10));
private static TaskFactory factory = new TaskFactory(new LimitedConcurrencyLevelTaskScheduler(10));
public static string DecodeVoice(string source,string pcm,string to)
{
string ffmpeg = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Tools", "ffmpeg.exe");
@@ -45,7 +45,7 @@ namespace WechatPCMsgBakTool.Helpers
}
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
partial class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
// Indicates whether the current thread is processing work items.
[ThreadStatic]

View File

@@ -1,185 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using WechatPCMsgBakTool.Model;
namespace WechatPCMsgBakTool.Helpers
{
public class WechatDBHelper
{
private static string ResPath = "";
private static string CurrentPath = AppDomain.CurrentDomain.BaseDirectory;
private static string UserWorkPath = "";
private static int MaxMediaDBCount = 0;
private static int MaxMsgDBCount = 0;
public static DBInfo GetDBInfo()
{
return new DBInfo() { MaxMediaDBCount = MaxMediaDBCount, MaxMsgDBCount = MaxMsgDBCount, UserPath = UserWorkPath, ResPath = ResPath };
}
public static DBInfo GetDBinfoOnLocal(string path)
{
string md5 = GetMd5Hash(path);
string tmpPath = Path.Combine(CurrentPath, md5);
string decPath = Path.Combine(tmpPath, "DecDB");
string[] files = Directory.GetFiles(decPath);
int media = 0;
int msg = 0;
foreach(string file in files)
{
FileInfo fileInfo = new FileInfo(file);
if(fileInfo.Extension == ".db")
{
string name = fileInfo.Name.Replace(".db", "");
if(name.Substring(0,3) == "MSG")
{
name = name.Replace("MSG", "");
int currentDB = int.Parse(name);
if(currentDB > msg)
msg = currentDB;
continue;
}
if(name.Substring(0,8)== "MediaMSG")
{
name = name.Replace("MediaMSG", "");
int currentDB = int.Parse(name);
if (currentDB > media)
media = currentDB;
continue;
}
}
}
return new DBInfo() { MaxMediaDBCount = media, MaxMsgDBCount = msg, UserPath = tmpPath, ResPath = path };
}
public static void CreateUserWorkPath(string path)
{
ResPath = path;
string md5 = GetMd5Hash(path);
string tmpPath = Path.Combine(CurrentPath, md5);
if (!Directory.Exists(tmpPath))
{
Directory.CreateDirectory(tmpPath);
}
UserWorkPath = tmpPath;
}
public static string MoveUserData(string path)
{
if(UserWorkPath != "")
{
//创建db库
string db = Path.Combine(UserWorkPath, "DB");
if (!Directory.Exists(db))
{
Directory.CreateDirectory(db);
}
//核心数据库查找
List<string> dbPathArray = new List<string>();
string userDBPath = Path.Combine(path, "Msg");
if (!Directory.Exists(userDBPath))
return "用户目录不存在,创建失败";
string mainDB = Path.Combine(userDBPath, "MicroMsg.db");
if (!File.Exists(mainDB))
return "微信主数据库不存在,创建失败";
else
dbPathArray.Add(mainDB);
string actDB = Path.Combine(userDBPath, "MultiSearchChatMsg.db");
if(!File.Exists(actDB))
return "微信附件数据库不存在,创建失败";
else
dbPathArray.Add(actDB);
string dbmsg = Path.Combine(userDBPath, "Multi");
bool mediaDBExists = false;
bool msgDBExists = false;
for(int i = 0; i < 100; i++)
{
string mediaDBPath = Path.Combine(dbmsg, string.Format("MediaMSG{0}.db", i.ToString()));
string msgDBPath = Path.Combine(dbmsg, string.Format("MSG{0}.db", i.ToString()));
mediaDBExists = File.Exists(mediaDBPath);
msgDBExists = File.Exists(msgDBPath);
if (i == 0 && !mediaDBExists && !msgDBExists)
{
return "微信聊天记录数据不存在,创建失败";
}
if(mediaDBExists)
dbPathArray.Add(mediaDBPath);
if (msgDBExists)
dbPathArray.Add(msgDBPath);
if (!msgDBExists && !msgDBExists)
break;
}
foreach(string dbPath in dbPathArray) {
FileInfo file = new FileInfo(dbPath);
string to = Path.Combine(db, file.Name);
if(!File.Exists(to))
File.Copy(dbPath, to);
}
return "";
}
return "请复制目录至文本框内";
}
public static void DecryUserData(byte[] key,string source,string to)
{
string dbPath = source;
string decPath = to;
if(!Directory.Exists(decPath))
Directory.CreateDirectory(decPath);
string[] filePath = Directory.GetFiles(dbPath);
foreach (string file in filePath)
{
FileInfo info = new FileInfo(file);
var db_bytes = File.ReadAllBytes(file);
var decrypted_file_bytes = DecryptionHelper.DecryptDB(db_bytes, key);
if (decrypted_file_bytes == null || decrypted_file_bytes.Length == 0)
{
Console.WriteLine("解密后的数组为空");
}
else
{
File.WriteAllBytes(Path.Combine(decPath, info.Name), decrypted_file_bytes);
}
}
}
private static string GetMd5Hash(string input)
{
using (MD5 md5Hash = MD5.Create())
{
// Convert the input string to a byte array and compute the hash.
byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
// Create a new Stringbuilder to collect the bytes
// and create a string.
StringBuilder sBuilder = new StringBuilder();
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
for (int i = 0; i < data.Length; i++)
{
sBuilder.Append(data[i].ToString("x2"));
}
// Return the hexadecimal string.
return sBuilder.ToString();
}
}
}
}

View File

@@ -1,112 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WechatPCMsgBakTool.Interface;
using WechatPCMsgBakTool.Model;
namespace WechatPCMsgBakTool
{
public class HtmlExport : IExport
{
private string HtmlBody = "";
private WXSession? Session = null;
public void InitTemplate(WXSession session)
{
Session = session;
HtmlBody = "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>溯雪微信聊天记录备份工具</title><style>p{margin:0px;}.msg{padding-bottom:10px;}.nickname{font-size:10px;}.content{font-size:14px;}</style></head><body>";
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\"><b>与 {0}({1}) 的聊天记录</b></p>", Session.NickName, Session.UserName);
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\"><b>导出时间:{0}</b></p><hr/>", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
}
public void InitTemplate(WXContact contact)
{
WXSession session = new WXSession();
session.NickName = contact.NickName;
session.UserName = contact.UserName;
InitTemplate(session);
}
public void Save(string path = "",bool append = false)
{
if (!append)
{
File.WriteAllText(path, HtmlBody);
}
else
{
File.AppendAllText(path, HtmlBody);
HtmlBody = "";
}
}
public void SetEnd()
{
HtmlBody += "</body></html>";
}
public void SetMsg(WXUserReader reader,WXContact contact)
{
if (Session == null)
throw new Exception("请初始化模版Not Use InitTemplate");
List<WXMsg>? msgList = reader.GetWXMsgs(contact.UserName);
if (msgList == null)
throw new Exception("获取消息失败,请确认数据库读取正常");
msgList.Sort((x, y) => x.CreateTime.CompareTo(y.CreateTime));
foreach (var msg in msgList)
{
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\">{0} <span style=\"padding-left:10px;\">{1}</span></p>", msg.IsSender ? "我" : Session.NickName, TimeStampToDateTime(msg.CreateTime).ToString("yyyy-MM-dd HH:mm:ss"));
if (msg.Type == 1)
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", msg.StrContent);
else if (msg.Type == 3)
{
string? path = reader.GetAttachment(WXMsgType.Image, msg);
if (path == null)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "图片转换出现错误或文件不存在");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><img src=\"{0}\" style=\"max-height:1000px;max-width:1000px;\"/></p></div>", path);
}
else if (msg.Type == 43)
{
string? path = reader.GetAttachment(WXMsgType.Video, msg);
if (path == null)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "视频不存在");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><video controls style=\"max-height:300px;max-width:300px;\"><source src=\"{0}\" type=\"video/mp4\" /></video></p></div>", path);
}
else if (msg.Type == 34)
{
string? path = reader.GetAttachment(WXMsgType.Audio, msg);
if (path == null)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "语音不存在");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><audio controls src=\"{0}\"></audio></p></div>", path);
}
else
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "暂未支持的消息");
}
}
}
private static DateTime TimeStampToDateTime(long timeStamp, bool inMilli = false)
{
DateTimeOffset dateTimeOffset = inMilli ? DateTimeOffset.FromUnixTimeMilliseconds(timeStamp) : DateTimeOffset.FromUnixTimeSeconds(timeStamp);
return dateTimeOffset.LocalDateTime;
}
}
}

View File

@@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WechatPCMsgBakTool.Model;
namespace WechatPCMsgBakTool.Interface
{
public interface IExport
{
void InitTemplate(WXSession session);
void InitTemplate(WXContact session);
void SetMsg(WXUserReader reader, WXContact session);
void SetEnd();
void Save(string path = "", bool append = false);
}
}

View File

@@ -1,40 +0,0 @@
<Window x:Class="WechatPCMsgBakTool.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WechatPCMsgBakTool"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="溯雪微信备份工具" Height="450" Width="800">
<Grid>
<ListView Name="list_workspace" Margin="15,50,0,20" HorizontalAlignment="Left" Width="230" Grid.RowSpan="2" SelectionChanged="list_workspace_SelectionChanged">
<ListView.View>
<GridView>
<GridViewColumn Header="原始id" Width="140" DisplayMemberBinding="{Binding UserName,Mode=TwoWay}" />
<GridViewColumn Header="是否解密" Width="80" DisplayMemberBinding="{Binding Decrypt,Mode=TwoWay}" />
</GridView>
</ListView.View>
</ListView>
<Label Content="工作区:" HorizontalAlignment="Left" Margin="15,15,0,0" VerticalAlignment="Top" Height="25" Width="58"/>
<Button Content="新增" Width="50" HorizontalAlignment="Left" Margin="194,20,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.343,0.521" Height="19" Click="Button_Click_1"/>
<Label Content="用户路径:-" Name="user_path" HorizontalAlignment="Left" Margin="278,50,0,0" VerticalAlignment="Top" Height="25" Width="500"/>
<Button Content="解密" IsEnabled="False" Width="50" HorizontalAlignment="Left" Margin="285,20,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.343,0.521" Name="btn_decrypt" Click="btn_decrypt_Click" Height="19"/>
<Button Content="读取" IsEnabled="False" Width="50" HorizontalAlignment="Left" Margin="365,20,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.343,0.521" Name="btn_read" Click="btn_read_Click" Height="19" />
<ListView Name="list_sessions" Margin="278,130,0,20" HorizontalAlignment="Left" Width="290" MouseDoubleClick="list_sessions_MouseDoubleClick">
<ListView.View>
<GridView>
<GridViewColumn Header="昵称" Width="120" DisplayMemberBinding="{Binding NickName}" />
<GridViewColumn Header="原始id" Width="140" DisplayMemberBinding="{Binding UserName}" />
</GridView>
</ListView.View>
</ListView>
<Button Content="导出所选人员聊天记录" HorizontalAlignment="Left" Margin="609,130,0,0" VerticalAlignment="Top" Width="140" Click="Button_Click"/>
<Label Content="搜索:" HorizontalAlignment="Left" Margin="278,92,0,0" VerticalAlignment="Top"/>
<TextBox Name="find_user" HorizontalAlignment="Left" Margin="323,96,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="194" Height="20"/>
<Button Name="btn_search" Content="搜索" HorizontalAlignment="Left" Margin="525,96,0,0" VerticalAlignment="Top" Width="43" Click="btn_search_Click"/>
<Button Name="btn_analyse" Content="消息分析工具" HorizontalAlignment="Left" Margin="609,160,0,0" VerticalAlignment="Top" Width="140" Click="btn_analyse_Click"/>
<CheckBox Name="cb_del_search" Content="已删除人员强制从记录搜索" HorizontalAlignment="Left" Margin="610,99,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>

View File

@@ -1,247 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using WechatPCMsgBakTool.Helpers;
using WechatPCMsgBakTool.Interface;
using WechatPCMsgBakTool.Model;
namespace WechatPCMsgBakTool
{
/// <summary>
/// Main.xaml 的交互逻辑
/// </summary>
public partial class Main : Window
{
private UserBakConfig? CurrentUserBakConfig = null;
private WXUserReader? UserReader = null;
private ObservableCollection<UserBakConfig> userBakConfigs = new ObservableCollection<UserBakConfig>();
public Main()
{
Application.Current.DispatcherUnhandledException += Current_DispatcherUnhandledException;
InitializeComponent();
LoadWorkspace();
}
private void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
MessageBox.Show("发生了未知错误记录已写入到根目录err.log如果可以欢迎反馈给开发人员非常感谢", "错误");
File.AppendAllText("err.log", "\r\n\r\n\r\n=============================\r\n");
File.AppendAllText("err.log", string.Format("异常时间:{0}\r\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
File.AppendAllText("err.log", e.Exception.ToString());
return;
}
private void LoadWorkspace()
{
userBakConfigs.Clear();
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "workspace");
if (Directory.Exists(path))
{
string[] files = Directory.GetFiles(path);
foreach(string file in files)
{
string type = file.Substring(file.Length - 5, 5);
if(type == ".json")
{
string jsonString = File.ReadAllText(file);
UserBakConfig? userBakConfig = null;
try
{
userBakConfig = JsonConvert.DeserializeObject<UserBakConfig>(jsonString);
}
catch
{
MessageBox.Show("读取到异常工作区文件,请确认备份数据是否正常\r\n文件路径" + file,"错误");
}
if(userBakConfig != null)
{
userBakConfigs.Add(userBakConfig);
}
}
}
}
list_workspace.ItemsSource = userBakConfigs;
}
private void btn_decrypt_Click(object sender, RoutedEventArgs e)
{
if(CurrentUserBakConfig != null)
{
if (!CurrentUserBakConfig.Decrypt)
{
byte[]? key = null;
try
{
key = DecryptionHelper.GetWechatKey();
}
catch (Exception ex)
{
if(ex.Source == "Newtonsoft.Json")
{
MessageBox.Show("版本文件读取失败请检查版本文件内容是否为正确的json格式", "错误");
}
else
{
MessageBox.Show(ex.Message);
}
return;
}
//byte[]? key = DecryptionHelper.GetWechatKey();
if (key == null)
{
MessageBox.Show("微信密钥获取失败,请检查微信是否打开,或者版本不兼容");
return;
}
string key_string = BitConverter.ToString(key, 0).Replace("-", string.Empty).ToLower().ToUpper();
string source = Path.Combine(CurrentUserBakConfig.UserWorkspacePath, "OriginalDB");
string to = Path.Combine(CurrentUserBakConfig.UserWorkspacePath, "DecDB");
try
{
WechatDBHelper.DecryUserData(key, source, to);
MessageBox.Show("解密完成,请点击读取数据");
CurrentUserBakConfig.Decrypt = true;
WXWorkspace.SaveConfig(CurrentUserBakConfig);
LoadWorkspace();
}
catch (Exception ex)
{
MessageBox.Show("解密过程出现错误:" + ex.Message);
}
}
}
}
private void btn_read_Click(object sender, RoutedEventArgs e)
{
if(CurrentUserBakConfig == null)
{
MessageBox.Show("请先选择工作区");
return;
}
UserReader = new WXUserReader(CurrentUserBakConfig);
list_sessions.ItemsSource = UserReader.GetWXContacts();
}
private void list_workspace_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
CurrentUserBakConfig = list_workspace.SelectedItem as UserBakConfig;
if(CurrentUserBakConfig != null)
{
user_path.Content = "用户路径:" + CurrentUserBakConfig.UserResPath;
if (CurrentUserBakConfig.Decrypt)
{
btn_decrypt.IsEnabled = false;
btn_read.IsEnabled = true;
}
else
{
btn_decrypt.IsEnabled = true;
btn_read.IsEnabled = false;
}
}
}
private void list_sessions_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
}
private void Button_Click(object sender, RoutedEventArgs e)
{
WXContact? wXContact = list_sessions.SelectedItem as WXContact;
if(UserReader == null)
{
MessageBox.Show("请先点击读取已解密工作区");
return;
}
if(wXContact == null || CurrentUserBakConfig == null)
{
MessageBox.Show("请先选择要导出的联系人");
return;
}
IExport export = new HtmlExport();
export.InitTemplate(wXContact);
export.SetMsg(UserReader, wXContact);
export.SetEnd();
//string path = UserReader.GetSavePath(wXContact);
string path = Path.Combine(CurrentUserBakConfig.UserWorkspacePath, wXContact.UserName + ".html");
export.Save(path);
MessageBox.Show("导出完成");
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
SelectWechat selectWechat = new SelectWechat();
selectWechat.ShowDialog();
if(selectWechat.SelectProcess != null)
{
string path = selectWechat.SelectProcess.DBPath.Replace("\\Msg\\MicroMsg.db", "");
try
{
WXWorkspace wXWorkspace = new WXWorkspace(path);
wXWorkspace.MoveDB();
MessageBox.Show("创建工作区成功");
LoadWorkspace();
}
catch (Exception)
{
MessageBox.Show("创建工作区失败,请检查路径是否正确");
}
}
}
private void btn_search_Click(object sender, RoutedEventArgs e)
{
if(UserReader == null)
{
MessageBox.Show("请先读取工作区数据");
return;
}
if(cb_del_search.IsChecked != null)
{
if (!(bool)cb_del_search.IsChecked)
list_sessions.ItemsSource = UserReader.GetWXContacts(find_user.Text);
else
{
List<WXMsg>? wXMsgs = UserReader.GetWXMsgs(find_user.Text);
if(wXMsgs != null)
{
if(wXMsgs.Count > 0)
{
List<WXContact> wXContacts = new List<WXContact>() { new WXContact() { NickName = wXMsgs[0].StrTalker, UserName = wXMsgs[0].StrTalker } };
list_sessions.ItemsSource = wXContacts;
}
}
}
}
}
private void btn_analyse_Click(object sender, RoutedEventArgs e)
{
if(UserReader == null || CurrentUserBakConfig == null)
{
MessageBox.Show("请先读取数据");
return;
}
Analyse analyse = new Analyse(CurrentUserBakConfig, UserReader);
analyse.Show();
}
}
}

178
Main2.xaml Normal file
View File

@@ -0,0 +1,178 @@
<Window x:Class="WechatBakTool.Main2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WechatBakTool"
mc:Ignorable="d" WindowStartupLocation="CenterScreen" WindowStyle="None" WindowState="Normal" Background="Transparent" AllowsTransparency="True" ResizeMode="NoResize"
Title="WechatBakTool" Height="550" Width="950" >
<Window.Resources>
<Style TargetType="local:Main2">
<!-- 设置窗体的WindowChrome -->
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<!-- ResizeBorderThickness:拖拽改变窗体大小的边框厚度;-->
<!-- CornerRadius窗体圆角-->
<!-- CaptionHeight顶部标题的高度-->
<!-- GlassFrameThickness:默认边框的大小0为不使用默认边框这样定义的圆角才有效-1为使用默认边框默认值-->
<WindowChrome CornerRadius="5" CaptionHeight="5" GlassFrameThickness="0" />
</Setter.Value>
</Setter>
</Style>
<DrawingImage x:Key="svg_min">
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V1024 H1024 V0 H0 Z">
<GeometryDrawing Brush="#2775b6" Geometry="F1 M1024,1024z M0,0z M370.752,608L129.6,849.152A32,32,0,0,0,174.848,894.4L416,653.248 416,768A32,32,0,0,0,480,768L480,576A31.904,31.904,0,0,0,448,544L256,544A32,32,0,0,0,256,608L370.752,608z M553.376,470.624A31.904,31.904,0,0,1,544,448L544,256A32,32,0,0,1,608,256L608,370.752 849.152,129.6A32,32,0,1,1,894.4,174.848L653.248,416 768,416A32,32,0,0,1,768,480L576,480A31.904,31.904,0,0,1,553.376,470.624z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<DrawingImage x:Key="svg_close">
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V1024 H1024 V0 H0 Z">
<GeometryDrawing Brush="#2775b6" Geometry="F1 M1024,1024z M0,0z M556.8,512L828.8,240C841.6,227.2 841.6,208 828.8,195.2 816,182.4 796.8,182.4 784,195.2L512,467.2 240,195.2C227.2,182.4 208,182.4 195.2,195.2 182.4,208 182.4,227.2 195.2,240L467.2,512 195.2,784C182.4,796.8 182.4,816 195.2,828.8 208,841.6 227.2,841.6 240,828.8L512,556.8 784,828.8C796.8,841.6 816,841.6 828.8,828.8 841.6,816 841.6,796.8 828.8,784L556.8,512z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<DrawingImage x:Key="friends_nums">
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V1024 H1024 V0 H0 Z">
<GeometryDrawing Brush="#FFF" Geometry="F1 M1024,1024z M0,0z M512.018127441406,140.7236328125C386.078552246094,140.7236328125 283.536560058594,243.226898193359 283.536560058594,369.204376220703 283.536560058594,495.106048583984 386.003570556641,597.648864746094 512.018127441406,597.648864746094 637.994781494141,597.648864746094 740.535949707031,495.106872558594 740.535949707031,369.204376220703 740.535949707031,243.226898193359 637.994781494141,140.7236328125 512.018127441406,140.7236328125z" />
<GeometryDrawing Brush="#FFF" Geometry="F1 M1024,1024z M0,0z M688.11962890625,592.435577392578C639.437561035156,630.917663574219 578.812805175781,654.760192871094 512.018127441406,654.760192871094 445.222625732422,654.760192871094 384.636596679688,630.844329833984 335.842468261719,592.435577392578 243.466674804688,651.248413085937 179.148040771484,754.530334472656 170.313385009766,874.148376464844 202.656860351563,878.545104980469 260.212310791016,883.2763671875 342.347747802734,883.2763671875L681.539367675781,883.2763671875C763.712707519531,883.2763671875 821.304412841797,878.545104980469 853.686614990234,874.148376464844 844.924468994141,754.454528808594 780.606658935547,651.248413085938 688.11962890625,592.435577392578z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<DrawingImage x:Key="msg_nums">
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V1024 H1024 V0 H0 Z">
<GeometryDrawing Brush="#FFF" Geometry="F1 M1024,1024z M0,0z M512,917.333333A308.458667,308.458667,0,0,1,425.941333,908.181333C366.592,890.88 248.298666,903.808 153.365333,922.666667 153.365333,922.666667 145.173333,924.010667 144.661333,924.010667A42.282667,42.282667,0,0,1,103.253333,871.488C123.498666,779.754667,131.306666,670.741333,115.584,596.906667A405.333333,405.333333,0,1,1,512,917.333333z M339.008,640L552.341333,640A32,32,0,1,0,552.341333,576L339.008,576A32,32,0,0,0,339.008,640z M680.341333,405.333333L339.008,405.333333A32,32,0,0,0,339.008,469.333333L680.341333,469.333333A32,32,0,1,0,680.341333,405.333333z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<DataTemplate x:Key="ListViewItemContentTemplate">
<Grid Margin="5">
<Label Margin="0" Content="{Binding Account}" FontSize="16" VerticalAlignment="Top" HorizontalAlignment="Left" Foreground="White" />
<Image Source="{StaticResource friends_nums}" Width="15" Height="15" Margin="2,30,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" />
<Label Margin="13,25.5,0,0" Content="{Binding Friends_Number}" VerticalAlignment="Top" HorizontalAlignment="Left" Foreground="White" />
<Image Source="{StaticResource msg_nums}" Width="15" Height="15" Margin="60,30,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" />
<Label Margin="73,25.5,0,0" Content="{Binding Msg_Number}" VerticalAlignment="Top" HorizontalAlignment="Left" Foreground="White" />
<Border Margin="150,25.5,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" CornerRadius="5" BorderThickness="1" Background="White" >
<Label Padding="5" FontSize="8" Content="{Binding DecryptStatus}" Foreground="#2775b6" Background="Transparent" />
</Border>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid Background="White" MouseDown="Grid_MouseDown">
<Image Panel.ZIndex="100" Name="img_btn_min" Source="{StaticResource svg_min}" Width="20" Height="20" HorizontalAlignment="Left" Margin="860,20,0,0" VerticalAlignment="Top" MouseLeftButtonDown="img_btn_min_MouseLeftButtonDown">
<Image.RenderTransform>
<RotateTransform CenterX="0.5" CenterY="0.5" />
</Image.RenderTransform>
<Image.RenderTransformOrigin>
<Point>0.5,0.5</Point>
</Image.RenderTransformOrigin>
<Image.Triggers>
<EventTrigger RoutedEvent="Image.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="img_btn_min"
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
To="180" Duration="0:0:0.300"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Image.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="img_btn_min"
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
To="0" Duration="0:0:0.300" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
<Image Panel.ZIndex="100" Name="img_btn_close" Source="{StaticResource svg_close}" Width="20" Height="20" HorizontalAlignment="Left" Margin="900,20,0,0" VerticalAlignment="Top" MouseLeftButtonDown="img_btn_close_MouseLeftButtonDown">
<Image.RenderTransform>
<RotateTransform CenterX="0.5" CenterY="0.5" />
</Image.RenderTransform>
<Image.RenderTransformOrigin>
<Point>0.5,0.5</Point>
</Image.RenderTransformOrigin>
<Image.Triggers>
<EventTrigger RoutedEvent="Image.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="img_btn_close"
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
To="90" Duration="0:0:0.200"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Image.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="img_btn_close"
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
To="0" Duration="0:0:0.200" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
<Grid Width="230" Background="#2775b6" HorizontalAlignment="Left" IsHitTestVisible="True">
<ListView BorderThickness="0" Background="Transparent" Margin="0,0,0,85" Name="list_workspace" ItemTemplate="{DynamicResource ListViewItemContentTemplate}" SelectionChanged="list_workspace_SelectionChanged">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="查看" Click="MenuItem_Click" />
<MenuItem Header="管理" Click="MenuItem_Click_1" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
<Grid Name="new_workspace" Width="170" Height="40" VerticalAlignment="Bottom" Margin="30,45" IsHitTestVisible="True">
<Rectangle Name="new_workspace_fill" Fill="Transparent" RadiusX="0" RadiusY="0" Stroke="White" StrokeDashArray="5" MouseDown="new_workspace_fill_MouseDown">
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.MouseEnter">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetName="new_workspace_fill"
Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
To="White" Duration="0:0:0.300"/>
<ColorAnimation
Storyboard.TargetName="new_workspace_text"
Storyboard.TargetProperty="(Label.Foreground).(SolidColorBrush.Color)"
To="Black" Duration="0:0:0.300"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Rectangle.MouseLeave">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetName="new_workspace_fill"
Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
To="Transparent" Duration="0:0:0.300"/>
<ColorAnimation
Storyboard.TargetName="new_workspace_text"
Storyboard.TargetProperty="(Label.Foreground).(SolidColorBrush.Color)"
To="White" Duration="0:0:0.300"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
<Label Name="new_workspace_text" Content="新建工作区" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" IsHitTestVisible="False"/>
</Grid>
<Label Name="lab_version" Content="版本:" Margin="10" VerticalAlignment="Bottom" HorizontalAlignment="Center" Foreground="White" />
</Grid>
<Grid Margin="230,0,0,0" Width="720" Background="Transparent" HorizontalAlignment="Left" Panel.ZIndex="1">
<Frame Name="MainFrame" Source="/Pages/Welcome.xaml" NavigationUIVisibility="Hidden" />
</Grid>
</Grid>
</Window>

123
Main2.xaml.cs Normal file
View File

@@ -0,0 +1,123 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using WechatBakTool.Model;
namespace WechatBakTool
{
/// <summary>
/// Main2.xaml 的交互逻辑
/// </summary>
public partial class Main2 : Window
{
public static UserBakConfig? CurrentUserBakConfig;
private ObservableCollection<UserBakConfig> userBakConfigs = new ObservableCollection<UserBakConfig>();
public Main2()
{
InitializeComponent();
// 获取文件版本
lab_version.Content += $" {Application.ResourceAssembly.GetName().Version}";
//加载工作区
LoadWorkspace();
}
private void img_btn_close_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Application.Current.Shutdown();
}
public void LoadWorkspace()
{
userBakConfigs.Clear();
// 根目录worksapce读工作区
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "workspace");
if (Directory.Exists(path))
{
string[] files = Directory.GetFiles(path);
//目录内json文件为各工作区配置文件
foreach (string file in files)
{
string type = file.Substring(file.Length - 5, 5);
if (type == ".json")
{
string jsonString = File.ReadAllText(file);
UserBakConfig? userBakConfig = null;
try
{
userBakConfig = JsonConvert.DeserializeObject<UserBakConfig>(jsonString);
}
catch
{
MessageBox.Show("读取到异常工作区文件,请确认备份数据是否正常\r\n文件路径" + file, "错误");
}
if (userBakConfig != null)
{
userBakConfigs.Add(userBakConfig);
}
}
}
}
list_workspace.ItemsSource = userBakConfigs;
}
private void list_workspace_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
UserBakConfig? config = list_workspace.SelectedItem as UserBakConfig;
if(config == null)
{
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Welcome.xaml?datatime=" + DateTime.Now.Ticks));
return;
}
if (!config.Decrypt)
{
MessageBox.Show("请先到创建工作区进行解密");
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/CreateWork.xaml?datatime=" + DateTime.Now.Ticks));
return;
}
CurrentUserBakConfig = config;
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Workspace.xaml?datatime=" + DateTime.Now.Ticks));
}
private void new_workspace_fill_MouseDown(object sender, MouseButtonEventArgs e)
{
list_workspace.SelectedItem = null;
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/CreateWork.xaml?datatime=" + DateTime.Now.Ticks));
}
private void img_btn_min_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
WindowState = WindowState.Minimized;
}
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
DragMove();
}
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Workspace.xaml?datatime=" + DateTime.Now.Ticks));
}
private void MenuItem_Click_1(object sender, RoutedEventArgs e)
{
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Manager.xaml?datatime=" + DateTime.Now.Ticks));
}
}
}

View File

@@ -4,13 +4,14 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WechatPCMsgBakTool.Model
namespace WechatBakTool.Model
{
public class ProcessInfo
{
public string ProcessName { get; set; } = "";
public string ProcessId { get; set; } = "";
public string DBPath { get; set; } = "";
public string Account { get; set; } = "";
}
public class DBInfo
{
@@ -20,6 +21,12 @@ namespace WechatPCMsgBakTool.Model
public string ResPath { get; set; } = "";
}
public class ExportItem
{
public string Name { get; set; } = "";
public int Value { get; set; }
}
public class UserInfo
{
public string UserName { get; set; } = "";

25
Model/ProtobufModel.cs Normal file
View File

@@ -0,0 +1,25 @@
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WechatBakTool.Model
{
[ProtoContract]
public class TVType
{
[ProtoMember(1)]
public int Type;
[ProtoMember(2)]
public string TypeValue = "";
}
[ProtoContract]
public class ProtoMsg
{
[ProtoMember(3)]
public List<TVType>? TVMsg;
}
}

View File

@@ -5,23 +5,31 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Media.Imaging;
namespace WechatPCMsgBakTool.Model
namespace WechatBakTool.Model
{
public class UserBakConfig : INotifyPropertyChanged
public class UserBakConfig
{
public string UserResPath { get; set; } = "";
public string UserWorkspacePath { get; set; } = "";
public bool Decrypt { get; set; } = false;
public string DecryptStatus
{
get { return Decrypt ? "已解密" : "未解密"; }
}
public string Hash { get; set; } = "";
public string NickName { get; set; } = "";
public string UserName { get; set; } = "";
public string Account { get; set; } = "";
public string Friends_Number { get; set; } = "-";
public string Msg_Number { get; set; } = "-";
}
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class WXCount
{
public int Count { get; set; }
}
public class WXMsgGroup
@@ -34,6 +42,14 @@ namespace WechatPCMsgBakTool.Model
public string NickName { get; set; } = "";
}
[Table("ContactHeadImg1")]
public class ContactHeadImg
{
public string usrName { get; set; } = "";
public int createTime { get; set; }
public byte[]? smallHeadBuf { get; set; }
}
public class WXUserInfo
{
public string UserName { get; set; } = "";
@@ -98,8 +114,12 @@ namespace WechatPCMsgBakTool.Model
{
[Column("localId")]
public int LocalId { get; set; }
[Column("MsgSequence")]
public int MsgSequence { get; set; }
[Column("Type")]
public int Type { get; set; }
[Column("SubType")]
public int SubType { get; set; }
[Column("CreateTime")]
public long CreateTime { get; set; }
[Column("IsSender")]
@@ -110,6 +130,26 @@ namespace WechatPCMsgBakTool.Model
public string StrTalker { get; set; } = "";
[Column("StrContent")]
public string StrContent { get; set; } = "";
public string DisplayContent { get; set; } = "";
[Column("CompressContent")]
public byte[]? CompressContent { get; set; }
[Column("BytesExtra")]
public byte[]? BytesExtra { get; set; }
public string NickName { get; set; } = "";
}
[Table("ChatRoom")]
public class WXChatRoom
{
[Column("ChatRoomName")]
public string ChatRoomName { get; set; } = "";
[Column("UserNameList")]
public string UserNameList { get; set; } = "";
[Column("DisplayNameList")]
public string DisplayNameList { get; set; } = "";
[Column("RoomData")]
public byte[]? RoomData { get; set; }
}
[Table("Media")]
@@ -120,6 +160,15 @@ namespace WechatPCMsgBakTool.Model
public string Reserved0 { get; set; } = "";
}
public class WXContactHT
{
public string UserName { get; set; } = "";
public string NickName { get; set; } = "";
public string LastMsg { get; set; } = "";
public int FileCount { get; set; } = 1;
public string AvatarString { get; set; } = "";
public bool Hidden { get; set; } = false;
}
[Table("Contact")]
public class WXContact
{
@@ -129,5 +178,22 @@ namespace WechatPCMsgBakTool.Model
public string Alias { get; set; } = "";
[Column("NickName")]
public string NickName { get; set; } = "";
[Column("strContent")]
public string LastMsg { get; set; } = "";
[Column("ExtraBuf")]
public byte[]? ExtraBuf { get; set; }
public BitmapImage? Avatar { get; set; }
[Column("Remark")]
public string Remark { get; set; } = "";
}
[Table("ContactHeadImgUrl")]
public class WXUserImg {
[Column("usrName")]
public string UserName { get; set; } = "";
[Column("smallHeadImgUrl")]
public string SmallImg { get; set; } = "";
[Column("bigHeadImgUrl")]
public string BigImg { get; set; } = "";
}
}

28
Model/YearReport.cs Normal file
View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WechatBakTool.Model
{
public class YearReport
{
public List<ReportItem>? List { get; set; }
public int Version { get; set; }
}
public class ReportItem
{
public string ImgName { get; set; } = "";
public string Type { get; set; } = "";
public List<TextPostion>? TextPostions { get; set; }
}
public class TextPostion
{
public double X { get; set; }
public double Y { get; set; }
public string TextTemplate { get; set; } = "";
}
}

45
Pages/CreateWork.xaml Normal file
View File

@@ -0,0 +1,45 @@
<Page x:Class="WechatBakTool.Pages.CreateWork"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WechatBakTool.ViewModel"
mc:Ignorable="d"
d:DesignHeight="550" d:DesignWidth="800"
Title="Welcome" Background="White">
<Page.Resources>
<local:GetKeyConverter x:Key="getKeyConverterKey" />
</Page.Resources>
<Grid>
<Label FontSize="20" Margin="30,15" Content="新建工作区" HorizontalAlignment="Left" VerticalAlignment="Top" />
<Label Margin="30,55,0,0" Content="请选择要创建工作区的微信,可以通过微信路径判断是哪一个微信哦!" HorizontalAlignment="Left" VerticalAlignment="Top" />
<ListView Name="list_process" Margin="35,95,35,0" IsEnabled="{Binding IsEnable}" VerticalAlignment="Top" Height="160" ItemsSource="{Binding ProcessInfos}" SelectionChanged="list_process_SelectionChanged" SelectedItem="{Binding SelectProcess}">
<ListView.View>
<GridView>
<GridViewColumn Header="进程名" Width="120" DisplayMemberBinding="{Binding ProcessName}" />
<GridViewColumn Header="PID" Width="80" DisplayMemberBinding="{Binding ProcessId}" />
<GridViewColumn Header="路径" Width="430" DisplayMemberBinding="{Binding DBPath}" />
</GridView>
</ListView.View>
</ListView>
<Label Margin="30,275,0,0" Content="选择微信后,请确认下方自动获取的微信名是否正确。不正确请修改!" FontWeight="Bold" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<TextBox IsEnabled="{Binding IsEnable}" x:Name="txt_username" Margin="35,300,0,0" Width="280" HorizontalAlignment="Left" VerticalAlignment="Top" BorderThickness="0,0,0,1" Text="{Binding UserName}" />
<Label Margin="30,350,0,0" Content="请选择解密方式:" FontWeight="Bold" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<RadioButton Margin="35,380,0,0" Content="固定地址查找【保底】" HorizontalAlignment="Left" VerticalAlignment="Top" GroupName="rb_find_key" HorizontalContentAlignment="Center" IsEnabled="{Binding IsEnable}" VerticalContentAlignment="Center" IsChecked="{Binding KeyType, Converter={StaticResource ResourceKey=getKeyConverterKey}, ConverterParameter=1}" />
<RadioButton Margin="35,405,0,0" Content="用户名推断查找【不稳定】" HorizontalAlignment="Left" VerticalAlignment="Top" GroupName="rb_find_key" HorizontalContentAlignment="Center" IsEnabled="{Binding IsEnable}" VerticalContentAlignment="Center" IsChecked="{Binding KeyType, Converter={StaticResource ResourceKey=getKeyConverterKey}, ConverterParameter=2}"/>
<RadioButton Margin="35,430,0,0" Content="公钥头推断查找【推荐】" HorizontalAlignment="Left" VerticalAlignment="Top" GroupName="rb_find_key" HorizontalContentAlignment="Center" IsEnabled="{Binding IsEnable}" VerticalContentAlignment="Center" IsChecked="{Binding KeyType, Converter={StaticResource ResourceKey=getKeyConverterKey}, ConverterParameter=3}"/>
<Button Name="btn_create_worksapce" Margin="0,0,35,50" Height="60" Width="100" HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="创建工作区" BorderThickness="0" IsEnabled="{Binding IsEnable}" Background="#2775b6" Foreground="White" Click="btn_create_worksapce_Click">
<Button.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="8"/>
</Style>
</Button.Resources>
</Button>
<Label Margin="210,350,0,0" Content="其他选项:" FontWeight="Bold" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<CheckBox Margin="215,380,0,0" Content="打包资源文件夹(功能规划中)" IsEnabled="False" HorizontalAlignment="Left" VerticalAlignment="Top" />
<CheckBox Name="cb_manual" Checked="cb_manual_Checked" Margin="215,405,0,0" Content="手动模式" Visibility="Visible" HorizontalAlignment="Left" VerticalAlignment="Top" />
<Label Name="lab_status" Content="{Binding LabelStatus}" HorizontalAlignment="Left" Margin="30,450,0,0" VerticalAlignment="Top"/>
</Grid>
</Page>

178
Pages/CreateWork.xaml.cs Normal file
View File

@@ -0,0 +1,178 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using WechatBakTool.Helpers;
using WechatBakTool.Model;
using WechatBakTool.ViewModel;
namespace WechatBakTool.Pages
{
/// <summary>
/// CreateWork.xaml 的交互逻辑
/// </summary>
public partial class CreateWork : Page
{
private CreateWorkViewModel ViewModel = new CreateWorkViewModel();
public CreateWork()
{
DataContext = ViewModel;
InitializeComponent();
GetWechatProcessInfos();
}
private void GetWechatProcessInfos()
{
ViewModel.ProcessInfos.Clear();
Process[] processes = Process.GetProcessesByName("wechat");
foreach (Process p in processes)
{
var lHandles = NativeAPIHelper.GetHandleInfoForPID((uint)p.Id);
foreach (var h in lHandles)
{
string name = NativeAPIHelper.FindHandleName(h, p);
if (name != "")
{
// 预留handle log
if (File.Exists("handle.log"))
{
File.AppendAllText("handle.log", string.Format("{0}|{1}|{2}|{3}\n", p.Id, h.ObjectTypeIndex, h.HandleValue, name));
}
if (name.Contains("\\MicroMsg.db") && name.Substring(name.Length - 3, 3) == ".db")
{
ProcessInfo info = new ProcessInfo();
info.ProcessId = p.Id.ToString();
info.ProcessName = p.ProcessName;
info.DBPath = DevicePathMapper.FromDevicePath(name);
ViewModel.ProcessInfos.Add(info);
}
}
}
}
}
private void list_process_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ViewModel.SelectProcess != null)
{
string[] name_raw = ViewModel.SelectProcess.DBPath.Split("\\");
ViewModel.UserName = name_raw[name_raw.Length - 3];
FileInfo fileInfo = new FileInfo(ViewModel.SelectProcess.DBPath);
DirectoryInfo msgParent = fileInfo.Directory!.Parent!;
DirectoryInfo[] accounts = msgParent.GetDirectories();
DirectoryInfo? newUserName = null;
foreach ( DirectoryInfo account in accounts )
{
if(account.Name.Contains("account_")) {
if(newUserName == null)
newUserName = account;
else
{
if (newUserName.LastWriteTime < account.LastWriteTime)
newUserName = account;
}
}
}
if(newUserName != null)
{
ViewModel.UserName = newUserName.Name.Split("_")[1];
}
}
}
private void btn_create_worksapce_Click(object sender, RoutedEventArgs e)
{
ViewModel.IsEnable = false;
Task.Run(() => {
if (ViewModel.KeyType != -1)
{
if (ViewModel.SelectProcess != null)
{
ViewModel.LabelStatus = "数据准备";
string path = ViewModel.SelectProcess.DBPath.Replace("\\Msg\\MicroMsg.db", "");
try
{
ViewModel.LabelStatus = "准备创建工作区";
//创建工作区
WXWorkspace wXWorkspace = new WXWorkspace(path, ViewModel.UserName);
//DB移动
wXWorkspace.MoveDB(ViewModel);
if(ViewModel.SelectProcess == null)
return;
//开始解密数据库
try
{
ViewModel.LabelStatus = "开始解密数据库";
wXWorkspace.DecryptDB(ViewModel.SelectProcess.ProcessId, ViewModel.KeyType,ViewModel);
MessageBox.Show("创建工作区成功");
Dispatcher.Invoke(() =>
{
((Main2)Window.GetWindow(this)).LoadWorkspace();
});
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
ViewModel.IsEnable = true;
}
}
catch (Exception)
{
MessageBox.Show("创建工作区失败,请检查路径是否正确");
ViewModel.IsEnable = true;
}
}
}
else
{
MessageBox.Show("请选择Key获取方式", "错误");
}
ViewModel.IsEnable = true;
});
}
private void cb_manual_Checked(object sender, RoutedEventArgs e)
{
MessageBox.Show("该功能仅限用于网络安全研究用途使用,红队同学请在合规授权下进行相关操作","重要提醒!!!!!!!!!");
if (MessageBox.Show("我确认获取到合规授权,仅用于网络安全用途使用", "信息确认", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
if (File.Exists("auth.txt"))
{
string auth = File.ReadAllText("auth.txt");
// 我已知晓手动模式可能潜在的法律及道德风险,我明白非法使用将要承担相关法律责任。
if (DecryptionHelper.GetMD5(auth) == "295f634af60d61dfa52a5f35849ac42b")
{
MessageBox.Show("已经创建空的配置文件,请完善该配置文件后,点击开始解密","提示");
MessageBox.Show("该功能现阶段暂未启用","错误");
}
}
else
{
MessageBox.Show("未完成声明文件,请先确认声明", "错误");
}
}
else
{
cb_manual.IsChecked = false;
}
}
}
}

25
Pages/Manager.xaml Normal file
View File

@@ -0,0 +1,25 @@
<Page x:Class="WechatBakTool.Pages.Manager"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WechatBakTool.Pages"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="720"
Title="Manager" Background="White">
<Grid>
<Label FontSize="20" Margin="30,15" Content="管理" HorizontalAlignment="Left" VerticalAlignment="Top" />
<Label Margin="30,55" Content="批量导出聊天记录" HorizontalAlignment="Left" VerticalAlignment="Top" FontWeight="Bold" />
<CheckBox Name="cb_group" Margin="35,85" Content="群聊" HorizontalAlignment="Left" VerticalAlignment="Top" />
<CheckBox Name="cb_user" Margin="90,85" Content="好友聊天" HorizontalAlignment="Left" VerticalAlignment="Top" />
<Button Name="btn_export_all" Margin="35,110" Height="26" Width="60" Content="导出" HorizontalAlignment="Left" VerticalAlignment="Top" Background="#2775b6" Foreground="White" BorderThickness="0" Click="btn_export_all_Click"></Button>
<Label Content="{Binding LabelStatus}" HorizontalAlignment="Left" Margin="110,110,0,0" VerticalAlignment="Top"/>
<Label Margin="30,155,0,0" Content="表情包预下载" HorizontalAlignment="Left" VerticalAlignment="Top" FontWeight="Bold" />
<Button Name="btn_emoji_download" Margin="35,185,0,0" Height="26" Width="60" Content="导出" HorizontalAlignment="Left" VerticalAlignment="Top" Background="#2775b6" Foreground="White" BorderThickness="0" Click="btn_emoji_download_Click"></Button>
<Label Margin="30,225,0,0" Content="旧版分析工具" HorizontalAlignment="Left" VerticalAlignment="Top" FontWeight="Bold" />
<Button Name="btn_analyse" Margin="35,255,0,0" Height="26" Width="60" Content="打开" HorizontalAlignment="Left" VerticalAlignment="Top" Background="#2775b6" Foreground="White" BorderThickness="0" Click="btn_analyse_Click" ></Button>
</Grid>
</Page>

145
Pages/Manager.xaml.cs Normal file
View File

@@ -0,0 +1,145 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Xml;
using WechatBakTool.Export;
using WechatBakTool.Model;
using WechatBakTool.ViewModel;
namespace WechatBakTool.Pages
{
/// <summary>
/// Manager.xaml 的交互逻辑
/// </summary>
public partial class Manager : Page
{
private WorkspaceViewModel workspaceViewModel = new WorkspaceViewModel();
public WXUserReader? UserReader;
private List<WXContact>? ExpContacts;
private bool Suspend = false;
public Manager()
{
DataContext = workspaceViewModel;
InitializeComponent();
UserBakConfig? config = Main2.CurrentUserBakConfig;
if (config != null)
{
UserReader = new WXUserReader(config);
if (!config.Decrypt)
{
MessageBox.Show("请先解密数据库", "错误");
return;
}
}
}
private void btn_export_all_Click(object sender, RoutedEventArgs e)
{
Task.Run(() =>
{
bool group = false, user = false;
Dispatcher.Invoke(() =>
{
if (cb_group.IsChecked == null || cb_user.IsChecked == null)
return;
group = (bool)cb_group.IsChecked;
user = (bool)cb_user.IsChecked;
});
if (UserReader != null)
{
if (!Suspend)
ExpContacts = UserReader.GetWXContacts().ToList();
else
Suspend = false;
foreach (var contact in ExpContacts!)
{
if (Suspend)
{
workspaceViewModel.ExportCount = "已暂停";
return;
}
if (group && contact.UserName.Contains("@chatroom"))
{
workspaceViewModel.WXContact = contact;
ExportMsg(contact);
}
if (user && !contact.UserName.Contains("@chatroom") && !contact.UserName.Contains("gh_"))
{
workspaceViewModel.WXContact = contact;
ExportMsg(contact);
}
}
MessageBox.Show("批量导出完成", "提示");
}
});
}
private void ExportMsg(WXContact contact)
{
workspaceViewModel.ExportCount = "";
// string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, contact.UserName + ".html");
string name = contact.NickName;
name = name.Replace(@"\", "");
name = Regex.Replace(name, "[ \\[ \\] \\^ \\-_*×――(^)$%~!/@#$…&%¥—+=<>《》|!??::•`·、。,;,.;\"‘’“”-]", "");
string path = Path.Combine(
Main2.CurrentUserBakConfig!.UserWorkspacePath,
string.Format(
"{0}-{1}.html",
contact.UserName,
contact.Remark == "" ? name : contact.Remark
)
);
IExport export = new HtmlExport();
export.InitTemplate(contact, path);
if(export.SetMsg(UserReader!, contact, workspaceViewModel))
{
export.SetEnd();
export.Save(path);
}
}
private void btn_emoji_download_Click(object sender, RoutedEventArgs e)
{
if (UserReader != null)
{
Task.Run(() =>
{
UserReader.PreDownloadEmoji();
MessageBox.Show("所有表情预下载完毕");
});
}
}
private void btn_analyse_Click(object sender, RoutedEventArgs e)
{
if (UserReader == null || Main2.CurrentUserBakConfig == null)
{
MessageBox.Show("请先读取数据");
return;
}
Analyse analyse = new Analyse(Main2.CurrentUserBakConfig, UserReader);
analyse.Show();
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using WechatBakTool.Model;
namespace WechatBakTool.Pages
{
public class MsgTemplateSelector : DataTemplateSelector
{
public override DataTemplate? SelectTemplate(object item, DependencyObject container)
{
FrameworkElement? element = container as FrameworkElement;
if (element != null && item != null && item is WXMsg)
{
WXMsg? wxmsg = item as WXMsg;
if (wxmsg == null)
return null;
if (wxmsg.Type == 1)
return
element.FindResource("MsgText") as DataTemplate;
else if (wxmsg.Type == 3)
return
element.FindResource("MsgImage") as DataTemplate;
else
return
element.FindResource("MsgText") as DataTemplate;
}
return null;
}
}
}

30
Pages/Welcome.xaml Normal file
View File

@@ -0,0 +1,30 @@
<Page x:Class="WechatBakTool.Pages.Welcome"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WechatBakTool.Pages"
mc:Ignorable="d"
d:DesignHeight="550" d:DesignWidth="800"
Title="Welcome" Background="White">
<Grid>
<Label FontSize="20" Margin="30,15" Content="欢迎页" HorizontalAlignment="Left" VerticalAlignment="Top" />
<Label Margin="30,45,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="欢迎使用溯雪微信备份工具,阅读使用说明有助于您对本工具的了解。"></Label>
<Label Margin="30,65,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="1、如果您是第一次使用本工具请点击左下角新建工作区开始进行微信备份。"></Label>
<Label Margin="30,85,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="2、如果您是需要对同一微信进行再次备份导出也请点击新建工作区会自行覆盖。"></Label>
<Label Margin="30,105,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="3、完成工作建立后请点击左侧建立的工作区进入工作台。"></Label>
<Label Margin="30,125,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="4、未解密备份请先解密微信备份请保持微信PC版是打开状态。"></Label>
<Label Margin="30,145,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="---关于解密方式"></Label>
<Label Margin="30,165,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="---版本推定"></Label>
<Label Margin="30,185,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="版本推定是通过version.json内的版本基址来直接获取key进行解密不需要依赖用户名缺点是每次微信版本更新"></Label>
<Label Margin="30,205,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="都需要额外维护version.json更新不一定及时。"></Label>
<Label Margin="30,225,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="---用户名推定"></Label>
<Label Margin="30,245,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="用户名推定是通过新建工作区的时候填写用户名工具通过内存搜索的方式自动推出key所在地址并获取。"></Label>
<Label Margin="30,265,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="该方式的优点在于不受版本限制,推荐使用该方式进行解密。缺点就是劳烦您一定要写对微信用户名。"></Label>
<Label Margin="30,285,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="5、解密完成后读取就能愉快使用工具啦"></Label>
<Label Margin="30,335,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="如果感觉工具好用欢迎到github给我点个Star点击本条去Star >>> SuxueCode/WechatBakTool" Foreground="#2775b6" MouseDown="StarGithub_MouseDown"></Label>
</Grid>
</Page>

34
Pages/Welcome.xaml.cs Normal file
View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WechatBakTool.Pages
{
/// <summary>
/// Welcome.xaml 的交互逻辑
/// </summary>
public partial class Welcome : Page
{
public Welcome()
{
InitializeComponent();
}
private void StarGithub_MouseDown(object sender, MouseButtonEventArgs e)
{
Process.Start("explorer.exe", "https://github.com/SuxueCode/WechatBakTool");
}
}
}

237
Pages/Workspace.xaml Normal file
View File

@@ -0,0 +1,237 @@
<Page x:Class="WechatBakTool.Pages.Workspace"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WechatBakTool.Pages"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="720"
Title="Workspace" Background="White">
<Page.Resources>
<local:MsgTemplateSelector x:Key="MsgTemplateSelector"/>
<Style TargetType="ToggleButton" x:Key="ComboxStyleBtn">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!--下拉按钮内部背景色-->
<Border x:Name="Back" Background="{Binding Background, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="1,0,0,0" CornerRadius="0,3,3,0">
<!--下拉按钮内边框-->
<Path Name="PathFill" Fill="{Binding Foreground, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Width="10" Height="6" StrokeThickness="0" Data="M5,0 L10,10 L0,10 z" RenderTransformOrigin="0.5,0.5" Stretch="Fill">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="180"/>
<TranslateTransform/>
</TransformGroup>
</Path.RenderTransform>
</Path>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="PathFill" Property="Fill" Value="White"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsEnabled, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Value="False">
<Setter Property="Background" Value="#efefef" />
<Setter Property="Opacity" Value="1" />
<Setter Property="Foreground" Value="#000000" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsEnabled, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Value="True">
<Setter Property="Background" Value="#2775b6" />
<Setter Property="Foreground" Value="#ffffff" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
</Style>
<!--Combox-->
<Style TargetType="ComboBox" x:Key="ComboBoxStyle">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<!--ComBoxItem-->
<Style TargetType="ComboBoxItem">
<Setter Property="MinHeight" Value="22"></Setter>
<Setter Property="MinWidth" Value="60"></Setter>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border Name="Back" Background="Transparent" BorderThickness="0">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Left" Margin="10,0,0,0" ></ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Back" Property="Background" Value="#BB2775b6"></Setter>
</Trigger>
<!--下拉框背景色-->
<Trigger Property="IsHighlighted" Value="True">
<Setter TargetName="Back" Property="Background" Value="#2775b6"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="0.3*" MaxWidth="30"/>
</Grid.ColumnDefinitions>
<!--文字区域背景和边线样式-->
<Border Grid.Column="0" BorderThickness="1" BorderBrush="{Binding Background, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" CornerRadius="3,0,0,3">
<Button Name="export" Click="Export_Click" Background="{Binding Background, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Foreground="White" Grid.Column="0" BorderThickness="0" Content="{TemplateBinding Text}" HorizontalContentAlignment="Left" VerticalContentAlignment="Center" Padding="8,0,0,0">
</Button>
</Border>
<!--右侧下拉button设置-->
<Border Grid.Column="1" BorderThickness="0">
<ToggleButton BorderThickness="3" Style="{StaticResource ComboxStyleBtn}" IsChecked="{Binding Path=IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" ClickMode="Press"></ToggleButton>
</Border>
<!--弹出popup整体设置-->
<Popup IsOpen="{TemplateBinding IsDropDownOpen}" Placement="Bottom" x:Name="Popup" Focusable="False" AllowsTransparency="False" PopupAnimation="Slide" >
<!--弹出popup边框-->
<Border CornerRadius="3" BorderThickness="0" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{TemplateBinding ActualWidth}" x:Name="DropDown" SnapsToDevicePixels="True">
<!--下拉幕布边界背景设置 MaxHeight="{TemplateBinding MaxDropDownHeight}"-->
<ScrollViewer Margin="0,0,0,0" Background="Gray" SnapsToDevicePixels="True" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" BorderBrush="Gray" BorderThickness="0" >
<!--StackPanel 用于显示子级,方法是将 IsItemsHost 设置为 True-->
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" Background="#BB2775b6" />
</ScrollViewer>
</Border>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="RepeatButtonTransparent" TargetType="{x:Type RepeatButton}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Background" Value="#2775b6"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border Background="{TemplateBinding Background}" Height="{TemplateBinding Height}" Width="{TemplateBinding Width}" CornerRadius="4"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 这里是listview滚动条的滑动块部分样式-->
<Style x:Key="ScrollBarThumbVertical" TargetType="{x:Type Thumb}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border x:Name="rectangle" Background="#BB2775b6" Height="{TemplateBinding Height}" SnapsToDevicePixels="True" Width="{TemplateBinding Width}" CornerRadius="4"/>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" TargetName="rectangle" Value="#772775b6"/>
</Trigger>
<Trigger Property="IsDragging" Value="true">
<Setter Property="Background" TargetName="rectangle" Value="#772775b6"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="ListViewItemContentTemplate">
<Grid Margin="0">
<Image Width="40" Height="40" Margin="10" VerticalAlignment="Top" HorizontalAlignment="Left" Source="{Binding Avatar}" />
<Label Margin="60,8,0,0" FontWeight="Bold" VerticalAlignment="Top" HorizontalAlignment="Left" Content="{Binding NickName}" Width="130"/>
<Label Margin="60,25,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="140" Content="{Binding LastMsg}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="MsgText">
<Grid Margin="0">
<Label Margin="60,8,0,0" FontWeight="Bold" VerticalAlignment="Top" Content="{Binding NickName}"/>
<Label Margin="60,25,0,8" VerticalAlignment="Top" HorizontalAlignment="Left" Width="380">
<TextBlock Text="{Binding DisplayContent}" TextWrapping="Wrap" />
</Label>
</Grid>
</DataTemplate>
<DataTemplate x:Key="MsgImage">
<Grid Margin="0">
<Label Margin="60,8,0,0" FontWeight="Bold" VerticalAlignment="Top" HorizontalAlignment="Left" Content="{Binding NickName}" Width="130"/>
<Label Margin="60,25,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="140" Content="1111"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="MsgAudio">
<Grid Margin="0">
<Image Width="40" Height="40" Margin="10" VerticalAlignment="Top" HorizontalAlignment="Left" Source="{Binding Avatar}" />
<Label Margin="60,8,0,0" FontWeight="Bold" VerticalAlignment="Top" HorizontalAlignment="Left" Content="{Binding NickName}" Width="130"/>
<Label Margin="60,25,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="140" Content="{Binding LastMsg}"/>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid>
<TextBox Name="txt_find_user" Padding="10,0" Height="30" VerticalAlignment="Top" VerticalContentAlignment="Center" FontSize="14" HorizontalAlignment="Left" Width="230" BorderThickness="0,0,10,1" BorderBrush="#2775b6" TextChanged="txt_find_user_TextChanged" GotFocus="txt_find_user_GotFocus" Text="{Binding SearchString, Mode=TwoWay}" />
<ListView Margin="0,30,0,0" Background="Transparent" HorizontalAlignment="Left" Width="230" Name="list_users" ItemTemplate="{DynamicResource ListViewItemContentTemplate}" BorderThickness="0,0,1,0" BorderBrush="#2775b6" SelectionChanged="list_users_SelectionChanged" ItemsSource="{Binding Contacts}">
<ListView.Resources>
<Style TargetType="{x:Type ScrollBar}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollBar}">
<Grid x:Name="Bg" SnapsToDevicePixels="true" Width="8" HorizontalAlignment="Right">
<Grid.RowDefinitions>
<RowDefinition Height="0"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="0"/>
</Grid.RowDefinitions>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Row="1" CornerRadius="5"/>
<Track x:Name="PART_Track" IsDirectionReversed="true" IsEnabled="{TemplateBinding IsMouseOver}" Grid.Row="1">
<Track.DecreaseRepeatButton>
<RepeatButton Command="{x:Static ScrollBar.PageUpCommand}" Style="{StaticResource RepeatButtonTransparent}" HorizontalAlignment="Right" Width="8"/>
</Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton>
<RepeatButton Command="{x:Static ScrollBar.PageDownCommand}" Style="{StaticResource RepeatButtonTransparent}" HorizontalAlignment="Right" Width="8"/>
</Track.IncreaseRepeatButton>
<Track.Thumb>
<Thumb Style="{StaticResource ScrollBarThumbVertical}" Width="8" />
</Track.Thumb>
</Track>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.Resources>
</ListView>
<Label Content="{Binding WXContact.NickName}" HorizontalAlignment="Left" Margin="258,21,0,0" VerticalAlignment="Top"/>
<ListView x:Name="list_msg" Margin="230,60,0,60" Background="Transparent" BorderThickness="0,1,0,1" BorderBrush="#BB2775b6" ItemTemplateSelector="{StaticResource MsgTemplateSelector}" ItemsSource="{Binding WXMsgs}" ScrollViewer.ScrollChanged="list_msg_ScrollChanged" >
</ListView>
<ComboBox Name="cb_export" Width="120" Height="30" Style="{StaticResource ComboBoxStyle}" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="30,15" ItemsSource="{Binding ExportItems}" SelectedItem="{Binding SelectExportItem}" DisplayMemberPath="Name" SelectedValuePath="Value" IsEnabled="{Binding SelectContact}" Background="#2775b6" />
<Button x:Name="btn_open_workspace" Width="80" Height="30" Style="{StaticResource ButtonStyle}" Content="打开文件夹" BorderBrush="Transparent" BorderThickness="0" Background="#2775b6" Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,390,15" Click="btn_open_workspace_Click">
<Button.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="3"/>
</Style>
</Button.Resources>
</Button>
<Button x:Name="btn_pre_emoji" Width="80" Height="30" Style="{StaticResource ButtonStyle}" Content="表情预下载" BorderBrush="Transparent" BorderThickness="0" Background="#2775b6" Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,292,15" IsEnabled="{Binding SelectContact}" Click="btn_pre_emoji_Click" >
<Button.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="3"/>
</Style>
</Button.Resources>
</Button>
<Label Content="{Binding ExportCount}" HorizontalAlignment="Right" Margin="0,0,160,15" VerticalAlignment="Bottom"/>
</Grid>
</Page>

345
Pages/Workspace.xaml.cs Normal file
View File

@@ -0,0 +1,345 @@
using JiebaNet.Segmenter;
using JiebaNet.Segmenter.Common;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using WechatBakTool.Export;
using WechatBakTool.Helpers;
using WechatBakTool.Model;
using WechatBakTool.ViewModel;
using WordCloudSharp;
using System.Drawing;
using System.Windows.Controls;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using System.Drawing.Imaging;
using System.Threading;
using System.Runtime.CompilerServices;
namespace WechatBakTool.Pages
{
/// <summary>
/// Workspace.xaml 的交互逻辑
/// </summary>
public partial class Workspace : Page
{
public WXUserReader? UserReader;
private WorkspaceViewModel ViewModel = new WorkspaceViewModel();
private int PageSize = 100;
private int Postion = 0;
private bool Loading = false;
public Workspace()
{
ViewModel.ExportItems = new System.Collections.ObjectModel.ObservableCollection<ExportItem> {
new ExportItem(){ Name="导出为HTML",Value=1 },
new ExportItem(){ Name="导出为TXT",Value=2 },
new ExportItem(){ Name="与他的词云",Value=3 },
};
ViewModel.SelectExportItem = ViewModel.ExportItems[0];
InitializeComponent();
list_users.Items.Clear();
DataContext = ViewModel;
UserBakConfig? config = Main2.CurrentUserBakConfig;
if (config != null)
{
UserReader = new WXUserReader(config);
if (config.Decrypt)
{
ViewModel.Contacts = null;
Task.Run(() => {
ViewModel.Contacts = UserReader.GetWXContacts();
});
}
}
}
private void btn_read_Click(object sender, RoutedEventArgs e)
{
if (Main2.CurrentUserBakConfig == null)
{
MessageBox.Show("工作区配置加载失败,请检查配置文件是否正常","错误");
return;
}
}
private void list_users_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ViewModel.WXMsgs.Clear();
Postion = 0;
ViewModel.ExportCount = "";
loadMsg();
if (ViewModel.WXMsgs.Count == 0)
return;
}
private void loadMsg()
{
Loading = true;
ViewModel.WXContact = list_users.SelectedItem as WXContact;
if (ViewModel.WXContact == null || UserReader == null)
return;
List<WXMsg>? list = UserReader.GetWXMsgs(ViewModel.WXContact.UserName, Postion, PageSize);
// Trace.WriteLine(string.Format("{0}->{1}", PageSize, Postion));
if (list == null)
return;
if (list.Count == 0)
return;
foreach (WXMsg w in list)
{
ViewModel.WXMsgs.Add(w);
}
Postion = int.Parse(list.Max(x => x.CreateTime).ToString());
list_msg.ScrollIntoView(list[0]);
Task.Run(() => {
Thread.Sleep(500);
Loading = false;
});
}
private void txt_find_user_TextChanged(object sender, TextChangedEventArgs e)
{
if (UserReader == null)
return;
string findName = txt_find_user.Text;
if (txt_find_user.Text == "搜索...")
findName = "";
Task.Run(() =>
{
ViewModel.Contacts = UserReader.GetWXContacts(findName);
// 保底回落搜索已删除人员
if (ViewModel.Contacts.Count == 0)
{
var i = UserReader.GetWXMsgs(txt_find_user.Text);
if (i != null)
{
var g = i.GroupBy(x => x.StrTalker);
ViewModel.Contacts = new System.Collections.ObjectModel.ObservableCollection<WXContact>();
foreach (var x in g)
{
string name = x.Key;
ViewModel.Contacts.Add(new WXContact() { UserName = name, NickName = name });
}
}
}
});
}
private void txt_find_user_GotFocus(object sender, RoutedEventArgs e)
{
if (txt_find_user.Text == "搜索...")
txt_find_user.Text = "";
Debug.WriteLine(ViewModel.SearchString);
}
private void btn_export_Click(object sender, RoutedEventArgs e)
{
if(ViewModel.WXContact == null || UserReader == null)
{
MessageBox.Show("请选择联系人", "错误");
return;
}
try
{
string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, ViewModel.WXContact.UserName + ".txt");
IExport export = new TXTExport();
export.InitTemplate(ViewModel.WXContact, path);
export.SetMsg(UserReader, ViewModel.WXContact, ViewModel);
export.SetEnd();
export.Save(path);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
MessageBox.Show("导出完成");
}
private void btn_open_workspace_Click(object sender, RoutedEventArgs e)
{
Process.Start("explorer.exe ", Main2.CurrentUserBakConfig!.UserWorkspacePath);
}
private void Export_Click(object sender, RoutedEventArgs e)
{
Task.Run(() =>
{
if (ViewModel.WXContact == null || UserReader == null)
{
MessageBox.Show("请选择联系人", "错误");
return;
}
if (ViewModel.SelectExportItem == null)
{
MessageBox.Show("请选择导出方式", "错误");
return;
}
if(ViewModel.SelectExportItem.Value == 3)
{
if(UserReader != null && ViewModel.WXContact != null)
{
System.Drawing.Image? mask = null;
if (File.Exists("mask.png"))
mask = System.Drawing.Image.FromFile("mask.png");
WordCloudSettingViewModel setting = new WordCloudSettingViewModel() {
ImgWidth = mask == null ? "1000": mask.Width.ToString(),
ImgHeight = mask == null ? "1000" : mask.Height.ToString(),
EnableRemoveOneKey = true,
};
Dispatcher.Invoke(() => {
WordCloudSetting wordCloudSetting = new WordCloudSetting(setting);
wordCloudSetting.ShowDialog();
});
var jieba = new JiebaSegmenter();
Counter<string> counter = new Counter<string>();
try
{
ViewModel.ExportCount = "词频统计ing...";
List<WXMsg>? msgs = UserReader.GetWXMsgs(ViewModel.WXContact.UserName);
if (msgs != null)
{
foreach (WXMsg msg in msgs)
{
if (msg.Type == 1)
{
List<string> list = jieba.Cut(msg.StrContent).ToList();
counter.Add(list);
}
}
}
}
catch
{
ViewModel.ExportCount = "异常";
MessageBox.Show("词频统计发生异常,请检查字典文件是否存在", "错误");
return;
}
var orderBy = counter.MostCommon();
ViewModel.ExportCount = "移除部分词语...";
string[] remove_string_list = setting.RemoveKey.Split(",");
foreach(string remove_string in remove_string_list)
{
counter.Remove(remove_string);
}
foreach(var key in orderBy)
{
if (key.Key.Length == 1 && setting.EnableRemoveOneKey)
counter.Remove(key.Key);
}
ViewModel.ExportCount = "渲染词云结果";
string resultPath = "result.jpg";
WordCloud wordCloud;
if(mask != null)
wordCloud = new WordCloud(int.Parse(setting.ImgWidth), int.Parse(setting.ImgHeight), mask: mask, allowVerical: true, fontname: setting.Font);
else
wordCloud = new WordCloud(int.Parse(setting.ImgWidth), int.Parse(setting.ImgHeight), allowVerical: true, fontname: setting.Font);
if (orderBy.Count() >= setting.MaxKeyCount)
orderBy = orderBy.Take(setting.MaxKeyCount);
var result = wordCloud.Draw(orderBy.Select(it => it.Key).ToList(), orderBy.Select(it => it.Value).ToList());
result.Save(resultPath);
ViewModel.ExportCount = "完成";
MessageBox.Show("生成完毕请查看软件根目录result.jpg", "提示");
}
return;
}
string name = ViewModel.WXContact.NickName;
name = name.Replace(@"\", "");
name = Regex.Replace(name, "[ \\[ \\] \\^ \\-_*×――(^)$%~!/@#$…&%¥—+=<>《》|!??::•`·、。,;,.;\"‘’“”-]", "");
string path = Path.Combine(
Main2.CurrentUserBakConfig!.UserWorkspacePath,
string.Format(
"{0}-{1}",
ViewModel.WXContact.UserName,
ViewModel.WXContact.Remark == "" ? name : ViewModel.WXContact.Remark
)
);
IExport export;
#if DEBUG
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
#endif
if (ViewModel.SelectExportItem.Value == 2)
{
path += ".txt";
export = new TXTExport();
}
else
{
path += ".html";
export = new HtmlExport();
}
export.InitTemplate(ViewModel.WXContact, path);
export.SetMsg(UserReader, ViewModel.WXContact, ViewModel);
export.SetEnd();
export.Save(path);
#if DEBUG
stopwatch.Stop();
MessageBox.Show(stopwatch.Elapsed.ToString());
#endif
MessageBox.Show("导出完成", "提示");
});
}
private void btn_pre_emoji_Click(object sender, RoutedEventArgs e)
{
if(UserReader != null && ViewModel.WXContact != null)
{
Task.Run(() => {
UserReader.PreDownloadEmoji(ViewModel.WXContact.UserName);
MessageBox.Show("用户所有表情预下载完毕");
});
}
}
private void list_msg_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (ViewModel.WXMsgs.Count == 0)
return;
if (e.VerticalOffset + e.ViewportHeight == e.ExtentHeight && !Loading)
{
// 滚动条到达底部的处理逻辑
loadMsg();
}
}
}
}

108
README.md
View File

@@ -1,32 +1,86 @@
# WechatPCMsgBakTool
微信PC聊天记录备份工具仅支持Windows
- 支持3.9.6.33版本后若版本更新可在version.json添加版本号和地址即可完成新版本支持
- 导出图片、视频、音频
- 导出Html文件
- 支持聊天频率分析,全消息库内容搜索
# WechatBakTool
基于C#开发的微信聊天记录备份分析工具,努力做最好用的微信备份工具。
本项目仅做学习使用,主要供个人备份自己的微信记录,请勿用于非法用途。
本项目严禁商用
如果有什么好的建议或意见或者遇到什么问题欢迎提issue看到会回。
- 理论支持64位版本所有微信支持两种方式非直接地址获取Key[1]
- 工作区概念,支持多微信切换操作
- 支持导出Html文件TXT文件支持批量导出
- 支持聊天频率分析,全消息库内容搜索
- 目前支持以下类型消息解析
- [x] 文本消息
- [x] 图片
- [x] 语音
- [x] 分享链接
- [x] 群聊
- [x] 系统消息
- [x] 文件
- [x] 引用/转发消息
- [x] 表情(需要预下载)
#### 使用
<p>1.打开微信,并登录。</p>
<p>2.在工作区上方点击新增,选择要创建的工作区微信</p>
<p>3.点新建会弹出Handle64的协议说明同意即可。如没有内容显示重新点一下新建即可</p>
<p>4.选中刚刚创建的工作区,点击解密。(如当前多开微信,请选择对应的微信进行解谜)</p>
<p>5.选中刚刚创建的工作区,点击读取</p>
<p><b>尽情使用吧!</b></p>
如果有什么好的建议或意见或者遇到什么问题欢迎提issue看到会回。
#### 注意
<p>本项目基于.NET开发需要安装.NET Desktop Runtime如未安装双击EXE时会提示。</p>
<p>如果使用过程中发生崩溃请删除工作区试一下工作区即根据用户名在运行目录下生成的md5文件夹。</p>
<p>已解密的工作区可以直接读取。</p>
<p>再次强调,主要用于个人备份自己微信使用,请勿用于非法用途,严禁商用!</p>
> [!NOTE]
> 反馈群815054692<br/>
> 如果觉得不错欢迎右上角点个star这是对作者的鼓励谢谢<br/>
> 进群请先Star项目然后问答消息留id<br/>
<br/>
#### 参考/引用
都是站在大佬们的肩膀上完成的项目,本项目 参考/引用 了以下 项目/文章 内代码。
##### [Mr0x01/WXDBDecrypt.NET](https://github.com/Mr0x01/WXDBDecrypt.NET)
##### [AdminTest0/SharpWxDump](https://github.com/AdminTest0/SharpWxDump)
##### [kn007/silk-v3-decoder](https://github.com/kn007/silk-v3-decoder)
##### [吾爱破解chenhahacjl/微信 DAT 图片解密 C#](https://www.52pojie.cn/forum.php?mod=viewthread&tid=1507922)
### 免责声明
**本项目仅供学习、研究使用,严禁商业使用**<br/>
**用于网络安全用途的,请确保在国家法律法规下使用**<br/>
**本项目完全免费,问你要钱的都是骗子**<br/>
**使用本项目初衷是作者研究微信数据库的运行使用,您使用本软件导致的后果,包含但不限于数据损坏,记录丢失等问题,作者不承担相关责任。**<br/>
**因软件特殊性质,请在使用时获得微信账号所有人授权,你当确保不侵犯他人个人隐私权,后果自行承担**<br/>
<br/>
### 隐私声明
**本项目不会上传任何你的数据至任何第三方系统**<br/>
**如果发生任何回传行为,请检查是否为第三方修改版本**<br/>
<br/>
### 近期开发规划
本项目技术栈为:
C# + .NET6.0 + WPF <br/>
- [x] ~~新版本UI界面开发~~
- [x] 完善各类消息支持(已经初步完成)
- [x] ~~词云~~
- [ ] 性能优化
- [ ] 打包资源文件夹
- [ ] 手动模式(合适离线分析)
- [ ] 年度报告类分析(等美术资源中,没有资源不做)
<br/>
### 部分问题
Q支持手机端吗<br/>
A<b>在手机端</b>使用迁移功能即可,路径:我->设置->聊天->聊天记录迁移与备份->迁移<br/>
<br/>
Q怎么导出全部的记录<br/>
A工作区->右键->管理,就见了。<br/>
<br/>
Q解密工作区提示no such teble:MSG怎么办<br/>
A基本上都是因为刚迁移完缓存没写入到数据库导致的建议迁移完重启一次微信后再创建工作区<br/>
<br/>
### 使用说明
0.安装.NET Desktop Runtime(注意是6.0版本的Desktop Runtime如已经安装忽略)<br/>
1.打开微信,并登录。<br/>
2.在软件左侧下方点击**新建工作区**<br/>
3.在**新建工作区界面**,选择要创建工作区的微信进程,并**确认下方微信号是否正确**<br/>
4.解密方式**推荐选择用户名推断查找**该方式理论支持所有64位版本微信。**但该模式需要确保微信账号正确**<br/>
5.新手请忽略其他选项,直接**点击创建工作区**,程序会自动进行工作区创建、解密。<br/><br/>
**工作区创建完毕,点击左侧工作区,尽情使用吧!**<br/>
<br/>
### 参考/引用
项目在开发过程中参考了以下项目或资料,有引用相关代码,如有需要,推荐您可以去参考下相关资料:
1. C#使用OpenSSL解密微信数据库这里注意一下64位适配问题注意dll引用另外解密的资源优化不是很好可以参考一下我改写的C#还需要注意一下超大文件的问题 [Mr0x01/WXDBDecrypt.NET](https://github.com/Mr0x01/WXDBDecrypt.NET)<br/>
2. C#使用地址获取微信Key [AdminTest0/SharpWxDump](https://github.com/AdminTest0/SharpWxDump)
3. 解密微信语音我是直接调用解密反正都要ffmpeg多一个也是多多两个也是多懒得头铁实现 [kn007/silk-v3-decoder](https://github.com/kn007/silk-v3-decoder)
4. 解密微信图片 [吾爱破解chenhahacjl/微信 DAT 图片解密 C#](https://www.52pojie.cn/forum.php?mod=viewthread&tid=1507922)
5. 参考了句柄名称实现,注意获取句柄别看这里,#10 这个issue就是血泪 [huiyadanli/RevokeMsgPatcher](https://github.com/huiyadanli/RevokeMsgPatcher)
6. 参考了句柄获取 [FuzzySecurity/Sharp-Suite](https://github.com/FuzzySecurity/Sharp-Suite)
7. 这个获取秘钥更通用一些 [SnowMeteors/GetWeChatKey](https://github.com/SnowMeteors/GetWeChatKey) ,用户名不是很稳定
### 其他声明
[1] 理论支持所有64位版本指用户名推断和公钥头推断地址直接获取方式需要version.json支持更新不是很及时。

79460
Resources/char_state_tab.json Normal file

File diff suppressed because it is too large Load Diff

17817
Resources/cn_synonym.txt Normal file

File diff suppressed because one or more lines are too long

349046
Resources/dict.txt Normal file

File diff suppressed because it is too large Load Diff

270132
Resources/idf.txt Normal file

File diff suppressed because it is too large Load Diff

89711
Resources/pos_prob_emit.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,258 @@
{
"E-e": -3.14e+100,
"E-d": -3.14e+100,
"E-g": -3.14e+100,
"E-f": -3.14e+100,
"E-a": -3.14e+100,
"E-c": -3.14e+100,
"E-b": -3.14e+100,
"E-m": -3.14e+100,
"S-rg": -10.275268591948773,
"E-o": -3.14e+100,
"E-n": -3.14e+100,
"E-i": -3.14e+100,
"E-h": -3.14e+100,
"E-k": -3.14e+100,
"E-j": -3.14e+100,
"E-u": -3.14e+100,
"E-t": -3.14e+100,
"E-w": -3.14e+100,
"E-v": -3.14e+100,
"E-q": -3.14e+100,
"E-p": -3.14e+100,
"E-s": -3.14e+100,
"M-bg": -3.14e+100,
"M-uj": -3.14e+100,
"E-y": -3.14e+100,
"E-x": -3.14e+100,
"E-z": -3.14e+100,
"B-uz": -3.14e+100,
"S-d": -3.903919764181873,
"M-rg": -3.14e+100,
"E-nt": -3.14e+100,
"B-d": -3.9750475297585357,
"B-uv": -3.14e+100,
"E-vi": -3.14e+100,
"B-mq": -6.78695300139688,
"M-rr": -3.14e+100,
"S-ag": -6.954113917960154,
"M-jn": -3.14e+100,
"E-l": -3.14e+100,
"M-rz": -3.14e+100,
"B-ud": -3.14e+100,
"S-an": -12.84021794941031,
"B-qg": -3.14e+100,
"B-ug": -3.14e+100,
"M-y": -3.14e+100,
"S-qg": -3.14e+100,
"S-z": -3.14e+100,
"S-y": -6.1970794699489575,
"S-x": -8.427419656069674,
"S-w": -3.14e+100,
"S-v": -3.053292303412302,
"S-u": -6.940320595827818,
"S-t": -3.14e+100,
"B-nrt": -4.985642733519195,
"S-r": -2.7635336784127853,
"S-q": -4.888658618255058,
"M-zg": -3.14e+100,
"S-o": -8.464460927750023,
"S-n": -3.8551483897645107,
"B-zg": -3.14e+100,
"S-l": -3.14e+100,
"S-k": -6.940320595827818,
"S-in": -3.14e+100,
"S-i": -3.14e+100,
"S-h": -8.650563207383884,
"S-g": -6.507826815331734,
"B-f": -5.491630418482717,
"S-e": -5.942513006281674,
"M-en": -3.14e+100,
"S-c": -4.786966795861212,
"S-b": -6.472888763970454,
"S-a": -3.9025396831295227,
"B-g": -3.14e+100,
"B-b": -5.018374362109218,
"B-c": -3.423880184954888,
"M-ug": -3.14e+100,
"B-a": -4.762305214596967,
"E-qe": -3.14e+100,
"M-x": -3.14e+100,
"E-nz": -3.14e+100,
"M-z": -3.14e+100,
"M-u": -3.14e+100,
"B-k": -3.14e+100,
"M-w": -3.14e+100,
"B-jn": -3.14e+100,
"S-yg": -13.533365129970255,
"B-o": -8.433498702146057,
"B-l": -4.905883584659895,
"B-m": -3.6524299819046386,
"M-m": -3.14e+100,
"M-l": -3.14e+100,
"M-o": -3.14e+100,
"M-n": -3.14e+100,
"M-i": -3.14e+100,
"M-h": -3.14e+100,
"B-t": -3.3647479094528574,
"M-ul": -3.14e+100,
"B-z": -7.045681111485645,
"M-d": -3.14e+100,
"M-mg": -3.14e+100,
"B-y": -9.844485675856319,
"M-a": -3.14e+100,
"S-nrt": -3.14e+100,
"M-c": -3.14e+100,
"M-uz": -3.14e+100,
"E-mg": -3.14e+100,
"B-i": -6.1157847275557105,
"M-b": -3.14e+100,
"E-uz": -3.14e+100,
"B-n": -1.6966257797548328,
"E-uv": -3.14e+100,
"M-ud": -3.14e+100,
"M-p": -3.14e+100,
"E-ul": -3.14e+100,
"E-mq": -3.14e+100,
"M-s": -3.14e+100,
"M-yg": -3.14e+100,
"E-uj": -3.14e+100,
"E-ud": -3.14e+100,
"S-ln": -3.14e+100,
"M-r": -3.14e+100,
"E-ng": -3.14e+100,
"B-r": -3.4098187790818413,
"E-en": -3.14e+100,
"M-qg": -3.14e+100,
"B-s": -5.522673590839954,
"S-rr": -3.14e+100,
"B-p": -4.200984132085048,
"B-dg": -3.14e+100,
"M-uv": -3.14e+100,
"S-zg": -3.14e+100,
"B-v": -2.6740584874265685,
"S-tg": -6.272842531880403,
"B-w": -3.14e+100,
"B-e": -8.563551830394255,
"M-k": -3.14e+100,
"M-j": -3.14e+100,
"B-df": -8.888974230828882,
"M-e": -3.14e+100,
"E-tg": -3.14e+100,
"M-t": -3.14e+100,
"E-nr": -3.14e+100,
"M-nrfg": -3.14e+100,
"B-nr": -2.2310495913769506,
"E-df": -3.14e+100,
"E-dg": -3.14e+100,
"S-jn": -3.14e+100,
"M-q": -3.14e+100,
"B-mg": -3.14e+100,
"B-ln": -3.14e+100,
"M-f": -3.14e+100,
"E-ln": -3.14e+100,
"E-yg": -3.14e+100,
"S-bg": -3.14e+100,
"E-ns": -3.14e+100,
"B-tg": -3.14e+100,
"E-qg": -3.14e+100,
"S-nr": -4.483663103956885,
"S-ns": -3.14e+100,
"M-vn": -3.14e+100,
"S-nt": -12.147070768850364,
"S-nz": -3.14e+100,
"S-ad": -11.048458480182255,
"B-yg": -3.14e+100,
"M-v": -3.14e+100,
"E-vn": -3.14e+100,
"S-ng": -4.913434861102905,
"M-g": -3.14e+100,
"M-nt": -3.14e+100,
"S-en": -3.14e+100,
"M-nr": -3.14e+100,
"M-ns": -3.14e+100,
"S-vq": -3.14e+100,
"B-uj": -3.14e+100,
"M-nz": -3.14e+100,
"B-qe": -3.14e+100,
"M-in": -3.14e+100,
"M-ng": -3.14e+100,
"S-vn": -11.453923588290419,
"E-zg": -3.14e+100,
"S-vi": -3.14e+100,
"S-vg": -5.9430181843676895,
"S-vd": -3.14e+100,
"B-ad": -6.680066036784177,
"E-rz": -3.14e+100,
"B-ag": -3.14e+100,
"B-vd": -9.044728760238115,
"S-mq": -3.14e+100,
"B-vi": -12.434752841302146,
"E-rr": -3.14e+100,
"B-rr": -12.434752841302146,
"M-vq": -3.14e+100,
"E-jn": -3.14e+100,
"B-vn": -4.3315610890163585,
"S-mg": -10.825314928868044,
"B-in": -3.14e+100,
"M-vi": -3.14e+100,
"M-an": -3.14e+100,
"M-vd": -3.14e+100,
"B-rg": -3.14e+100,
"M-vg": -3.14e+100,
"M-ad": -3.14e+100,
"M-ag": -3.14e+100,
"E-rg": -3.14e+100,
"S-uz": -9.299258625372996,
"B-en": -3.14e+100,
"S-uv": -8.15808672228609,
"S-df": -3.14e+100,
"S-dg": -8.948397651299683,
"M-qe": -3.14e+100,
"B-ng": -3.14e+100,
"E-bg": -3.14e+100,
"S-ul": -8.4153713175535,
"S-uj": -6.85251045118004,
"S-ug": -7.5394037026636855,
"B-ns": -2.8228438314969213,
"S-ud": -7.728230161053767,
"B-nt": -4.846091668182416,
"B-ul": -3.14e+100,
"E-in": -3.14e+100,
"B-bg": -3.14e+100,
"M-df": -3.14e+100,
"M-dg": -3.14e+100,
"M-nrt": -3.14e+100,
"B-j": -5.0576191284681915,
"E-ug": -3.14e+100,
"E-vq": -3.14e+100,
"B-vg": -3.14e+100,
"B-nz": -3.94698846057672,
"S-qe": -3.14e+100,
"B-rz": -7.946116471570005,
"B-nrfg": -5.873722175405573,
"E-ad": -3.14e+100,
"E-ag": -3.14e+100,
"B-u": -9.163917277503234,
"M-ln": -3.14e+100,
"B-an": -8.697083223018778,
"M-mq": -3.14e+100,
"E-an": -3.14e+100,
"S-s": -3.14e+100,
"B-q": -6.998123858956596,
"E-nrt": -3.14e+100,
"B-h": -13.533365129970255,
"E-r": -3.14e+100,
"S-p": -2.9868401813596317,
"M-tg": -3.14e+100,
"S-rz": -3.14e+100,
"S-nrfg": -3.14e+100,
"B-vq": -12.147070768850364,
"B-x": -3.14e+100,
"E-vd": -3.14e+100,
"E-nrfg": -3.14e+100,
"S-m": -3.269200652116097,
"E-vg": -3.14e+100,
"S-f": -5.194820249981676,
"S-j": -4.911992119644354
}

File diff suppressed because it is too large Load Diff

35234
Resources/prob_emit.json Normal file

File diff suppressed because it is too large Load Diff

18
Resources/prob_trans.json Normal file
View File

@@ -0,0 +1,18 @@
{
"M": {
"M": -1.2603623820268226,
"E": -0.33344856811948514
},
"S": {
"S": -0.6658631448798212,
"B": -0.7211965654669841
},
"B": {
"M": -0.916290731874155,
"E": -0.51082562376599
},
"E": {
"S": -0.8085250474669937,
"B": -0.5897149736854513
}
}

653
Resources/stopwords.txt Normal file
View File

@@ -0,0 +1,653 @@
i
me
my
myself
we
our
ours
ourselves
you
your
yours
yourself
yourselves
he
him
his
himself
she
her
hers
herself
it
its
itself
they
them
their
theirs
themselves
what
which
who
whom
this
that
these
those
am
is
are
was
were
be
been
being
have
has
had
having
do
does
did
doing
a
an
the
and
but
if
or
because
as
until
while
of
at
by
for
with
about
against
between
into
through
during
before
after
above
below
to
from
up
down
in
out
on
off
over
under
again
further
then
once
here
there
when
where
why
how
all
any
both
each
few
more
most
other
some
such
no
nor
not
only
own
same
so
than
too
very
s
t
can
will
just
don
should
now
一番
一直
一个
一些
许多
有的是
也就是说
哎呀
哎哟
俺们
按照
吧哒
罢了
本着
比方
比如
鄙人
彼此
别的
别说
并且
不比
不成
不单
不但
不独
不管
不光
不过
不仅
不拘
不论
不怕
不然
不如
不特
不惟
不问
不只
朝着
趁着
除此之外
除非
除了
此间
此外
从而
但是
当着
的话
等等
叮咚
对于
多少
而况
而且
而是
而外
而言
而已
尔后
反过来
反过来说
反之
非但
非徒
否则
嘎登
各个
各位
各种
各自
根据
故此
固然
关于
果然
果真
何处
何况
何时
哼唷
呼哧
还是
还有
换句话说
换言之
或是
或者
极了
及其
及至
即便
即或
即令
即若
即使
几时
既然
既是
继而
加之
假如
假若
假使
鉴于
较之
接着
结果
紧接着
进而
尽管
经过
就是
就是说
具体地说
具体说来
开始
开外
可见
可是
可以
况且
来着
例如
连同
两者
另外
另一方面
慢说
漫说
每当
莫若
某个
某些
哪边
哪儿
哪个
哪里
哪年
哪怕
哪天
哪些
哪样
那边
那儿
那个
那会儿
那里
那么
那么些
那么样
那时
那些
那样
乃至
你们
宁可
宁肯
宁愿
啪达
旁人
凭借
其次
其二
其他
其它
其一
其余
其中
起见
起见
岂但
恰恰相反
前后
前者
然而
然后
然则
人家
任何
任凭
如此
如果
如何
如其
如若
如上所述
若非
若是
上下
尚且
设若
设使
甚而
甚么
甚至
省得
时候
什么
什么样
使得
是的
首先
顺着
似的
虽然
虽说
虽则
随着
所以
他们
他人
它们
她们
倘或
倘然
倘若
倘使
通过
同时
万一
为何
为了
为什么
为着
嗡嗡
我们
呜呼
乌乎
无论
无宁
毋宁
相对而言
向着
沿
沿着
要不
要不然
要不是
要么
要是
也罢
也好
一旦
一方面
一来
一切
一样
一则
依照
以便
以及
以免
以至
以至于
以致
抑或
因此
因而
因为
由此可见
由于
有的
有关
有些
于是
于是乎
与此同时
与否
与其
越是
云云
再说
再者
在下
咱们
怎么办
怎么样
照着
这边
这儿
这个
这会儿
这就是说
这里
这么
这么点儿
这么些
这么样
这时
这些
这样
正如
之类
之所以
之一
只是
只限
只要
只有
至于
诸位
着呢
自从
自个儿
自各儿
自己
自家
自身
综上所述
总的来看
总的来说
总的说来
总而言之
总之
纵令
纵然
纵使
遵照
作为
喔唷
.
,
:
;
"
"
[
]
<
>
(
)
@
#
*
&
%
$
-
+
=
|
\

View File

@@ -1,24 +0,0 @@
<Window x:Class="WechatPCMsgBakTool.SelectWechat"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WechatPCMsgBakTool"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="选择微信" Height="300" Width="600">
<Grid>
<Label Content="请选择您要打开的微信:" HorizontalAlignment="Left" Margin="29,27,0,0" VerticalAlignment="Top"/>
<ListView Name="list_process" Margin="32,55,32,67" SelectionChanged="list_process_SelectionChanged">
<ListView.View>
<GridView>
<GridViewColumn Header="进程名" Width="80" DisplayMemberBinding="{Binding ProcessName}" />
<GridViewColumn Header="PID" Width="50" DisplayMemberBinding="{Binding ProcessId}" />
<GridViewColumn Header="路径" Width="300" DisplayMemberBinding="{Binding DBPath}" />
</GridView>
</ListView.View>
</ListView>
<Button Name="btn_close" Content="确定并返回" HorizontalAlignment="Left" Margin="240,231,0,0" VerticalAlignment="Top" Width="97" Click="btn_close_Click"/>
</Grid>
</Window>

View File

@@ -1,95 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using WechatPCMsgBakTool.Model;
namespace WechatPCMsgBakTool
{
/// <summary>
/// SelectWechat.xaml 的交互逻辑
/// </summary>
public partial class SelectWechat : Window
{
List<ProcessInfo> processInfos = new List<ProcessInfo>();
public ProcessInfo? SelectProcess { get; set; } = null;
public SelectWechat()
{
InitializeComponent();
GetWechatProcess();
}
public void GetWechatProcess()
{
Process p = new Process();
p.StartInfo.FileName = "tools/handle64.exe";
p.StartInfo.Arguments = "-p wechat.exe";
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
string i = p.StandardOutput.ReadToEnd();
if (i.Contains("SYSINTERNALS SOFTWARE LICENSE TERMS"))
{
MessageBox.Show("请先同意Handle64的使用协议同意后关闭弹窗重新打开新增工作区即可");
Process p1 = new Process();
p1.StartInfo.FileName = "tools/handle64.exe";
p1.StartInfo.Arguments = "-p wechat.exe";
p1.Start();
}
string[] lines = i.Split(new string[] { "\r\n" }, StringSplitOptions.None);
bool hitFind = false;
ProcessInfo processInfo = new ProcessInfo();
foreach (string line in lines)
{
if (line.Length < 6)
continue;
if (line.Substring(0, 6).ToLower() == "wechat")
{
hitFind = true;
processInfo = new ProcessInfo();
string[] lineInfo = line.Split(' ');
processInfo.ProcessName = lineInfo[0];
processInfo.ProcessId = lineInfo[2];
}
if (hitFind)
{
if (line.Substring(line.Length - 11, 11) == "MicroMsg.db")
{
Regex regex = new Regex("[a-zA-Z]:\\\\([a-zA-Z0-9() ]*\\\\)*\\w*.*\\w*");
string path = regex.Match(line).Value;
processInfo.DBPath = path;
processInfos.Add(processInfo);
hitFind = false;
}
}
}
list_process.ItemsSource = processInfos;
}
private void list_process_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectProcess = list_process.SelectedItem as ProcessInfo;
}
private void btn_close_Click(object sender, RoutedEventArgs e)
{
Close();
}
}
}

15
Tools.xaml Normal file
View File

@@ -0,0 +1,15 @@
<Window x:Class="WechatBakTool.Tools"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WechatBakTool"
mc:Ignorable="d"
Title="资源回退工具" Height="450" Width="800" WindowStartupLocation="CenterScreen">
<Grid>
<Button Name="back_video_file" Content="回退视频文件" HorizontalAlignment="Left" Margin="191,39,0,0" VerticalAlignment="Top" Height="28" Width="96" Click="back_video_file_Click"/>
<TextBox Name="txt_log" VerticalScrollBarVisibility="Auto" Margin="40,88,40,31" TextWrapping="Wrap" Text="" AcceptsReturn="True" IsReadOnly="True"/>
<ComboBox Name="list_workspace" DisplayMemberPath="UserName" HorizontalAlignment="Left" Margin="40,43,0,0" VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>

167
Tools.xaml.cs Normal file
View File

@@ -0,0 +1,167 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using WechatBakTool.Model;
namespace WechatBakTool
{
/// <summary>
/// Tools.xaml 的交互逻辑
/// </summary>
public partial class Tools : Window
{
public Tools()
{
InitializeComponent();
LoadWorkspace();
}
private void LoadWorkspace()
{
list_workspace.Items.Clear();
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "workspace");
if (Directory.Exists(path))
{
string[] files = Directory.GetFiles(path);
foreach (string file in files)
{
string type = file.Substring(file.Length - 5, 5);
if (type == ".json")
{
string jsonString = File.ReadAllText(file);
UserBakConfig? userBakConfig = null;
try
{
userBakConfig = JsonConvert.DeserializeObject<UserBakConfig>(jsonString);
}
catch
{
MessageBox.Show("读取到异常工作区文件,请确认备份数据是否正常\r\n文件路径" + file, "错误");
}
if (userBakConfig != null)
{
list_workspace.Items.Add(userBakConfig);
}
}
}
}
}
private void back_video_file_Click(object sender, RoutedEventArgs e)
{
Task.Run(() => {
UserBakConfig? selectConfig = null;
Dispatcher.Invoke(() => {
selectConfig = list_workspace.SelectedItem as UserBakConfig;
});
if (selectConfig != null)
{
if (!selectConfig.Decrypt)
{
MessageBox.Show("工作区未解密,请先用主程序进行解密");
return;
}
// 检查工作区视频文件夹
string video_dir = Path.Combine(selectConfig.UserWorkspacePath, "Video");
string[] files = Directory.GetFiles(video_dir);
if (!Directory.Exists(video_dir))
{
Dispatcher.Invoke(() => {
txt_log.Text += video_dir + "不存在\r\n";
txt_log.ScrollToEnd();
});
return;
}
WXUserReader UserReader = new WXUserReader(selectConfig);
// 获取用户
var atc_list = UserReader.GetWXMsgAtc();
if(atc_list == null)
{
Dispatcher.Invoke(() => {
txt_log.Text += "视频列表没有内容,无法回退\r\n";
txt_log.ScrollToEnd();
});
return;
}
foreach (string file in files)
{
FileInfo fileInfo = new FileInfo(file);
var search = atc_list.FindAll(x => x.attachPath.Contains(fileInfo.Name));
if (search != null)
{
WXSessionAttachInfo? select_atc = null;
if (search.Count > 1)
{
foreach (var s in search)
{
Dispatcher.Invoke(() =>
{
txt_log.Text += s + "\r\n";
txt_log.ScrollToEnd();
});
if (s.attachPath.Contains("_raw"))
select_atc = s;
}
}
else if (search.Count == 1)
select_atc = search[0];
else
{
Dispatcher.Invoke(() =>
{
txt_log.Text += "匹配不到文件\r\n";
txt_log.ScrollToEnd();
});
continue;
}
if (select_atc == null)
{
Dispatcher.Invoke(() =>
{
txt_log.Text += "匹配失败\r\n";
txt_log.ScrollToEnd();
});
continue;
}
// 建立路径
string source_video_file = Path.Combine(selectConfig.UserResPath, select_atc.attachPath);
if (File.Exists(source_video_file))
{
Dispatcher.Invoke(() => {
txt_log.Text += source_video_file + "已经存在\r\n";
txt_log.ScrollToEnd();
});
continue;
}
else
{
Dispatcher.Invoke(() => {
txt_log.Text += source_video_file + "开始发起回退\r\n";
txt_log.ScrollToEnd();
});
File.Copy(fileInfo.FullName, source_video_file);
}
}
}
}
});
}
}
}

Binary file not shown.

View File

@@ -0,0 +1,54 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using WechatBakTool.Model;
using WechatBakTool.Pages;
namespace WechatBakTool.ViewModel
{
public partial class CreateWorkViewModel : ObservableObject
{
[ObservableProperty]
private List<ProcessInfo> processInfos = new List<ProcessInfo>();
[ObservableProperty]
private ProcessInfo? selectProcess;
[ObservableProperty]
private string userName = "";
[ObservableProperty]
private int keyType = -1;
[ObservableProperty]
private bool isEnable = true;
private string labelStatus = "-";
public string LabelStatus
{
get { return "状态:" + labelStatus; }
set
{
labelStatus = value;
OnPropertyChanged("LabelStatus");
}
}
}
public class GetKeyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (int.Parse(parameter.ToString()!) == (int)value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value ? parameter : Binding.DoNothing;
}
}
}

View File

@@ -0,0 +1,33 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WechatBakTool.ViewModel
{
public partial class WordCloudSettingViewModel : ObservableObject
{
[ObservableProperty]
private string imgHeight = "";
[ObservableProperty]
private string imgWidth = "";
[ObservableProperty]
private bool enableRemoveOneKey = true;
[ObservableProperty]
private string removeKey = "";
[ObservableProperty]
private int maxKeyCount = 200;
[ObservableProperty]
private string font = "微软雅黑";
[ObservableProperty]
private List<string> fontList = new List<string>();
}
}

View File

@@ -0,0 +1,80 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WechatBakTool.Model;
namespace WechatBakTool.ViewModel
{
public partial class WorkspaceViewModel : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(SelectContact))]
[NotifyPropertyChangedFor(nameof(LabelStatus))]
private WXContact? wXContact = null;
[ObservableProperty]
private ObservableCollection<WXMsg> wXMsgs = new ObservableCollection<WXMsg>();
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(LabelStatus))]
private string exportCount = "";
public string LabelStatus
{
get
{
if (WXContact == null)
return ExportCount;
string name = WXContact.NickName;
if(WXContact.Remark != "")
name = WXContact.Remark;
return string.Format("{0}:{1}", name, ExportCount);
}
}
public bool SelectContact
{
get
{
if (WXContact == null)
return false;
else
return true;
}
}
[ObservableProperty]
private ObservableCollection<WXContact>? contacts;
[ObservableProperty]
private ObservableCollection<ExportItem>? exportItems;
[ObservableProperty]
private ExportItem? selectExportItem;
private string searchString = "";
public string SearchString
{
set
{
if (value == "搜索...")
searchString = "";
else
searchString = value;
OnPropertyChanged("SearchString");
}
get
{
if (searchString == "")
return "搜索...";
return searchString;
}
}
}
}

View File

@@ -1,27 +1,44 @@
using SQLite;
using K4os.Compression.LZ4.Encoders;
using K4os.Compression.LZ4;
using SQLite;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net.Mime;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using System.Xml;
using System.Xml.Linq;
using WechatPCMsgBakTool.Helpers;
using WechatPCMsgBakTool.Model;
using WechatBakTool.Helpers;
using WechatBakTool.Model;
using System.Windows;
using System.Net.Http;
using System.Reflection.Metadata;
using System.Threading;
using Newtonsoft.Json;
namespace WechatPCMsgBakTool
namespace WechatBakTool
{
public class WXUserReader
{
private Dictionary<string, SQLiteConnection> DBInfo = new Dictionary<string, SQLiteConnection>();
private UserBakConfig? UserBakConfig = null;
private Hashtable HeadImgCache = new Hashtable();
private Hashtable UserNameCache = new Hashtable();
private Hashtable EmojiCache = new Hashtable();
private HttpClient httpClient = new HttpClient();
public WXUserReader(UserBakConfig userBakConfig) {
string path = Path.Combine(userBakConfig.UserWorkspacePath, "DecDB");
UserBakConfig = userBakConfig;
LoadDB(path);
InitCache();
EmojiCacheInit();
}
public void LoadDB(string path)
@@ -38,59 +55,446 @@ namespace WechatPCMsgBakTool
}
}
public List<WXContact>? GetWXContacts(string? name = null)
private SQLiteConnection? getCon(string name)
{
SQLiteConnection con = DBInfo["MicroMsg"];
if (con == null)
return null;
string query = "select * from contact";
if (name != null)
if (DBInfo.ContainsKey(name))
{
query = "select * from contact where username = ? or alias = ?";
return con.Query<WXContact>(query, name, name);
return DBInfo[name];
}
else
{
return null;
}
return con.Query<WXContact>(query);
}
public void InitCache()
{
SQLiteConnection? con = getCon("Misc");
if (con == null)
return;
string query = @"SELECT * FROM ContactHeadImg1";
List<ContactHeadImg> imgs = con.Query<ContactHeadImg>(query);
foreach(ContactHeadImg item in imgs)
{
if (!HeadImgCache.ContainsKey(item.usrName))
{
HeadImgCache.Add(item.usrName, item);
}
}
List<WXContact> contacts = GetWXContacts(null, true).ToList();
foreach(WXContact contact in contacts)
{
if (!UserNameCache.ContainsKey(contact.UserName))
UserNameCache.Add(contact.UserName, contact);
}
}
public void EmojiCacheInit()
{
string emoji_path = Path.Combine(UserBakConfig!.UserWorkspacePath, "Emoji");
if (Directory.Exists(emoji_path))
{
string[] files = Directory.GetFiles(emoji_path);
foreach (string file in files)
{
FileInfo fileInfo = new FileInfo(file);
string[] names = fileInfo.Name.Split(".");
if (!EmojiCache.ContainsKey(names[0]))
{
EmojiCache.Add(names[0], 1);
}
}
}
}
public void PreDownloadEmoji(string username = "")
{
if (UserBakConfig == null)
return;
HttpClientHandler handler = new HttpClientHandler() { AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate };
HttpClient httpClient = new HttpClient(handler);
List<WXMsg> msgs = GetTypeMsg("47", username);
int i = 0;
// 下载前的Emoji Cache不用做了在Init的时候已经做了
foreach (var msg in msgs)
{
i++;
if (i % 5 == 0)
{
// 每5次让下载线程休息1秒
Thread.Sleep(1000);
}
try
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(msg.StrContent);
XmlNode? node = xmlDocument.SelectSingleNode("/msg/emoji");
if (node != null)
{
if (node.Attributes != null)
{
string type = "";
string md5 = "";
string url = "";
XmlNode? item = node.Attributes.GetNamedItem("type");
type = item != null ? item.InnerText : "";
item = node.Attributes.GetNamedItem("md5");
md5 = item != null ? item.InnerText : "";
item = node.Attributes.GetNamedItem("cdnurl");
url = item != null ? item.InnerText : "";
if (EmojiCache.ContainsKey(md5))
{
i--;
continue;
}
if (url == "")
{
i--;
continue;
}
else
{
string path = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "Emoji", md5 + ".gif");
try
{
HttpResponseMessage res = httpClient.GetAsync(url).Result;
if (res.IsSuccessStatusCode)
{
using (FileStream fs = File.Create(path))
{
res.Content.ReadAsStream().CopyTo(fs);
}
}
}
catch (Exception ex)
{
}
}
}
}
}
catch (Exception ex)
{
}
}
// 下载完成后可能变化,检查一下
EmojiCacheInit();
}
public byte[]? GetHeadImgCahce(string username)
{
if (HeadImgCache.ContainsKey(username))
{
ContactHeadImg? img = HeadImgCache[username] as ContactHeadImg;
if (img == null)
return null;
else
return img.smallHeadBuf;
}
return null;
}
public int[] GetWXCount()
{
SQLiteConnection? con = getCon("MicroMsg");
if (con == null)
return new int[] { 0, 0 };
string query = @"select count(*) as count from contact where type != 4";
int userCount = con.Query<WXCount>(query)[0].Count;
int msgCount = 0;
for (int i = 0; i <= 99; i++)
{
con = getCon("MSG" + i.ToString());
if (con == null)
return new int[] { userCount, msgCount };
query = "select count(*) as count from MSG";
msgCount += con.Query<WXCount>(query)[0].Count;
}
return new int[] { userCount, msgCount };
}
public ObservableCollection<WXContact> GetWXContacts(string? name = null,bool all = false)
{
SQLiteConnection? con = getCon("MicroMsg");
if (con == null)
return new ObservableCollection<WXContact>();
string query = @"select contact.*,session.strContent,contactHeadImgUrl.smallHeadImgUrl,contactHeadImgUrl.bigHeadImgUrl from contact
left join session on session.strUsrName = contact.username
left join contactHeadImgUrl on contactHeadImgUrl.usrName = contact.username
where type != 4 {searchName}
order by nOrder desc";
if (all)
{
query = query.Replace("where type != 4 ", "");
}
List<WXContact>? contacts = null;
if (name != null)
{
query = query.Replace("{searchName}", " and (username like ? or alias like ? or nickname like ? or remark like ?)");
contacts = con.Query<WXContact>(query, $"%{name}%", $"%{name}%", $"%{name}%", $"%{name}%");
}
else
{
query = query.Replace("{searchName}", "");
contacts = con.Query<WXContact>(query);
}
foreach (WXContact contact in contacts)
{
if(contact.Remark != "")
contact.NickName = contact.Remark;
byte[]? imgBytes = GetHeadImgCahce(contact.UserName);
if (imgBytes != null)
{
try
{
MemoryStream stream = new MemoryStream(imgBytes);
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
bitmapImage.StreamSource = stream;
bitmapImage.EndInit();
bitmapImage.Freeze();
contact.Avatar = bitmapImage;
}
catch
{
#if DEBUG
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "BitmapConvert Err=>Length", imgBytes.Length));
#endif
}
}
else
continue;
}
return new ObservableCollection<WXContact>(contacts);
}
public List<WXUserImg>? GetUserImgs()
{
SQLiteConnection? con = getCon("MicroMsg");
if (con == null)
return null;
string query = "select * from contactHeadImgUrl";
return con.Query<WXUserImg>(query);
}
public List<WXChatRoom>? GetWXChatRooms()
{
SQLiteConnection? con = getCon("MicroMsg");
if (con == null)
return null;
string query = "select * from ChatRoom";
return con.Query<WXChatRoom>(query);
}
public List<WXMsg> GetTypeMsg(string type,string username)
{
List<WXMsg> tmp = new List<WXMsg>();
for (int i = 0; i <= 99; i++)
{
SQLiteConnection? con = getCon("MSG" + i.ToString());
if (con == null)
return tmp;
List<WXMsg> wXMsgs;
if (username == "")
{
string query = "select * from MSG where Type=?";
wXMsgs = con.Query<WXMsg>(query, type);
}
else
{
string query = "select * from MSG where Type=? and StrTalker = ?";
wXMsgs = con.Query<WXMsg>(query, type, username);
}
tmp.AddRange(wXMsgs);
}
return tmp;
}
public List<WXMsg>? GetWXMsgs(string uid,int time,int page)
{
List<WXMsg> tmp = new List<WXMsg>();
for (int i = 0; i <= 99; i++)
{
SQLiteConnection? con = getCon("MSG" + i.ToString());
if (con == null)
return tmp;
List<WXMsg>? wXMsgs = null;
string query = "select * from MSG where StrTalker=? and CreateTime>? Limit ?";
wXMsgs = con.Query<WXMsg>(query, uid, time, page);
if (wXMsgs.Count != 0) {
return ProcessMsg(wXMsgs, uid);
}
}
return tmp;
}
public List<WXMsg>? GetWXMsgs(string uid,string msg = "")
{
List<WXMsg> tmp = new List<WXMsg>();
for (int i = 0; i <= 99; i++)
{
if(DBInfo.ContainsKey("MSG" + i.ToString()))
SQLiteConnection? con = getCon("MSG" + i.ToString());
if (con == null)
return tmp;
List<WXMsg>? wXMsgs = null;
if (msg == "")
{
SQLiteConnection con = DBInfo["MSG" + i.ToString()];
if (con == null)
return tmp;
List<WXMsg>? wXMsgs = null;
if (msg == "")
{
string query = "select * from MSG where StrTalker=?";
wXMsgs = con.Query<WXMsg>(query, uid);
}
else if(uid == "")
{
string query = "select * from MSG where StrContent like ?";
wXMsgs = con.Query<WXMsg>(query, string.Format("%{0}%", msg));
}
else
{
string query = "select * from MSG where StrTalker=? and StrContent like ?";
wXMsgs = con.Query<WXMsg>(query, uid, string.Format("%{0}%", msg));
}
foreach (WXMsg w in wXMsgs)
{
tmp.Add(w);
}
string query = "select * from MSG where StrTalker=?";
wXMsgs = con.Query<WXMsg>(query, uid);
}
else if (uid == "")
{
string query = "select * from MSG where StrContent like ?";
wXMsgs = con.Query<WXMsg>(query, string.Format("%{0}%", msg));
}
else
{
string query = "select * from MSG where StrTalker=? and StrContent like ?";
wXMsgs = con.Query<WXMsg>(query, uid, string.Format("%{0}%", msg));
}
tmp.AddRange(ProcessMsg(wXMsgs, uid));
}
return tmp;
}
private List<WXMsg> ProcessMsg(List<WXMsg> msgs,string uid)
{
foreach (WXMsg w in msgs)
{
if (UserNameCache.ContainsKey(w.StrTalker))
{
WXContact? contact = UserNameCache[w.StrTalker] as WXContact;
if (contact != null)
{
if (contact.Remark != "")
w.NickName = contact.Remark;
else
w.NickName = contact.NickName;
w.StrTalker = contact.UserName;
}
}
else
{
w.NickName = uid;
}
// 群聊处理
if (uid.Contains("@chatroom"))
{
string userId = "";
if (w.BytesExtra == null)
continue;
string sl = BitConverter.ToString(w.BytesExtra).Replace("-", "");
ProtoMsg protoMsg;
using (MemoryStream stream = new MemoryStream(w.BytesExtra))
{
protoMsg = ProtoBuf.Serializer.Deserialize<ProtoMsg>(stream);
}
if (protoMsg.TVMsg != null)
{
foreach (TVType _tmp in protoMsg.TVMsg)
{
if (_tmp.Type == 1)
userId = _tmp.TypeValue;
}
}
if (!w.IsSender)
{
if (UserNameCache.ContainsKey(userId))
{
WXContact? contact = UserNameCache[userId] as WXContact;
if (contact != null)
w.NickName = contact.Remark == "" ? contact.NickName : contact.Remark;
}
else
{
w.NickName = userId;
}
}
}
// 发送人名字处理
if (w.IsSender)
w.NickName = "我";
w.DisplayContent = w.StrContent;
// 额外格式处理
if (w.Type != 1)
{
if (w.Type == 10000)
{
w.Type = 1;
w.NickName = "系统消息";
w.DisplayContent = w.StrContent.Replace("<revokemsg>", "").Replace("</revokemsg>", "");
}
else if (w.Type == 49 && (w.SubType == 6 || w.SubType == 19 || w.SubType == 40))
{
WXSessionAttachInfo? attachInfos = GetWXMsgAtc(w);
if (attachInfos == null)
{
w.DisplayContent = "附件不存在";
}
else
{
w.DisplayContent = Path.Combine(UserBakConfig!.UserResPath, attachInfos.attachPath);
}
}
else
{
w.DisplayContent = "[界面未支持格式]Type=" + w.Type;
}
}
}
return msgs;
}
public List<WXSessionAttachInfo>? GetWXMsgAtc()
{
SQLiteConnection? con = getCon("MultiSearchChatMsg");
if (con == null)
return null;
string query = "select * from SessionAttachInfo";
List<WXSessionAttachInfo> list = con.Query<WXSessionAttachInfo>(query);
if (list.Count != 0)
{
return list;
}
else
return null;
}
public WXSessionAttachInfo? GetWXMsgAtc(WXMsg msg)
{
SQLiteConnection con = DBInfo["MultiSearchChatMsg"];
SQLiteConnection? con = getCon("MultiSearchChatMsg");
if (con == null)
return null;
@@ -119,17 +523,14 @@ namespace WechatPCMsgBakTool
{
for (int i = 0; i <= 99; i++)
{
if(DBInfo.ContainsKey("MediaMSG" + i.ToString()))
{
SQLiteConnection con = DBInfo["MediaMSG" + i.ToString()];
if (con == null)
continue;
SQLiteConnection? con = getCon("MediaMSG" + i.ToString());
if (con == null)
continue;
string query = "select * from Media where Reserved0=?";
List<WXMediaMsg> wXMsgs = con.Query<WXMediaMsg>(query, msg.MsgSvrID);
if (wXMsgs.Count != 0)
return wXMsgs[0];
}
string query = "select * from Media where Reserved0=?";
List<WXMediaMsg> wXMsgs = con.Query<WXMediaMsg>(query, msg.MsgSvrID);
if (wXMsgs.Count != 0)
return wXMsgs[0];
}
return null;
}
@@ -138,13 +539,14 @@ namespace WechatPCMsgBakTool
if (UserBakConfig == null)
return null;
string? tmpPath = Path.Combine(UserBakConfig.UserWorkspacePath, "Temp");
string? tmpPath = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "Temp");
if (!Directory.Exists(tmpPath))
Directory.CreateDirectory(tmpPath);
// 这部分是查找
// 如果是图片和视频,从附件库中搜索
string? path = null;
if (type == WXMsgType.Image || type == WXMsgType.Video)
if (type == WXMsgType.Image || type == WXMsgType.Video || type == WXMsgType.File)
{
WXSessionAttachInfo? atcInfo = GetWXMsgAtc(msg);
if (atcInfo == null)
@@ -168,27 +570,69 @@ namespace WechatPCMsgBakTool
}
path = tmp_file_path;
}
else if (type == WXMsgType.Emoji)
{
try
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(msg.StrContent);
XmlNode? node = xmlDocument.SelectSingleNode("/msg/emoji");
if (node != null)
{
if (node.Attributes != null)
{
XmlNode? item = node.Attributes.GetNamedItem("md5");
string md5 = item != null ? item.InnerText : "";
if (EmojiCache.ContainsKey(md5))
{
path = string.Format("Emoji\\{0}.gif", md5);
}
}
}
}
catch
{
return null;
}
}
if (path == null)
return null;
// 这部分是解密
// 获取到原路径后,开始进行解密转移,只有图片和语音需要解密,解密后是直接归档目录
if(type == WXMsgType.Image || type== WXMsgType.Audio)
if (type == WXMsgType.Image || type == WXMsgType.Audio)
{
path = DecryptAttachment(type, path);
path = DecryptAttachment(type, path, msg.StrTalker);
}
else if (type == WXMsgType.Video)
else if (type == WXMsgType.Video || type == WXMsgType.File)
{
string video_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "Video");
if(!Directory.Exists(video_dir))
Directory.CreateDirectory(video_dir);
string to_dir;
if (type == WXMsgType.Video)
to_dir = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "Video");
else
to_dir = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "File");
if (!Directory.Exists(to_dir))
Directory.CreateDirectory(to_dir);
FileInfo fileInfo = new FileInfo(path);
string video_file_path = Path.Combine(video_dir, fileInfo.Name);
// 目标视频路径
string to_file_path = Path.Combine(to_dir, fileInfo.Name);
// 视频的路径是相对路径,需要加上资源目录
path = Path.Combine(UserBakConfig.UserResPath, path);
if(!File.Exists(video_file_path))
File.Move(path, video_file_path);
path = video_file_path;
// 原文件存在,目标不存在
if (!File.Exists(to_file_path) && File.Exists(path))
{
// 复制
File.Copy(path, to_file_path);
path = to_file_path;
}
else if (File.Exists(to_file_path))
{
path = to_file_path;
}
else
return null;
}
if (path == null)
@@ -199,7 +643,7 @@ namespace WechatPCMsgBakTool
return path;
}
public string? DecryptAttachment(WXMsgType type, string path)
public string? DecryptAttachment(WXMsgType type, string path,string username)
{
if (UserBakConfig == null)
return null;
@@ -208,17 +652,21 @@ namespace WechatPCMsgBakTool
switch (type)
{
case WXMsgType.Image:
string img_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "Image");
string img_dir = Path.Combine(UserBakConfig.UserWorkspacePath, username, "Image");
if (!Directory.Exists(img_dir))
Directory.CreateDirectory(img_dir);
// 图片的路径是相对路径,需要加上资源目录
path = Path.Combine(UserBakConfig.UserResPath, path);
if (!File.Exists(path))
return null;
byte[] decFileByte = DecryptionHelper.DecImage(path);
if (decFileByte.Length < 2)
new Exception("解密失败,可能是未支持的格式");
string decFiletype = DecryptionHelper.CheckFileType(decFileByte);
file_path = DecryptionHelper.SaveDecImage(decFileByte, path, img_dir, decFiletype);
break;
case WXMsgType.Audio:
string audio_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "Audio");
string audio_dir = Path.Combine(UserBakConfig.UserWorkspacePath, username, "Audio");
if (!Directory.Exists(audio_dir))
Directory.CreateDirectory(audio_dir);
FileInfo fileInfo = new FileInfo(path);
@@ -234,23 +682,21 @@ namespace WechatPCMsgBakTool
List<WXMsgGroup> g = new List<WXMsgGroup>();
for (int i = 0; i <= 99; i++)
{
if (DBInfo.ContainsKey("MSG" + i.ToString()))
{
SQLiteConnection con = DBInfo["MSG" + i.ToString()];
if (con == null)
return g;
SQLiteConnection? con = getCon("MSG" + i.ToString());
if (con == null)
return g;
string query = "select StrTalker,Count(localId) as MsgCount from MSG GROUP BY StrTalker";
List<WXMsgGroup> wXMsgs = con.Query<WXMsgGroup>(query);
foreach (WXMsgGroup w in wXMsgs)
{
WXMsgGroup? tmp = g.Find(x => x.UserName == w.UserName);
if (tmp == null)
g.Add(w);
else
tmp.MsgCount += g.Count;
}
string query = "select StrTalker,Count(localId) as MsgCount from MSG GROUP BY StrTalker";
List<WXMsgGroup> wXMsgs = con.Query<WXMsgGroup>(query);
foreach (WXMsgGroup w in wXMsgs)
{
WXMsgGroup? tmp = g.Find(x => x.UserName == w.UserName);
if (tmp == null)
g.Add(w);
else
tmp.MsgCount += g.Count;
}
}
return g;
}
@@ -262,5 +708,6 @@ namespace WechatPCMsgBakTool
Video = 1,
Audio = 2,
File = 3,
Emoji = 4,
}
}

View File

@@ -1,21 +1,24 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using WechatPCMsgBakTool.Model;
using WechatBakTool.Helpers;
using WechatBakTool.Model;
using WechatBakTool.ViewModel;
namespace WechatPCMsgBakTool
namespace WechatBakTool
{
public class WXWorkspace
{
private UserBakConfig UserBakConfig = new UserBakConfig();
public WXWorkspace(string path) {
string checkResult = Init(path);
public WXWorkspace(string path,string account = "") {
string checkResult = Init(path, false, account);
if (checkResult != "")
new Exception(checkResult);
}
@@ -24,7 +27,52 @@ namespace WechatPCMsgBakTool
{
UserBakConfig = userBakConfig;
}
public void MoveDB()
public void DecryptDB(string pid,int type,CreateWorkViewModel viewModel,string pwd = "")
{
if (UserBakConfig == null)
{
throw new Exception("没有工作区文件,无法解密");
}
if (!UserBakConfig.Decrypt)
{
byte[]? key = null;
viewModel.LabelStatus = "正在获取秘钥需要1 - 10秒左右";
if(pwd == "")
key = DecryptionHelper.GetWechatKey(pid, type, UserBakConfig.Account);
else
{
key = new byte[pwd.Length / 2];
for(int i = 0;i<pwd.Length / 2; i++)
{
key[i] = Convert.ToByte(pwd.Substring(i * 2, 2), 16);
}
}
#if DEBUG
File.WriteAllText("key.log", BitConverter.ToString(key!));
#endif
if (key == null)
{
throw new Exception("获取到的密钥为空,获取失败");
}
string source = Path.Combine(UserBakConfig.UserWorkspacePath, "OriginalDB");
string to = Path.Combine(UserBakConfig.UserWorkspacePath, "DecDB");
DecryptionHelper.DecryUserData(key, source, to, viewModel);
UserBakConfig.Decrypt = true;
WXUserReader reader = new WXUserReader(UserBakConfig);
int[] count = reader.GetWXCount();
UserBakConfig.Friends_Number = count[0].ToString();
UserBakConfig.Msg_Number = count[1].ToString();
SaveConfig(UserBakConfig);
}
}
public void MoveDB(CreateWorkViewModel viewModel)
{
string sourceBase = Path.Combine(UserBakConfig.UserResPath, "Msg");
string sourceMulit = Path.Combine(UserBakConfig.UserResPath, "Msg/Multi");
@@ -32,8 +80,9 @@ namespace WechatPCMsgBakTool
foreach (string file in files)
{
FileInfo fileInfo = new FileInfo(file);
if(fileInfo.Extension == ".db")
if (fileInfo.Extension == ".db")
{
viewModel.LabelStatus = "正在迁移" + fileInfo.Name;
string to_path = Path.Combine(UserBakConfig.UserWorkspacePath, "OriginalDB", fileInfo.Name);
File.Copy(file, to_path, true);
}
@@ -45,12 +94,16 @@ namespace WechatPCMsgBakTool
FileInfo fileInfo = new FileInfo(file);
if (fileInfo.Extension == ".db")
{
viewModel.LabelStatus = "正在迁移" + fileInfo.Name;
string to_path = Path.Combine(UserBakConfig.UserWorkspacePath, "OriginalDB", fileInfo.Name);
File.Copy(file, to_path, true);
}
}
}
public UserBakConfig ReturnConfig()
{
return UserBakConfig;
}
public static void SaveConfig(UserBakConfig userBakConfig)
{
if(userBakConfig.UserWorkspacePath != "")
@@ -64,7 +117,7 @@ namespace WechatPCMsgBakTool
}
}
}
private string Init(string path)
private string Init(string path,bool manual,string account = "")
{
string curPath = AppDomain.CurrentDomain.BaseDirectory;
string md5 = GetMd5Hash(path);
@@ -74,6 +127,7 @@ namespace WechatPCMsgBakTool
UserBakConfig.UserWorkspacePath = Path.Combine(curPath, "workspace", md5);
UserBakConfig.Hash = md5;
UserBakConfig.UserName = username;
UserBakConfig.Account = account;
if (!Directory.Exists(UserBakConfig.UserResPath))
{

83
WechatBakTool.csproj Normal file
View File

@@ -0,0 +1,83 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyVersion>0.9.6.4</AssemblyVersion>
<FileVersion>0.9.6.4</FileVersion>
<Version>0.9.6.4</Version>
</PropertyGroup>
<ItemGroup>
<Compile Remove="YearReport\**" />
<EmbeddedResource Remove="YearReport\**" />
<None Remove="YearReport\**" />
<Page Remove="YearReport\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="jieba.NET" Version="0.42.2" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="protobuf-net" Version="3.2.30" />
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
<PackageReference Include="WordCloudSharp" Version="1.1.0" />
</ItemGroup>
<ItemGroup>
<None Update="libcrypto-1_1.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="libssl-1_1.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="mask.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\char_state_tab.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\cn_synonym.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\dict.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\idf.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\pos_prob_emit.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\pos_prob_start.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\pos_prob_trans.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\prob_emit.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\prob_trans.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\stopwords.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Tools\ffmpeg.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Tools\silk_v3_decoder.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="version.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33530.505
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WechatPCMsgBakTool", "WechatPCMsgBakTool.csproj", "{2F385240-6FD0-47C5-9B5E-CC8D9AA55B25}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WechatBakTool", "WechatBakTool.csproj", "{2F385240-6FD0-47C5-9B5E-CC8D9AA55B25}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@@ -1,40 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyVersion>0.4.1.0</AssemblyVersion>
<FileVersion>0.4.1.0</FileVersion>
<Version>0.4.1.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
</ItemGroup>
<ItemGroup>
<None Update="libcrypto-1_1.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="libssl-1_1.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Tools\ffmpeg.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Tools\handle64.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Tools\silk_v3_decoder.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="version.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

25
WordCloudSetting.xaml Normal file
View File

@@ -0,0 +1,25 @@
<Window x:Class="WechatBakTool.WordCloudSetting"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WechatBakTool"
mc:Ignorable="d"
Title="词云渲染设置" Height="360" Width="300" WindowStartupLocation="CenterScreen">
<Grid>
<Label HorizontalAlignment="left" VerticalAlignment="Top" Margin="20,10,0,0">渲染高度</Label>
<TextBox HorizontalAlignment="left" VerticalAlignment="Top" Margin="25,35,0,0" Width="220" Text="{Binding ImgHeight}"/>
<Label HorizontalAlignment="left" VerticalAlignment="Top" Margin="20,60,0,0">渲染宽度</Label>
<TextBox HorizontalAlignment="left" VerticalAlignment="Top" Margin="25,85,0,0" Width="220" Text="{Binding ImgWidth}"/>
<Label HorizontalAlignment="left" VerticalAlignment="Top" Margin="20,110,0,0">最大词数</Label>
<TextBox HorizontalAlignment="left" VerticalAlignment="Top" Margin="25,135,0,0" Width="220" Text="{Binding MaxKeyCount}"/>
<Label HorizontalAlignment="left" VerticalAlignment="Top" Margin="20,160,0,0">渲染字体</Label>
<ComboBox HorizontalAlignment="left" VerticalAlignment="Top" Margin="25,185,0,0" Width="220" SelectedItem="{Binding Font}" ItemsSource="{Binding FontList}"/>
<CheckBox HorizontalAlignment="left" VerticalAlignment="Top" Margin="25,215,0,0" Content="过滤只有一个字的关键词【推荐】" IsChecked="{Binding EnableRemoveOneKey}" />
<Label HorizontalAlignment="left" VerticalAlignment="Top" Margin="20,235,0,0">自定义过滤词(英文,号分隔)</Label>
<TextBox HorizontalAlignment="left" VerticalAlignment="Top" Margin="25,260,0,0" Width="220" Text="{Binding RemoveKey}"/>
<Button Content="确定" HorizontalAlignment="Left" Margin="91,293,0,0" VerticalAlignment="Top" Width="100" Click="Button_Click"/>
</Grid>
</Window>

48
WordCloudSetting.xaml.cs Normal file
View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Drawing.Text;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using WechatBakTool.ViewModel;
namespace WechatBakTool
{
/// <summary>
/// WordCloudSetting.xaml 的交互逻辑
/// </summary>
public partial class WordCloudSetting : Window
{
public WordCloudSetting(WordCloudSettingViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
viewModel.FontList = LoadFont();
}
private List<string> LoadFont()
{
InstalledFontCollection installedFontCollection = new InstalledFontCollection();
var fontFamilies = installedFontCollection.Families;
List<string> list = new List<string>();
foreach ( var fontFamily in fontFamilies )
{
list.Add(fontFamily.Name);
}
return list;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Close();
}
}
}

BIN
mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -8,5 +8,11 @@
},{
"Version":"3.9.7.29",
"BaseAddr": 63488256
},{
"Version":"3.9.8.15",
"BaseAddr": 64997904
},{
"Version":"3.9.8.25",
"BaseAddr": 65002192
}
]