Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3da9a923b4 | ||
|
|
f07d93c461 | ||
|
|
6653bcd035 | ||
|
|
85f77c468e | ||
|
|
f16e0b020b | ||
|
|
00b6596237 | ||
|
|
3dfb81b990 | ||
|
|
f3d73d2507 | ||
|
|
b276815960 | ||
|
|
c507e68080 | ||
|
|
932b2d099f | ||
|
|
49c39b13cf | ||
|
|
cc63fe94a6 | ||
|
|
ddfec75201 | ||
|
|
e174f6d00a | ||
|
|
6234ad3084 | ||
|
|
ce393253a8 | ||
|
|
db79e51305 | ||
|
|
cf7d75625d | ||
|
|
cca9e0626d | ||
|
|
f02a1164cf | ||
|
|
5ae3e6ef5d | ||
|
|
2399fa5f4a | ||
|
|
a49f9ec9de | ||
|
|
eaacb554ac | ||
|
|
c996a89303 | ||
|
|
eb4d4fa3fa | ||
|
|
9bf5d1c7d3 | ||
|
|
24f2962475 | ||
|
|
5475b5414f | ||
|
|
07f58afd98 | ||
|
|
f8a06fa000 | ||
|
|
375f17fe49 | ||
|
|
985505b2e7 | ||
|
|
9ad5f6b311 | ||
|
|
25b006b992 | ||
|
|
6c7614dcac | ||
|
|
4cb96b4449 | ||
|
|
3957f2462e | ||
|
|
1929ef64fe | ||
|
|
74ad9297c2 | ||
|
|
ffbc855a0f | ||
|
|
50c3b22c74 | ||
|
|
d1a0f3897f | ||
|
|
4b5554cbe1 | ||
|
|
585e6683cd | ||
|
|
e83d3869cc | ||
|
|
191849e9c2 | ||
|
|
486a797c5f | ||
|
|
33089f8fcf | ||
|
|
d4aa12cf07 | ||
|
|
85b0b1db52 | ||
|
|
8305a58c39 | ||
|
|
de7a18a28e |
@@ -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">
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
6
App.xaml
6
App.xaml
@@ -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>
|
||||
|
||||
@@ -6,7 +6,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace WechatPCMsgBakTool
|
||||
namespace WechatBakTool
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
|
||||
18
Export/ExportInterface.cs
Normal file
18
Export/ExportInterface.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using WechatBakTool.Model;
|
||||
using WechatBakTool.ViewModel;
|
||||
|
||||
namespace WechatBakTool.Export
|
||||
{
|
||||
public interface IExport
|
||||
{
|
||||
void InitTemplate(WXContact session,string path);
|
||||
bool SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel);
|
||||
void SetEnd();
|
||||
void Save(string path = "");
|
||||
}
|
||||
}
|
||||
355
Export/HtmlExport.cs
Normal file
355
Export/HtmlExport.cs
Normal file
@@ -0,0 +1,355 @@
|
||||
using K4os.Compression.LZ4.Encoders;
|
||||
using K4os.Compression.LZ4;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using WechatBakTool.Model;
|
||||
using System.Xml;
|
||||
using Newtonsoft.Json;
|
||||
using WechatBakTool.ViewModel;
|
||||
using System.Security.Policy;
|
||||
using System.Windows;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace WechatBakTool.Export
|
||||
{
|
||||
public class HtmlExport : IExport
|
||||
{
|
||||
private string HtmlBody = "";
|
||||
private WXSession? Session = null;
|
||||
private string Path = "";
|
||||
public void InitTemplate(WXSession session)
|
||||
{
|
||||
Session = session;
|
||||
HtmlBody = "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>WechatBakTool</title><style>p{margin:0px;}.msg{padding-bottom:10px;}.nickname{font-size:10px;}.content{font-size:14px;}</style></head><body>";
|
||||
|
||||
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\"><b>与 {0}({1}) 的聊天记录</b></p>", Session.NickName, Session.UserName);
|
||||
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\"><b>导出时间:{0}</b></p><hr/>", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
public void InitTemplate(WXContact contact, string p)
|
||||
{
|
||||
Path = p;
|
||||
WXSession session = new WXSession();
|
||||
session.NickName = contact.NickName;
|
||||
session.UserName = contact.UserName;
|
||||
InitTemplate(session);
|
||||
}
|
||||
|
||||
public void Save(string path = "")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void SetEnd()
|
||||
{
|
||||
HtmlBody += "</body></html>";
|
||||
File.AppendAllText(Path, HtmlBody);
|
||||
}
|
||||
|
||||
public bool SetMsg(WXUserReader reader, WXContact contact,WorkspaceViewModel viewModel)
|
||||
{
|
||||
if (Session == null)
|
||||
throw new Exception("请初始化模版:Not Use InitTemplate");
|
||||
|
||||
List<WXMsg>? msgList = reader.GetWXMsgs(contact.UserName);
|
||||
if (msgList == null)
|
||||
throw new Exception("获取消息失败,请确认数据库读取正常");
|
||||
|
||||
if(msgList.Count == 0)
|
||||
{
|
||||
viewModel.ExportCount = "没有消息,忽略";
|
||||
return false;
|
||||
}
|
||||
msgList.Sort((x, y) => x.CreateTime.CompareTo(y.CreateTime));
|
||||
|
||||
bool err = false;
|
||||
int msgCount = 0;
|
||||
|
||||
StreamWriter streamWriter = new StreamWriter(Path, true);
|
||||
foreach (var msg in msgList)
|
||||
{
|
||||
try
|
||||
{
|
||||
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\">{0} <span style=\"padding-left:10px;\">{1}</span></p>", msg.IsSender ? "我" : msg.NickName, TimeStampToDateTime(msg.CreateTime).ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
|
||||
if (msg.Type == 1)
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", msg.StrContent);
|
||||
else if (msg.Type == 3)
|
||||
{
|
||||
string? path = reader.GetAttachment(WXMsgType.Image, msg);
|
||||
if (path == null)
|
||||
{
|
||||
#if DEBUG
|
||||
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Img Error Path=>", path));
|
||||
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Img Error Msg=>", JsonConvert.SerializeObject(msg)));
|
||||
#endif
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "图片转换出现错误或文件不存在");
|
||||
continue;
|
||||
}
|
||||
HtmlBody += string.Format("<p class=\"content\"><img src=\"{0}\" style=\"max-height:1000px;max-width:1000px;\"/></p></div>", path);
|
||||
}
|
||||
else if (msg.Type == 43)
|
||||
{
|
||||
string? path = reader.GetAttachment(WXMsgType.Video, msg);
|
||||
if (path == null)
|
||||
{
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "视频不存在");
|
||||
continue;
|
||||
}
|
||||
HtmlBody += string.Format("<p class=\"content\"><video controls style=\"max-height:300px;max-width:300px;\"><source src=\"{0}\" type=\"video/mp4\" /></video></p></div>", path);
|
||||
}
|
||||
else if (msg.Type == 47)
|
||||
{
|
||||
string? path = reader.GetAttachment(WXMsgType.Emoji, msg);
|
||||
if (path == null)
|
||||
{
|
||||
#if DEBUG
|
||||
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Emoji Error Path=>", path));
|
||||
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Emoji Error Msg=>", JsonConvert.SerializeObject(msg)));
|
||||
#endif
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "表情未预下载或加密表情");
|
||||
continue;
|
||||
}
|
||||
HtmlBody += string.Format("<p class=\"content\"><img src=\"{0}\" style=\"max-height:300px;max-width:300px;\"/></p></div>", path);
|
||||
}
|
||||
else if (msg.Type == 49)
|
||||
{
|
||||
if (msg.SubType == 6 || msg.SubType == 40)
|
||||
{
|
||||
string? path = reader.GetAttachment(WXMsgType.File, msg);
|
||||
if (path == null)
|
||||
{
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "文件不存在");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p><p><a href=\"{1}\">点击访问</a></p></div>", "文件:" + path, path);
|
||||
}
|
||||
}
|
||||
else if (msg.SubType == 19)
|
||||
{
|
||||
using (var decoder = LZ4Decoder.Create(true, 64))
|
||||
{
|
||||
byte[] target = new byte[10240];
|
||||
int res = 0;
|
||||
if (msg.CompressContent != null)
|
||||
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
|
||||
|
||||
byte[] data = target.Skip(0).Take(res).ToArray();
|
||||
string xml = Encoding.UTF8.GetString(data);
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
xml = xml.Replace("\n", "");
|
||||
XmlDocument xmlObj = new XmlDocument();
|
||||
xmlObj.LoadXml(xml);
|
||||
if (xmlObj.DocumentElement != null)
|
||||
{
|
||||
string title = "";
|
||||
string record = "";
|
||||
string url = "";
|
||||
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
|
||||
if (findNode != null)
|
||||
{
|
||||
if (findNode.Count > 0)
|
||||
{
|
||||
title = findNode[0]!.InnerText;
|
||||
}
|
||||
}
|
||||
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p>", title);
|
||||
try
|
||||
{
|
||||
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/recorditem");
|
||||
if (findNode != null)
|
||||
{
|
||||
if (findNode.Count > 0)
|
||||
{
|
||||
XmlDocument itemObj = new XmlDocument();
|
||||
itemObj.LoadXml(findNode[0]!.InnerText);
|
||||
XmlNodeList? itemNode = itemObj.DocumentElement.SelectNodes("/recordinfo/datalist/dataitem");
|
||||
if (itemNode.Count > 0)
|
||||
{
|
||||
foreach (XmlNode node in itemNode)
|
||||
{
|
||||
string nodeMsg;
|
||||
string name = node["sourcename"].InnerText;
|
||||
if (node.Attributes["datatype"].InnerText == "1")
|
||||
nodeMsg = node["datadesc1"].InnerText;
|
||||
else if (node.Attributes["datatype"].InnerText == "2")
|
||||
nodeMsg = "不支持的消息";
|
||||
else
|
||||
nodeMsg = node["datatitle"].InnerText;
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}:{1}</p>", name, nodeMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p>", "解析异常");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (msg.SubType == 57)
|
||||
{
|
||||
using (var decoder = LZ4Decoder.Create(true, 64))
|
||||
{
|
||||
byte[] target = new byte[10240];
|
||||
int res = 0;
|
||||
if (msg.CompressContent != null)
|
||||
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
|
||||
|
||||
byte[] data = target.Skip(0).Take(res).ToArray();
|
||||
string xml = Encoding.UTF8.GetString(data);
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
xml = xml.Replace("\n", "");
|
||||
XmlDocument xmlObj = new XmlDocument();
|
||||
xmlObj.LoadXml(xml);
|
||||
if (xmlObj.DocumentElement != null)
|
||||
{
|
||||
string title = "";
|
||||
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
|
||||
if (findNode != null)
|
||||
{
|
||||
if (findNode.Count > 0)
|
||||
{
|
||||
title = findNode[0]!.InnerText;
|
||||
}
|
||||
}
|
||||
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p>", title);
|
||||
|
||||
XmlNode? type = xmlObj.DocumentElement.SelectSingleNode("/msg/appmsg/refermsg/type");
|
||||
if(type != null)
|
||||
{
|
||||
XmlNode? source = xmlObj.DocumentElement.SelectSingleNode("/msg/appmsg/refermsg/displayname");
|
||||
XmlNode? text = xmlObj.DocumentElement.SelectSingleNode("/msg/appmsg/refermsg/content");
|
||||
if(type.InnerText == "1" && source != null && text != null)
|
||||
{
|
||||
HtmlBody += string.Format("<p class=\"content\">[引用]{0}:{1}</p>", source.InnerText, text.InnerText);
|
||||
}
|
||||
else if(type.InnerText != "1" && source != null && text != null)
|
||||
{
|
||||
HtmlBody += string.Format("<p class=\"content\">[引用]{0}:非文本消息类型-{1}</p>", source.InnerText, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
HtmlBody += string.Format("<p class=\"content\">未知的引用消息</p>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var decoder = LZ4Decoder.Create(true, 64))
|
||||
{
|
||||
byte[] target = new byte[10240];
|
||||
int res = 0;
|
||||
if (msg.CompressContent != null)
|
||||
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
|
||||
|
||||
byte[] data = target.Skip(0).Take(res).ToArray();
|
||||
string xml = Encoding.UTF8.GetString(data);
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
xml = xml.Replace("\n", "");
|
||||
XmlDocument xmlObj = new XmlDocument();
|
||||
xmlObj.LoadXml(xml);
|
||||
if (xmlObj.DocumentElement != null)
|
||||
{
|
||||
string title = "";
|
||||
string appName = "";
|
||||
string url = "";
|
||||
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
|
||||
if (findNode != null)
|
||||
{
|
||||
if (findNode.Count > 0)
|
||||
{
|
||||
title = findNode[0]!.InnerText;
|
||||
}
|
||||
}
|
||||
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/sourcedisplayname");
|
||||
if (findNode != null)
|
||||
{
|
||||
if (findNode.Count > 0)
|
||||
{
|
||||
appName = findNode[0]!.InnerText;
|
||||
}
|
||||
}
|
||||
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/url");
|
||||
if (findNode != null)
|
||||
{
|
||||
if (findNode.Count > 0)
|
||||
{
|
||||
url = findNode[0]!.InnerText;
|
||||
}
|
||||
}
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}|{1}</p><p><a href=\"{2}\">点击访问</a></p></div>", appName, title, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (msg.Type == 34)
|
||||
{
|
||||
string? path = reader.GetAttachment(WXMsgType.Audio, msg);
|
||||
if (path == null)
|
||||
{
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "语音不存在");
|
||||
continue;
|
||||
}
|
||||
HtmlBody += string.Format("<p class=\"content\"><audio controls src=\"{0}\"></audio></p></div>", path);
|
||||
}
|
||||
else
|
||||
{
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "暂未支持的消息");
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
err = true;
|
||||
File.AppendAllText("Err.log", JsonConvert.SerializeObject(msg));
|
||||
File.AppendAllText("Err.log", ex.ToString());
|
||||
}
|
||||
|
||||
msgCount++;
|
||||
if(msgCount % 50 == 0)
|
||||
{
|
||||
streamWriter.WriteLine(HtmlBody);
|
||||
HtmlBody = "";
|
||||
viewModel.ExportCount = msgCount.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
if(msgCount % 50 != 0)
|
||||
{
|
||||
streamWriter.WriteLine(HtmlBody);
|
||||
HtmlBody = "";
|
||||
viewModel.ExportCount = msgCount.ToString();
|
||||
if (err)
|
||||
{
|
||||
MessageBox.Show("本次导出发生了异常,部分消息被跳过,更新至最新版本后还有此问题,请将Err.log反馈给开发,谢谢。", "错误");
|
||||
}
|
||||
}
|
||||
streamWriter.Close();
|
||||
streamWriter.Dispose();
|
||||
return true;
|
||||
}
|
||||
private static DateTime TimeStampToDateTime(long timeStamp, bool inMilli = false)
|
||||
{
|
||||
DateTimeOffset dateTimeOffset = inMilli ? DateTimeOffset.FromUnixTimeMilliseconds(timeStamp) : DateTimeOffset.FromUnixTimeSeconds(timeStamp);
|
||||
return dateTimeOffset.LocalDateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
158
Export/TXTExport.cs
Normal file
158
Export/TXTExport.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using K4os.Compression.LZ4.Encoders;
|
||||
using K4os.Compression.LZ4;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using WechatBakTool.Model;
|
||||
using WechatBakTool.ViewModel;
|
||||
|
||||
namespace WechatBakTool.Export
|
||||
{
|
||||
public class TXTExport : IExport
|
||||
{
|
||||
private WXContact? Contact { get; set; } = null;
|
||||
private string Path { get; set; } = "";
|
||||
public void InitTemplate(WXContact contact,string p)
|
||||
{
|
||||
Contact = contact;
|
||||
Path = p;
|
||||
if (File.Exists(Path))
|
||||
{
|
||||
File.WriteAllText(Path, "");
|
||||
}
|
||||
File.AppendAllText(Path, string.Format("WechatBakTool\n"));
|
||||
File.AppendAllText(Path, string.Format("与 {0} 的聊天记录\n", Contact.NickName));
|
||||
File.AppendAllText(Path, string.Format("导出时间:{0}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
|
||||
File.AppendAllText(Path, string.Format("=================================================================\n\n\n"));
|
||||
}
|
||||
|
||||
void IExport.Save(string path)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IExport.SetEnd()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public bool SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel)
|
||||
{
|
||||
if (Contact == null)
|
||||
throw new Exception("请初始化模版:Not Use InitTemplate");
|
||||
|
||||
List<WXMsg>? msgList = reader.GetWXMsgs(Contact.UserName);
|
||||
if (msgList == null)
|
||||
throw new Exception("获取消息失败,请确认数据库读取正常");
|
||||
|
||||
msgList.Sort((x, y) => x.CreateTime.CompareTo(y.CreateTime));
|
||||
|
||||
int msgCount = 0;
|
||||
foreach (var msg in msgList)
|
||||
{
|
||||
string txtMsg = "";
|
||||
switch (msg.Type)
|
||||
{
|
||||
case 1:
|
||||
txtMsg = msg.StrContent;
|
||||
break;
|
||||
case 3:
|
||||
txtMsg = "[图片]";
|
||||
break;
|
||||
case 34:
|
||||
txtMsg = "[语音]";
|
||||
break;
|
||||
case 43:
|
||||
txtMsg = "[视频]";
|
||||
break;
|
||||
case 49:
|
||||
if (msg.SubType == 6 || msg.SubType == 19 || msg.SubType == 40)
|
||||
{
|
||||
txtMsg = "[文件]";
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var decoder = LZ4Decoder.Create(true, 64))
|
||||
{
|
||||
byte[] target = new byte[10240];
|
||||
int res = 0;
|
||||
if (msg.CompressContent != null)
|
||||
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
|
||||
|
||||
byte[] data = target.Skip(0).Take(res).ToArray();
|
||||
string xml = Encoding.UTF8.GetString(data);
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
xml = xml.Replace("\n", "");
|
||||
XmlDocument xmlObj = new XmlDocument();
|
||||
xmlObj.LoadXml(xml);
|
||||
if (xmlObj.DocumentElement != null)
|
||||
{
|
||||
string title = "";
|
||||
string appName = "";
|
||||
string url = "";
|
||||
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
|
||||
if (findNode != null)
|
||||
{
|
||||
if (findNode.Count > 0)
|
||||
{
|
||||
title = findNode[0]!.InnerText;
|
||||
}
|
||||
}
|
||||
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/sourcedisplayname");
|
||||
if (findNode != null)
|
||||
{
|
||||
if (findNode.Count > 0)
|
||||
{
|
||||
appName = findNode[0]!.InnerText;
|
||||
}
|
||||
}
|
||||
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/url");
|
||||
if (findNode != null)
|
||||
{
|
||||
if (findNode.Count > 0)
|
||||
{
|
||||
url = findNode[0]!.InnerText;
|
||||
}
|
||||
}
|
||||
txtMsg = string.Format("{0},标题:{1},链接:{2}", appName, title, url);
|
||||
}
|
||||
else
|
||||
{
|
||||
txtMsg = "[分享链接出错了]";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
txtMsg = "[分享链接出错了]";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
txtMsg = "[分享链接出错了]";
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
string row = string.Format("{2} | {0}:{1}\n", msg.IsSender ? "我" : msg.NickName, txtMsg, TimeStampToDateTime(msg.CreateTime).ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
File.AppendAllText(Path, row);
|
||||
msgCount++;
|
||||
viewModel.ExportCount = msgCount.ToString();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static DateTime TimeStampToDateTime(long timeStamp, bool inMilli = false)
|
||||
{
|
||||
DateTimeOffset dateTimeOffset = inMilli ? DateTimeOffset.FromUnixTimeMilliseconds(timeStamp) : DateTimeOffset.FromUnixTimeSeconds(timeStamp);
|
||||
return dateTimeOffset.LocalDateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,199 @@ 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("搜索不到微信账号,请确认用户名是否正确,如错误请重新新建工作区,务必确认账号是否正确");
|
||||
}
|
||||
}
|
||||
else if (find_key_type == 3)
|
||||
{
|
||||
string searchString = "-----BEGIN PUBLIC KEY-----";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static byte[] DecryptDB(byte[] db_file_bytes, byte[] password_bytes)
|
||||
public static string GetMD5(string text)
|
||||
{
|
||||
MD5 md5 = MD5.Create();
|
||||
byte[] bs = Encoding.UTF8.GetBytes(text);
|
||||
byte[] hs = md5.ComputeHash(bs);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach(byte b in hs)
|
||||
{
|
||||
sb.Append(b.ToString("x2"));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
public static void DecryptDB(string file, string to_file, byte[] password_bytes)
|
||||
{
|
||||
//数据库头16字节是盐值
|
||||
var salt = db_file_bytes.Take(16).ToArray();
|
||||
byte[] salt_key = new byte[16];
|
||||
|
||||
FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
|
||||
fileStream.Read(salt_key, 0, 16);
|
||||
|
||||
//HMAC验证时用的盐值需要亦或0x3a
|
||||
byte[] hmac_salt = new byte[16];
|
||||
for (int i = 0; i < salt.Length; i++)
|
||||
for (int i = 0; i < salt_key.Length; i++)
|
||||
{
|
||||
hmac_salt[i] = (byte)(salt[i] ^ 0x3a);
|
||||
hmac_salt[i] = (byte)(salt_key[i] ^ 0x3a);
|
||||
}
|
||||
//计算保留段长度
|
||||
int reserved = IV_SIZE;
|
||||
long reserved = IV_SIZE;
|
||||
reserved += HMAC_SHA1_SIZE;
|
||||
reserved = ((reserved % AES_BLOCK_SIZE) == 0) ? reserved : ((reserved / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE;
|
||||
|
||||
//密钥扩展,分别对应AES解密密钥和HMAC验证密钥
|
||||
byte[] key = new byte[KEY_SIZE];
|
||||
byte[] hmac_key = new byte[KEY_SIZE];
|
||||
OpenSSLInterop.PKCS5_PBKDF2_HMAC_SHA1(password_bytes, password_bytes.Length, salt, salt.Length, DEFAULT_ITER, key.Length, key);
|
||||
OpenSSLInterop.PKCS5_PBKDF2_HMAC_SHA1(password_bytes, password_bytes.Length, salt_key, salt_key.Length, DEFAULT_ITER, key.Length, key);
|
||||
OpenSSLInterop.PKCS5_PBKDF2_HMAC_SHA1(key, key.Length, hmac_salt, hmac_salt.Length, 2, hmac_key.Length, hmac_key);
|
||||
|
||||
int page_no = 0;
|
||||
int offset = 16;
|
||||
long page_no = 0;
|
||||
long offset = 16;
|
||||
Console.WriteLine("开始解密...");
|
||||
var hmac_sha1 = HMAC.Create("HMACSHA1");
|
||||
hmac_sha1!.Key = hmac_key;
|
||||
|
||||
List<byte> decrypted_file_bytes = new List<byte>();
|
||||
while (page_no < db_file_bytes.Length / DEFAULT_PAGESIZE)
|
||||
FileStream tofileStream = new FileStream(to_file, FileMode.OpenOrCreate, FileAccess.Write);
|
||||
|
||||
using (fileStream)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 当前分页小于计算分页数
|
||||
while (page_no < fileStream.Length / DEFAULT_PAGESIZE)
|
||||
{
|
||||
// 读内容
|
||||
byte[] decryped_page_bytes = new byte[DEFAULT_PAGESIZE];
|
||||
byte[] going_to_hashed = new byte[DEFAULT_PAGESIZE - reserved - offset + IV_SIZE + 4];
|
||||
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + offset, SeekOrigin.Begin);
|
||||
fileStream.Read(going_to_hashed, 0, Convert.ToInt32(DEFAULT_PAGESIZE - reserved - offset + IV_SIZE));
|
||||
|
||||
// 分页标志
|
||||
var page_bytes = BitConverter.GetBytes(page_no + 1);
|
||||
page_bytes.CopyTo(going_to_hashed, DEFAULT_PAGESIZE - reserved - offset + IV_SIZE);
|
||||
var hash_mac_compute = hmac_sha1.ComputeHash(going_to_hashed, 0, going_to_hashed.Length);
|
||||
|
||||
// 取分页hash
|
||||
byte[] hash_mac_cached = new byte[hash_mac_compute.Length];
|
||||
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + DEFAULT_PAGESIZE - reserved + IV_SIZE, SeekOrigin.Begin);
|
||||
fileStream.Read(hash_mac_cached, 0, hash_mac_compute.Length);
|
||||
|
||||
if (!hash_mac_compute.SequenceEqual(hash_mac_cached) && page_no == 0)
|
||||
{
|
||||
Console.WriteLine("Hash错误...");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (page_no == 0)
|
||||
{
|
||||
var header_bytes = Encoding.ASCII.GetBytes(SQLITE_HEADER);
|
||||
header_bytes.CopyTo(decryped_page_bytes, 0);
|
||||
}
|
||||
|
||||
// 加密内容
|
||||
byte[] page_content = new byte[DEFAULT_PAGESIZE - reserved - offset];
|
||||
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + offset, SeekOrigin.Begin);
|
||||
fileStream.Read(page_content, 0, Convert.ToInt32(DEFAULT_PAGESIZE - reserved - offset));
|
||||
|
||||
// iv
|
||||
byte[] iv = new byte[16];
|
||||
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + (DEFAULT_PAGESIZE - reserved), SeekOrigin.Begin);
|
||||
fileStream.Read(iv, 0, 16);
|
||||
|
||||
var decrypted_content = AESDecrypt(page_content, key, iv);
|
||||
decrypted_content.CopyTo(decryped_page_bytes, offset);
|
||||
|
||||
// 保留
|
||||
byte[] reserved_byte = new byte[reserved];
|
||||
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + DEFAULT_PAGESIZE - reserved, SeekOrigin.Begin);
|
||||
fileStream.Read(reserved_byte, 0, Convert.ToInt32(reserved));
|
||||
reserved_byte.CopyTo(decryped_page_bytes, DEFAULT_PAGESIZE - reserved);
|
||||
|
||||
tofileStream.Write(decryped_page_bytes, 0, decryped_page_bytes.Length);
|
||||
|
||||
}
|
||||
page_no++;
|
||||
offset = 0;
|
||||
}
|
||||
}catch(Exception ex)
|
||||
{
|
||||
File.AppendAllText("err.log", "page=>" + page_no.ToString() + "\r\n");
|
||||
File.AppendAllText("err.log", "size=>" + fileStream.Length.ToString() + "\r\n");
|
||||
File.AppendAllText("err.log", "postion=>" + ((page_no * DEFAULT_PAGESIZE) + offset).ToString() + "\r\n");
|
||||
File.AppendAllText("err.log", ex.ToString() + "\r\n");
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 旧版解密
|
||||
while (page_no < fileStream.Length / DEFAULT_PAGESIZE)
|
||||
{
|
||||
byte[] decryped_page_bytes = new byte[DEFAULT_PAGESIZE];
|
||||
byte[] going_to_hashed = new byte[DEFAULT_PAGESIZE - reserved - offset + IV_SIZE + 4];
|
||||
@@ -130,8 +267,7 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
var page_bytes = BitConverter.GetBytes(page_no + 1);
|
||||
page_bytes.CopyTo(going_to_hashed, DEFAULT_PAGESIZE - reserved - offset + IV_SIZE);
|
||||
//计算分页的Hash
|
||||
var hash_mac_compute = hmac_sha1.ComputeHash(going_to_hashed, 0, going_to_hashed.Count());
|
||||
//取出分页中存储的Hash
|
||||
var hash_mac_compute = hmac_sha1.ComputeHash(going_to_hashed, 0, going_to_hashed.Length);
|
||||
var hash_mac_cached = db_file_bytes.Skip((page_no * DEFAULT_PAGESIZE) + DEFAULT_PAGESIZE - reserved + IV_SIZE).Take(hash_mac_compute.Length).ToArray();
|
||||
//对比两个Hash
|
||||
if (!hash_mac_compute.SequenceEqual(hash_mac_cached))
|
||||
@@ -160,8 +296,9 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
{
|
||||
decrypted_file_bytes.Add(item);
|
||||
}
|
||||
}
|
||||
return decrypted_file_bytes.ToArray();
|
||||
}*/
|
||||
tofileStream.Close();
|
||||
tofileStream.Dispose();
|
||||
}
|
||||
public static byte[] AESDecrypt(byte[] content, byte[] key, byte[] iv)
|
||||
{
|
||||
@@ -180,99 +317,50 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
{
|
||||
return BitConverter.ToString(bytes, 0).Replace("-", string.Empty).ToLower().ToUpper();
|
||||
}
|
||||
|
||||
private readonly static List<byte[]> ImgHeader = new List<byte[]>()
|
||||
{
|
||||
new byte[] { 0xFF, 0xD8 },//JPG
|
||||
new byte[] { 0x89, 0x50 },//PNG
|
||||
new byte[] { 0x42, 0x4D },//BMP
|
||||
new byte[] { 0x47, 0x49 },//GIF
|
||||
new byte[] { 0x49, 0x49 },//TIF
|
||||
new byte[] { 0x4D, 0x4D },//TIF
|
||||
};
|
||||
public static byte[] DecImage(string source)
|
||||
{
|
||||
//读取数据
|
||||
byte[] fileBytes = File.ReadAllBytes(source);
|
||||
//算差异转换
|
||||
byte key = GetImgKey(fileBytes);
|
||||
fileBytes = ConvertData(fileBytes, key);
|
||||
return fileBytes;
|
||||
foreach (byte[] b in ImgHeader)
|
||||
{
|
||||
byte t = (byte)(fileBytes[0] ^ b[0]);
|
||||
byte[] decData = fileBytes.Select(b => (byte)(b ^ t)).ToArray();
|
||||
if (b[1] != decData[1])
|
||||
continue;
|
||||
else
|
||||
{
|
||||
return decData;
|
||||
}
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
public static string CheckFileType(byte[] data)
|
||||
{
|
||||
switch (data[0])
|
||||
{
|
||||
case 0XFF: //byte[] jpg = new byte[] { 0xFF, 0xD8, 0xFF };
|
||||
{
|
||||
if (data[1] == 0xD8 && data[2] == 0xFF)
|
||||
{
|
||||
return ".jpg";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x89: //byte[] png = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
|
||||
{
|
||||
if (data[1] == 0x50 && data[2] == 0x4E && data[7] == 0x0A)
|
||||
{
|
||||
return ".png";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x42: //byte[] bmp = new byte[] { 0x42, 0x4D };
|
||||
{
|
||||
if (data[1] == 0X4D)
|
||||
{
|
||||
return ".bmp";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x47: //byte[] gif = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39(0x37), 0x61 };
|
||||
{
|
||||
if (data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x38 && data[5] == 0x61)
|
||||
{
|
||||
return ".gif";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x49: // byte[] tif = new byte[] { 0x49, 0x49, 0x2A, 0x00 };
|
||||
{
|
||||
if (data[1] == 0x49 && data[2] == 0x2A && data[3] == 0x00)
|
||||
{
|
||||
return ".tif";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x4D: //byte[] tif = new byte[] { 0x4D, 0x4D, 0x2A, 0x00 };
|
||||
{
|
||||
if (data[1] == 0x4D && data[2] == 0x2A && data[3] == 0x00)
|
||||
{
|
||||
return ".tif";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ".dat";
|
||||
}
|
||||
private static byte GetImgKey(byte[] fileRaw)
|
||||
{
|
||||
byte[] raw = new byte[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
raw[i] = fileRaw[i];
|
||||
}
|
||||
|
||||
for (byte key = 0x01; key < 0xFF; key++)
|
||||
{
|
||||
byte[] buf = new byte[8];
|
||||
raw.CopyTo(buf, 0);
|
||||
|
||||
if (CheckFileType(ConvertData(buf, key)) != ".dat")
|
||||
{
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return 0x00;
|
||||
}
|
||||
private static byte[] ConvertData(byte[] data, byte key)
|
||||
{
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
data[i] ^= key;
|
||||
}
|
||||
|
||||
return data;
|
||||
if (data[0] == 0xFF && data[1] == 0xD8)
|
||||
return ".jpg";
|
||||
else if (data[0] == 0x89 && data[1] == 0x50)
|
||||
return ".png";
|
||||
else if (data[0] == 0x42 && data[1] == 0X4D)
|
||||
return ".bmp";
|
||||
else if (data[0] == 0x47 && data[1] == 0x49)
|
||||
return ".gif";
|
||||
else if (data[0] == 0x49 && data[1] == 0x49)
|
||||
return ".tif";
|
||||
else if (data[0] == 0x4D && data[1] == 0x4D)
|
||||
return ".tif";
|
||||
else
|
||||
return ".dat";
|
||||
}
|
||||
public static string SaveDecImage(byte[] fileRaw,string source,string to_dir,string type)
|
||||
{
|
||||
@@ -286,6 +374,22 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
}
|
||||
return saveFilePath;
|
||||
}
|
||||
public static void DecryUserData(byte[] key, string source, string to,CreateWorkViewModel viewModel)
|
||||
{
|
||||
string dbPath = source;
|
||||
string decPath = to;
|
||||
if (!Directory.Exists(decPath))
|
||||
Directory.CreateDirectory(decPath);
|
||||
|
||||
string[] filePath = Directory.GetFiles(dbPath);
|
||||
foreach (string file in filePath)
|
||||
{
|
||||
FileInfo info = new FileInfo(file);
|
||||
viewModel.LabelStatus = "正在解密" + info.Name;
|
||||
string to_file = Path.Combine(decPath, info.Name);
|
||||
DecryptDB(file,to_file, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WechatPCMsgBakTool.Helpers
|
||||
namespace WechatBakTool.Helpers
|
||||
{
|
||||
public static class DevicePathMapper
|
||||
{
|
||||
@@ -15,7 +15,10 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
|
||||
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()) :
|
||||
null;
|
||||
|
||||
239
Helpers/NativeAPI.cs
Normal file
239
Helpers/NativeAPI.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace WechatBakTool.Helpers
|
||||
{
|
||||
public class NativeAPI
|
||||
{
|
||||
// Constants
|
||||
//=================================================
|
||||
|
||||
internal static uint NTSTATUS_STATUS_SUCCESS = 0x0;
|
||||
internal static uint NTSTATUS_STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
|
||||
internal static uint NTSTATUS_STATUS_ACCESS_DENIED = 0xC0000022;
|
||||
|
||||
internal static uint MEM_COMMIT = 0x1000;
|
||||
internal static uint PAGE_READONLY = 0x02;
|
||||
internal static uint PAGE_READWRITE = 0x04;
|
||||
internal static uint PAGE_EXECUTE = 0x10;
|
||||
internal static uint PAGE_EXECUTE_READ = 0x20;
|
||||
|
||||
// API Constants
|
||||
internal static uint SystemExtendedHandleInformation = 0x40;
|
||||
internal static uint DUPLICATE_SAME_ACCESS = 0x2;
|
||||
|
||||
|
||||
// Structs
|
||||
//=================================================
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct OBJECT_NAME_INFORMATION
|
||||
{
|
||||
public UNICODE_STRING Name;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct OSVERSIONINFOEX
|
||||
{
|
||||
public uint OSVersionInfoSize;
|
||||
public uint MajorVersion;
|
||||
public uint MinorVersion;
|
||||
public uint BuildNumber;
|
||||
public uint PlatformId;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string CSDVersion;
|
||||
public ushort ServicePackMajor;
|
||||
public ushort ServicePackMinor;
|
||||
public ushort SuiteMask;
|
||||
public byte ProductType;
|
||||
public byte Reserved;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct UNICODE_STRING
|
||||
{
|
||||
public ushort Length;
|
||||
public ushort MaximumLength;
|
||||
public IntPtr Buffer;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct GENERIC_MAPPING
|
||||
{
|
||||
public uint GenericRead;
|
||||
public uint GenericWrite;
|
||||
public uint GenericExecute;
|
||||
public uint GenericAll;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct OBJECT_TYPE_INFORMATION
|
||||
{
|
||||
public UNICODE_STRING TypeName;
|
||||
public uint TotalNumberOfObjects;
|
||||
public uint TotalNumberOfHandles;
|
||||
public uint TotalPagedPoolUsage;
|
||||
public uint TotalNonPagedPoolUsage;
|
||||
public uint TotalNamePoolUsage;
|
||||
public uint TotalHandleTableUsage;
|
||||
public uint HighWaterNumberOfObjects;
|
||||
public uint HighWaterNumberOfHandles;
|
||||
public uint HighWaterPagedPoolUsage;
|
||||
public uint HighWaterNonPagedPoolUsage;
|
||||
public uint HighWaterNamePoolUsage;
|
||||
public uint HighWaterHandleTableUsage;
|
||||
public uint InvalidAttributes;
|
||||
public GENERIC_MAPPING GenericMapping;
|
||||
public uint ValidAccessMask;
|
||||
public byte SecurityRequired;
|
||||
public byte MaintainHandleCount;
|
||||
public byte TypeIndex;
|
||||
public byte ReservedByte;
|
||||
public uint PoolType;
|
||||
public uint DefaultPagedPoolCharge;
|
||||
public uint DefaultNonPagedPoolCharge;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct OBJECT_ALL_TYPES_INFORMATION
|
||||
{
|
||||
public uint NumberOfObjectTypes;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct SYSTEM_HANDLE_INFORMATION_EX
|
||||
{
|
||||
public IntPtr NumberOfHandles;
|
||||
public IntPtr Reserved;
|
||||
public SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX[] Handles;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
|
||||
{
|
||||
public IntPtr Object;
|
||||
public IntPtr UniqueProcessId;
|
||||
public IntPtr HandleValue;
|
||||
public uint GrantedAccess;
|
||||
public ushort CreatorBackTraceIndex;
|
||||
public ushort ObjectTypeIndex;
|
||||
public uint HandleAttributes;
|
||||
public uint Reserved;
|
||||
}
|
||||
|
||||
|
||||
public struct MEMORY_BASIC_INFORMATION64
|
||||
{
|
||||
public IntPtr BaseAddress;
|
||||
public IntPtr AllocationBase;
|
||||
public uint AllocationProtect;
|
||||
public uint __alignment1;
|
||||
public ulong RegionSize;
|
||||
public uint State;
|
||||
public uint Protect;
|
||||
public uint Type;
|
||||
public uint __alignment2;
|
||||
}
|
||||
|
||||
// Enums
|
||||
//=================================================
|
||||
|
||||
internal enum OBJECT_INFORMATION_CLASS
|
||||
{
|
||||
ObjectBasicInformation = 0,
|
||||
ObjectNameInformation = 1,
|
||||
ObjectTypeInformation = 2,
|
||||
ObjectAllTypesInformation = 3,
|
||||
ObjectHandleInformation = 4
|
||||
}
|
||||
|
||||
internal enum POOL_TYPE
|
||||
{
|
||||
NonPagedPool,
|
||||
NonPagedPoolExecute = NonPagedPool,
|
||||
PagedPool,
|
||||
NonPagedPoolMustSucceed = NonPagedPool + 2,
|
||||
DontUseThisType,
|
||||
NonPagedPoolCacheAligned = NonPagedPool + 4,
|
||||
PagedPoolCacheAligned,
|
||||
NonPagedPoolCacheAlignedMustS = NonPagedPool + 6,
|
||||
MaxPoolType,
|
||||
NonPagedPoolBase = 0,
|
||||
NonPagedPoolBaseMustSucceed = NonPagedPoolBase + 2,
|
||||
NonPagedPoolBaseCacheAligned = NonPagedPoolBase + 4,
|
||||
NonPagedPoolBaseCacheAlignedMustS = NonPagedPoolBase + 6,
|
||||
NonPagedPoolSession = 32,
|
||||
PagedPoolSession = NonPagedPoolSession + 1,
|
||||
NonPagedPoolMustSucceedSession = PagedPoolSession + 1,
|
||||
DontUseThisTypeSession = NonPagedPoolMustSucceedSession + 1,
|
||||
NonPagedPoolCacheAlignedSession = DontUseThisTypeSession + 1,
|
||||
PagedPoolCacheAlignedSession = NonPagedPoolCacheAlignedSession + 1,
|
||||
NonPagedPoolCacheAlignedMustSSession = PagedPoolCacheAlignedSession + 1,
|
||||
NonPagedPoolNx = 512,
|
||||
NonPagedPoolNxCacheAligned = NonPagedPoolNx + 4,
|
||||
NonPagedPoolSessionNx = NonPagedPoolNx + 32,
|
||||
}
|
||||
|
||||
internal enum PROCESS_ACCESS_FLAGS : uint
|
||||
{
|
||||
All = 0x001F0FFF,
|
||||
Terminate = 0x00000001,
|
||||
CreateThread = 0x00000002,
|
||||
VMOperation = 0x00000008,
|
||||
VMRead = 0x00000010,
|
||||
VMWrite = 0x00000020,
|
||||
DupHandle = 0x00000040,
|
||||
SetInformation = 0x00000200,
|
||||
QueryInformation = 0x00000400,
|
||||
Synchronize = 0x00100000
|
||||
}
|
||||
|
||||
// API
|
||||
//=================================================
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("ntdll.dll")]
|
||||
internal static extern uint RtlGetVersion(
|
||||
ref OSVERSIONINFOEX VersionInformation);
|
||||
|
||||
[DllImport("ntdll.dll")]
|
||||
internal static extern void RtlZeroMemory(
|
||||
IntPtr Destination,
|
||||
uint length);
|
||||
|
||||
[DllImport("ntdll.dll")]
|
||||
internal static extern uint NtQueryObject(
|
||||
IntPtr objectHandle,
|
||||
OBJECT_INFORMATION_CLASS informationClass,
|
||||
IntPtr informationPtr,
|
||||
uint informationLength,
|
||||
ref uint returnLength);
|
||||
|
||||
[DllImport("ntdll.dll")]
|
||||
internal static extern uint NtQuerySystemInformation(
|
||||
uint SystemInformationClass,
|
||||
IntPtr SystemInformation,
|
||||
uint SystemInformationLength,
|
||||
ref uint ReturnLength);
|
||||
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern IntPtr OpenProcess(PROCESS_ACCESS_FLAGS dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern IntPtr GetCurrentProcess();
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION64 lpBuffer, uint dwLength);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead);
|
||||
|
||||
}
|
||||
}
|
||||
200
Helpers/NativeAPIHelper.cs
Normal file
200
Helpers/NativeAPIHelper.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static WechatBakTool.Helpers.NativeAPI;
|
||||
|
||||
namespace WechatBakTool.Helpers
|
||||
{
|
||||
public class NativeAPIHelper
|
||||
{
|
||||
// Managed native buffer
|
||||
internal static IntPtr AllocManagedMemory(uint iSize)
|
||||
{
|
||||
IntPtr pAlloc = Marshal.AllocHGlobal((int)iSize);
|
||||
RtlZeroMemory(pAlloc, iSize);
|
||||
|
||||
return pAlloc;
|
||||
}
|
||||
|
||||
// Free managed buffer
|
||||
internal static bool FreeManagedMemory(IntPtr pAlloc)
|
||||
{
|
||||
Marshal.FreeHGlobal(pAlloc);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get an array of OBJECT_ALL_TYPES_INFORMATION, describing all object types
|
||||
// Win8+ only
|
||||
internal static string FindHandleName(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX systemHandleInformation, Process process)
|
||||
{
|
||||
IntPtr ipHandle = IntPtr.Zero;
|
||||
IntPtr openProcessHandle = IntPtr.Zero;
|
||||
IntPtr hObjectName = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
PROCESS_ACCESS_FLAGS flags = PROCESS_ACCESS_FLAGS.DupHandle | PROCESS_ACCESS_FLAGS.VMRead;
|
||||
openProcessHandle = OpenProcess(flags, false, process.Id);
|
||||
// 通过 DuplicateHandle 访问句柄
|
||||
if (!DuplicateHandle(openProcessHandle, systemHandleInformation.HandleValue, GetCurrentProcess(), out ipHandle, 0, false, DUPLICATE_SAME_ACCESS))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
uint nLength = 0;
|
||||
hObjectName = AllocManagedMemory(256 * 1024);
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
// 查询句柄名称
|
||||
while (NtQueryObject(ipHandle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, hObjectName, nLength, ref nLength) == NTSTATUS_STATUS_INFO_LENGTH_MISMATCH)
|
||||
{
|
||||
FreeManagedMemory(hObjectName);
|
||||
if (nLength == 0)
|
||||
{
|
||||
Console.WriteLine("Length returned at zero!");
|
||||
}
|
||||
hObjectName = AllocManagedMemory(nLength);
|
||||
}
|
||||
}).Wait(100);
|
||||
OBJECT_NAME_INFORMATION? objObjectName = new OBJECT_NAME_INFORMATION();
|
||||
objObjectName = Marshal.PtrToStructure(hObjectName, objObjectName.GetType()) as OBJECT_NAME_INFORMATION?;
|
||||
if (objObjectName == null)
|
||||
return "";
|
||||
if (objObjectName.Value.Name.Buffer != IntPtr.Zero)
|
||||
{
|
||||
string? strObjectName = Marshal.PtrToStringUni(objObjectName.Value.Name.Buffer);
|
||||
if (strObjectName != null)
|
||||
return strObjectName;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
FreeManagedMemory(hObjectName);
|
||||
CloseHandle(ipHandle);
|
||||
CloseHandle(openProcessHandle);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
internal static List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> GetHandleInfoForPID(uint ProcId)
|
||||
{
|
||||
// Create return object
|
||||
List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> ltei = new List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX>();
|
||||
|
||||
// Create Buffer variable
|
||||
IntPtr BuffPtr = IntPtr.Zero;
|
||||
|
||||
// Loop till success
|
||||
uint LoopSize = 0;
|
||||
while (true)
|
||||
{
|
||||
BuffPtr = AllocManagedMemory(LoopSize);
|
||||
uint SystemInformationLength = 0;
|
||||
uint CallRes = NtQuerySystemInformation(SystemExtendedHandleInformation, BuffPtr, LoopSize, ref SystemInformationLength);
|
||||
if (CallRes == NTSTATUS_STATUS_INFO_LENGTH_MISMATCH)
|
||||
{
|
||||
FreeManagedMemory(BuffPtr);
|
||||
LoopSize = Math.Max(LoopSize, SystemInformationLength);
|
||||
}
|
||||
else if (CallRes == NTSTATUS_STATUS_SUCCESS)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (CallRes == NTSTATUS_STATUS_ACCESS_DENIED)
|
||||
{
|
||||
FreeManagedMemory(BuffPtr);
|
||||
throw new AccessViolationException("[!] Failed to query SystemExtendedHandleInformation: Access Denied");
|
||||
}
|
||||
else
|
||||
{
|
||||
FreeManagedMemory(BuffPtr);
|
||||
throw new InvalidOperationException("[!] Failed to query SystemExtendedHandleInformation.");
|
||||
}
|
||||
}
|
||||
|
||||
// Read handle count
|
||||
Int32 HandleCount = Marshal.ReadInt32(BuffPtr);
|
||||
|
||||
// Move Buff ptr
|
||||
BuffPtr = (IntPtr)(BuffPtr.ToInt64() + (IntPtr.Size * 2));
|
||||
|
||||
// Loop handles
|
||||
for (int i = 0; i < HandleCount; i++)
|
||||
{
|
||||
ulong iCurrProcId = (ulong)Marshal.ReadIntPtr((IntPtr)(BuffPtr.ToInt64() + IntPtr.Size));
|
||||
if (ProcId == iCurrProcId)
|
||||
{
|
||||
// Ptr -> SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
|
||||
SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX? tei = (SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX?)Marshal.PtrToStructure(BuffPtr, typeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX));
|
||||
|
||||
if (tei == null)
|
||||
continue;
|
||||
else
|
||||
ltei.Add(tei.Value);
|
||||
}
|
||||
|
||||
// Move Buffptr
|
||||
BuffPtr = (IntPtr)(BuffPtr.ToInt64() + Marshal.SizeOf(typeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX)));
|
||||
}
|
||||
|
||||
// Return list
|
||||
return ltei;
|
||||
}
|
||||
|
||||
public static List<long> SearchProcessAllMemory(Process process, string searchString)
|
||||
{
|
||||
IntPtr minAddress = IntPtr.Zero;
|
||||
IntPtr maxAddress = IntPtr.MaxValue;
|
||||
List<long> addrList = new List<long>();
|
||||
|
||||
while (minAddress.ToInt64() < maxAddress.ToInt64())
|
||||
{
|
||||
MEMORY_BASIC_INFORMATION64 memInfo;
|
||||
int result = VirtualQueryEx(process.Handle, minAddress, out memInfo, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION64)));
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (memInfo.State == MEM_COMMIT && (memInfo.Protect == PAGE_EXECUTE || memInfo.Protect == PAGE_EXECUTE_READ || memInfo.Protect == PAGE_EXECUTE_READ || memInfo.Protect == PAGE_READWRITE || memInfo.Protect == PAGE_READONLY))
|
||||
{
|
||||
byte[] buffer = new byte[(long)memInfo.RegionSize];
|
||||
bool success = ReadProcessMemory(process.Handle, memInfo.BaseAddress, buffer, buffer.Length, out _);
|
||||
|
||||
if (success)
|
||||
{
|
||||
byte[] search = Encoding.ASCII.GetBytes(searchString);
|
||||
for (int i = 0; i < buffer.Length - 8; i++)
|
||||
{
|
||||
if (buffer[i] == search[0])
|
||||
{
|
||||
for (int s = 1; s < search.Length; s++)
|
||||
{
|
||||
if (buffer[i + s] != search[s])
|
||||
break;
|
||||
if (s == search.Length - 1)
|
||||
{
|
||||
addrList.Add((long)memInfo.BaseAddress + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
minAddress = new IntPtr(memInfo.BaseAddress.ToInt64() + (long)memInfo.RegionSize);
|
||||
}
|
||||
return addrList;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace WechatPCMsgBakTool.Helpers
|
||||
namespace WechatBakTool.Helpers
|
||||
{
|
||||
public class OpenSSLInterop
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -8,35 +9,10 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace WechatPCMsgBakTool.Helpers
|
||||
namespace WechatBakTool.Helpers
|
||||
{
|
||||
public class ProcessHelper
|
||||
{
|
||||
private const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
|
||||
private const int DUPLICATE_SAME_ACCESS = 0x2;
|
||||
|
||||
private const int CNST_SYSTEM_HANDLE_INFORMATION = 0x10;
|
||||
|
||||
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);
|
||||
@@ -56,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)
|
||||
{
|
||||
@@ -83,176 +59,18 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static List<SYSTEM_HANDLE_INFORMATION> GetHandles(Process process)
|
||||
{
|
||||
List<SYSTEM_HANDLE_INFORMATION> aHandles = new List<SYSTEM_HANDLE_INFORMATION>();
|
||||
int handle_info_size = Marshal.SizeOf(new SYSTEM_HANDLE_INFORMATION()) * 20000;
|
||||
IntPtr ptrHandleData = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
ptrHandleData = Marshal.AllocHGlobal(handle_info_size);
|
||||
int nLength = 0;
|
||||
|
||||
while (NtQuerySystemInformation(CNST_SYSTEM_HANDLE_INFORMATION, ptrHandleData, handle_info_size, ref nLength) == STATUS_INFO_LENGTH_MISMATCH)
|
||||
{
|
||||
handle_info_size = nLength;
|
||||
Marshal.FreeHGlobal(ptrHandleData);
|
||||
ptrHandleData = Marshal.AllocHGlobal(nLength);
|
||||
}
|
||||
|
||||
long handle_count = Marshal.ReadIntPtr(ptrHandleData).ToInt64();
|
||||
IntPtr ptrHandleItem = ptrHandleData + Marshal.SizeOf(ptrHandleData);
|
||||
|
||||
for (long lIndex = 0; lIndex < handle_count; lIndex++)
|
||||
{
|
||||
SYSTEM_HANDLE_INFORMATION? oSystemHandleInfo = new SYSTEM_HANDLE_INFORMATION();
|
||||
oSystemHandleInfo = Marshal.PtrToStructure(ptrHandleItem, oSystemHandleInfo.GetType()) as SYSTEM_HANDLE_INFORMATION?;
|
||||
if (oSystemHandleInfo == null)
|
||||
throw new Exception("获取SYSTEM_HANDLE_INFORMATION失败");
|
||||
ptrHandleItem += Marshal.SizeOf(new SYSTEM_HANDLE_INFORMATION());
|
||||
if (oSystemHandleInfo.Value.ProcessID != process.Id) { continue; }
|
||||
aHandles.Add(oSystemHandleInfo.Value);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(ptrHandleData);
|
||||
}
|
||||
return aHandles;
|
||||
}
|
||||
public static string FindHandleName(SYSTEM_HANDLE_INFORMATION systemHandleInformation, Process process)
|
||||
{
|
||||
IntPtr ipHandle = IntPtr.Zero;
|
||||
IntPtr openProcessHandle = IntPtr.Zero;
|
||||
IntPtr hObjectName = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
PROCESS_ACCESS_FLAGS flags = PROCESS_ACCESS_FLAGS.DupHandle | PROCESS_ACCESS_FLAGS.VMRead;
|
||||
openProcessHandle = OpenProcess(flags, false, process.Id);
|
||||
// 通过 DuplicateHandle 访问句柄
|
||||
if (!DuplicateHandle(openProcessHandle, new IntPtr(systemHandleInformation.Handle), GetCurrentProcess(), out ipHandle, 0, false, DUPLICATE_SAME_ACCESS))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
int nLength = 0;
|
||||
hObjectName = Marshal.AllocHGlobal(256 * 1024);
|
||||
|
||||
// 查询句柄名称
|
||||
while ((uint)(NtQueryObject(ipHandle, (int)OBJECT_INFORMATION_CLASS.ObjectNameInformation, hObjectName, nLength, ref nLength)) == STATUS_INFO_LENGTH_MISMATCH)
|
||||
{
|
||||
Marshal.FreeHGlobal(hObjectName);
|
||||
if (nLength == 0)
|
||||
{
|
||||
Console.WriteLine("Length returned at zero!");
|
||||
return "";
|
||||
}
|
||||
hObjectName = Marshal.AllocHGlobal(nLength);
|
||||
}
|
||||
OBJECT_NAME_INFORMATION? objObjectName = new OBJECT_NAME_INFORMATION();
|
||||
objObjectName = Marshal.PtrToStructure(hObjectName, objObjectName.GetType()) as OBJECT_NAME_INFORMATION?;
|
||||
if (objObjectName == null)
|
||||
return "";
|
||||
if (objObjectName.Value.Name.Buffer != IntPtr.Zero)
|
||||
{
|
||||
string? strObjectName = Marshal.PtrToStringUni(objObjectName.Value.Name.Buffer);
|
||||
if (strObjectName != null)
|
||||
return strObjectName;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(hObjectName);
|
||||
CloseHandle(ipHandle);
|
||||
CloseHandle(openProcessHandle);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
// 这里开始下面是对Windows API引用声明
|
||||
public static byte[]? ReadMemoryDate(IntPtr hProcess, IntPtr lpBaseAddress, int nSize = 100)
|
||||
{
|
||||
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);
|
||||
[DllImport("ntdll.dll")]
|
||||
private static extern uint NtQuerySystemInformation(int SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, ref int returnLength);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr OpenProcess(PROCESS_ACCESS_FLAGS dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr GetCurrentProcess();
|
||||
|
||||
[DllImport("ntdll.dll")]
|
||||
private static extern int NtQueryObject(IntPtr ObjectHandle, int ObjectInformationClass, IntPtr ObjectInformation, int ObjectInformationLength, ref int returnLength);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct SYSTEM_HANDLE_INFORMATION
|
||||
{ // Information Class 16
|
||||
public ushort ProcessID;
|
||||
public ushort CreatorBackTrackIndex;
|
||||
public byte ObjectType;
|
||||
public byte HandleAttribute;
|
||||
public ushort Handle;
|
||||
public IntPtr Object_Pointer;
|
||||
public IntPtr AccessMask;
|
||||
}
|
||||
private enum OBJECT_INFORMATION_CLASS : int
|
||||
{
|
||||
ObjectBasicInformation = 0,
|
||||
ObjectNameInformation = 1,
|
||||
ObjectTypeInformation = 2,
|
||||
ObjectAllTypesInformation = 3,
|
||||
ObjectHandleInformation = 4
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
private struct OBJECT_NAME_INFORMATION
|
||||
{
|
||||
public UNICODE_STRING Name;
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct UNICODE_STRING
|
||||
{
|
||||
public ushort Length;
|
||||
public ushort MaximumLength;
|
||||
public IntPtr Buffer;
|
||||
}
|
||||
[Flags]
|
||||
private enum PROCESS_ACCESS_FLAGS : uint
|
||||
{
|
||||
All = 0x001F0FFF,
|
||||
Terminate = 0x00000001,
|
||||
CreateThread = 0x00000002,
|
||||
VMOperation = 0x00000008,
|
||||
VMRead = 0x00000010,
|
||||
VMWrite = 0x00000020,
|
||||
DupHandle = 0x00000040,
|
||||
SetInformation = 0x00000200,
|
||||
QueryInformation = 0x00000400,
|
||||
Synchronize = 0x00100000
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
168
HtmlExport.cs
168
HtmlExport.cs
@@ -1,168 +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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
42
Main.xaml
42
Main.xaml
@@ -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>
|
||||
268
Main.xaml.cs
268
Main.xaml.cs
@@ -1,268 +0,0 @@
|
||||
using K4os.Compression.LZ4;
|
||||
using K4os.Compression.LZ4.Encoders;
|
||||
using K4os.Compression.LZ4.Streams;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using WechatPCMsgBakTool.Helpers;
|
||||
using WechatPCMsgBakTool.Interface;
|
||||
using WechatPCMsgBakTool.Model;
|
||||
|
||||
namespace WechatPCMsgBakTool
|
||||
{
|
||||
/// <summary>
|
||||
/// Main.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class Main : Window
|
||||
{
|
||||
private UserBakConfig? CurrentUserBakConfig = null;
|
||||
private WXUserReader? UserReader = null;
|
||||
private ObservableCollection<UserBakConfig> userBakConfigs = new ObservableCollection<UserBakConfig>();
|
||||
public Main()
|
||||
{
|
||||
Application.Current.DispatcherUnhandledException += Current_DispatcherUnhandledException;
|
||||
InitializeComponent();
|
||||
LoadWorkspace();
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
178
Main2.xaml
Normal file
178
Main2.xaml
Normal file
@@ -0,0 +1,178 @@
|
||||
<Window x:Class="WechatBakTool.Main2"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:WechatBakTool"
|
||||
mc:Ignorable="d" WindowStartupLocation="CenterScreen" WindowStyle="None" WindowState="Normal" Background="Transparent" AllowsTransparency="True" ResizeMode="NoResize"
|
||||
Title="WechatBakTool" Height="550" Width="950" >
|
||||
<Window.Resources>
|
||||
<Style TargetType="local:Main2">
|
||||
<!-- 设置窗体的WindowChrome -->
|
||||
<Setter Property="WindowChrome.WindowChrome">
|
||||
<Setter.Value>
|
||||
<!-- ResizeBorderThickness:拖拽改变窗体大小的边框厚度;-->
|
||||
<!-- CornerRadius:窗体圆角;-->
|
||||
<!-- CaptionHeight顶部标题的高度;-->
|
||||
<!-- GlassFrameThickness:默认边框的大小,0为不使用默认边框(这样定义的圆角才有效),-1为使用默认边框默认值-->
|
||||
<WindowChrome CornerRadius="5" CaptionHeight="5" GlassFrameThickness="0" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<DrawingImage x:Key="svg_min">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V1024 H1024 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#2775b6" Geometry="F1 M1024,1024z M0,0z M370.752,608L129.6,849.152A32,32,0,0,0,174.848,894.4L416,653.248 416,768A32,32,0,0,0,480,768L480,576A31.904,31.904,0,0,0,448,544L256,544A32,32,0,0,0,256,608L370.752,608z M553.376,470.624A31.904,31.904,0,0,1,544,448L544,256A32,32,0,0,1,608,256L608,370.752 849.152,129.6A32,32,0,1,1,894.4,174.848L653.248,416 768,416A32,32,0,0,1,768,480L576,480A31.904,31.904,0,0,1,553.376,470.624z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DrawingImage x:Key="svg_close">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V1024 H1024 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#2775b6" Geometry="F1 M1024,1024z M0,0z M556.8,512L828.8,240C841.6,227.2 841.6,208 828.8,195.2 816,182.4 796.8,182.4 784,195.2L512,467.2 240,195.2C227.2,182.4 208,182.4 195.2,195.2 182.4,208 182.4,227.2 195.2,240L467.2,512 195.2,784C182.4,796.8 182.4,816 195.2,828.8 208,841.6 227.2,841.6 240,828.8L512,556.8 784,828.8C796.8,841.6 816,841.6 828.8,828.8 841.6,816 841.6,796.8 828.8,784L556.8,512z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DrawingImage x:Key="friends_nums">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V1024 H1024 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#FFF" Geometry="F1 M1024,1024z M0,0z M512.018127441406,140.7236328125C386.078552246094,140.7236328125 283.536560058594,243.226898193359 283.536560058594,369.204376220703 283.536560058594,495.106048583984 386.003570556641,597.648864746094 512.018127441406,597.648864746094 637.994781494141,597.648864746094 740.535949707031,495.106872558594 740.535949707031,369.204376220703 740.535949707031,243.226898193359 637.994781494141,140.7236328125 512.018127441406,140.7236328125z" />
|
||||
<GeometryDrawing Brush="#FFF" Geometry="F1 M1024,1024z M0,0z M688.11962890625,592.435577392578C639.437561035156,630.917663574219 578.812805175781,654.760192871094 512.018127441406,654.760192871094 445.222625732422,654.760192871094 384.636596679688,630.844329833984 335.842468261719,592.435577392578 243.466674804688,651.248413085937 179.148040771484,754.530334472656 170.313385009766,874.148376464844 202.656860351563,878.545104980469 260.212310791016,883.2763671875 342.347747802734,883.2763671875L681.539367675781,883.2763671875C763.712707519531,883.2763671875 821.304412841797,878.545104980469 853.686614990234,874.148376464844 844.924468994141,754.454528808594 780.606658935547,651.248413085938 688.11962890625,592.435577392578z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DrawingImage x:Key="msg_nums">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V1024 H1024 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#FFF" Geometry="F1 M1024,1024z M0,0z M512,917.333333A308.458667,308.458667,0,0,1,425.941333,908.181333C366.592,890.88 248.298666,903.808 153.365333,922.666667 153.365333,922.666667 145.173333,924.010667 144.661333,924.010667A42.282667,42.282667,0,0,1,103.253333,871.488C123.498666,779.754667,131.306666,670.741333,115.584,596.906667A405.333333,405.333333,0,1,1,512,917.333333z M339.008,640L552.341333,640A32,32,0,1,0,552.341333,576L339.008,576A32,32,0,0,0,339.008,640z M680.341333,405.333333L339.008,405.333333A32,32,0,0,0,339.008,469.333333L680.341333,469.333333A32,32,0,1,0,680.341333,405.333333z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DataTemplate x:Key="ListViewItemContentTemplate">
|
||||
<Grid Margin="5">
|
||||
<Label Margin="0" Content="{Binding Account}" FontSize="16" VerticalAlignment="Top" HorizontalAlignment="Left" Foreground="White" />
|
||||
<Image Source="{StaticResource friends_nums}" Width="15" Height="15" Margin="2,30,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" />
|
||||
<Label Margin="13,25.5,0,0" Content="{Binding Friends_Number}" VerticalAlignment="Top" HorizontalAlignment="Left" Foreground="White" />
|
||||
<Image Source="{StaticResource msg_nums}" Width="15" Height="15" Margin="60,30,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" />
|
||||
<Label Margin="73,25.5,0,0" Content="{Binding Msg_Number}" VerticalAlignment="Top" HorizontalAlignment="Left" Foreground="White" />
|
||||
<Border Margin="150,25.5,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" CornerRadius="5" BorderThickness="1" Background="White" >
|
||||
<Label Padding="5" FontSize="8" Content="{Binding DecryptStatus}" Foreground="#2775b6" Background="Transparent" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Window.Resources>
|
||||
<Grid Background="White" MouseDown="Grid_MouseDown">
|
||||
<Image Panel.ZIndex="100" Name="img_btn_min" Source="{StaticResource svg_min}" Width="20" Height="20" HorizontalAlignment="Left" Margin="860,20,0,0" VerticalAlignment="Top" MouseLeftButtonDown="img_btn_min_MouseLeftButtonDown">
|
||||
<Image.RenderTransform>
|
||||
<RotateTransform CenterX="0.5" CenterY="0.5" />
|
||||
</Image.RenderTransform>
|
||||
<Image.RenderTransformOrigin>
|
||||
<Point>0.5,0.5</Point>
|
||||
</Image.RenderTransformOrigin>
|
||||
<Image.Triggers>
|
||||
<EventTrigger RoutedEvent="Image.MouseEnter">
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="img_btn_min"
|
||||
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
|
||||
To="180" Duration="0:0:0.300"/>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
<EventTrigger RoutedEvent="Image.MouseLeave">
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="img_btn_min"
|
||||
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
|
||||
To="0" Duration="0:0:0.300" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</Image.Triggers>
|
||||
</Image>
|
||||
<Image Panel.ZIndex="100" Name="img_btn_close" Source="{StaticResource svg_close}" Width="20" Height="20" HorizontalAlignment="Left" Margin="900,20,0,0" VerticalAlignment="Top" MouseLeftButtonDown="img_btn_close_MouseLeftButtonDown">
|
||||
<Image.RenderTransform>
|
||||
<RotateTransform CenterX="0.5" CenterY="0.5" />
|
||||
</Image.RenderTransform>
|
||||
<Image.RenderTransformOrigin>
|
||||
<Point>0.5,0.5</Point>
|
||||
</Image.RenderTransformOrigin>
|
||||
<Image.Triggers>
|
||||
<EventTrigger RoutedEvent="Image.MouseEnter">
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="img_btn_close"
|
||||
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
|
||||
To="90" Duration="0:0:0.200"/>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
<EventTrigger RoutedEvent="Image.MouseLeave">
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="img_btn_close"
|
||||
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
|
||||
To="0" Duration="0:0:0.200" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</Image.Triggers>
|
||||
</Image>
|
||||
|
||||
<Grid Width="230" Background="#2775b6" HorizontalAlignment="Left" IsHitTestVisible="True">
|
||||
|
||||
<ListView BorderThickness="0" Background="Transparent" Margin="0,0,0,85" Name="list_workspace" ItemTemplate="{DynamicResource ListViewItemContentTemplate}" SelectionChanged="list_workspace_SelectionChanged">
|
||||
<ListView.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="查看" Click="MenuItem_Click" />
|
||||
<MenuItem Header="管理" Click="MenuItem_Click_1" />
|
||||
</ContextMenu>
|
||||
</ListView.ContextMenu>
|
||||
</ListView>
|
||||
<Grid Name="new_workspace" Width="170" Height="40" VerticalAlignment="Bottom" Margin="30,45" IsHitTestVisible="True">
|
||||
<Rectangle Name="new_workspace_fill" Fill="Transparent" RadiusX="0" RadiusY="0" Stroke="White" StrokeDashArray="5" MouseDown="new_workspace_fill_MouseDown">
|
||||
<Rectangle.Triggers>
|
||||
<EventTrigger RoutedEvent="Rectangle.MouseEnter">
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<ColorAnimation
|
||||
Storyboard.TargetName="new_workspace_fill"
|
||||
Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
|
||||
To="White" Duration="0:0:0.300"/>
|
||||
<ColorAnimation
|
||||
Storyboard.TargetName="new_workspace_text"
|
||||
Storyboard.TargetProperty="(Label.Foreground).(SolidColorBrush.Color)"
|
||||
To="Black" Duration="0:0:0.300"/>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
<EventTrigger RoutedEvent="Rectangle.MouseLeave">
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<ColorAnimation
|
||||
Storyboard.TargetName="new_workspace_fill"
|
||||
Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
|
||||
To="Transparent" Duration="0:0:0.300"/>
|
||||
<ColorAnimation
|
||||
Storyboard.TargetName="new_workspace_text"
|
||||
Storyboard.TargetProperty="(Label.Foreground).(SolidColorBrush.Color)"
|
||||
To="White" Duration="0:0:0.300"/>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</Rectangle.Triggers>
|
||||
</Rectangle>
|
||||
<Label Name="new_workspace_text" Content="新建工作区" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" IsHitTestVisible="False"/>
|
||||
</Grid>
|
||||
<Label Name="lab_version" Content="版本:" Margin="10" VerticalAlignment="Bottom" HorizontalAlignment="Center" Foreground="White" />
|
||||
</Grid>
|
||||
<Grid Margin="230,0,0,0" Width="720" Background="Transparent" HorizontalAlignment="Left" Panel.ZIndex="1">
|
||||
<Frame Name="MainFrame" Source="/Pages/Welcome.xaml" NavigationUIVisibility="Hidden" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</Window>
|
||||
123
Main2.xaml.cs
Normal file
123
Main2.xaml.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using WechatBakTool.Model;
|
||||
|
||||
namespace WechatBakTool
|
||||
{
|
||||
/// <summary>
|
||||
/// Main2.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class Main2 : Window
|
||||
{
|
||||
public static UserBakConfig? CurrentUserBakConfig;
|
||||
private ObservableCollection<UserBakConfig> userBakConfigs = new ObservableCollection<UserBakConfig>();
|
||||
public Main2()
|
||||
{
|
||||
InitializeComponent();
|
||||
// 获取文件版本
|
||||
lab_version.Content += $" {Application.ResourceAssembly.GetName().Version}";
|
||||
//加载工作区
|
||||
LoadWorkspace();
|
||||
}
|
||||
|
||||
private void img_btn_close_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
public void LoadWorkspace()
|
||||
{
|
||||
userBakConfigs.Clear();
|
||||
// 根目录worksapce读工作区
|
||||
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "workspace");
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
string[] files = Directory.GetFiles(path);
|
||||
//目录内json文件为各工作区配置文件
|
||||
foreach (string file in files)
|
||||
{
|
||||
string type = file.Substring(file.Length - 5, 5);
|
||||
if (type == ".json")
|
||||
{
|
||||
string jsonString = File.ReadAllText(file);
|
||||
UserBakConfig? userBakConfig = null;
|
||||
try
|
||||
{
|
||||
userBakConfig = JsonConvert.DeserializeObject<UserBakConfig>(jsonString);
|
||||
}
|
||||
catch
|
||||
{
|
||||
MessageBox.Show("读取到异常工作区文件,请确认备份数据是否正常\r\n文件路径:" + file, "错误");
|
||||
}
|
||||
if (userBakConfig != null)
|
||||
{
|
||||
userBakConfigs.Add(userBakConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
list_workspace.ItemsSource = userBakConfigs;
|
||||
}
|
||||
|
||||
private void list_workspace_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
UserBakConfig? config = list_workspace.SelectedItem as UserBakConfig;
|
||||
if(config == null)
|
||||
{
|
||||
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Welcome.xaml?datatime=" + DateTime.Now.Ticks));
|
||||
return;
|
||||
}
|
||||
if (!config.Decrypt)
|
||||
{
|
||||
MessageBox.Show("请先到创建工作区进行解密");
|
||||
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/CreateWork.xaml?datatime=" + DateTime.Now.Ticks));
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentUserBakConfig = config;
|
||||
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Workspace.xaml?datatime=" + DateTime.Now.Ticks));
|
||||
}
|
||||
|
||||
private void new_workspace_fill_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
list_workspace.SelectedItem = null;
|
||||
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/CreateWork.xaml?datatime=" + DateTime.Now.Ticks));
|
||||
}
|
||||
|
||||
private void img_btn_min_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
WindowState = WindowState.Minimized;
|
||||
}
|
||||
|
||||
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
DragMove();
|
||||
}
|
||||
}
|
||||
|
||||
private void MenuItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Workspace.xaml?datatime=" + DateTime.Now.Ticks));
|
||||
}
|
||||
|
||||
private void MenuItem_Click_1(object sender, RoutedEventArgs e)
|
||||
{
|
||||
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Manager.xaml?datatime=" + DateTime.Now.Ticks));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
25
Model/ProtobufModel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -5,24 +5,31 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace WechatPCMsgBakTool.Model
|
||||
namespace WechatBakTool.Model
|
||||
{
|
||||
public class UserBakConfig : INotifyPropertyChanged
|
||||
public class UserBakConfig
|
||||
{
|
||||
public string UserResPath { get; set; } = "";
|
||||
public string UserWorkspacePath { get; set; } = "";
|
||||
public bool Decrypt { get; set; } = false;
|
||||
public string DecryptStatus
|
||||
{
|
||||
get { return Decrypt ? "已解密" : "未解密"; }
|
||||
}
|
||||
public string Hash { get; set; } = "";
|
||||
public string NickName { get; set; } = "";
|
||||
public string UserName { get; set; } = "";
|
||||
public string Account { get; set; } = "";
|
||||
public string Friends_Number { get; set; } = "-";
|
||||
public string Msg_Number { get; set; } = "-";
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
private void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
public class WXCount
|
||||
{
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
public class WXMsgGroup
|
||||
@@ -35,6 +42,14 @@ namespace WechatPCMsgBakTool.Model
|
||||
public string NickName { get; set; } = "";
|
||||
}
|
||||
|
||||
[Table("ContactHeadImg1")]
|
||||
public class ContactHeadImg
|
||||
{
|
||||
public string usrName { get; set; } = "";
|
||||
public int createTime { get; set; }
|
||||
public byte[]? smallHeadBuf { get; set; }
|
||||
}
|
||||
|
||||
public class WXUserInfo
|
||||
{
|
||||
public string UserName { get; set; } = "";
|
||||
@@ -99,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")]
|
||||
@@ -111,8 +130,26 @@ namespace WechatPCMsgBakTool.Model
|
||||
public string StrTalker { get; set; } = "";
|
||||
[Column("StrContent")]
|
||||
public string StrContent { get; set; } = "";
|
||||
public string DisplayContent { get; set; } = "";
|
||||
[Column("CompressContent")]
|
||||
public byte[]? CompressContent { get; set; }
|
||||
[Column("BytesExtra")]
|
||||
public byte[]? BytesExtra { get; set; }
|
||||
public string NickName { get; set; } = "";
|
||||
}
|
||||
|
||||
[Table("ChatRoom")]
|
||||
public class WXChatRoom
|
||||
{
|
||||
[Column("ChatRoomName")]
|
||||
public string ChatRoomName { get; set; } = "";
|
||||
[Column("UserNameList")]
|
||||
public string UserNameList { get; set; } = "";
|
||||
[Column("DisplayNameList")]
|
||||
public string DisplayNameList { get; set; } = "";
|
||||
[Column("RoomData")]
|
||||
public byte[]? RoomData { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[Table("Media")]
|
||||
@@ -123,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
|
||||
{
|
||||
@@ -132,5 +178,22 @@ namespace WechatPCMsgBakTool.Model
|
||||
public string Alias { get; set; } = "";
|
||||
[Column("NickName")]
|
||||
public string NickName { get; set; } = "";
|
||||
[Column("strContent")]
|
||||
public string LastMsg { get; set; } = "";
|
||||
[Column("ExtraBuf")]
|
||||
public byte[]? ExtraBuf { get; set; }
|
||||
public BitmapImage? Avatar { get; set; }
|
||||
[Column("Remark")]
|
||||
public string Remark { get; set; } = "";
|
||||
}
|
||||
|
||||
[Table("ContactHeadImgUrl")]
|
||||
public class WXUserImg {
|
||||
[Column("usrName")]
|
||||
public string UserName { get; set; } = "";
|
||||
[Column("smallHeadImgUrl")]
|
||||
public string SmallImg { get; set; } = "";
|
||||
[Column("bigHeadImgUrl")]
|
||||
public string BigImg { get; set; } = "";
|
||||
}
|
||||
}
|
||||
|
||||
28
Model/YearReport.cs
Normal file
28
Model/YearReport.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WechatBakTool.Model
|
||||
{
|
||||
public class YearReport
|
||||
{
|
||||
public List<ReportItem>? List { get; set; }
|
||||
public int Version { get; set; }
|
||||
}
|
||||
|
||||
public class ReportItem
|
||||
{
|
||||
public string ImgName { get; set; } = "";
|
||||
public string Type { get; set; } = "";
|
||||
public List<TextPostion>? TextPostions { get; set; }
|
||||
}
|
||||
|
||||
public class TextPostion
|
||||
{
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
public string TextTemplate { get; set; } = "";
|
||||
}
|
||||
}
|
||||
45
Pages/CreateWork.xaml
Normal file
45
Pages/CreateWork.xaml
Normal file
@@ -0,0 +1,45 @@
|
||||
<Page x:Class="WechatBakTool.Pages.CreateWork"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:WechatBakTool.ViewModel"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="550" d:DesignWidth="800"
|
||||
Title="Welcome" Background="White">
|
||||
<Page.Resources>
|
||||
<local:GetKeyConverter x:Key="getKeyConverterKey" />
|
||||
</Page.Resources>
|
||||
<Grid>
|
||||
<Label FontSize="20" Margin="30,15" Content="新建工作区" HorizontalAlignment="Left" VerticalAlignment="Top" />
|
||||
<Label Margin="30,55,0,0" Content="请选择要创建工作区的微信,可以通过微信路径判断是哪一个微信哦!" HorizontalAlignment="Left" VerticalAlignment="Top" />
|
||||
<ListView Name="list_process" Margin="35,95,35,0" IsEnabled="{Binding IsEnable}" VerticalAlignment="Top" Height="160" ItemsSource="{Binding ProcessInfos}" SelectionChanged="list_process_SelectionChanged" SelectedItem="{Binding SelectProcess}">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn Header="进程名" Width="120" DisplayMemberBinding="{Binding ProcessName}" />
|
||||
<GridViewColumn Header="PID" Width="80" DisplayMemberBinding="{Binding ProcessId}" />
|
||||
<GridViewColumn Header="路径" Width="430" DisplayMemberBinding="{Binding DBPath}" />
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
<Label Margin="30,275,0,0" Content="选择微信后,请确认下方自动获取的微信名是否正确。不正确请修改!" FontWeight="Bold" HorizontalAlignment="Left" VerticalAlignment="Top"/>
|
||||
<TextBox IsEnabled="{Binding IsEnable}" x:Name="txt_username" Margin="35,300,0,0" Width="280" HorizontalAlignment="Left" VerticalAlignment="Top" BorderThickness="0,0,0,1" Text="{Binding UserName}" />
|
||||
|
||||
<Label Margin="30,350,0,0" Content="请选择解密方式:" FontWeight="Bold" HorizontalAlignment="Left" VerticalAlignment="Top"/>
|
||||
<RadioButton Margin="35,380,0,0" Content="固定地址查找【保底】" HorizontalAlignment="Left" VerticalAlignment="Top" GroupName="rb_find_key" HorizontalContentAlignment="Center" IsEnabled="{Binding IsEnable}" VerticalContentAlignment="Center" IsChecked="{Binding KeyType, Converter={StaticResource ResourceKey=getKeyConverterKey}, ConverterParameter=1}" />
|
||||
<RadioButton Margin="35,405,0,0" Content="用户名推断查找【不稳定】" HorizontalAlignment="Left" VerticalAlignment="Top" GroupName="rb_find_key" HorizontalContentAlignment="Center" IsEnabled="{Binding IsEnable}" VerticalContentAlignment="Center" IsChecked="{Binding KeyType, Converter={StaticResource ResourceKey=getKeyConverterKey}, ConverterParameter=2}"/>
|
||||
<RadioButton Margin="35,430,0,0" Content="公钥头推断查找【推荐】" HorizontalAlignment="Left" VerticalAlignment="Top" GroupName="rb_find_key" HorizontalContentAlignment="Center" IsEnabled="{Binding IsEnable}" VerticalContentAlignment="Center" IsChecked="{Binding KeyType, Converter={StaticResource ResourceKey=getKeyConverterKey}, ConverterParameter=3}"/>
|
||||
|
||||
<Button Name="btn_create_worksapce" Margin="0,0,35,50" Height="60" Width="100" HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="创建工作区" BorderThickness="0" IsEnabled="{Binding IsEnable}" Background="#2775b6" Foreground="White" Click="btn_create_worksapce_Click">
|
||||
<Button.Resources>
|
||||
<Style TargetType="{x:Type Border}">
|
||||
<Setter Property="CornerRadius" Value="8"/>
|
||||
</Style>
|
||||
</Button.Resources>
|
||||
</Button>
|
||||
<Label Margin="210,350,0,0" Content="其他选项:" FontWeight="Bold" HorizontalAlignment="Left" VerticalAlignment="Top"/>
|
||||
<CheckBox Margin="215,380,0,0" Content="打包资源文件夹(功能规划中)" IsEnabled="False" HorizontalAlignment="Left" VerticalAlignment="Top" />
|
||||
<CheckBox Name="cb_manual" Checked="cb_manual_Checked" Margin="215,405,0,0" Content="手动模式" Visibility="Visible" HorizontalAlignment="Left" VerticalAlignment="Top" />
|
||||
<Label Name="lab_status" Content="{Binding LabelStatus}" HorizontalAlignment="Left" Margin="30,450,0,0" VerticalAlignment="Top"/>
|
||||
</Grid>
|
||||
</Page>
|
||||
178
Pages/CreateWork.xaml.cs
Normal file
178
Pages/CreateWork.xaml.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using WechatBakTool.Helpers;
|
||||
using WechatBakTool.Model;
|
||||
using WechatBakTool.ViewModel;
|
||||
|
||||
namespace WechatBakTool.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// CreateWork.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class CreateWork : Page
|
||||
{
|
||||
private CreateWorkViewModel ViewModel = new CreateWorkViewModel();
|
||||
public CreateWork()
|
||||
{
|
||||
DataContext = ViewModel;
|
||||
InitializeComponent();
|
||||
GetWechatProcessInfos();
|
||||
}
|
||||
|
||||
private void GetWechatProcessInfos()
|
||||
{
|
||||
ViewModel.ProcessInfos.Clear();
|
||||
Process[] processes = Process.GetProcessesByName("wechat");
|
||||
foreach (Process p in processes)
|
||||
{
|
||||
var lHandles = NativeAPIHelper.GetHandleInfoForPID((uint)p.Id);
|
||||
foreach (var h in lHandles)
|
||||
{
|
||||
string name = NativeAPIHelper.FindHandleName(h, p);
|
||||
if (name != "")
|
||||
{
|
||||
// 预留handle log
|
||||
if (File.Exists("handle.log"))
|
||||
{
|
||||
File.AppendAllText("handle.log", string.Format("{0}|{1}|{2}|{3}\n", p.Id, h.ObjectTypeIndex, h.HandleValue, name));
|
||||
}
|
||||
if (name.Contains("\\MicroMsg.db") && name.Substring(name.Length - 3, 3) == ".db")
|
||||
{
|
||||
ProcessInfo info = new ProcessInfo();
|
||||
info.ProcessId = p.Id.ToString();
|
||||
info.ProcessName = p.ProcessName;
|
||||
info.DBPath = DevicePathMapper.FromDevicePath(name);
|
||||
ViewModel.ProcessInfos.Add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void list_process_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ViewModel.SelectProcess != null)
|
||||
{
|
||||
string[] name_raw = ViewModel.SelectProcess.DBPath.Split("\\");
|
||||
ViewModel.UserName = name_raw[name_raw.Length - 3];
|
||||
|
||||
FileInfo fileInfo = new FileInfo(ViewModel.SelectProcess.DBPath);
|
||||
DirectoryInfo msgParent = fileInfo.Directory!.Parent!;
|
||||
DirectoryInfo[] accounts = msgParent.GetDirectories();
|
||||
|
||||
DirectoryInfo? newUserName = null;
|
||||
foreach ( DirectoryInfo account in accounts )
|
||||
{
|
||||
if(account.Name.Contains("account_")) {
|
||||
if(newUserName == null)
|
||||
newUserName = account;
|
||||
else
|
||||
{
|
||||
if (newUserName.LastWriteTime < account.LastWriteTime)
|
||||
newUserName = account;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(newUserName != null)
|
||||
{
|
||||
ViewModel.UserName = newUserName.Name.Split("_")[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void btn_create_worksapce_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.IsEnable = false;
|
||||
|
||||
Task.Run(() => {
|
||||
if (ViewModel.KeyType != -1)
|
||||
{
|
||||
if (ViewModel.SelectProcess != null)
|
||||
{
|
||||
ViewModel.LabelStatus = "数据准备";
|
||||
string path = ViewModel.SelectProcess.DBPath.Replace("\\Msg\\MicroMsg.db", "");
|
||||
try
|
||||
{
|
||||
ViewModel.LabelStatus = "准备创建工作区";
|
||||
//创建工作区
|
||||
WXWorkspace wXWorkspace = new WXWorkspace(path, ViewModel.UserName);
|
||||
//DB移动
|
||||
wXWorkspace.MoveDB(ViewModel);
|
||||
if(ViewModel.SelectProcess == null)
|
||||
return;
|
||||
|
||||
//开始解密数据库
|
||||
try
|
||||
{
|
||||
ViewModel.LabelStatus = "开始解密数据库";
|
||||
wXWorkspace.DecryptDB(ViewModel.SelectProcess.ProcessId, ViewModel.KeyType,ViewModel);
|
||||
|
||||
MessageBox.Show("创建工作区成功");
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
((Main2)Window.GetWindow(this)).LoadWorkspace();
|
||||
});
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message);
|
||||
ViewModel.IsEnable = true;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
MessageBox.Show("创建工作区失败,请检查路径是否正确");
|
||||
ViewModel.IsEnable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("请选择Key获取方式", "错误");
|
||||
}
|
||||
ViewModel.IsEnable = true;
|
||||
});
|
||||
}
|
||||
|
||||
private void cb_manual_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
MessageBox.Show("该功能仅限用于网络安全研究用途使用,红队同学请在合规授权下进行相关操作","重要提醒!!!!!!!!!");
|
||||
if (MessageBox.Show("我确认获取到合规授权,仅用于网络安全用途使用", "信息确认", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
if (File.Exists("auth.txt"))
|
||||
{
|
||||
string auth = File.ReadAllText("auth.txt");
|
||||
// 我已知晓手动模式可能潜在的法律及道德风险,我明白非法使用将要承担相关法律责任。
|
||||
if (DecryptionHelper.GetMD5(auth) == "295f634af60d61dfa52a5f35849ac42b")
|
||||
{
|
||||
MessageBox.Show("已经创建空的配置文件,请完善该配置文件后,点击开始解密","提示");
|
||||
MessageBox.Show("该功能现阶段暂未启用","错误");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("未完成声明文件,请先确认声明", "错误");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cb_manual.IsChecked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Pages/Manager.xaml
Normal file
25
Pages/Manager.xaml
Normal file
@@ -0,0 +1,25 @@
|
||||
<Page x:Class="WechatBakTool.Pages.Manager"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:WechatBakTool.Pages"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="720"
|
||||
Title="Manager" Background="White">
|
||||
<Grid>
|
||||
<Label FontSize="20" Margin="30,15" Content="管理" HorizontalAlignment="Left" VerticalAlignment="Top" />
|
||||
<Label Margin="30,55" Content="批量导出聊天记录" HorizontalAlignment="Left" VerticalAlignment="Top" FontWeight="Bold" />
|
||||
<CheckBox Name="cb_group" Margin="35,85" Content="群聊" HorizontalAlignment="Left" VerticalAlignment="Top" />
|
||||
<CheckBox Name="cb_user" Margin="90,85" Content="好友聊天" HorizontalAlignment="Left" VerticalAlignment="Top" />
|
||||
<Button Name="btn_export_all" Margin="35,110" Height="26" Width="60" Content="导出" HorizontalAlignment="Left" VerticalAlignment="Top" Background="#2775b6" Foreground="White" BorderThickness="0" Click="btn_export_all_Click"></Button>
|
||||
<Label Content="{Binding LabelStatus}" HorizontalAlignment="Left" Margin="110,110,0,0" VerticalAlignment="Top"/>
|
||||
|
||||
<Label Margin="30,155,0,0" Content="表情包预下载" HorizontalAlignment="Left" VerticalAlignment="Top" FontWeight="Bold" />
|
||||
<Button Name="btn_emoji_download" Margin="35,185,0,0" Height="26" Width="60" Content="导出" HorizontalAlignment="Left" VerticalAlignment="Top" Background="#2775b6" Foreground="White" BorderThickness="0" Click="btn_emoji_download_Click"></Button>
|
||||
|
||||
<Label Margin="30,225,0,0" Content="旧版分析工具" HorizontalAlignment="Left" VerticalAlignment="Top" FontWeight="Bold" />
|
||||
<Button Name="btn_analyse" Margin="35,255,0,0" Height="26" Width="60" Content="打开" HorizontalAlignment="Left" VerticalAlignment="Top" Background="#2775b6" Foreground="White" BorderThickness="0" Click="btn_analyse_Click" ></Button>
|
||||
|
||||
</Grid>
|
||||
</Page>
|
||||
145
Pages/Manager.xaml.cs
Normal file
145
Pages/Manager.xaml.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Xml;
|
||||
using WechatBakTool.Export;
|
||||
using WechatBakTool.Model;
|
||||
using WechatBakTool.ViewModel;
|
||||
|
||||
namespace WechatBakTool.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Manager.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class Manager : Page
|
||||
{
|
||||
private WorkspaceViewModel workspaceViewModel = new WorkspaceViewModel();
|
||||
public WXUserReader? UserReader;
|
||||
private List<WXContact>? ExpContacts;
|
||||
private bool Suspend = false;
|
||||
public Manager()
|
||||
{
|
||||
DataContext = workspaceViewModel;
|
||||
InitializeComponent();
|
||||
UserBakConfig? config = Main2.CurrentUserBakConfig;
|
||||
if (config != null)
|
||||
{
|
||||
UserReader = new WXUserReader(config);
|
||||
if (!config.Decrypt)
|
||||
{
|
||||
MessageBox.Show("请先解密数据库", "错误");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void btn_export_all_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
bool group = false, user = false;
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (cb_group.IsChecked == null || cb_user.IsChecked == null)
|
||||
return;
|
||||
|
||||
group = (bool)cb_group.IsChecked;
|
||||
user = (bool)cb_user.IsChecked;
|
||||
});
|
||||
if (UserReader != null)
|
||||
{
|
||||
if (!Suspend)
|
||||
ExpContacts = UserReader.GetWXContacts().ToList();
|
||||
else
|
||||
Suspend = false;
|
||||
|
||||
foreach (var contact in ExpContacts!)
|
||||
{
|
||||
if (Suspend)
|
||||
{
|
||||
workspaceViewModel.ExportCount = "已暂停";
|
||||
return;
|
||||
}
|
||||
|
||||
if (group && contact.UserName.Contains("@chatroom"))
|
||||
{
|
||||
workspaceViewModel.WXContact = contact;
|
||||
ExportMsg(contact);
|
||||
}
|
||||
if (user && !contact.UserName.Contains("@chatroom") && !contact.UserName.Contains("gh_"))
|
||||
{
|
||||
workspaceViewModel.WXContact = contact;
|
||||
ExportMsg(contact);
|
||||
}
|
||||
}
|
||||
MessageBox.Show("批量导出完成", "提示");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ExportMsg(WXContact contact)
|
||||
{
|
||||
workspaceViewModel.ExportCount = "";
|
||||
// string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, contact.UserName + ".html");
|
||||
string name = contact.NickName;
|
||||
name = name.Replace(@"\", "");
|
||||
name = Regex.Replace(name, "[ \\[ \\] \\^ \\-_*×――(^)$%~!/@#$…&%¥—+=<>《》|!!???::•`·、。,;,.;\"‘’“”-]", "");
|
||||
string path = Path.Combine(
|
||||
Main2.CurrentUserBakConfig!.UserWorkspacePath,
|
||||
string.Format(
|
||||
"{0}-{1}.html",
|
||||
contact.UserName,
|
||||
contact.Remark == "" ? name : contact.Remark
|
||||
)
|
||||
);
|
||||
|
||||
IExport export = new HtmlExport();
|
||||
export.InitTemplate(contact, path);
|
||||
if(export.SetMsg(UserReader!, contact, workspaceViewModel))
|
||||
{
|
||||
export.SetEnd();
|
||||
export.Save(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void btn_emoji_download_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (UserReader != null)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
UserReader.PreDownloadEmoji();
|
||||
MessageBox.Show("所有表情预下载完毕");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void btn_analyse_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (UserReader == null || Main2.CurrentUserBakConfig == null)
|
||||
{
|
||||
MessageBox.Show("请先读取数据");
|
||||
return;
|
||||
}
|
||||
Analyse analyse = new Analyse(Main2.CurrentUserBakConfig, UserReader);
|
||||
analyse.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Pages/MsgTemplateSelector .cs
Normal file
40
Pages/MsgTemplateSelector .cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using WechatBakTool.Model;
|
||||
|
||||
namespace WechatBakTool.Pages
|
||||
{
|
||||
public class MsgTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
|
||||
public override DataTemplate? SelectTemplate(object item, DependencyObject container)
|
||||
{
|
||||
FrameworkElement? element = container as FrameworkElement;
|
||||
|
||||
if (element != null && item != null && item is WXMsg)
|
||||
{
|
||||
WXMsg? wxmsg = item as WXMsg;
|
||||
|
||||
if (wxmsg == null)
|
||||
return null;
|
||||
|
||||
if (wxmsg.Type == 1)
|
||||
return
|
||||
element.FindResource("MsgText") as DataTemplate;
|
||||
else if (wxmsg.Type == 3)
|
||||
return
|
||||
element.FindResource("MsgImage") as DataTemplate;
|
||||
else
|
||||
return
|
||||
element.FindResource("MsgText") as DataTemplate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
30
Pages/Welcome.xaml
Normal file
30
Pages/Welcome.xaml
Normal file
@@ -0,0 +1,30 @@
|
||||
<Page x:Class="WechatBakTool.Pages.Welcome"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:WechatBakTool.Pages"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="550" d:DesignWidth="800"
|
||||
Title="Welcome" Background="White">
|
||||
|
||||
<Grid>
|
||||
<Label FontSize="20" Margin="30,15" Content="欢迎页" HorizontalAlignment="Left" VerticalAlignment="Top" />
|
||||
<Label Margin="30,45,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="欢迎使用溯雪微信备份工具,阅读使用说明有助于您对本工具的了解。"></Label>
|
||||
<Label Margin="30,65,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="1、如果您是第一次使用本工具,请点击左下角新建工作区,开始进行微信备份。"></Label>
|
||||
<Label Margin="30,85,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="2、如果您是需要对同一微信进行再次备份导出,也请点击新建工作区,会自行覆盖。"></Label>
|
||||
<Label Margin="30,105,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="3、完成工作建立后,请点击左侧建立的工作区,进入工作台。"></Label>
|
||||
<Label Margin="30,125,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="4、未解密备份,请先解密微信备份,请保持微信PC版是打开状态。"></Label>
|
||||
<Label Margin="30,145,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="---关于解密方式"></Label>
|
||||
<Label Margin="30,165,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="---版本推定"></Label>
|
||||
<Label Margin="30,185,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="版本推定是通过version.json内的版本基址来直接获取key进行解密,不需要依赖用户名,缺点是每次微信版本更新"></Label>
|
||||
<Label Margin="30,205,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="都需要额外维护version.json,更新不一定及时。"></Label>
|
||||
<Label Margin="30,225,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="---用户名推定"></Label>
|
||||
<Label Margin="30,245,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="用户名推定是通过新建工作区的时候,填写用户名,工具通过内存搜索的方式,自动推出key所在地址,并获取。"></Label>
|
||||
<Label Margin="30,265,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="该方式的优点在于不受版本限制,推荐使用该方式进行解密。缺点就是劳烦您一定要写对微信用户名。"></Label>
|
||||
<Label Margin="30,285,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="5、解密完成后,读取,就能愉快使用工具啦!"></Label>
|
||||
|
||||
<Label Margin="30,335,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="如果感觉工具好用,欢迎到github给我点个Star!点击本条去Star >>> SuxueCode/WechatBakTool" Foreground="#2775b6" MouseDown="StarGithub_MouseDown"></Label>
|
||||
|
||||
</Grid>
|
||||
</Page>
|
||||
34
Pages/Welcome.xaml.cs
Normal file
34
Pages/Welcome.xaml.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace WechatBakTool.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Welcome.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class Welcome : Page
|
||||
{
|
||||
public Welcome()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void StarGithub_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Process.Start("explorer.exe", "https://github.com/SuxueCode/WechatBakTool");
|
||||
}
|
||||
}
|
||||
}
|
||||
237
Pages/Workspace.xaml
Normal file
237
Pages/Workspace.xaml
Normal file
@@ -0,0 +1,237 @@
|
||||
<Page x:Class="WechatBakTool.Pages.Workspace"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:WechatBakTool.Pages"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="720"
|
||||
Title="Workspace" Background="White">
|
||||
<Page.Resources>
|
||||
<local:MsgTemplateSelector x:Key="MsgTemplateSelector"/>
|
||||
<Style TargetType="ToggleButton" x:Key="ComboxStyleBtn">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<!--下拉按钮内部背景色-->
|
||||
<Border x:Name="Back" Background="{Binding Background, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="1,0,0,0" CornerRadius="0,3,3,0">
|
||||
<!--下拉按钮内边框-->
|
||||
<Path Name="PathFill" Fill="{Binding Foreground, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Width="10" Height="6" StrokeThickness="0" Data="M5,0 L10,10 L0,10 z" RenderTransformOrigin="0.5,0.5" Stretch="Fill">
|
||||
<Path.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform/>
|
||||
<SkewTransform/>
|
||||
<RotateTransform Angle="180"/>
|
||||
<TranslateTransform/>
|
||||
</TransformGroup>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="PathFill" Property="Fill" Value="White"></Setter>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Path=IsEnabled, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Value="False">
|
||||
<Setter Property="Background" Value="#efefef" />
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
<Setter Property="Foreground" Value="#000000" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Path=IsEnabled, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Value="True">
|
||||
<Setter Property="Background" Value="#2775b6" />
|
||||
<Setter Property="Foreground" Value="#ffffff" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
|
||||
|
||||
</Style>
|
||||
<!--Combox-->
|
||||
<Style TargetType="ComboBox" x:Key="ComboBoxStyle">
|
||||
<Setter Property="ItemContainerStyle">
|
||||
<Setter.Value>
|
||||
<!--ComBoxItem-->
|
||||
<Style TargetType="ComboBoxItem">
|
||||
<Setter Property="MinHeight" Value="22"></Setter>
|
||||
<Setter Property="MinWidth" Value="60"></Setter>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ComboBoxItem">
|
||||
<Border Name="Back" Background="Transparent" BorderThickness="0">
|
||||
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Left" Margin="10,0,0,0" ></ContentPresenter>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Back" Property="Background" Value="#BB2775b6"></Setter>
|
||||
</Trigger>
|
||||
<!--下拉框背景色-->
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter TargetName="Back" Property="Background" Value="#2775b6"></Setter>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ComboBox">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="0.7*"/>
|
||||
<ColumnDefinition Width="0.3*" MaxWidth="30"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<!--文字区域背景和边线样式-->
|
||||
|
||||
<Border Grid.Column="0" BorderThickness="1" BorderBrush="{Binding Background, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" CornerRadius="3,0,0,3">
|
||||
<Button Name="export" Click="Export_Click" Background="{Binding Background, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Foreground="White" Grid.Column="0" BorderThickness="0" Content="{TemplateBinding Text}" HorizontalContentAlignment="Left" VerticalContentAlignment="Center" Padding="8,0,0,0">
|
||||
</Button>
|
||||
</Border>
|
||||
<!--右侧下拉button设置-->
|
||||
<Border Grid.Column="1" BorderThickness="0">
|
||||
<ToggleButton BorderThickness="3" Style="{StaticResource ComboxStyleBtn}" IsChecked="{Binding Path=IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" ClickMode="Press"></ToggleButton>
|
||||
</Border>
|
||||
<!--弹出popup整体设置-->
|
||||
<Popup IsOpen="{TemplateBinding IsDropDownOpen}" Placement="Bottom" x:Name="Popup" Focusable="False" AllowsTransparency="False" PopupAnimation="Slide" >
|
||||
<!--弹出popup边框-->
|
||||
<Border CornerRadius="3" BorderThickness="0" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{TemplateBinding ActualWidth}" x:Name="DropDown" SnapsToDevicePixels="True">
|
||||
<!--下拉幕布边界背景设置 MaxHeight="{TemplateBinding MaxDropDownHeight}"-->
|
||||
<ScrollViewer Margin="0,0,0,0" Background="Gray" SnapsToDevicePixels="True" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" BorderBrush="Gray" BorderThickness="0" >
|
||||
<!--StackPanel 用于显示子级,方法是将 IsItemsHost 设置为 True-->
|
||||
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" Background="#BB2775b6" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="RepeatButtonTransparent" TargetType="{x:Type RepeatButton}">
|
||||
<Setter Property="OverridesDefaultStyle" Value="true"/>
|
||||
<Setter Property="Background" Value="#2775b6"/>
|
||||
<Setter Property="Focusable" Value="false"/>
|
||||
<Setter Property="IsTabStop" Value="false"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type RepeatButton}">
|
||||
<Border Background="{TemplateBinding Background}" Height="{TemplateBinding Height}" Width="{TemplateBinding Width}" CornerRadius="4"/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- 这里是listview滚动条的滑动块部分样式-->
|
||||
<Style x:Key="ScrollBarThumbVertical" TargetType="{x:Type Thumb}">
|
||||
<Setter Property="OverridesDefaultStyle" Value="true"/>
|
||||
<Setter Property="IsTabStop" Value="false"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Thumb}">
|
||||
<Border x:Name="rectangle" Background="#BB2775b6" Height="{TemplateBinding Height}" SnapsToDevicePixels="True" Width="{TemplateBinding Width}" CornerRadius="4"/>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter Property="Background" TargetName="rectangle" Value="#772775b6"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsDragging" Value="true">
|
||||
<Setter Property="Background" TargetName="rectangle" Value="#772775b6"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<DataTemplate x:Key="ListViewItemContentTemplate">
|
||||
<Grid Margin="0">
|
||||
<Image Width="40" Height="40" Margin="10" VerticalAlignment="Top" HorizontalAlignment="Left" Source="{Binding Avatar}" />
|
||||
<Label Margin="60,8,0,0" FontWeight="Bold" VerticalAlignment="Top" HorizontalAlignment="Left" Content="{Binding NickName}" Width="130"/>
|
||||
<Label Margin="60,25,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="140" Content="{Binding LastMsg}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="MsgText">
|
||||
<Grid Margin="0">
|
||||
<Label Margin="60,8,0,0" FontWeight="Bold" VerticalAlignment="Top" Content="{Binding NickName}"/>
|
||||
<Label Margin="60,25,0,8" VerticalAlignment="Top" HorizontalAlignment="Left" Width="380">
|
||||
<TextBlock Text="{Binding DisplayContent}" TextWrapping="Wrap" />
|
||||
</Label>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="MsgImage">
|
||||
<Grid Margin="0">
|
||||
<Label Margin="60,8,0,0" FontWeight="Bold" VerticalAlignment="Top" HorizontalAlignment="Left" Content="{Binding NickName}" Width="130"/>
|
||||
<Label Margin="60,25,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="140" Content="1111"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="MsgAudio">
|
||||
<Grid Margin="0">
|
||||
<Image Width="40" Height="40" Margin="10" VerticalAlignment="Top" HorizontalAlignment="Left" Source="{Binding Avatar}" />
|
||||
<Label Margin="60,8,0,0" FontWeight="Bold" VerticalAlignment="Top" HorizontalAlignment="Left" Content="{Binding NickName}" Width="130"/>
|
||||
<Label Margin="60,25,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="140" Content="{Binding LastMsg}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Page.Resources>
|
||||
<Grid>
|
||||
<TextBox Name="txt_find_user" Padding="10,0" Height="30" VerticalAlignment="Top" VerticalContentAlignment="Center" FontSize="14" HorizontalAlignment="Left" Width="230" BorderThickness="0,0,10,1" BorderBrush="#2775b6" TextChanged="txt_find_user_TextChanged" GotFocus="txt_find_user_GotFocus" Text="{Binding SearchString, Mode=TwoWay}" />
|
||||
<ListView Margin="0,30,0,0" Background="Transparent" HorizontalAlignment="Left" Width="230" Name="list_users" ItemTemplate="{DynamicResource ListViewItemContentTemplate}" BorderThickness="0,0,1,0" BorderBrush="#2775b6" SelectionChanged="list_users_SelectionChanged" ItemsSource="{Binding Contacts}">
|
||||
<ListView.Resources>
|
||||
<Style TargetType="{x:Type ScrollBar}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ScrollBar}">
|
||||
<Grid x:Name="Bg" SnapsToDevicePixels="true" Width="8" HorizontalAlignment="Right">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="0"/>
|
||||
<RowDefinition Height="1*"/>
|
||||
<RowDefinition Height="0"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Row="1" CornerRadius="5"/>
|
||||
<Track x:Name="PART_Track" IsDirectionReversed="true" IsEnabled="{TemplateBinding IsMouseOver}" Grid.Row="1">
|
||||
<Track.DecreaseRepeatButton>
|
||||
<RepeatButton Command="{x:Static ScrollBar.PageUpCommand}" Style="{StaticResource RepeatButtonTransparent}" HorizontalAlignment="Right" Width="8"/>
|
||||
</Track.DecreaseRepeatButton>
|
||||
<Track.IncreaseRepeatButton>
|
||||
<RepeatButton Command="{x:Static ScrollBar.PageDownCommand}" Style="{StaticResource RepeatButtonTransparent}" HorizontalAlignment="Right" Width="8"/>
|
||||
</Track.IncreaseRepeatButton>
|
||||
<Track.Thumb>
|
||||
<Thumb Style="{StaticResource ScrollBarThumbVertical}" Width="8" />
|
||||
</Track.Thumb>
|
||||
</Track>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListView.Resources>
|
||||
</ListView>
|
||||
<Label Content="{Binding WXContact.NickName}" HorizontalAlignment="Left" Margin="258,21,0,0" VerticalAlignment="Top"/>
|
||||
<ListView x:Name="list_msg" Margin="230,60,0,60" Background="Transparent" BorderThickness="0,1,0,1" BorderBrush="#BB2775b6" ItemTemplateSelector="{StaticResource MsgTemplateSelector}" ItemsSource="{Binding WXMsgs}" ScrollViewer.ScrollChanged="list_msg_ScrollChanged" >
|
||||
|
||||
</ListView>
|
||||
<ComboBox Name="cb_export" Width="120" Height="30" Style="{StaticResource ComboBoxStyle}" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="30,15" ItemsSource="{Binding ExportItems}" SelectedItem="{Binding SelectExportItem}" DisplayMemberPath="Name" SelectedValuePath="Value" IsEnabled="{Binding SelectContact}" Background="#2775b6" />
|
||||
<Button x:Name="btn_open_workspace" Width="80" Height="30" Style="{StaticResource ButtonStyle}" Content="打开文件夹" BorderBrush="Transparent" BorderThickness="0" Background="#2775b6" Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,390,15" Click="btn_open_workspace_Click">
|
||||
<Button.Resources>
|
||||
<Style TargetType="{x:Type Border}">
|
||||
<Setter Property="CornerRadius" Value="3"/>
|
||||
</Style>
|
||||
</Button.Resources>
|
||||
</Button>
|
||||
<Button x:Name="btn_pre_emoji" Width="80" Height="30" Style="{StaticResource ButtonStyle}" Content="表情预下载" BorderBrush="Transparent" BorderThickness="0" Background="#2775b6" Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,292,15" IsEnabled="{Binding SelectContact}" Click="btn_pre_emoji_Click" >
|
||||
<Button.Resources>
|
||||
<Style TargetType="{x:Type Border}">
|
||||
<Setter Property="CornerRadius" Value="3"/>
|
||||
</Style>
|
||||
</Button.Resources>
|
||||
</Button>
|
||||
<Label Content="{Binding ExportCount}" HorizontalAlignment="Right" Margin="0,0,160,15" VerticalAlignment="Bottom"/>
|
||||
</Grid>
|
||||
</Page>
|
||||
345
Pages/Workspace.xaml.cs
Normal file
345
Pages/Workspace.xaml.cs
Normal file
@@ -0,0 +1,345 @@
|
||||
using JiebaNet.Segmenter;
|
||||
using JiebaNet.Segmenter.Common;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using WechatBakTool.Export;
|
||||
using WechatBakTool.Helpers;
|
||||
using WechatBakTool.Model;
|
||||
using WechatBakTool.ViewModel;
|
||||
using WordCloudSharp;
|
||||
using System.Drawing;
|
||||
using System.Windows.Controls;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Threading;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace WechatBakTool.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Workspace.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class Workspace : Page
|
||||
{
|
||||
public WXUserReader? UserReader;
|
||||
private WorkspaceViewModel ViewModel = new WorkspaceViewModel();
|
||||
private int PageSize = 100;
|
||||
private int Postion = 0;
|
||||
private bool Loading = false;
|
||||
public Workspace()
|
||||
{
|
||||
ViewModel.ExportItems = new System.Collections.ObjectModel.ObservableCollection<ExportItem> {
|
||||
new ExportItem(){ Name="导出为HTML",Value=1 },
|
||||
new ExportItem(){ Name="导出为TXT",Value=2 },
|
||||
new ExportItem(){ Name="与他的词云",Value=3 },
|
||||
};
|
||||
ViewModel.SelectExportItem = ViewModel.ExportItems[0];
|
||||
InitializeComponent();
|
||||
|
||||
list_users.Items.Clear();
|
||||
|
||||
DataContext = ViewModel;
|
||||
UserBakConfig? config = Main2.CurrentUserBakConfig;
|
||||
if (config != null)
|
||||
{
|
||||
UserReader = new WXUserReader(config);
|
||||
if (config.Decrypt)
|
||||
{
|
||||
ViewModel.Contacts = null;
|
||||
Task.Run(() => {
|
||||
ViewModel.Contacts = UserReader.GetWXContacts();
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void btn_read_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Main2.CurrentUserBakConfig == null)
|
||||
{
|
||||
MessageBox.Show("工作区配置加载失败,请检查配置文件是否正常","错误");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void list_users_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
|
||||
ViewModel.WXMsgs.Clear();
|
||||
Postion = 0;
|
||||
ViewModel.ExportCount = "";
|
||||
|
||||
loadMsg();
|
||||
if (ViewModel.WXMsgs.Count == 0)
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
private void loadMsg()
|
||||
{
|
||||
Loading = true;
|
||||
ViewModel.WXContact = list_users.SelectedItem as WXContact;
|
||||
if (ViewModel.WXContact == null || UserReader == null)
|
||||
return;
|
||||
|
||||
List<WXMsg>? list = UserReader.GetWXMsgs(ViewModel.WXContact.UserName, Postion, PageSize);
|
||||
// Trace.WriteLine(string.Format("{0}->{1}", PageSize, Postion));
|
||||
if (list == null)
|
||||
return;
|
||||
if (list.Count == 0)
|
||||
return;
|
||||
|
||||
|
||||
foreach (WXMsg w in list)
|
||||
{
|
||||
ViewModel.WXMsgs.Add(w);
|
||||
}
|
||||
|
||||
Postion = int.Parse(list.Max(x => x.CreateTime).ToString());
|
||||
list_msg.ScrollIntoView(list[0]);
|
||||
Task.Run(() => {
|
||||
Thread.Sleep(500);
|
||||
Loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
private void txt_find_user_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (UserReader == null)
|
||||
return;
|
||||
|
||||
string findName = txt_find_user.Text;
|
||||
if (txt_find_user.Text == "搜索...")
|
||||
findName = "";
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
ViewModel.Contacts = UserReader.GetWXContacts(findName);
|
||||
// 保底回落搜索已删除人员
|
||||
if (ViewModel.Contacts.Count == 0)
|
||||
{
|
||||
var i = UserReader.GetWXMsgs(txt_find_user.Text);
|
||||
if (i != null)
|
||||
{
|
||||
var g = i.GroupBy(x => x.StrTalker);
|
||||
ViewModel.Contacts = new System.Collections.ObjectModel.ObservableCollection<WXContact>();
|
||||
foreach (var x in g)
|
||||
{
|
||||
string name = x.Key;
|
||||
ViewModel.Contacts.Add(new WXContact() { UserName = name, NickName = name });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void txt_find_user_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (txt_find_user.Text == "搜索...")
|
||||
txt_find_user.Text = "";
|
||||
|
||||
Debug.WriteLine(ViewModel.SearchString);
|
||||
}
|
||||
|
||||
private void btn_export_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(ViewModel.WXContact == null || UserReader == null)
|
||||
{
|
||||
MessageBox.Show("请选择联系人", "错误");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, ViewModel.WXContact.UserName + ".txt");
|
||||
IExport export = new TXTExport();
|
||||
export.InitTemplate(ViewModel.WXContact, path);
|
||||
export.SetMsg(UserReader, ViewModel.WXContact, ViewModel);
|
||||
export.SetEnd();
|
||||
export.Save(path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message);
|
||||
}
|
||||
|
||||
MessageBox.Show("导出完成");
|
||||
}
|
||||
|
||||
private void btn_open_workspace_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Process.Start("explorer.exe ", Main2.CurrentUserBakConfig!.UserWorkspacePath);
|
||||
}
|
||||
|
||||
private void Export_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (ViewModel.WXContact == null || UserReader == null)
|
||||
{
|
||||
MessageBox.Show("请选择联系人", "错误");
|
||||
return;
|
||||
}
|
||||
if (ViewModel.SelectExportItem == null)
|
||||
{
|
||||
MessageBox.Show("请选择导出方式", "错误");
|
||||
return;
|
||||
}
|
||||
if(ViewModel.SelectExportItem.Value == 3)
|
||||
{
|
||||
if(UserReader != null && ViewModel.WXContact != null)
|
||||
{
|
||||
System.Drawing.Image? mask = null;
|
||||
if (File.Exists("mask.png"))
|
||||
mask = System.Drawing.Image.FromFile("mask.png");
|
||||
|
||||
WordCloudSettingViewModel setting = new WordCloudSettingViewModel() {
|
||||
ImgWidth = mask == null ? "1000": mask.Width.ToString(),
|
||||
ImgHeight = mask == null ? "1000" : mask.Height.ToString(),
|
||||
EnableRemoveOneKey = true,
|
||||
};
|
||||
Dispatcher.Invoke(() => {
|
||||
WordCloudSetting wordCloudSetting = new WordCloudSetting(setting);
|
||||
wordCloudSetting.ShowDialog();
|
||||
});
|
||||
|
||||
var jieba = new JiebaSegmenter();
|
||||
Counter<string> counter = new Counter<string>();
|
||||
|
||||
try
|
||||
{
|
||||
ViewModel.ExportCount = "词频统计ing...";
|
||||
List<WXMsg>? msgs = UserReader.GetWXMsgs(ViewModel.WXContact.UserName);
|
||||
if (msgs != null)
|
||||
{
|
||||
foreach (WXMsg msg in msgs)
|
||||
{
|
||||
if (msg.Type == 1)
|
||||
{
|
||||
List<string> list = jieba.Cut(msg.StrContent).ToList();
|
||||
counter.Add(list);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ViewModel.ExportCount = "异常";
|
||||
MessageBox.Show("词频统计发生异常,请检查字典文件是否存在", "错误");
|
||||
return;
|
||||
}
|
||||
|
||||
var orderBy = counter.MostCommon();
|
||||
ViewModel.ExportCount = "移除部分词语...";
|
||||
string[] remove_string_list = setting.RemoveKey.Split(",");
|
||||
foreach(string remove_string in remove_string_list)
|
||||
{
|
||||
counter.Remove(remove_string);
|
||||
}
|
||||
foreach(var key in orderBy)
|
||||
{
|
||||
if (key.Key.Length == 1 && setting.EnableRemoveOneKey)
|
||||
counter.Remove(key.Key);
|
||||
}
|
||||
|
||||
ViewModel.ExportCount = "渲染词云结果";
|
||||
string resultPath = "result.jpg";
|
||||
|
||||
WordCloud wordCloud;
|
||||
if(mask != null)
|
||||
wordCloud = new WordCloud(int.Parse(setting.ImgWidth), int.Parse(setting.ImgHeight), mask: mask, allowVerical: true, fontname: setting.Font);
|
||||
else
|
||||
wordCloud = new WordCloud(int.Parse(setting.ImgWidth), int.Parse(setting.ImgHeight), allowVerical: true, fontname: setting.Font);
|
||||
|
||||
if (orderBy.Count() >= setting.MaxKeyCount)
|
||||
orderBy = orderBy.Take(setting.MaxKeyCount);
|
||||
|
||||
var result = wordCloud.Draw(orderBy.Select(it => it.Key).ToList(), orderBy.Select(it => it.Value).ToList());
|
||||
result.Save(resultPath);
|
||||
ViewModel.ExportCount = "完成";
|
||||
MessageBox.Show("生成完毕,请查看软件根目录result.jpg", "提示");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
string name = ViewModel.WXContact.NickName;
|
||||
name = name.Replace(@"\", "");
|
||||
name = Regex.Replace(name, "[ \\[ \\] \\^ \\-_*×――(^)$%~!/@#$…&%¥—+=<>《》|!!???::•`·、。,;,.;\"‘’“”-]", "");
|
||||
string path = Path.Combine(
|
||||
Main2.CurrentUserBakConfig!.UserWorkspacePath,
|
||||
string.Format(
|
||||
"{0}-{1}",
|
||||
ViewModel.WXContact.UserName,
|
||||
ViewModel.WXContact.Remark == "" ? name : ViewModel.WXContact.Remark
|
||||
)
|
||||
);
|
||||
IExport export;
|
||||
|
||||
#if DEBUG
|
||||
Stopwatch stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
#endif
|
||||
if (ViewModel.SelectExportItem.Value == 2)
|
||||
{
|
||||
path += ".txt";
|
||||
export = new TXTExport();
|
||||
}
|
||||
else
|
||||
{
|
||||
path += ".html";
|
||||
export = new HtmlExport();
|
||||
}
|
||||
export.InitTemplate(ViewModel.WXContact, path);
|
||||
export.SetMsg(UserReader, ViewModel.WXContact, ViewModel);
|
||||
export.SetEnd();
|
||||
export.Save(path);
|
||||
#if DEBUG
|
||||
stopwatch.Stop();
|
||||
MessageBox.Show(stopwatch.Elapsed.ToString());
|
||||
#endif
|
||||
MessageBox.Show("导出完成", "提示");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void btn_pre_emoji_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(UserReader != null && ViewModel.WXContact != null)
|
||||
{
|
||||
Task.Run(() => {
|
||||
UserReader.PreDownloadEmoji(ViewModel.WXContact.UserName);
|
||||
MessageBox.Show("用户所有表情预下载完毕");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void list_msg_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
if (ViewModel.WXMsgs.Count == 0)
|
||||
return;
|
||||
|
||||
if (e.VerticalOffset + e.ViewportHeight == e.ExtentHeight && !Loading)
|
||||
{
|
||||
// 滚动条到达底部的处理逻辑
|
||||
loadMsg();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
README.md
101
README.md
@@ -1,43 +1,86 @@
|
||||
# WechatPCMsgBakTool
|
||||
微信PC聊天记录备份工具,仅支持Windows
|
||||
|
||||
- 支持3.9.6.33版本后,若版本更新可在version.json添加版本号和地址即可完成新版本支持
|
||||
- 导出图片、视频、音频、分享链接
|
||||
- 导出Html文件
|
||||
# WechatBakTool
|
||||
基于C#开发的微信聊天记录备份分析工具,努力做最好用的微信备份工具。
|
||||
|
||||
- 理论支持64位版本所有微信,支持两种方式非直接地址获取Key[1]
|
||||
- 工作区概念,支持多微信切换操作。
|
||||
- 支持导出Html文件,TXT文件,支持批量导出
|
||||
- 支持聊天频率分析,全消息库内容搜索
|
||||
|
||||
**本项目仅做学习使用,主要供个人备份自己的微信记录,请勿用于非法用途。**
|
||||
|
||||
**本项目严禁商用**
|
||||
- 目前支持以下类型消息解析
|
||||
- [x] 文本消息
|
||||
- [x] 图片
|
||||
- [x] 语音
|
||||
- [x] 分享链接
|
||||
- [x] 群聊
|
||||
- [x] 系统消息
|
||||
- [x] 文件
|
||||
- [x] 引用/转发消息
|
||||
- [x] 表情(需要预下载)
|
||||
|
||||
如果有什么好的建议或意见,或者遇到什么问题,欢迎提issue,看到会回。
|
||||
|
||||
|
||||
> [!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/>
|
||||
|
||||
### 使用说明
|
||||
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
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
17817
Resources/cn_synonym.txt
Normal file
File diff suppressed because one or more lines are too long
349046
Resources/dict.txt
Normal file
349046
Resources/dict.txt
Normal file
File diff suppressed because it is too large
Load Diff
270132
Resources/idf.txt
Normal file
270132
Resources/idf.txt
Normal file
File diff suppressed because it is too large
Load Diff
89711
Resources/pos_prob_emit.json
Normal file
89711
Resources/pos_prob_emit.json
Normal file
File diff suppressed because it is too large
Load Diff
258
Resources/pos_prob_start.json
Normal file
258
Resources/pos_prob_start.json
Normal 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
|
||||
}
|
||||
5639
Resources/pos_prob_trans.json
Normal file
5639
Resources/pos_prob_trans.json
Normal file
File diff suppressed because it is too large
Load Diff
35234
Resources/prob_emit.json
Normal file
35234
Resources/prob_emit.json
Normal file
File diff suppressed because it is too large
Load Diff
18
Resources/prob_trans.json
Normal file
18
Resources/prob_trans.json
Normal 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
653
Resources/stopwords.txt
Normal 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
|
||||
一番
|
||||
一直
|
||||
一个
|
||||
一些
|
||||
许多
|
||||
种
|
||||
有的是
|
||||
也就是说
|
||||
阿
|
||||
哎呀
|
||||
哎哟
|
||||
俺
|
||||
俺们
|
||||
按
|
||||
按照
|
||||
吧
|
||||
吧哒
|
||||
把
|
||||
罢了
|
||||
被
|
||||
本
|
||||
本着
|
||||
比
|
||||
比方
|
||||
比如
|
||||
鄙人
|
||||
彼
|
||||
彼此
|
||||
边
|
||||
别
|
||||
别的
|
||||
别说
|
||||
并
|
||||
并且
|
||||
不比
|
||||
不成
|
||||
不单
|
||||
不但
|
||||
不独
|
||||
不管
|
||||
不光
|
||||
不过
|
||||
不仅
|
||||
不拘
|
||||
不论
|
||||
不怕
|
||||
不然
|
||||
不如
|
||||
不特
|
||||
不惟
|
||||
不问
|
||||
不只
|
||||
朝
|
||||
朝着
|
||||
趁
|
||||
趁着
|
||||
乘
|
||||
冲
|
||||
除
|
||||
除此之外
|
||||
除非
|
||||
除了
|
||||
此
|
||||
此间
|
||||
此外
|
||||
从
|
||||
从而
|
||||
打
|
||||
待
|
||||
但
|
||||
但是
|
||||
当
|
||||
当着
|
||||
到
|
||||
得
|
||||
的
|
||||
的话
|
||||
等
|
||||
等等
|
||||
地
|
||||
第
|
||||
叮咚
|
||||
对
|
||||
对于
|
||||
多
|
||||
多少
|
||||
而
|
||||
而况
|
||||
而且
|
||||
而是
|
||||
而外
|
||||
而言
|
||||
而已
|
||||
尔后
|
||||
反过来
|
||||
反过来说
|
||||
反之
|
||||
非但
|
||||
非徒
|
||||
否则
|
||||
嘎
|
||||
嘎登
|
||||
该
|
||||
赶
|
||||
个
|
||||
各
|
||||
各个
|
||||
各位
|
||||
各种
|
||||
各自
|
||||
给
|
||||
根据
|
||||
跟
|
||||
故
|
||||
故此
|
||||
固然
|
||||
关于
|
||||
管
|
||||
归
|
||||
果然
|
||||
果真
|
||||
过
|
||||
和
|
||||
何
|
||||
何处
|
||||
何况
|
||||
何时
|
||||
嘿
|
||||
哼
|
||||
哼唷
|
||||
呼哧
|
||||
乎
|
||||
哗
|
||||
还是
|
||||
还有
|
||||
换句话说
|
||||
换言之
|
||||
或
|
||||
或是
|
||||
或者
|
||||
极了
|
||||
及
|
||||
及其
|
||||
及至
|
||||
即
|
||||
即便
|
||||
即或
|
||||
即令
|
||||
即若
|
||||
即使
|
||||
几
|
||||
几时
|
||||
己
|
||||
既
|
||||
既然
|
||||
既是
|
||||
继而
|
||||
加之
|
||||
假如
|
||||
假若
|
||||
假使
|
||||
鉴于
|
||||
将
|
||||
较
|
||||
较之
|
||||
叫
|
||||
接着
|
||||
结果
|
||||
借
|
||||
紧接着
|
||||
进而
|
||||
尽
|
||||
尽管
|
||||
经
|
||||
经过
|
||||
就
|
||||
就是
|
||||
就是说
|
||||
据
|
||||
具体地说
|
||||
具体说来
|
||||
开始
|
||||
开外
|
||||
靠
|
||||
咳
|
||||
可
|
||||
可见
|
||||
可是
|
||||
可以
|
||||
况且
|
||||
啦
|
||||
来
|
||||
来着
|
||||
离
|
||||
例如
|
||||
哩
|
||||
连
|
||||
连同
|
||||
两者
|
||||
了
|
||||
临
|
||||
另
|
||||
另外
|
||||
另一方面
|
||||
论
|
||||
嘛
|
||||
吗
|
||||
慢说
|
||||
漫说
|
||||
冒
|
||||
么
|
||||
每
|
||||
每当
|
||||
们
|
||||
莫若
|
||||
某
|
||||
某个
|
||||
某些
|
||||
拿
|
||||
哪
|
||||
哪边
|
||||
哪儿
|
||||
哪个
|
||||
哪里
|
||||
哪年
|
||||
哪怕
|
||||
哪天
|
||||
哪些
|
||||
哪样
|
||||
那
|
||||
那边
|
||||
那儿
|
||||
那个
|
||||
那会儿
|
||||
那里
|
||||
那么
|
||||
那么些
|
||||
那么样
|
||||
那时
|
||||
那些
|
||||
那样
|
||||
乃
|
||||
乃至
|
||||
呢
|
||||
能
|
||||
你
|
||||
你们
|
||||
您
|
||||
宁
|
||||
宁可
|
||||
宁肯
|
||||
宁愿
|
||||
哦
|
||||
啪达
|
||||
旁人
|
||||
凭
|
||||
凭借
|
||||
其
|
||||
其次
|
||||
其二
|
||||
其他
|
||||
其它
|
||||
其一
|
||||
其余
|
||||
其中
|
||||
起
|
||||
起见
|
||||
起见
|
||||
岂但
|
||||
恰恰相反
|
||||
前后
|
||||
前者
|
||||
且
|
||||
然而
|
||||
然后
|
||||
然则
|
||||
让
|
||||
人家
|
||||
任
|
||||
任何
|
||||
任凭
|
||||
如
|
||||
如此
|
||||
如果
|
||||
如何
|
||||
如其
|
||||
如若
|
||||
如上所述
|
||||
若
|
||||
若非
|
||||
若是
|
||||
啥
|
||||
上下
|
||||
尚且
|
||||
设若
|
||||
设使
|
||||
甚而
|
||||
甚么
|
||||
甚至
|
||||
省得
|
||||
时候
|
||||
什么
|
||||
什么样
|
||||
使得
|
||||
是
|
||||
是的
|
||||
首先
|
||||
谁
|
||||
顺
|
||||
顺着
|
||||
似的
|
||||
虽
|
||||
虽然
|
||||
虽说
|
||||
虽则
|
||||
随
|
||||
随着
|
||||
所
|
||||
所以
|
||||
他
|
||||
他们
|
||||
他人
|
||||
它
|
||||
它们
|
||||
她
|
||||
她们
|
||||
倘
|
||||
倘或
|
||||
倘然
|
||||
倘若
|
||||
倘使
|
||||
腾
|
||||
替
|
||||
通过
|
||||
同
|
||||
同时
|
||||
哇
|
||||
万一
|
||||
往
|
||||
望
|
||||
为
|
||||
为何
|
||||
为了
|
||||
为什么
|
||||
为着
|
||||
喂
|
||||
嗡嗡
|
||||
我
|
||||
我们
|
||||
呜
|
||||
呜呼
|
||||
乌乎
|
||||
无论
|
||||
无宁
|
||||
毋宁
|
||||
嘻
|
||||
吓
|
||||
相对而言
|
||||
像
|
||||
向
|
||||
向着
|
||||
嘘
|
||||
焉
|
||||
沿
|
||||
沿着
|
||||
要
|
||||
要不
|
||||
要不然
|
||||
要不是
|
||||
要么
|
||||
要是
|
||||
也
|
||||
也罢
|
||||
也好
|
||||
一
|
||||
一旦
|
||||
一方面
|
||||
一来
|
||||
一切
|
||||
一样
|
||||
一则
|
||||
依
|
||||
依照
|
||||
矣
|
||||
以
|
||||
以便
|
||||
以及
|
||||
以免
|
||||
以至
|
||||
以至于
|
||||
以致
|
||||
抑或
|
||||
因
|
||||
因此
|
||||
因而
|
||||
因为
|
||||
用
|
||||
由
|
||||
由此可见
|
||||
由于
|
||||
有
|
||||
有的
|
||||
有关
|
||||
有些
|
||||
又
|
||||
于
|
||||
于是
|
||||
于是乎
|
||||
与
|
||||
与此同时
|
||||
与否
|
||||
与其
|
||||
越是
|
||||
云云
|
||||
哉
|
||||
再说
|
||||
再者
|
||||
在
|
||||
在下
|
||||
咱
|
||||
咱们
|
||||
则
|
||||
怎
|
||||
怎么办
|
||||
怎么样
|
||||
咋
|
||||
照
|
||||
照着
|
||||
者
|
||||
这
|
||||
这边
|
||||
这儿
|
||||
这个
|
||||
这会儿
|
||||
这就是说
|
||||
这里
|
||||
这么
|
||||
这么点儿
|
||||
这么些
|
||||
这么样
|
||||
这时
|
||||
这些
|
||||
这样
|
||||
正如
|
||||
吱
|
||||
之
|
||||
之类
|
||||
之所以
|
||||
之一
|
||||
只是
|
||||
只限
|
||||
只要
|
||||
只有
|
||||
至
|
||||
至于
|
||||
诸位
|
||||
着
|
||||
着呢
|
||||
自
|
||||
自从
|
||||
自个儿
|
||||
自各儿
|
||||
自己
|
||||
自家
|
||||
自身
|
||||
综上所述
|
||||
总的来看
|
||||
总的来说
|
||||
总的说来
|
||||
总而言之
|
||||
总之
|
||||
纵
|
||||
纵令
|
||||
纵然
|
||||
纵使
|
||||
遵照
|
||||
作为
|
||||
兮
|
||||
呗
|
||||
咚
|
||||
咦
|
||||
喏
|
||||
啐
|
||||
喔唷
|
||||
嗬
|
||||
嗯
|
||||
嗳
|
||||
。
|
||||
,
|
||||
:
|
||||
;
|
||||
、
|
||||
“
|
||||
”
|
||||
【
|
||||
】
|
||||
《
|
||||
》
|
||||
(
|
||||
)
|
||||
—
|
||||
…
|
||||
.
|
||||
,
|
||||
:
|
||||
;
|
||||
"
|
||||
"
|
||||
[
|
||||
]
|
||||
<
|
||||
>
|
||||
(
|
||||
)
|
||||
@
|
||||
#
|
||||
*
|
||||
&
|
||||
%
|
||||
¥
|
||||
$
|
||||
-
|
||||
+
|
||||
=
|
||||
|
|
||||
\
|
||||
@@ -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>
|
||||
@@ -1,143 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
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)
|
||||
{
|
||||
var h_list = ProcessHelper.GetHandles(p);
|
||||
foreach (var h in h_list)
|
||||
{
|
||||
string name = ProcessHelper.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.ObjectType, h.Handle, 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 的交互逻辑
|
||||
|
||||
54
ViewModel/CreateWorkViewModel.cs
Normal file
54
ViewModel/CreateWorkViewModel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
ViewModel/WordCloudSettingViewModel.cs
Normal file
33
ViewModel/WordCloudSettingViewModel.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
80
ViewModel/WorkspaceViewModel.cs
Normal file
80
ViewModel/WorkspaceViewModel.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using WechatBakTool.Model;
|
||||
|
||||
namespace WechatBakTool.ViewModel
|
||||
{
|
||||
public partial class WorkspaceViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(SelectContact))]
|
||||
[NotifyPropertyChangedFor(nameof(LabelStatus))]
|
||||
private WXContact? wXContact = null;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<WXMsg> wXMsgs = new ObservableCollection<WXMsg>();
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(LabelStatus))]
|
||||
private string exportCount = "";
|
||||
|
||||
public string LabelStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
if (WXContact == null)
|
||||
return ExportCount;
|
||||
|
||||
string name = WXContact.NickName;
|
||||
if(WXContact.Remark != "")
|
||||
name = WXContact.Remark;
|
||||
|
||||
return string.Format("{0}:{1}", name, ExportCount);
|
||||
}
|
||||
}
|
||||
|
||||
public bool SelectContact
|
||||
{
|
||||
get
|
||||
{
|
||||
if (WXContact == null)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<WXContact>? contacts;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ExportItem>? exportItems;
|
||||
|
||||
[ObservableProperty]
|
||||
private ExportItem? selectExportItem;
|
||||
|
||||
private string searchString = "";
|
||||
public string SearchString
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == "搜索...")
|
||||
searchString = "";
|
||||
else
|
||||
searchString = value;
|
||||
|
||||
OnPropertyChanged("SearchString");
|
||||
}
|
||||
get
|
||||
{
|
||||
if (searchString == "")
|
||||
return "搜索...";
|
||||
return searchString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
596
WXUserReader.cs
596
WXUserReader.cs
@@ -1,27 +1,44 @@
|
||||
using SQLite;
|
||||
using K4os.Compression.LZ4.Encoders;
|
||||
using K4os.Compression.LZ4;
|
||||
using SQLite;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using WechatPCMsgBakTool.Helpers;
|
||||
using WechatPCMsgBakTool.Model;
|
||||
using WechatBakTool.Helpers;
|
||||
using WechatBakTool.Model;
|
||||
using System.Windows;
|
||||
using System.Net.Http;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace WechatPCMsgBakTool
|
||||
namespace WechatBakTool
|
||||
{
|
||||
public class WXUserReader
|
||||
{
|
||||
private Dictionary<string, SQLiteConnection> DBInfo = new Dictionary<string, SQLiteConnection>();
|
||||
private UserBakConfig? UserBakConfig = null;
|
||||
private Hashtable HeadImgCache = new Hashtable();
|
||||
private Hashtable UserNameCache = new Hashtable();
|
||||
private Hashtable EmojiCache = new Hashtable();
|
||||
private HttpClient httpClient = new HttpClient();
|
||||
public WXUserReader(UserBakConfig userBakConfig) {
|
||||
string path = Path.Combine(userBakConfig.UserWorkspacePath, "DecDB");
|
||||
UserBakConfig = userBakConfig;
|
||||
LoadDB(path);
|
||||
InitCache();
|
||||
EmojiCacheInit();
|
||||
}
|
||||
|
||||
public void LoadDB(string path)
|
||||
@@ -38,59 +55,431 @@ namespace WechatPCMsgBakTool
|
||||
}
|
||||
}
|
||||
|
||||
public List<WXContact>? GetWXContacts(string? name = null)
|
||||
private SQLiteConnection? getCon(string name)
|
||||
{
|
||||
SQLiteConnection con = DBInfo["MicroMsg"];
|
||||
if (con == null)
|
||||
return null;
|
||||
string query = "select * from contact";
|
||||
if (name != null)
|
||||
if (DBInfo.ContainsKey(name))
|
||||
{
|
||||
query = "select * from contact where username like ? or alias like ? or nickname like ? or remark like ?";
|
||||
return con.Query<WXContact>(query, $"%{name}%", $"%{name}%", $"%{name}%", $"%{name}%");
|
||||
return DBInfo[name];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return con.Query<WXContact>(query);
|
||||
}
|
||||
|
||||
public void InitCache()
|
||||
{
|
||||
SQLiteConnection? con = getCon("Misc");
|
||||
if (con == null)
|
||||
return;
|
||||
|
||||
string query = @"SELECT * FROM ContactHeadImg1";
|
||||
List<ContactHeadImg> imgs = con.Query<ContactHeadImg>(query);
|
||||
foreach(ContactHeadImg item in imgs)
|
||||
{
|
||||
if (!HeadImgCache.ContainsKey(item.usrName))
|
||||
{
|
||||
HeadImgCache.Add(item.usrName, item);
|
||||
}
|
||||
}
|
||||
|
||||
List<WXContact> contacts = GetWXContacts(null, true).ToList();
|
||||
foreach(WXContact contact in contacts)
|
||||
{
|
||||
if (!UserNameCache.ContainsKey(contact.UserName))
|
||||
UserNameCache.Add(contact.UserName, contact);
|
||||
}
|
||||
}
|
||||
|
||||
public void EmojiCacheInit()
|
||||
{
|
||||
string emoji_path = Path.Combine(UserBakConfig!.UserWorkspacePath, "Emoji");
|
||||
if (Directory.Exists(emoji_path))
|
||||
{
|
||||
string[] files = Directory.GetFiles(emoji_path);
|
||||
foreach (string file in files)
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(file);
|
||||
string[] names = fileInfo.Name.Split(".");
|
||||
if (!EmojiCache.ContainsKey(names[0]))
|
||||
{
|
||||
EmojiCache.Add(names[0], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void PreDownloadEmoji(string username = "")
|
||||
{
|
||||
if (UserBakConfig == null)
|
||||
return;
|
||||
|
||||
HttpClientHandler handler = new HttpClientHandler() { AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate };
|
||||
HttpClient httpClient = new HttpClient(handler);
|
||||
|
||||
List<WXMsg> msgs = GetTypeMsg("47", username);
|
||||
int i = 0;
|
||||
|
||||
// 下载前的Emoji Cache不用做了,在Init的时候已经做了
|
||||
foreach (var msg in msgs)
|
||||
{
|
||||
i++;
|
||||
if (i % 5 == 0)
|
||||
{
|
||||
// 每5次让下载线程休息1秒
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
try
|
||||
{
|
||||
XmlDocument xmlDocument = new XmlDocument();
|
||||
xmlDocument.LoadXml(msg.StrContent);
|
||||
XmlNode? node = xmlDocument.SelectSingleNode("/msg/emoji");
|
||||
if (node != null)
|
||||
{
|
||||
if (node.Attributes != null)
|
||||
{
|
||||
string type = "";
|
||||
string md5 = "";
|
||||
string url = "";
|
||||
XmlNode? item = node.Attributes.GetNamedItem("type");
|
||||
type = item != null ? item.InnerText : "";
|
||||
|
||||
item = node.Attributes.GetNamedItem("md5");
|
||||
md5 = item != null ? item.InnerText : "";
|
||||
|
||||
item = node.Attributes.GetNamedItem("cdnurl");
|
||||
url = item != null ? item.InnerText : "";
|
||||
|
||||
if (EmojiCache.ContainsKey(md5))
|
||||
{
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
if (url == "")
|
||||
{
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
string path = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "Emoji", md5 + ".gif");
|
||||
try
|
||||
{
|
||||
HttpResponseMessage res = httpClient.GetAsync(url).Result;
|
||||
if (res.IsSuccessStatusCode)
|
||||
{
|
||||
using (FileStream fs = File.Create(path))
|
||||
{
|
||||
res.Content.ReadAsStream().CopyTo(fs);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 下载完成后可能变化,检查一下
|
||||
EmojiCacheInit();
|
||||
}
|
||||
|
||||
public byte[]? GetHeadImgCahce(string username)
|
||||
{
|
||||
if (HeadImgCache.ContainsKey(username))
|
||||
{
|
||||
ContactHeadImg? img = HeadImgCache[username] as ContactHeadImg;
|
||||
if (img == null)
|
||||
return null;
|
||||
else
|
||||
return img.smallHeadBuf;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int[] GetWXCount()
|
||||
{
|
||||
SQLiteConnection? con = getCon("MicroMsg");
|
||||
if (con == null)
|
||||
return new int[] { 0, 0 };
|
||||
|
||||
string query = @"select count(*) as count from contact where type != 4";
|
||||
int userCount = con.Query<WXCount>(query)[0].Count;
|
||||
|
||||
int msgCount = 0;
|
||||
for (int i = 0; i <= 99; i++)
|
||||
{
|
||||
con = getCon("MSG" + i.ToString());
|
||||
if (con == null)
|
||||
return new int[] { userCount, msgCount };
|
||||
|
||||
query = "select count(*) as count from MSG";
|
||||
msgCount += con.Query<WXCount>(query)[0].Count;
|
||||
}
|
||||
return new int[] { userCount, msgCount };
|
||||
}
|
||||
|
||||
public ObservableCollection<WXContact> GetWXContacts(string? name = null,bool all = false)
|
||||
{
|
||||
SQLiteConnection? con = getCon("MicroMsg");
|
||||
if (con == null)
|
||||
return new ObservableCollection<WXContact>();
|
||||
string query = @"select contact.*,session.strContent,contactHeadImgUrl.smallHeadImgUrl,contactHeadImgUrl.bigHeadImgUrl from contact
|
||||
left join session on session.strUsrName = contact.username
|
||||
left join contactHeadImgUrl on contactHeadImgUrl.usrName = contact.username
|
||||
where type != 4 {searchName}
|
||||
order by nOrder desc";
|
||||
|
||||
if (all)
|
||||
{
|
||||
query = query.Replace("where type != 4 ", "");
|
||||
}
|
||||
|
||||
List<WXContact>? contacts = null;
|
||||
if (name != null)
|
||||
{
|
||||
query = query.Replace("{searchName}", " and (username like ? or alias like ? or nickname like ? or remark like ?)");
|
||||
contacts = con.Query<WXContact>(query, $"%{name}%", $"%{name}%", $"%{name}%", $"%{name}%");
|
||||
}
|
||||
else
|
||||
{
|
||||
query = query.Replace("{searchName}", "");
|
||||
contacts = con.Query<WXContact>(query);
|
||||
}
|
||||
|
||||
foreach (WXContact contact in contacts)
|
||||
{
|
||||
if(contact.Remark != "")
|
||||
contact.NickName = contact.Remark;
|
||||
|
||||
byte[]? imgBytes = GetHeadImgCahce(contact.UserName);
|
||||
if (imgBytes != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
MemoryStream stream = new MemoryStream(imgBytes);
|
||||
BitmapImage bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
|
||||
bitmapImage.StreamSource = stream;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
contact.Avatar = bitmapImage;
|
||||
}
|
||||
catch
|
||||
{
|
||||
#if DEBUG
|
||||
File.AppendAllText("debug.log", string.Format("[D]{0} {1}:{2}\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "BitmapConvert Err=>Length", imgBytes.Length));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
return new ObservableCollection<WXContact>(contacts);
|
||||
}
|
||||
|
||||
public List<WXUserImg>? GetUserImgs()
|
||||
{
|
||||
SQLiteConnection? con = getCon("MicroMsg");
|
||||
if (con == null)
|
||||
return null;
|
||||
string query = "select * from contactHeadImgUrl";
|
||||
return con.Query<WXUserImg>(query);
|
||||
}
|
||||
|
||||
public List<WXChatRoom>? GetWXChatRooms()
|
||||
{
|
||||
SQLiteConnection? con = getCon("MicroMsg");
|
||||
if (con == null)
|
||||
return null;
|
||||
string query = "select * from ChatRoom";
|
||||
return con.Query<WXChatRoom>(query);
|
||||
}
|
||||
|
||||
public List<WXMsg> GetTypeMsg(string type,string username)
|
||||
{
|
||||
List<WXMsg> tmp = new List<WXMsg>();
|
||||
for (int i = 0; i <= 99; i++)
|
||||
{
|
||||
SQLiteConnection? con = getCon("MSG" + i.ToString());
|
||||
if (con == null)
|
||||
return tmp;
|
||||
|
||||
List<WXMsg> wXMsgs;
|
||||
if (username == "")
|
||||
{
|
||||
string query = "select * from MSG where Type=?";
|
||||
wXMsgs = con.Query<WXMsg>(query, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
string query = "select * from MSG where Type=? and StrTalker = ?";
|
||||
wXMsgs = con.Query<WXMsg>(query, type, username);
|
||||
}
|
||||
tmp.AddRange(wXMsgs);
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
public List<WXMsg>? GetWXMsgs(string uid,int time,int page)
|
||||
{
|
||||
List<WXMsg> tmp = new List<WXMsg>();
|
||||
for (int i = 0; i <= 99; i++)
|
||||
{
|
||||
SQLiteConnection? con = getCon("MSG" + i.ToString());
|
||||
if (con == null)
|
||||
return tmp;
|
||||
|
||||
List<WXMsg>? wXMsgs = null;
|
||||
string query = "select * from MSG where StrTalker=? and CreateTime>? Limit ?";
|
||||
wXMsgs = con.Query<WXMsg>(query, uid, time, page);
|
||||
if (wXMsgs.Count != 0) {
|
||||
return ProcessMsg(wXMsgs, uid);
|
||||
}
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
public List<WXMsg>? GetWXMsgs(string uid,string msg = "")
|
||||
{
|
||||
List<WXMsg> tmp = new List<WXMsg>();
|
||||
for (int i = 0; i <= 99; i++)
|
||||
{
|
||||
if(DBInfo.ContainsKey("MSG" + i.ToString()))
|
||||
SQLiteConnection? con = getCon("MSG" + i.ToString());
|
||||
if (con == null)
|
||||
return tmp;
|
||||
|
||||
List<WXMsg>? wXMsgs = null;
|
||||
if (msg == "")
|
||||
{
|
||||
SQLiteConnection con = DBInfo["MSG" + i.ToString()];
|
||||
if (con == null)
|
||||
return tmp;
|
||||
|
||||
List<WXMsg>? wXMsgs = null;
|
||||
if (msg == "")
|
||||
{
|
||||
string query = "select * from MSG where StrTalker=?";
|
||||
wXMsgs = con.Query<WXMsg>(query, uid);
|
||||
}
|
||||
else if(uid == "")
|
||||
{
|
||||
string query = "select * from MSG where StrContent like ?";
|
||||
wXMsgs = con.Query<WXMsg>(query, string.Format("%{0}%", msg));
|
||||
}
|
||||
else
|
||||
{
|
||||
string query = "select * from MSG where StrTalker=? and StrContent like ?";
|
||||
wXMsgs = con.Query<WXMsg>(query, uid, string.Format("%{0}%", msg));
|
||||
}
|
||||
|
||||
foreach (WXMsg w in wXMsgs)
|
||||
{
|
||||
tmp.Add(w);
|
||||
}
|
||||
string query = "select * from MSG where StrTalker=?";
|
||||
wXMsgs = con.Query<WXMsg>(query, uid);
|
||||
}
|
||||
else if (uid == "")
|
||||
{
|
||||
string query = "select * from MSG where StrContent like ?";
|
||||
wXMsgs = con.Query<WXMsg>(query, string.Format("%{0}%", msg));
|
||||
}
|
||||
else
|
||||
{
|
||||
string query = "select * from MSG where StrTalker=? and StrContent like ?";
|
||||
wXMsgs = con.Query<WXMsg>(query, uid, string.Format("%{0}%", msg));
|
||||
}
|
||||
|
||||
tmp.AddRange(ProcessMsg(wXMsgs, uid));
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
private List<WXMsg> ProcessMsg(List<WXMsg> msgs,string uid)
|
||||
{
|
||||
foreach (WXMsg w in msgs)
|
||||
{
|
||||
if (UserNameCache.ContainsKey(w.StrTalker))
|
||||
{
|
||||
WXContact? contact = UserNameCache[w.StrTalker] as WXContact;
|
||||
if (contact != null)
|
||||
{
|
||||
if (contact.Remark != "")
|
||||
w.NickName = contact.Remark;
|
||||
else
|
||||
w.NickName = contact.NickName;
|
||||
|
||||
w.StrTalker = contact.UserName;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
w.NickName = uid;
|
||||
}
|
||||
|
||||
// 群聊处理
|
||||
if (uid.Contains("@chatroom"))
|
||||
{
|
||||
string userId = "";
|
||||
|
||||
if (w.BytesExtra == null)
|
||||
continue;
|
||||
|
||||
string sl = BitConverter.ToString(w.BytesExtra).Replace("-", "");
|
||||
|
||||
ProtoMsg protoMsg;
|
||||
using (MemoryStream stream = new MemoryStream(w.BytesExtra))
|
||||
{
|
||||
protoMsg = ProtoBuf.Serializer.Deserialize<ProtoMsg>(stream);
|
||||
}
|
||||
|
||||
if (protoMsg.TVMsg != null)
|
||||
{
|
||||
foreach (TVType _tmp in protoMsg.TVMsg)
|
||||
{
|
||||
if (_tmp.Type == 1)
|
||||
userId = _tmp.TypeValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!w.IsSender)
|
||||
{
|
||||
if (UserNameCache.ContainsKey(userId))
|
||||
{
|
||||
WXContact? contact = UserNameCache[userId] as WXContact;
|
||||
if (contact != null)
|
||||
w.NickName = contact.Remark == "" ? contact.NickName : contact.Remark;
|
||||
}
|
||||
else
|
||||
{
|
||||
w.NickName = userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 发送人名字处理
|
||||
if (w.IsSender)
|
||||
w.NickName = "我";
|
||||
|
||||
w.DisplayContent = w.StrContent;
|
||||
// 额外格式处理
|
||||
if (w.Type != 1)
|
||||
{
|
||||
if (w.Type == 10000)
|
||||
{
|
||||
w.Type = 1;
|
||||
w.NickName = "系统消息";
|
||||
w.DisplayContent = w.StrContent.Replace("<revokemsg>", "").Replace("</revokemsg>", "");
|
||||
}
|
||||
else if (w.Type == 49 && (w.SubType == 6 || w.SubType == 19 || w.SubType == 40))
|
||||
{
|
||||
WXSessionAttachInfo? attachInfos = GetWXMsgAtc(w);
|
||||
if (attachInfos == null)
|
||||
{
|
||||
w.DisplayContent = "附件不存在";
|
||||
}
|
||||
else
|
||||
{
|
||||
w.DisplayContent = Path.Combine(UserBakConfig!.UserResPath, attachInfos.attachPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
w.DisplayContent = "[界面未支持格式]Type=" + w.Type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return msgs;
|
||||
}
|
||||
public List<WXSessionAttachInfo>? GetWXMsgAtc()
|
||||
{
|
||||
SQLiteConnection con = DBInfo["MultiSearchChatMsg"];
|
||||
SQLiteConnection? con = getCon("MultiSearchChatMsg");
|
||||
if (con == null)
|
||||
return null;
|
||||
|
||||
@@ -105,7 +494,7 @@ namespace WechatPCMsgBakTool
|
||||
}
|
||||
public WXSessionAttachInfo? GetWXMsgAtc(WXMsg msg)
|
||||
{
|
||||
SQLiteConnection con = DBInfo["MultiSearchChatMsg"];
|
||||
SQLiteConnection? con = getCon("MultiSearchChatMsg");
|
||||
if (con == null)
|
||||
return null;
|
||||
|
||||
@@ -134,17 +523,14 @@ namespace WechatPCMsgBakTool
|
||||
{
|
||||
for (int i = 0; i <= 99; i++)
|
||||
{
|
||||
if(DBInfo.ContainsKey("MediaMSG" + i.ToString()))
|
||||
{
|
||||
SQLiteConnection con = DBInfo["MediaMSG" + i.ToString()];
|
||||
if (con == null)
|
||||
continue;
|
||||
SQLiteConnection? con = getCon("MediaMSG" + i.ToString());
|
||||
if (con == null)
|
||||
continue;
|
||||
|
||||
string query = "select * from Media where Reserved0=?";
|
||||
List<WXMediaMsg> wXMsgs = con.Query<WXMediaMsg>(query, msg.MsgSvrID);
|
||||
if (wXMsgs.Count != 0)
|
||||
return wXMsgs[0];
|
||||
}
|
||||
string query = "select * from Media where Reserved0=?";
|
||||
List<WXMediaMsg> wXMsgs = con.Query<WXMediaMsg>(query, msg.MsgSvrID);
|
||||
if (wXMsgs.Count != 0)
|
||||
return wXMsgs[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -153,13 +539,14 @@ namespace WechatPCMsgBakTool
|
||||
if (UserBakConfig == null)
|
||||
return null;
|
||||
|
||||
string? tmpPath = Path.Combine(UserBakConfig.UserWorkspacePath, "Temp");
|
||||
string? tmpPath = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "Temp");
|
||||
if (!Directory.Exists(tmpPath))
|
||||
Directory.CreateDirectory(tmpPath);
|
||||
|
||||
// 这部分是查找
|
||||
// 如果是图片和视频,从附件库中搜索
|
||||
string? path = null;
|
||||
if (type == WXMsgType.Image || type == WXMsgType.Video)
|
||||
if (type == WXMsgType.Image || type == WXMsgType.Video || type == WXMsgType.File)
|
||||
{
|
||||
WXSessionAttachInfo? atcInfo = GetWXMsgAtc(msg);
|
||||
if (atcInfo == null)
|
||||
@@ -183,27 +570,69 @@ namespace WechatPCMsgBakTool
|
||||
}
|
||||
path = tmp_file_path;
|
||||
}
|
||||
else if (type == WXMsgType.Emoji)
|
||||
{
|
||||
try
|
||||
{
|
||||
XmlDocument xmlDocument = new XmlDocument();
|
||||
xmlDocument.LoadXml(msg.StrContent);
|
||||
XmlNode? node = xmlDocument.SelectSingleNode("/msg/emoji");
|
||||
if (node != null)
|
||||
{
|
||||
if (node.Attributes != null)
|
||||
{
|
||||
XmlNode? item = node.Attributes.GetNamedItem("md5");
|
||||
string md5 = item != null ? item.InnerText : "";
|
||||
if (EmojiCache.ContainsKey(md5))
|
||||
{
|
||||
path = string.Format("Emoji\\{0}.gif", md5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
return null;
|
||||
|
||||
|
||||
// 这部分是解密
|
||||
// 获取到原路径后,开始进行解密转移,只有图片和语音需要解密,解密后是直接归档目录
|
||||
if(type == WXMsgType.Image || type== WXMsgType.Audio)
|
||||
if (type == WXMsgType.Image || type == WXMsgType.Audio)
|
||||
{
|
||||
path = DecryptAttachment(type, path);
|
||||
path = DecryptAttachment(type, path, msg.StrTalker);
|
||||
}
|
||||
else if (type == WXMsgType.Video)
|
||||
else if (type == WXMsgType.Video || type == WXMsgType.File)
|
||||
{
|
||||
string video_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "Video");
|
||||
if(!Directory.Exists(video_dir))
|
||||
Directory.CreateDirectory(video_dir);
|
||||
string to_dir;
|
||||
if (type == WXMsgType.Video)
|
||||
to_dir = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "Video");
|
||||
else
|
||||
to_dir = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "File");
|
||||
if (!Directory.Exists(to_dir))
|
||||
Directory.CreateDirectory(to_dir);
|
||||
FileInfo fileInfo = new FileInfo(path);
|
||||
string video_file_path = Path.Combine(video_dir, fileInfo.Name);
|
||||
// 目标视频路径
|
||||
string to_file_path = Path.Combine(to_dir, fileInfo.Name);
|
||||
// 视频的路径是相对路径,需要加上资源目录
|
||||
path = Path.Combine(UserBakConfig.UserResPath, path);
|
||||
if(!File.Exists(video_file_path))
|
||||
File.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)
|
||||
@@ -214,7 +643,7 @@ namespace WechatPCMsgBakTool
|
||||
return path;
|
||||
|
||||
}
|
||||
public string? DecryptAttachment(WXMsgType type, string path)
|
||||
public string? DecryptAttachment(WXMsgType type, string path,string username)
|
||||
{
|
||||
if (UserBakConfig == null)
|
||||
return null;
|
||||
@@ -223,17 +652,21 @@ namespace WechatPCMsgBakTool
|
||||
switch (type)
|
||||
{
|
||||
case WXMsgType.Image:
|
||||
string img_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "Image");
|
||||
string img_dir = Path.Combine(UserBakConfig.UserWorkspacePath, username, "Image");
|
||||
if (!Directory.Exists(img_dir))
|
||||
Directory.CreateDirectory(img_dir);
|
||||
// 图片的路径是相对路径,需要加上资源目录
|
||||
path = Path.Combine(UserBakConfig.UserResPath, path);
|
||||
if (!File.Exists(path))
|
||||
return null;
|
||||
byte[] decFileByte = DecryptionHelper.DecImage(path);
|
||||
if (decFileByte.Length < 2)
|
||||
new Exception("解密失败,可能是未支持的格式");
|
||||
string decFiletype = DecryptionHelper.CheckFileType(decFileByte);
|
||||
file_path = DecryptionHelper.SaveDecImage(decFileByte, path, img_dir, decFiletype);
|
||||
break;
|
||||
case WXMsgType.Audio:
|
||||
string audio_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "Audio");
|
||||
string audio_dir = Path.Combine(UserBakConfig.UserWorkspacePath, username, "Audio");
|
||||
if (!Directory.Exists(audio_dir))
|
||||
Directory.CreateDirectory(audio_dir);
|
||||
FileInfo fileInfo = new FileInfo(path);
|
||||
@@ -249,23 +682,21 @@ namespace WechatPCMsgBakTool
|
||||
List<WXMsgGroup> g = new List<WXMsgGroup>();
|
||||
for (int i = 0; i <= 99; i++)
|
||||
{
|
||||
if (DBInfo.ContainsKey("MSG" + i.ToString()))
|
||||
{
|
||||
SQLiteConnection con = DBInfo["MSG" + i.ToString()];
|
||||
if (con == null)
|
||||
return g;
|
||||
SQLiteConnection? con = getCon("MSG" + i.ToString());
|
||||
if (con == null)
|
||||
return g;
|
||||
|
||||
string query = "select StrTalker,Count(localId) as MsgCount from MSG GROUP BY StrTalker";
|
||||
List<WXMsgGroup> wXMsgs = con.Query<WXMsgGroup>(query);
|
||||
foreach (WXMsgGroup w in wXMsgs)
|
||||
{
|
||||
WXMsgGroup? tmp = g.Find(x => x.UserName == w.UserName);
|
||||
if (tmp == null)
|
||||
g.Add(w);
|
||||
else
|
||||
tmp.MsgCount += g.Count;
|
||||
}
|
||||
string query = "select StrTalker,Count(localId) as MsgCount from MSG GROUP BY StrTalker";
|
||||
List<WXMsgGroup> wXMsgs = con.Query<WXMsgGroup>(query);
|
||||
foreach (WXMsgGroup w in wXMsgs)
|
||||
{
|
||||
WXMsgGroup? tmp = g.Find(x => x.UserName == w.UserName);
|
||||
if (tmp == null)
|
||||
g.Add(w);
|
||||
else
|
||||
tmp.MsgCount += g.Count;
|
||||
}
|
||||
|
||||
}
|
||||
return g;
|
||||
}
|
||||
@@ -277,5 +708,6 @@ namespace WechatPCMsgBakTool
|
||||
Video = 1,
|
||||
Audio = 2,
|
||||
File = 3,
|
||||
Emoji = 4,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,12 +94,16 @@ namespace WechatPCMsgBakTool
|
||||
FileInfo fileInfo = new FileInfo(file);
|
||||
if (fileInfo.Extension == ".db")
|
||||
{
|
||||
viewModel.LabelStatus = "正在迁移" + fileInfo.Name;
|
||||
string to_path = Path.Combine(UserBakConfig.UserWorkspacePath, "OriginalDB", fileInfo.Name);
|
||||
File.Copy(file, to_path, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public UserBakConfig ReturnConfig()
|
||||
{
|
||||
return UserBakConfig;
|
||||
}
|
||||
public static void SaveConfig(UserBakConfig userBakConfig)
|
||||
{
|
||||
if(userBakConfig.UserWorkspacePath != "")
|
||||
@@ -64,7 +117,7 @@ namespace WechatPCMsgBakTool
|
||||
}
|
||||
}
|
||||
}
|
||||
private string Init(string path,string account = "")
|
||||
private string Init(string path,bool manual,string account = "")
|
||||
{
|
||||
string curPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||
string md5 = GetMd5Hash(path);
|
||||
|
||||
83
WechatBakTool.csproj
Normal file
83
WechatBakTool.csproj
Normal file
@@ -0,0 +1,83 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<AssemblyVersion>0.9.6.4</AssemblyVersion>
|
||||
<FileVersion>0.9.6.4</FileVersion>
|
||||
<Version>0.9.6.4</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="YearReport\**" />
|
||||
<EmbeddedResource Remove="YearReport\**" />
|
||||
<None Remove="YearReport\**" />
|
||||
<Page Remove="YearReport\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
<PackageReference Include="jieba.NET" Version="0.42.2" />
|
||||
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="protobuf-net" Version="3.2.30" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
|
||||
<PackageReference Include="WordCloudSharp" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="libcrypto-1_1.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="libssl-1_1.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="mask.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\char_state_tab.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\cn_synonym.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\dict.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\idf.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\pos_prob_emit.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\pos_prob_start.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\pos_prob_trans.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\prob_emit.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\prob_trans.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\stopwords.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Tools\ffmpeg.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Tools\silk_v3_decoder.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="version.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -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
|
||||
@@ -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.5.0.1</AssemblyVersion>
|
||||
<FileVersion>0.5.0.1</FileVersion>
|
||||
<Version>0.5.0.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
<PackageReference Include="System.Management" Version="7.0.2" />
|
||||
</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
25
WordCloudSetting.xaml
Normal 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
48
WordCloudSetting.xaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,5 +11,8 @@
|
||||
},{
|
||||
"Version":"3.9.8.15",
|
||||
"BaseAddr": 64997904
|
||||
},{
|
||||
"Version":"3.9.8.25",
|
||||
"BaseAddr": 65002192
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user