26 Commits

Author SHA1 Message Date
suxue
c66a539d48 v0.9.7.7
1.完善了的版本支持
2.修复了导出时非法文件名及XML字符问题
2025-04-21 11:30:06 +08:00
suxue
51da1fd0ae v0.9.7.6 Release!
1.完善了版本支持。
2.修改了解密界面的推荐,目前仅固定地址查找稳定。
3.修复了部分一些小BUG
2024-09-19 10:02:27 +08:00
Suxue
4213395c9f v0.9.7.5 Release!
1.支持3.9.10.19版本
2024-04-20 10:01:11 +08:00
Suxue
b43a517322 v0.9.7.4 Release!
1.完善批量导出时支持时间范围选择。1
2024-04-08 10:42:55 +08:00
Suxue
5637af45f3 v0.9.7.3 Release!
1.新增导出时间范围选择
2024-03-26 23:48:04 +08:00
Suxue
5cb9cd922b 更新version.json 2024-02-02 10:36:50 +08:00
Suxue
dca42a7849 v0.9.7.2 Release!
1.保底支持3.9.9.27版本。
2.新增批量导出暂停功能。
2024-01-25 13:54:01 +08:00
Suxue
76def416e6 v0.9.7.1 Release!
1.优化批量导出时的体验。现在起记录为空的用户将不会被导出。
2.修复批量导出的部分问题。
2024-01-10 23:32:22 +08:00
Suxue
affafe324f fix bug 2024-01-09 20:49:30 +08:00
Suxue
ef3c84a724 v0.9.7.0 Releases!
1.优化解密的内存开销,同时修复大于2gb文件解密出错的问题。
2.现在起工作区加载联系人也开始为异步操作了。
3.现在起工作区查看聊天记录改为分页查看了。
4.保底版本支持3.9.8.25。
2024-01-09 20:33:00 +08:00
Suxue
0f70591cdb v0.9.6.4 Release!
1.新增部分消息容错。
2.新增已删除但有消息记录的人员记录导出。
2024-01-06 11:25:14 +08:00
Suxue
029403e8ae v0.9.6.3 Release!
1.新增引用消息支持。
2.词云分词异常时错误提示。
2023-12-23 00:07:43 +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
6234ad3084 v0.9.6.0 Release!
1.新增词云功能,导出时选择与他的词云即可。
2.修复公钥头推断失败BUG
3.修复创建工作区与已有工作区切换不成功BUG
2023-12-15 22:48:40 +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
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
c996a89303 v0.9.2.0版本!
1.异步优化,现在起创建工作区,导出聊天记录,都是独立线程操作了!体验更好!并且多了相关状态提示,体验更好。
2.新增批量导出模式,左侧工作区,右键->管理就见啦!
3.修复视频、图片导出时逻辑bug
2023-12-11 16:43:11 +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
f8a06fa000 优化导出,新增txt格式导出 2023-12-07 15:55:50 +08:00
Suxue
25b006b992 修复欢迎页项目名称错误 2023-12-06 22:00:10 +08:00
Suxue
4cb96b4449 更新url 2023-12-06 20:38:05 +08:00
Suxue
1929ef64fe v0.9.0.0 release! 2023-12-06 20:25:35 +08:00
Suxue
486a797c5f readme update 2023-12-04 15:56:17 +08:00
66 changed files with 851171 additions and 1347 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

View File

@@ -0,0 +1,25 @@
<Window x:Class="WechatBakTool.Dialog.MsgDatetimePicker"
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.Dialog"
xmlns:local2="clr-namespace:WechatBakTool.ViewModel"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d"
Title="选择导出日期" Height="350" Width="330">
<Window.Resources>
<local2:DateTypeConverter x:Key="dateTypeConverter" />
</Window.Resources>
<Grid>
<RadioButton GroupName="Date" Content="全部" HorizontalAlignment="Left" Margin="45,60,0,0" VerticalAlignment="Top" IsChecked="{Binding DateType, Converter={StaticResource ResourceKey=dateTypeConverter}, ConverterParameter=1}"/>
<RadioButton GroupName="Date" Content="昨天" HorizontalAlignment="Left" Margin="45,90,0,0" VerticalAlignment="Top" IsChecked="{Binding DateType, Converter={StaticResource ResourceKey=dateTypeConverter}, ConverterParameter=2}"/>
<RadioButton GroupName="Date" Content="指定日期" HorizontalAlignment="Left" Margin="45,120,0,0" VerticalAlignment="Top" IsChecked="{Binding DateType, Converter={StaticResource ResourceKey=dateTypeConverter}, ConverterParameter=3}"/>
<DatePicker HorizontalAlignment="Left" Margin="45,140,0,0" VerticalAlignment="Top" SelectedDate="{Binding PickDate}"/>
<RadioButton GroupName="Date" Content="指定范围日期" HorizontalAlignment="Left" Margin="45,180,0,0" VerticalAlignment="Top" IsChecked="{Binding DateType, Converter={StaticResource ResourceKey=dateTypeConverter}, ConverterParameter=4}"/>
<DatePicker Margin="45,200,0,0" VerticalAlignment="Top" SelectedDate="{Binding StartDate}" HorizontalAlignment="Left" Width="100"/>
<DatePicker Margin="185,200,0,0" VerticalAlignment="Top" SelectedDate="{Binding EndDate}" HorizontalAlignment="Left" Width="100"/>
<Label Content="至" Margin="0,200,0,0" VerticalAlignment="Top" HorizontalAlignment="Center" Width="22"/>
<Button Content="提交" Margin="100,265,0,0" VerticalAlignment="Top" Height="40" Click="Button_Click" HorizontalAlignment="Left" Width="140"/>
</Grid>
</Window>

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
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.Dialog
{
/// <summary>
/// MsgDatetimePicker.xaml 的交互逻辑
/// </summary>
public partial class MsgDatetimePicker : Window
{
public MsgDatetimePicker(DatetimePickerViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
}
}

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, DatetimePickerViewModel dateModel);
void SetEnd();
void Save(string path = "");
}
}

356
Export/HtmlExport.cs Normal file
View File

@@ -0,0 +1,356 @@
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;
using WechatBakTool.Helpers;
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, DatetimePickerViewModel dateModel)
{
if (Session == null)
throw new Exception("请初始化模版Not Use InitTemplate");
List<WXMsg>? msgList = reader.GetWXMsgs(contact.UserName, dateModel);
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 = StringHelper.CleanInvalidXmlChars(xml);
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 = StringHelper.CleanInvalidXmlChars(xml);
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 = StringHelper.CleanInvalidXmlChars(xml);
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, DatetimePickerViewModel dateModel)
{
if (Contact == null)
throw new Exception("请初始化模版Not Use InitTemplate");
List<WXMsg>? msgList = reader.GetWXMsgs(Contact.UserName, dateModel);
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,31 +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 System.Windows;
using WechatPCMsgBakTool.Model;
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(bool mem_find_key,string account)
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)
{
@@ -41,9 +40,7 @@ namespace WechatPCMsgBakTool.Helpers
return null;
}
if (!mem_find_key)
if (find_key_type == 1)
{
List<VersionInfo>? info = null;
string json = File.ReadAllText("version.json");
@@ -70,59 +67,196 @@ namespace WechatPCMsgBakTool.Helpers
}
}
}
else
else if(find_key_type == 2)
{
List<int> read = ProcessHelper.FindProcessMemory(process.Handle, module, account);
if(read.Count >= 2)
if (read.Count >= 2)
{
byte[] buffer = new byte[8];
int key_offset = read[1] - 64;
if (ProcessHelper.ReadProcessMemory(process.Handle, module.BaseAddress + key_offset, buffer, buffer.Length, out _))
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(ProcessHelper.ReadProcessMemory(process.Handle, (IntPtr)addr, key_bytes, key_bytes.Length, out _))
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
{
MessageBox.Show("搜索不到微信账号,请确认用户名是否正确,如错误请重新新建工作区,务必确认账号是否正确", "错误");
throw new Exception("搜索不到微信账号,请确认用户名是否正确,如错误请重新新建工作区,务必确认账号是否正确");
}
}
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 = page_bytes.Take(4).ToArray();
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];
@@ -130,8 +264,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))
@@ -160,8 +293,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)
{
@@ -180,99 +314,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)
{
@@ -286,6 +371,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

@@ -6,22 +6,25 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace WechatPCMsgBakTool.Helpers
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)
public static string? FromDevicePath(string devicePath)
{
var drive = Array.Find(DriveInfo.GetDrives(), d => devicePath.StartsWith(d.GetDevicePath(), StringComparison.InvariantCultureIgnoreCase));
var drive = Array.Find(
DriveInfo.GetDrives(), d =>
devicePath.StartsWith(d.GetDevicePath() + "\\", StringComparison.InvariantCultureIgnoreCase)
);
return drive != null ?
devicePath.ReplaceFirst(drive.GetDevicePath(), drive.GetDriveLetter()) :
devicePath.ReplaceFirst(drive.GetDevicePath()!, drive.GetDriveLetter()) :
null;
}
private static string GetDevicePath(this DriveInfo driveInfo)
private static string? GetDevicePath(this DriveInfo driveInfo)
{
var devicePathBuilder = new StringBuilder(128);
return QueryDosDevice(driveInfo.GetDriveLetter(), devicePathBuilder, devicePathBuilder.Capacity + 1) != 0 ?

View File

@@ -1,7 +1,7 @@
using System;
using System.Runtime.InteropServices;
namespace WechatPCMsgBakTool.Helpers
namespace WechatBakTool.Helpers
{
public class NativeAPI
{
@@ -12,6 +12,12 @@ namespace WechatPCMsgBakTool.Helpers
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;
@@ -115,6 +121,20 @@ namespace WechatPCMsgBakTool.Helpers
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
//=================================================
@@ -208,5 +228,12 @@ namespace WechatPCMsgBakTool.Helpers
[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);
}
}

View File

@@ -5,9 +5,9 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static WechatPCMsgBakTool.Helpers.NativeAPI;
using static WechatBakTool.Helpers.NativeAPI;
namespace WechatPCMsgBakTool.Helpers
namespace WechatBakTool.Helpers
{
public class NativeAPIHelper
{
@@ -48,17 +48,19 @@ namespace WechatPCMsgBakTool.Helpers
uint nLength = 0;
hObjectName = AllocManagedMemory(256 * 1024);
// 查询句柄名称
while (NtQueryObject(ipHandle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, hObjectName, nLength, ref nLength) == NTSTATUS_STATUS_INFO_LENGTH_MISMATCH)
Task.Run(() =>
{
FreeManagedMemory(hObjectName);
if (nLength == 0)
// 查询句柄名称
while (NtQueryObject(ipHandle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, hObjectName, nLength, ref nLength) == NTSTATUS_STATUS_INFO_LENGTH_MISMATCH)
{
Console.WriteLine("Length returned at zero!");
return "";
FreeManagedMemory(hObjectName);
if (nLength == 0)
{
Console.WriteLine("Length returned at zero!");
}
hObjectName = AllocManagedMemory(nLength);
}
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)
@@ -147,5 +149,52 @@ namespace WechatPCMsgBakTool.Helpers
// 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

@@ -9,31 +9,10 @@ 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);
@@ -53,7 +32,7 @@ namespace WechatPCMsgBakTool.Helpers
List<int> offset = new List<int>();
int readBytes;
bool success = ReadProcessMemory(processHandle, module.BaseAddress, buffer, buffer.Length,out readBytes);
bool success = NativeAPI.ReadProcessMemory(processHandle, module.BaseAddress, buffer, buffer.Length,out readBytes);
if (!success || readBytes == 0)
{
@@ -85,14 +64,12 @@ namespace WechatPCMsgBakTool.Helpers
{
byte[] array = new byte[nSize];
int readByte;
if (!ReadProcessMemory(hProcess, lpBaseAddress, array, nSize, out readByte))
if (!NativeAPI.ReadProcessMemory(hProcess, lpBaseAddress, array, nSize, out readByte))
return null;
else
return array;
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead);
}

46
Helpers/StringHelper.cs Normal file
View File

@@ -0,0 +1,46 @@
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace WechatBakTool.Helpers
{
public static class StringHelper
{
/// <summary>
/// 清理XML中的非法字符
/// </summary>
/// <param name="input">需要清理的字符串</param>
/// <returns>清理后的字符串</returns>
public static string CleanInvalidXmlChars(string input)
{
if (string.IsNullOrEmpty(input))
return input;
// #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
// 这里使用正则表达式匹配非法字符并替换
return Regex.Replace(input, @"[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD]", "");
}
/// <summary>
/// 替换文件名中的非法字符为指定字符
/// </summary>
/// <param name="fileName">原始文件名</param>
/// <param name="replacement">用于替换非法字符的字符,默认为 "-"</param>
/// <returns>清理后的文件名</returns>
public static string SanitizeFileName(string fileName, char replacement = '-')
{
if (string.IsNullOrEmpty(fileName))
return fileName;
// 处理Windows系统中文件名不允许的特殊字符
char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
foreach (char invalidChar in invalidFileNameChars)
{
fileName = fileName.Replace(invalidChar, '-');
}
return fileName;
}
}
}

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,166 +0,0 @@
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 WechatPCMsgBakTool.Interface;
using WechatPCMsgBakTool.Model;
using System.Xml;
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== 49)
{
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>", "暂未支持的消息");
}
}
}
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,42 +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,68,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="285,47,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"/>
<RadioButton Name="rb_find_file" GroupName="find_addr_function" Content="使用version.json进行基址查找" HorizontalAlignment="Left" Margin="348,22,0,0" VerticalAlignment="Top" IsChecked="True"/>
<RadioButton Name="rb_find_mem" GroupName="find_addr_function" Content="使用推定进行基址查找【推荐】" HorizontalAlignment="Left" Margin="546,22,0,0" VerticalAlignment="Top" Checked="rb_find_mem_Checked"/>
</Grid>
</Window>

View File

@@ -1,303 +0,0 @@
using K4os.Compression.LZ4;
using K4os.Compression.LZ4.Encoders;
using K4os.Compression.LZ4.Streams;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
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 System.Xml;
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();
this.Title += $" {Application.ResourceAssembly.GetName().Version}";
}
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)
{
bool? mem_find_key = rb_find_mem.IsChecked;
if(mem_find_key == null)
{
MessageBox.Show("请选择key获取方式");
return;
}
byte[]? key = null;
try
{
key = DecryptionHelper.GetWechatKey((bool)mem_find_key,CurrentUserBakConfig.Account);
if (key == null)
MessageBox.Show("获取到的秘钥为空");
File.AppendAllText("debug.log", BitConverter.ToString(key, 0));
}
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, selectWechat.SelectProcess.Account);
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();
}
private void rb_find_mem_Checked(object sender, RoutedEventArgs e)
{
if(CurrentUserBakConfig!= null)
{
if (string.IsNullOrEmpty(CurrentUserBakConfig.Account))
{
MessageBox.Show("使用该功能需要填写用户名,请务必确认用户名已经正确填写,否则请重建工作区");
return;
}
}
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
var list = UserReader.GetWXChatRooms();
var users = UserReader.GetWXContacts();
Hashtable hashtable = new Hashtable();
foreach(var u in users)
{
hashtable[u.UserName] = u;
}
foreach(var room in list)
{
if(room.ChatRoomName == "20647511469@chatroom")
{
string[] ids = room.UserNameList.Split("^G");
foreach(string id in ids) {
if (hashtable.ContainsKey(id))
{
WXContact? contact = hashtable[id] as WXContact;
Debug.WriteLine($"{id} 是 ${contact.NickName}");
}
else
{
Debug.WriteLine("不存在");
}
}
}
}
}
}
}

View File

@@ -1,11 +1,11 @@
<Window x:Class="WechatPCMsgBakTool.Main2"
<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:WechatPCMsgBakTool"
xmlns:local="clr-namespace:WechatBakTool"
mc:Ignorable="d" WindowStartupLocation="CenterScreen" WindowStyle="None" WindowState="Normal" Background="Transparent" AllowsTransparency="True" ResizeMode="NoResize"
Title="Main2" Height="550" Width="950" >
Title="WechatBakTool" Height="550" Width="950" >
<Window.Resources>
<Style TargetType="local:Main2">
<!-- 设置窗体的WindowChrome -->
@@ -15,7 +15,7 @@
<!-- CornerRadius窗体圆角-->
<!-- CaptionHeight顶部标题的高度-->
<!-- GlassFrameThickness:默认边框的大小0为不使用默认边框这样定义的圆角才有效-1为使用默认边框默认值-->
<WindowChrome CornerRadius="5" CaptionHeight="0" GlassFrameThickness="0" />
<WindowChrome CornerRadius="5" CaptionHeight="5" GlassFrameThickness="0" />
</Setter.Value>
</Setter>
</Style>
@@ -61,8 +61,8 @@
</Grid>
</DataTemplate>
</Window.Resources>
<Grid Background="White">
<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">
<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>
@@ -122,10 +122,19 @@
</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 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" >
<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>

View File

@@ -13,9 +13,9 @@ using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using WechatPCMsgBakTool.Model;
using WechatBakTool.Model;
namespace WechatPCMsgBakTool
namespace WechatBakTool
{
/// <summary>
/// Main2.xaml 的交互逻辑
@@ -27,8 +27,9 @@ namespace WechatPCMsgBakTool
public Main2()
{
InitializeComponent();
// 获取文件版本
lab_version.Content += $" {Application.ResourceAssembly.GetName().Version}";
// list_workspace.Items.Add(new { Name = "sxcoder", Friends_Number=23, Msg_Number=102302, Decrypt="已解密" });
//加载工作区
LoadWorkspace();
}
@@ -37,13 +38,15 @@ namespace WechatPCMsgBakTool
Application.Current.Shutdown();
}
private void LoadWorkspace()
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);
@@ -72,16 +75,50 @@ namespace WechatPCMsgBakTool
private void list_workspace_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
UserBakConfig? config = list_workspace.SelectedItem as UserBakConfig;
if(config != null)
if(config == null)
{
CurrentUserBakConfig = config;
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Workspace.xaml"));
}
else
{
MessageBox.Show("工作区配置文件异常,请确认工作区配置是否正常", "错误", MessageBoxButton.OK);
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Welcome.xaml?datatime=" + DateTime.Now.Ticks));
return;
}
CurrentUserBakConfig = config;
if (!config.Decrypt)
{
MessageBox.Show("请先到创建工作区进行解密");
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/CreateWork.xaml?datatime=" + DateTime.Now.Ticks));
return;
}
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,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WechatPCMsgBakTool.Model
namespace WechatBakTool.Model
{
public class ProcessInfo
{
@@ -21,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,10 +5,12 @@ 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; } = "";
@@ -23,12 +25,13 @@ namespace WechatPCMsgBakTool.Model
public string Account { get; set; } = "";
public string Friends_Number { get; set; } = "-";
public string Msg_Number { get; set; } = "-";
public string Key { get; set; } = "";
public bool Manual { get; set; } = false;
}
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
@@ -41,6 +44,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; } = "";
@@ -73,8 +84,6 @@ namespace WechatPCMsgBakTool.Model
public string Content { get; set; } = "";
[Column("nTime")]
public int LastTime { get; set; }
public int ReadCount { get; set; }
public int LastMsgId { get; set; }
}
[Table("SessionAttachInfo")]
@@ -105,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")]
@@ -117,10 +130,12 @@ 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")]
@@ -145,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
{
@@ -158,8 +182,9 @@ namespace WechatPCMsgBakTool.Model
public string LastMsg { get; set; } = "";
[Column("ExtraBuf")]
public byte[]? ExtraBuf { get; set; }
[Column("smallHeadImgUrl")]
public string Avatar { get; set; } = "";
public BitmapImage? Avatar { get; set; }
[Column("Remark")]
public string Remark { get; set; } = "";
}
[Table("ContactHeadImgUrl")]

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; } = "";
}
}

7
NuGet.config Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

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>

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

@@ -0,0 +1,224 @@
using JiebaNet.Segmenter.Common;
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 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();
isManualProcess();
}
private void isManualProcess()
{
if(Main2.CurrentUserBakConfig!= null)
{
cb_manual.IsChecked = Main2.CurrentUserBakConfig.Manual;
}
}
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;
bool m = (bool)cb_manual.IsChecked!;
Task.Run(() => {
if (ViewModel.KeyType != -1 && !m)
{
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 if (m)
{
WXWorkspace wXWorkspace = new WXWorkspace(Main2.CurrentUserBakConfig!);
ViewModel.LabelStatus = "开始解密数据库";
wXWorkspace.DecryptDB("", -1, ViewModel,Main2.CurrentUserBakConfig!.Key);
Dispatcher.Invoke(() =>
{
MessageBox.Show("解密完成");
((Main2)Window.GetWindow(this)).LoadWorkspace();
});
}
else
{
MessageBox.Show("请选择Key获取方式", "错误");
}
ViewModel.IsEnable = true;
});
}
private void cb_manual_Checked(object sender, RoutedEventArgs e)
{
MessageBox.Show("该功能仅限用于网络安全研究用途使用,红队同学请在合规授权下进行相关操作","重要提醒!!!!!!!!!");
if(Main2.CurrentUserBakConfig != null)
{
if (Main2.CurrentUserBakConfig.Manual)
{
return;
}
}
if (MessageBox.Show("我确认获取到合规授权,仅用于网络安全用途使用", "信息确认", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
if (File.Exists("auth.txt"))
{
string auth = File.ReadAllText("auth.txt");
/*
*
* pwd:
* 我已知晓手动模式可能潜在的法律及道德风险,我明白非法使用将要承担相关法律责任。
* tips:
* 请不要公开宣传手动模式,不提供任何使用解答,谢谢。
* 不要编写任何关于手动模式的教程,避免非法传播使用。
*
*/
if (DecryptionHelper.GetMD5(auth) == "295f634af60d61dfa52a5f35849ac42b")
{
string genHash = DateTime.Now.ToString();
string md5 = DecryptionHelper.GetMD5(genHash);
UserBakConfig config = new UserBakConfig();
config.Hash = md5;
string workspacePath = Path.Combine(Directory.GetCurrentDirectory(), "workspace");
config.UserWorkspacePath = Path.Combine(workspacePath, md5);
WXWorkspace workspace = new WXWorkspace(config);
workspace.ManualInit();
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>

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

@@ -0,0 +1,182 @@
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.Dialog;
using WechatBakTool.Export;
using WechatBakTool.Helpers;
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;
private int Status = 0;
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)
{
DatetimePickerViewModel datePickViewModel = new DatetimePickerViewModel();
if (Status == 0)
{
MsgDatetimePicker picker = new MsgDatetimePicker(datePickViewModel);
datePickViewModel.DateType = 1;
datePickViewModel.PickDate = DateTime.Now.AddDays(-1);
if (picker.ShowDialog() != true)
{
return;
}
}
// 0 未开始
if(Status == 0 || Status == 2)
{
Suspend = false;
btn_export_all.Content = "暂停";
}
// 1 进行中
else if (Status == 1)
{
// 开启暂停
Suspend = true;
Status = 2;
btn_export_all.Content = "继续";
return;
}
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 (Status == 0)
ExpContacts = UserReader.GetWXContacts().ToList();
else
Suspend = false;
List<WXContact> process = new List<WXContact>();
foreach (var contact in ExpContacts!)
{
if (Suspend)
{
foreach(WXContact p in process)
{
ExpContacts.Remove(p);
}
workspaceViewModel.ExportCount = "已暂停";
return;
}
Status = 1;
if (group && contact.UserName.Contains("@chatroom"))
{
workspaceViewModel.WXContact = contact;
ExportMsg(contact, datePickViewModel);
}
if (user && !contact.UserName.Contains("@chatroom") && !contact.UserName.Contains("gh_"))
{
workspaceViewModel.WXContact = contact;
ExportMsg(contact, datePickViewModel);
}
process.Add(contact);
}
Status = 0;
btn_export_all.Content = "导出";
MessageBox.Show("批量导出完成", "提示");
}
});
}
private void ExportMsg(WXContact contact, DatetimePickerViewModel dt)
{
workspaceViewModel.ExportCount = "";
// string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, contact.UserName + ".html");
string fileName = StringHelper.SanitizeFileName(string.Format(
"{0}-{1}.html",
contact.UserName,
contact.Remark == "" ? contact.NickName : contact.Remark
));
string path = Path.Combine(
Main2.CurrentUserBakConfig!.UserWorkspacePath,
fileName
);
IExport export = new HtmlExport();
export.InitTemplate(contact, path);
if (export.SetMsg(UserReader!, contact, workspaceViewModel, dt))
{
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;
}
}
}

View File

@@ -1,11 +1,11 @@
<Page x:Class="WechatPCMsgBakTool.Pages.Welcome"
<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:WechatPCMsgBakTool.Pages"
xmlns:local="clr-namespace:WechatBakTool.Pages"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DesignHeight="550" d:DesignWidth="800"
Title="Welcome" Background="White">
<Grid>
@@ -24,7 +24,7 @@
<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/WechatPCMsgBakTool" Foreground="#2775b6" MouseDown="StarGithub_MouseDown"></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>

View File

@@ -14,7 +14,7 @@ using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WechatPCMsgBakTool.Pages
namespace WechatBakTool.Pages
{
/// <summary>
/// Welcome.xaml 的交互逻辑
@@ -28,7 +28,7 @@ namespace WechatPCMsgBakTool.Pages
private void StarGithub_MouseDown(object sender, MouseButtonEventArgs e)
{
Process.Start("explorer.exe", "https://github.com/SuxueCode/WechatPCMsgBakTool");
Process.Start("explorer.exe", "https://github.com/SuxueCode/WechatBakTool");
}
}
}

View File

@@ -1,13 +1,119 @@
<Page x:Class="WechatPCMsgBakTool.Pages.Workspace"
<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:WechatPCMsgBakTool.Pages"
xmlns:local="clr-namespace:WechatBakTool.Pages"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
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"/>
@@ -21,7 +127,7 @@
</Setter.Value>
</Setter>
</Style>
<!-- 这里是listview滚动条的滑动块部分样式-->
<Style x:Key="ScrollBarThumbVertical" TargetType="{x:Type Thumb}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
@@ -51,15 +157,16 @@
</DataTemplate>
<DataTemplate x:Key="MsgText">
<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="{Binding LastMsg}"/>
<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">
<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}"/>
<Label Margin="60,25,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="140" Content="1111"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="MsgAudio">
@@ -71,24 +178,6 @@
</DataTemplate>
</Page.Resources>
<Grid>
<!--
<Label FontSize="20" Margin="30,15" Content="工作台" HorizontalAlignment="Left" VerticalAlignment="Top" />
<Label Name="lab_status" Margin="30,45,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="当前工作区还未解密,请先解密。"></Label>
<Button Name="btn_decrypt" Margin="35,80,0,0" Width="60" Height="30" Content="解密" Foreground="White" Background="#2775b6" BorderThickness="0" HorizontalAlignment="Left" VerticalAlignment="Top" Click="btn_decrypt_Click">
<Button.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="3" />
</Style>
</Button.Resources>
</Button>
<Button Name="btn_read" Margin="125,80,0,0" Width="60" Height="30" Content="读取" IsEnabled="False" Foreground="White" Background="#2775b6" BorderThickness="0" HorizontalAlignment="Left" VerticalAlignment="Top" Click="btn_read_Click">
<Button.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="3" />
</Style>
</Button.Resources>
</Button>
-->
<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>
@@ -124,12 +213,25 @@
</Style>
</ListView.Resources>
</ListView>
<Label Content="{Binding WXContact.UserName}" HorizontalAlignment="Left" Margin="258,21,0,0" VerticalAlignment="Top"/>
<ListView x:Name="list_msg" Margin="230,60,0,0" Background="Aqua" BorderThickness="0">
<Grid Margin="0">
<Label Margin="0,8,0,0" FontWeight="Bold" VerticalAlignment="Top" Content="溯雪" Width="130"/>
<Label Margin="60,25,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="140" Content="{Binding LastMsg}"/>
</Grid>
<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>

View File

@@ -1,13 +1,15 @@
using System;
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;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
@@ -15,22 +17,44 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using WechatPCMsgBakTool.Model;
using WechatPCMsgBakTool.ViewModel;
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;
using WechatBakTool.Dialog;
namespace WechatPCMsgBakTool.Pages
namespace WechatBakTool.Pages
{
/// <summary>
/// Workspace.xaml 的交互逻辑
/// </summary>
public partial class Workspace : Page
{
public WXUserReader? UserReader { get; set; }
private WorkspaceViewModel ViewModel { get; set; } = new WorkspaceViewModel();
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)
@@ -38,7 +62,11 @@ namespace WechatPCMsgBakTool.Pages
UserReader = new WXUserReader(config);
if (config.Decrypt)
{
ViewModel.Contacts = UserReader.GetWXContacts();
ViewModel.Contacts = null;
Task.Run(() => {
ViewModel.Contacts = UserReader.GetWXContacts();
});
}
}
}
@@ -54,13 +82,43 @@ namespace WechatPCMsgBakTool.Pages
private void list_users_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ViewModel.WXContact = list_users.SelectedItem as WXContact;
if(ViewModel.WXContact == null || UserReader == null)
{
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);
}
List<WXMsg>? msgs = UserReader.GetWXMsgs(ViewModel.WXContact.UserName);
ListViewItem i = new ListViewItem();
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)
@@ -72,7 +130,25 @@ namespace WechatPCMsgBakTool.Pages
if (txt_find_user.Text == "搜索...")
findName = "";
ViewModel.Contacts = UserReader.GetWXContacts(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)
@@ -82,5 +158,232 @@ namespace WechatPCMsgBakTool.Pages
Debug.WriteLine(ViewModel.SearchString);
}
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;
}
DatetimePickerViewModel datePickViewModel = new DatetimePickerViewModel();
Dispatcher.Invoke(() =>
{
MsgDatetimePicker picker = new MsgDatetimePicker(datePickViewModel);
datePickViewModel.DateType = 1;
datePickViewModel.PickDate = DateTime.Now.AddDays(-1);
if (picker.ShowDialog() != true)
{
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 fileName = StringHelper.SanitizeFileName(string.Format(
"{0}-{1}",
ViewModel.WXContact.UserName,
ViewModel.WXContact.Remark == "" ? ViewModel.WXContact.NickName : ViewModel.WXContact.Remark
));
string path = Path.Combine(
Main2.CurrentUserBakConfig!.UserWorkspacePath,
fileName
);
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, datePickViewModel);
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("用户所有表情预下载完毕");
});
}
/*
if (UserReader != null && ViewModel.WXContact != null)
{
Task.Run(() =>
{
List<WXMsg> msgs = UserReader.GetWXMsgs(ViewModel.WXContact.UserName).ToList();
List<WXContactHT> users = new List<WXContactHT>();
if (File.Exists("WXContact.json"))
{
string text = File.ReadAllText("WXContact.json");
text = text.Substring(8, text.Length - 8);
users = JsonConvert.DeserializeObject<List<WXContactHT>>(text);
}
int i = 1; int all = 1;
List<WXMsg> tmp = new List<WXMsg>();
foreach (WXMsg m in msgs)
{
m.BytesExtra = null;
tmp.Add(m);
if (all % 10000 == 0)
{
File.WriteAllText(ViewModel.WXContact.UserName + "-" + i.ToString() + ".json", string.Format("showMsg({0})", JsonConvert.SerializeObject(tmp)));
tmp.Clear();
i++;
}
all++;
}
if (users!.Find(x => x.UserName == ViewModel.WXContact.UserName) == null)
{
WXContactHT html = new WXContactHT();
html.NickName = ViewModel.WXContact.NickName;
html.UserName = ViewModel.WXContact.UserName;
html.LastMsg = ViewModel.WXContact.LastMsg;
if (ViewModel.WXContact.Avatar != null)
{
using (var ms = new MemoryStream())
{
ViewModel.WXContact.Avatar.StreamSource.CopyTo(ms);
byte[] bytes = new byte[ms.Length];
ms.Write(bytes, 0, bytes.Length);
html.AvatarString = Convert.ToBase64String(bytes);
}
}
html.FileCount = i;
users.Add(html);
}
File.WriteAllText(ViewModel.WXContact.UserName + "-" + i.ToString() + ".json", string.Format("showMsg({0})", JsonConvert.SerializeObject(tmp)));
File.WriteAllText("WXContact.json", string.Format("getUser({0})", JsonConvert.SerializeObject(users)));
MessageBox.Show("json已导出");
});
}
*/
}
private void list_msg_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (ViewModel.WXMsgs.Count == 0)
return;
if (e.VerticalOffset + e.ViewportHeight == e.ExtentHeight && !Loading)
{
// 滚动条到达底部的处理逻辑
loadMsg();
}
}
}
}

118
README.md
View File

@@ -1,56 +1,92 @@
> [!NOTE]
> 本分支为项目开发分支,变动较为频繁且可能不可用<br/>
> 如果你希望观察作者的开发动态,可以参考这个分支。
<br/>
# WechatPCMsgBakTool
微信PC聊天记录备份工具仅支持Windows
# WechatBakTool
基于C#开发的微信聊天记录备份分析工具,努力做最好用的微信备份工具。
- 支持3.9.6.33版本后若版本更新可在version.json添加版本号和地址即可完成新版本支持
- 支持用户名推定key位置无视版本创建工作区请正常录入微信号
- 导出图片、视频、音频、分享链接
- 导出Html文件
- 理论支持64位版本所有微信支持两种方式非直接地址获取Key[1]
- 工作区概念,支持多微信切换操作。
- 支持导出Html文件TXT文件支持批量导出
- 支持聊天频率分析,全消息库内容搜索
**本项目仅做学习使用,主要供个人备份自己的微信记录,请勿用于非法用途。**
**本项目严禁商用**
- 目前支持以下类型消息解析
- [x] 文本消息
- [x] 图片
- [x] 语音
- [x] 分享链接
- [x] 群聊
- [x] 系统消息
- [x] 文件
- [x] 引用/转发消息
- [x] 表情(需要预下载)
如果有什么好的建议或意见或者遇到什么问题欢迎提issue看到会回。
### 近期开发规划
- 【进行中】UI、界面交互全面更新;
- 【进行中】群聊支持;
- 各种消息记录完善
- 工作区更新逻辑完善
- 自定义HTML模版
- 各种数据信息统计
> [!NOTE]
> 反馈群815054692<br/>
> 如果觉得不错欢迎右上角点个star这是对作者的鼓励谢谢
> 如果觉得不错欢迎右上角点个star这是对作者的鼓励谢谢<br/>
> 进群请先Star项目然后问答消息留id<br/>
<br/>
### 使用
<p>1.打开微信,并登录。</p>
<p>2.在工作区上方点击新增,选择要创建的工作区微信</p>
<p>3.如同时运行多个微信,请选择微信,请注意通过路径进行区别</p>
<p>4.选中刚刚创建的工作区,点击解密。(如当前多开微信,请选择对应的微信进行解密)</p>
<p>5.选中刚刚创建的工作区,点击读取</p>
<p><b>尽情使用吧!</b></p>
### 免责声明
**本项目仅供学习、研究使用,严禁商业使用**<br/>
**用于网络安全用途的,请确保在国家法律法规下使用**<br/>
**本项目完全免费,问你要钱的都是骗子**<br/>
**使用本项目初衷是作者研究微信数据库的运行使用,您使用本软件导致的后果,包含但不限于数据损坏,记录丢失等问题,作者不承担相关责任。**<br/>
**因软件特殊性质,请在使用时获得微信账号所有人授权,你当确保不侵犯他人个人隐私权,后果自行承担**<br/>
<br/>
### 注意
<p>本项目基于.NET开发需要安装.NET Desktop Runtime如未安装双击EXE时会提示。</p>
<p>如果使用过程中发生崩溃请删除工作区试一下工作区即根据用户名在运行目录下生成的md5文件夹。</p>
<p>已解密的工作区可以直接读取。</p>
<p>再次强调,主要用于个人备份自己微信使用,请勿用于非法用途,严禁商用!</p>
### 隐私声明
**本项目不会上传任何你的数据至任何第三方系统**<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/>
Q解密工作区提示no such teble:XXXXXXX怎么办<br/>
A这个原因基本上是因为解密失败导致的回落使用固定地址查找方式解密请确保你的微信版本在version.json内支持<br/>
<br/>
Q解密时提示Unable to load DLL 'libcrypto-1_1' or one of its dependencies怎么办<br/>
A这个是因为加解密库的运行环境不满足安装vc++2015 x64运行库后再尝试<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/>
### 参考/引用
都是站在大佬们的肩膀上完成的项目,本项目 参考/引用 了以下 项目/文章 内代码。
##### [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)
##### [huiyadanli/RevokeMsgPatcher](https://github.com/huiyadanli/RevokeMsgPatcher)
项目在开发过程中参考了以下项目或资料,有引用相关代码,如有需要,推荐您可以去参考下相关资料:
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,27 +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="310" Width="600">
<Grid>
<Label Content="请选择您要打开的微信:" HorizontalAlignment="Left" Margin="29,27,0,0" VerticalAlignment="Top"/>
<ListView Name="list_process" Margin="32,55,32,110" 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="241,245,0,0" VerticalAlignment="Top" Width="97" Click="btn_close_Click"/>
<Label Content="用户名:" HorizontalAlignment="Left" Margin="34,190,0,0" VerticalAlignment="Top"/>
<TextBox Name="txt_username" HorizontalAlignment="Left" Margin="95,195,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="297"/>
<Label Content="注意确认用户名是否正确如修改过用户名请自行填写如果需要使用推定方式获取Key必须正确" HorizontalAlignment="Left" Margin="35,215,0,0" VerticalAlignment="Top" FontWeight="Bold"/>
</Grid>
</Window>

View File

@@ -1,144 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
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 System.Xml.Linq;
using WechatPCMsgBakTool.Helpers;
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();
GetWechatProcessInfos();
list_process.ItemsSource = processInfos;
}
private void GetWechatProcessInfos()
{
processInfos.Clear();
Process[] processes = Process.GetProcessesByName("wechat");
foreach (Process p in processes)
{
File.AppendAllText("debug.log", "wechat=>" + p.Id + "\r\n");
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);
processInfos.Add(info);
}
}
}
}
}
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;
if(SelectProcess != null)
{
string[] name_raw = SelectProcess.DBPath.Split("\\");
txt_username.Text = name_raw[name_raw.Length - 3];
}
}
private void btn_close_Click(object sender, RoutedEventArgs e)
{
if (SelectProcess != null)
{
SelectProcess.Account = txt_username.Text;
}
Close();
}
}
}

View File

@@ -1,9 +1,9 @@
<Window x:Class="WechatPCMsgBakTool.Tools"
<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:WechatPCMsgBakTool"
xmlns:local="clr-namespace:WechatBakTool"
mc:Ignorable="d"
Title="资源回退工具" Height="450" Width="800" WindowStartupLocation="CenterScreen">
<Grid>

View File

@@ -12,9 +12,9 @@ using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using WechatPCMsgBakTool.Model;
using WechatBakTool.Model;
namespace WechatPCMsgBakTool
namespace WechatBakTool
{
/// <summary>
/// Tools.xaml 的交互逻辑

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,39 @@
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;
namespace WechatBakTool.ViewModel
{
public partial class DatetimePickerViewModel : ObservableObject
{
[ObservableProperty]
private DateTime startDate = DateTime.Now.AddMonths(-1);
[ObservableProperty]
private DateTime endDate = DateTime.Now;
[ObservableProperty]
private DateTime pickDate = DateTime.Now.AddDays(-1);
[ObservableProperty]
private int dateType = 1;
}
public class DateTypeConverter : 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

@@ -1,20 +1,61 @@
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 WechatPCMsgBakTool.Model;
using WechatBakTool.Model;
namespace WechatPCMsgBakTool.ViewModel
namespace WechatBakTool.ViewModel
{
partial class WorkspaceViewModel : ObservableObject
public partial class WorkspaceViewModel : ObservableObject
{
[ObservableProperty]
public WXContact? wXContact;
[NotifyPropertyChangedFor(nameof(SelectContact))]
[NotifyPropertyChangedFor(nameof(LabelStatus))]
private WXContact? wXContact = null;
[ObservableProperty]
public List<WXContact>? contacts;
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
@@ -35,13 +76,5 @@ namespace WechatPCMsgBakTool.ViewModel
return searchString;
}
}
public string SearchRealString
{
get
{
return searchString;
}
}
}
}

View File

@@ -1,27 +1,45 @@
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;
using WechatBakTool.ViewModel;
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)
@@ -34,36 +52,250 @@ namespace WechatPCMsgBakTool
continue;
SQLiteConnection con = new SQLiteConnection(item);
string dbName = fileInfo.Name.Split('.')[0];
if (DBInfo.ContainsKey(dbName))
{
continue;
}
DBInfo.Add(dbName, con);
}
}
public List<WXContact>? GetWXContacts(string? name = null)
private SQLiteConnection? getCon(string name)
{
SQLiteConnection con = DBInfo["MicroMsg"];
if (con == null)
if (DBInfo.ContainsKey(name))
{
return DBInfo[name];
}
else
{
return null;
}
}
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 ?)");
return con.Query<WXContact>(query, $"%{name}%", $"%{name}%", $"%{name}%", $"%{name}%");
contacts = con.Query<WXContact>(query, $"%{name}%", $"%{name}%", $"%{name}%", $"%{name}%");
}
else
{
query = query.Replace("{searchName}", "");
return con.Query<WXContact>(query);
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 = DBInfo["MicroMsg"];
SQLiteConnection? con = getCon("MicroMsg");
if (con == null)
return null;
string query = "select * from contactHeadImgUrl";
@@ -72,51 +304,221 @@ namespace WechatPCMsgBakTool
public List<WXChatRoom>? GetWXChatRooms()
{
SQLiteConnection con = DBInfo["MicroMsg"];
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;
}
public List<WXMsg>? GetWXMsgs(string uid, DatetimePickerViewModel dateModel)
{
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 = "";
if (dateModel.DateType == 2 || dateModel.DateType == 3)
{
query = "select * from MSG where StrTalker=? and date(createtime,'unixepoch') = ?";
wXMsgs = con.Query<WXMsg>(query, uid, dateModel.PickDate.ToString("yyyy-MM-dd"));
}
else if(dateModel.DateType == 4 )
{
query = "select * from MSG where StrTalker=? and date(createtime,'unixepoch') >= ? and date(createtime,'unixepoch') <= ?";
wXMsgs = con.Query<WXMsg>(query, uid, dateModel.StartDate.ToString("yyyy-MM-dd"), dateModel.EndDate.ToString("yyyy-MM-dd"));
}
else
{
query = "select * from MSG where StrTalker=?";
wXMsgs = con.Query<WXMsg>(query, uid);
}
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 = DBInfo["MultiSearchChatMsg"];
SQLiteConnection? con = getCon("MultiSearchChatMsg");
if (con == null)
return null;
@@ -131,7 +533,7 @@ namespace WechatPCMsgBakTool
}
public WXSessionAttachInfo? GetWXMsgAtc(WXMsg msg)
{
SQLiteConnection con = DBInfo["MultiSearchChatMsg"];
SQLiteConnection? con = getCon("MultiSearchChatMsg");
if (con == null)
return null;
@@ -160,17 +562,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;
}
@@ -179,13 +578,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)
@@ -209,27 +609,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.Copy(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)
@@ -240,7 +682,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;
@@ -249,17 +691,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);
@@ -275,23 +721,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;
}
@@ -303,5 +747,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 account = "") {
string checkResult = Init(path, 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,38 +94,52 @@ 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 static void SaveConfig(UserBakConfig userBakConfig)
public UserBakConfig ReturnConfig()
{
return UserBakConfig;
}
public static void SaveConfig(UserBakConfig userBakConfig, bool manual = false)
{
if(userBakConfig.UserWorkspacePath != "")
{
DirectoryInfo directoryInfo = new DirectoryInfo(userBakConfig.UserWorkspacePath);
if(directoryInfo.Parent != null)
{
string json_path = Path.Combine(directoryInfo.Parent.FullName, userBakConfig.UserName + ".json");
string json_path = Path.Combine(directoryInfo.Parent.FullName, userBakConfig.Manual ? userBakConfig.Hash + ".json" : userBakConfig.UserName + ".json");
string json = JsonConvert.SerializeObject(userBakConfig);
File.WriteAllText(json_path, json);
}
}
}
private string Init(string path,string account = "")
public void ManualInit()
{
Init("", true, "");
}
private string Init(string path,bool manual = false,string account = "")
{
string curPath = AppDomain.CurrentDomain.BaseDirectory;
string md5 = GetMd5Hash(path);
string[] paths = path.Split(new string[] { "/", "\\" }, StringSplitOptions.None);
string username = paths[paths.Length - 1];
UserBakConfig.UserResPath = path;
UserBakConfig.UserWorkspacePath = Path.Combine(curPath, "workspace", md5);
UserBakConfig.Hash = md5;
UserBakConfig.UserName = username;
UserBakConfig.Account = account;
if (!manual)
{
string md5 = GetMd5Hash(path);
string[] paths = path.Split(new string[] { "/", "\\" }, StringSplitOptions.None);
string username = paths[paths.Length - 1];
UserBakConfig.UserResPath = path;
UserBakConfig.UserWorkspacePath = Path.Combine(curPath, "workspace", md5);
UserBakConfig.Hash = md5;
UserBakConfig.UserName = username;
UserBakConfig.Account = account;
}
if (!Directory.Exists(UserBakConfig.UserResPath))
UserBakConfig.Manual = manual;
if (!Directory.Exists(UserBakConfig.UserResPath) && !manual)
{
return "用户资源文件夹不存在,如需使用离线数据,请从工作区读取";
}
@@ -88,6 +151,7 @@ namespace WechatPCMsgBakTool
string db = Path.Combine(UserBakConfig.UserWorkspacePath, "OriginalDB");
string decDb = Path.Combine(UserBakConfig.UserWorkspacePath, "DecDB");
if (!Directory.Exists(db))
{
Directory.CreateDirectory (db);
@@ -96,7 +160,7 @@ namespace WechatPCMsgBakTool
{
Directory.CreateDirectory(decDb);
}
SaveConfig(UserBakConfig);
SaveConfig(UserBakConfig, manual);
return "";
}

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.7.7</AssemblyVersion>
<FileVersion>0.9.7.7</FileVersion>
<Version>0.9.7.7</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,39 +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.6.0.2</AssemblyVersion>
<FileVersion>0.6.0.2</FileVersion>
<Version>0.6.0.2</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.6" />
<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\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

@@ -2,14 +2,77 @@
{
"Version": "3.9.6.33",
"BaseAddr": 62031872
},{
"Version":"3.9.7.25",
},
{
"Version": "3.9.7.25",
"BaseAddr": 63484032
},{
"Version":"3.9.7.29",
},
{
"Version": "3.9.7.29",
"BaseAddr": 63488256
},{
"Version":"3.9.8.15",
},
{
"Version": "3.9.8.15",
"BaseAddr": 64997904
},
{
"Version": "3.9.8.25",
"BaseAddr": 65002192
},
{
"Version": "3.9.9.27",
"BaseAddr": 68066576
},
{
"Version": "3.9.9.35",
"BaseAddr": 68066576
},
{
"Version": "3.9.9.43",
"BaseAddr": 68067216
},
{
"Version": "3.9.10.19",
"BaseAddr": 95131040
},
{
"Version": "3.9.10.27",
"BaseAddr": 95126928
},
{
"Version": "3.9.11.19",
"BaseAddr": 93551568
},
{
"Version": "3.9.11.23",
"BaseAddr": 93700920
},
{
"Version": "3.9.11.25",
"BaseAddr": 93702352
},
{
"Version": "3.9.12.15",
"BaseAddr": 93814816
},
{
"Version": "3.9.12.17",
"BaseAddr": 93836256
},
{
"Version": "3.9.12.31",
"BaseAddr": 94518176
},
{
"Version": "3.9.12.37",
"BaseAddr": 94522080
},
{
"Version": "3.9.12.45",
"BaseAddr": 94505056
},
{
"Version": "3.9.12.51",
"BaseAddr": 94556448
}
]