41 Commits

Author SHA1 Message Date
Suxue
3da9a923b4 1.优化批量导出时的体验。现在起记录为空的用户将不会被导出。
2.修复批量导出的部分问题。
2024-01-10 23:20:15 +08:00
Suxue
f07d93c461 修复超大文件解密问题 2024-01-09 17:17:54 +08:00
Suxue
6653bcd035 update 2024-01-09 16:16:46 +08:00
Suxue
85f77c468e log 2024-01-09 16:08:43 +08:00
Suxue
f16e0b020b 1.解密内存优化
2.记录分页支持
3.模版选择初步支持
2024-01-09 15:01:36 +08:00
Suxue
00b6596237 update 2024-01-06 11:22:31 +08:00
Suxue
3dfb81b990 新增部分消息类型容错
新增已删除联系人记录导出
2024-01-06 11:20:50 +08:00
Suxue
f3d73d2507 修复bug
dev分支预下载暂时不可用,拿来调试功能了
2023-12-25 23:41:18 +08:00
Suxue
b276815960 新增引用消息支持
新增词云分词异常时,错误提示
2023-12-23 00:05:14 +08:00
Suxue
c507e68080 合并0.9.6.2 2023-12-21 15:32:23 +08:00
Suxue
932b2d099f v0.9.6.2 Release!
1.初步支持转发消息导出。
2.新增HTML导出全局异常处理,新增LOG方便直接定位
2023-12-19 23:17:39 +08:00
Suxue
49c39b13cf v0.9.6.1 Release!
1.支持词云蒙版,现在可以自定义词云形状啦!
2.导出附件按用户id归集。
3.修复图片不存在导致导出线程退出的问题
2023-12-18 22:41:10 +08:00
Suxue
cc63fe94a6 词云蒙版支持 2023-12-16 12:26:13 +08:00
Suxue
ddfec75201 导出附件按用户id归集 2023-12-16 09:15:23 +08:00
Suxue
e174f6d00a 合并 2023-12-16 08:37:02 +08:00
Suxue
6234ad3084 v0.9.6.0 Release!
1.新增词云功能,导出时选择与他的词云即可。
2.修复公钥头推断失败BUG
3.修复创建工作区与已有工作区切换不成功BUG
2023-12-15 22:48:40 +08:00
Suxue
ce393253a8 新增词云
修复创建工作区与已有工作区之间切换的bug
导出文件名优化
2023-12-15 18:46:15 +08:00
Suxue
cf7d75625d 新增公钥头推断查找 2023-12-14 23:11:55 +08:00
Suxue
cca9e0626d 合并 2023-12-14 21:54:44 +08:00
Suxue
f02a1164cf 1.修复导出html时,emoji xml导致线程退出的问题
2.修复图片导出问题
2023-12-14 21:50:07 +08:00
Suxue
a49f9ec9de v0.9.3.0
1.修复一处内存泄露问题。
2.修复部分用户在创建工作区时遇到“未找到适用于完成此操作的图像处理组件。”的问题
3.支持Type=10000的系统消息。
4.新增表情预下载功能,为后期表情支持做准备。
2023-12-12 22:50:30 +08:00
Suxue
eaacb554ac Merge branch 'master' into dev 2023-12-11 21:55:07 +08:00
Suxue
eb4d4fa3fa 新增批量导出 2023-12-11 16:18:42 +08:00
Suxue
9bf5d1c7d3 1.异步创建工作区,同时创建工作区支持状态显示了
2.异步导出聊天记录,html导出平滑优化
3.支持拖拽窗体
2023-12-11 13:50:27 +08:00
Suxue
07f58afd98 fix style 2023-12-07 15:56:49 +08:00
Suxue
375f17fe49 优化导出按钮
新增txt导出
2023-12-07 14:56:52 +08:00
Suxue
985505b2e7 导出改造
新增txt导出
2023-12-06 23:06:54 +08:00
Suxue
9ad5f6b311 Merge branch 'master' into dev 2023-12-06 22:07:42 +08:00
Suxue
6c7614dcac merge master 2023-12-06 21:58:17 +08:00
Suxue
3957f2462e 更新url 2023-12-06 20:37:19 +08:00
Suxue
74ad9297c2 清理旧版本文件和部分方法
修复联系人列表没有显示备注的问题
2023-12-06 19:50:58 +08:00
Suxue
ffbc855a0f 新增用户数和消息数统计
群聊导出支持用户名
2023-12-06 19:32:55 +08:00
Suxue
50c3b22c74 迁移解密至WXWorkspace
解密方式选择切换MVVM
完善新界面功能
2023-12-06 18:22:22 +08:00
Suxue
d1a0f3897f update readme 2023-12-06 15:34:20 +08:00
Suxue
4b5554cbe1 update readme 2023-12-06 15:27:57 +08:00
Suxue
585e6683cd 项目更名 2023-12-06 14:13:14 +08:00
Suxue
e83d3869cc 调整部分局部变量可见性
完善新版工作区创建
群聊数据改用标准方式获取
2023-12-06 12:05:34 +08:00
Suxue
191849e9c2 1.用户头像切换到本地缓存
2.修复多工作区切换,页面不变的问题
3.WXContact切换到ObservableCollection
4.群聊名称支持
2023-12-05 14:50:32 +08:00
Suxue
d4aa12cf07 WindowsAPI调用重构,改用SYSTEM_EXTENDED_HANDLE_INFORMATION解决 2023-12-04 15:46:55 +08:00
Suxue
85b0b1db52 features:
1.workspace page 引入mvvm
2.wxcontact修改为联合模型
2023-11-30 17:24:13 +08:00
Suxue
8305a58c39 dev init 2023-11-30 09:03:49 +08:00
35 changed files with 848921 additions and 295 deletions

View File

@@ -11,7 +11,7 @@ namespace WechatBakTool.Export
public interface IExport
{
void InitTemplate(WXContact session,string path);
void SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel);
bool SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel);
void SetEnd();
void Save(string path = "");
}

View File

@@ -11,6 +11,8 @@ using System.Xml;
using Newtonsoft.Json;
using WechatBakTool.ViewModel;
using System.Security.Policy;
using System.Windows;
using System.Xml.Linq;
namespace WechatBakTool.Export
{
@@ -26,7 +28,6 @@ namespace WechatBakTool.Export
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"));
File.WriteAllText(Path, HtmlBody);
}
public void InitTemplate(WXContact contact, string p)
@@ -49,7 +50,7 @@ namespace WechatBakTool.Export
File.AppendAllText(Path, HtmlBody);
}
public void SetMsg(WXUserReader reader, WXContact contact,WorkspaceViewModel viewModel)
public bool SetMsg(WXUserReader reader, WXContact contact,WorkspaceViewModel viewModel)
{
if (Session == null)
throw new Exception("请初始化模版Not Use InitTemplate");
@@ -58,136 +59,270 @@ namespace WechatBakTool.Export
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;
HtmlBody = "";
StreamWriter streamWriter = new StreamWriter(Path, true);
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 ? "我" : msg.NickName, TimeStampToDateTime(msg.CreateTime).ToString("yyyy-MM-dd HH:mm:ss"));
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 (msg.Type == 1)
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", msg.StrContent);
else if (msg.Type == 3)
{
#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 == 19||msg.SubType == 40)
{
string? path = reader.GetAttachment(WXMsgType.File, msg);
if(path == null)
string? path = reader.GetAttachment(WXMsgType.Image, msg);
if (path == null)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "文件不存在");
#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
{
HtmlBody += string.Format("<p class=\"content\">{0}</p><p><a href=\"{1}\">点击访问</a></p></div>", "文件:" + path, path);
}
}
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))
using (var decoder = LZ4Decoder.Create(true, 64))
{
xml = xml.Replace("\n", "");
XmlDocument xmlObj = new XmlDocument();
xmlObj.LoadXml(xml);
if (xmlObj.DocumentElement != null)
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))
{
string title = "";
string appName = "";
string url = "";
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
if (findNode != null)
xml = xml.Replace("\n", "");
XmlDocument xmlObj = new XmlDocument();
xmlObj.LoadXml(xml);
if (xmlObj.DocumentElement != null)
{
if (findNode.Count > 0)
string title = "";
string appName = "";
string url = "";
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
if (findNode != null)
{
title = findNode[0]!.InnerText;
if (findNode.Count > 0)
{
title = findNode[0]!.InnerText;
}
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/sourcedisplayname");
if (findNode != null)
{
if (findNode.Count > 0)
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/sourcedisplayname");
if (findNode != null)
{
appName = findNode[0]!.InnerText;
if (findNode.Count > 0)
{
appName = findNode[0]!.InnerText;
}
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/url");
if (findNode != null)
{
if (findNode.Count > 0)
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/url");
if (findNode != null)
{
url = findNode[0]!.InnerText;
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);
}
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)
else if (msg.Type == 34)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "语音不存在");
continue;
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>", "暂未支持的消息");
}
HtmlBody += string.Format("<p class=\"content\"><audio controls src=\"{0}\"></audio></p></div>", path);
}
else
catch(Exception ex)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "暂未支持的消息");
err = true;
File.AppendAllText("Err.log", JsonConvert.SerializeObject(msg));
File.AppendAllText("Err.log", ex.ToString());
}
msgCount++;
if(msgCount % 50 == 0)
{
@@ -202,10 +337,14 @@ namespace WechatBakTool.Export
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)
{

View File

@@ -40,7 +40,7 @@ namespace WechatBakTool.Export
}
public void SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel)
public bool SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel)
{
if (Contact == null)
throw new Exception("请初始化模版Not Use InitTemplate");
@@ -146,6 +146,7 @@ namespace WechatBakTool.Export
msgCount++;
viewModel.ExportCount = msgCount.ToString();
}
return true;
}
private static DateTime TimeStampToDateTime(long timeStamp, bool inMilli = false)

View File

@@ -19,12 +19,12 @@ 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(string pid, int find_key_type, string account)
{
@@ -102,7 +102,7 @@ namespace WechatBakTool.Helpers
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 - 8; i++)
for (int i = 0; i < buffer.Length - 1; i++)
{
if (buffer[i] == search[0])
{
@@ -142,35 +142,124 @@ namespace WechatBakTool.Helpers
}
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];
@@ -178,8 +267,7 @@ namespace WechatBakTool.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))
@@ -208,8 +296,9 @@ namespace WechatBakTool.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)
{
@@ -229,7 +318,7 @@ namespace WechatBakTool.Helpers
return BitConverter.ToString(bytes, 0).Replace("-", string.Empty).ToLower().ToUpper();
}
private static List<byte[]> ImgHeader = new List<byte[]>()
private readonly static List<byte[]> ImgHeader = new List<byte[]>()
{
new byte[] { 0xFF, 0xD8 },//JPG
new byte[] { 0x89, 0x50 },//PNG
@@ -297,16 +386,8 @@ namespace WechatBakTool.Helpers
{
FileInfo info = new FileInfo(file);
viewModel.LabelStatus = "正在解密" + info.Name;
var db_bytes = File.ReadAllBytes(file);
var decrypted_file_bytes = 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);
}
string to_file = Path.Combine(decPath, info.Name);
DecryptDB(file,to_file, key);
}
}
}

View File

@@ -15,7 +15,10 @@ namespace WechatBakTool.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;

View File

@@ -93,6 +93,7 @@ namespace WechatBakTool
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));
}

View File

@@ -114,6 +114,8 @@ namespace WechatBakTool.Model
{
[Column("localId")]
public int LocalId { get; set; }
[Column("MsgSequence")]
public int MsgSequence { get; set; }
[Column("Type")]
public int Type { get; set; }
[Column("SubType")]
@@ -158,6 +160,15 @@ namespace WechatBakTool.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
{

View File

@@ -39,7 +39,7 @@
</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 Margin="215,405,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>

View File

@@ -97,6 +97,7 @@ namespace WechatBakTool.Pages
private void btn_create_worksapce_Click(object sender, RoutedEventArgs e)
{
ViewModel.IsEnable = false;
Task.Run(() => {
if (ViewModel.KeyType != -1)
{
@@ -147,5 +148,31 @@ namespace WechatBakTool.Pages
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;
}
}
}
}

View File

@@ -6,6 +6,7 @@ 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;
@@ -30,6 +31,8 @@ namespace WechatBakTool.Pages
{
private WorkspaceViewModel workspaceViewModel = new WorkspaceViewModel();
public WXUserReader? UserReader;
private List<WXContact>? ExpContacts;
private bool Suspend = false;
public Manager()
{
DataContext = workspaceViewModel;
@@ -61,15 +64,25 @@ namespace WechatBakTool.Pages
});
if (UserReader != null)
{
List<WXContact>? contacts = UserReader.GetWXContacts().ToList();
foreach (var contact in contacts)
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)
if (user && !contact.UserName.Contains("@chatroom") && !contact.UserName.Contains("gh_"))
{
workspaceViewModel.WXContact = contact;
ExportMsg(contact);
@@ -83,12 +96,27 @@ namespace WechatBakTool.Pages
private void ExportMsg(WXContact contact)
{
workspaceViewModel.ExportCount = "";
string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, contact.UserName + ".html");
// 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);
export.SetMsg(UserReader!, contact, workspaceViewModel);
export.SetEnd();
export.Save(path);
if(export.SetMsg(UserReader!, contact, workspaceViewModel))
{
export.SetEnd();
export.Save(path);
}
}
private void btn_emoji_download_Click(object sender, RoutedEventArgs e)

View File

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

View File

@@ -3,11 +3,12 @@
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"
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>
@@ -164,9 +165,8 @@
</DataTemplate>
<DataTemplate x:Key="MsgImage">
<Grid Margin="0">
<Image Width="40" Height="40" Margin="10" VerticalAlignment="Top" HorizontalAlignment="Left" Source="{Binding Avatar}" />
<Label Margin="60,8,0,0" FontWeight="Bold" VerticalAlignment="Top" HorizontalAlignment="Left" Content="{Binding NickName}" Width="130"/>
<Label Margin="60,25,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="140" Content="{Binding LastMsg}"/>
<Label Margin="60,25,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="140" Content="1111"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="MsgAudio">
@@ -214,7 +214,7 @@
</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" ItemTemplate="{DynamicResource MsgText}">
<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" />

View File

@@ -1,4 +1,6 @@
using System;
using JiebaNet.Segmenter;
using JiebaNet.Segmenter.Common;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
@@ -8,7 +10,6 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
@@ -20,6 +21,14 @@ 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
{
@@ -30,11 +39,15 @@ namespace WechatBakTool.Pages
{
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();
@@ -48,7 +61,11 @@ namespace WechatBakTool.Pages
UserReader = new WXUserReader(config);
if (config.Decrypt)
{
ViewModel.Contacts = UserReader.GetWXContacts();
ViewModel.Contacts = null;
Task.Run(() => {
ViewModel.Contacts = UserReader.GetWXContacts();
});
}
}
}
@@ -64,14 +81,43 @@ namespace WechatBakTool.Pages
private void list_users_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ViewModel.WXMsgs.Clear();
Postion = 0;
ViewModel.ExportCount = "";
ViewModel.WXContact = list_users.SelectedItem as WXContact;
if(ViewModel.WXContact == null || UserReader == null)
{
loadMsg();
if (ViewModel.WXMsgs.Count == 0)
return;
}
private void loadMsg()
{
Loading = true;
ViewModel.WXContact = list_users.SelectedItem as WXContact;
if (ViewModel.WXContact == null || UserReader == null)
return;
List<WXMsg>? list = UserReader.GetWXMsgs(ViewModel.WXContact.UserName, Postion, PageSize);
// Trace.WriteLine(string.Format("{0}->{1}", PageSize, Postion));
if (list == null)
return;
if (list.Count == 0)
return;
foreach (WXMsg w in list)
{
ViewModel.WXMsgs.Add(w);
}
List<WXMsg>? msgs = UserReader.GetWXMsgs(ViewModel.WXContact.UserName);
list_msg.ItemsSource = msgs;
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)
@@ -83,7 +129,25 @@ namespace WechatBakTool.Pages
if (txt_find_user.Text == "搜索...")
findName = "";
ViewModel.Contacts = UserReader.GetWXContacts(findName);
Task.Run(() =>
{
ViewModel.Contacts = UserReader.GetWXContacts(findName);
// 保底回落搜索已删除人员
if (ViewModel.Contacts.Count == 0)
{
var i = UserReader.GetWXMsgs(txt_find_user.Text);
if (i != null)
{
var g = i.GroupBy(x => x.StrTalker);
ViewModel.Contacts = new System.Collections.ObjectModel.ObservableCollection<WXContact>();
foreach (var x in g)
{
string name = x.Key;
ViewModel.Contacts.Add(new WXContact() { UserName = name, NickName = name });
}
}
}
});
}
private void txt_find_user_GotFocus(object sender, RoutedEventArgs e)
@@ -109,9 +173,9 @@ namespace WechatBakTool.Pages
export.SetMsg(UserReader, ViewModel.WXContact, ViewModel);
export.SetEnd();
export.Save(path);
}catch(Exception ex)
}
catch (Exception ex)
{
File.AppendAllText("1.log", ex.Message);
MessageBox.Show(ex.Message);
}
@@ -137,7 +201,95 @@ namespace WechatBakTool.Pages
MessageBox.Show("请选择导出方式", "错误");
return;
}
string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, ViewModel.WXContact.UserName);
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
@@ -177,5 +329,17 @@ namespace WechatBakTool.Pages
});
}
}
private void list_msg_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (ViewModel.WXMsgs.Count == 0)
return;
if (e.VerticalOffset + e.ViewportHeight == e.ExtentHeight && !Loading)
{
// 滚动条到达底部的处理逻辑
loadMsg();
}
}
}
}

View File

@@ -14,6 +14,7 @@
- [x] 群聊
- [x] 系统消息
- [x] 文件
- [x] 引用/转发消息
- [x] 表情(需要预下载)
如果有什么好的建议或意见或者遇到什么问题欢迎提issue看到会回。
@@ -21,13 +22,15 @@
> [!NOTE]
> 反馈群815054692<br/>
> 如果觉得不错欢迎右上角点个star这是对作者的鼓励谢谢<br/>
> 进群请先Star项目然后问答消息留id<br/>
<br/>
### 免责声明
**本项目仅供学习使用,严禁商业使用**<br/>
**本项目仅供学习、研究使用严禁商业使用**<br/>
**用于网络安全用途的,请确保在国家法律法规下使用**<br/>
**本项目完全免费,问你要钱的都是骗子**<br/>
**使用本项目初衷是作者研究微信数据库的运行使用,您使用本软件导致的后果,包含但不限于数据损坏,记录丢失等问题,作者不承担相关责任。**<br/>
**因软件特殊性质,请在使用时获得微信账号所有人授权**<br/>
**因软件特殊性质,请在使用时获得微信账号所有人授权,你当确保不侵犯他人个人隐私权,后果自行承担**<br/>
<br/>
### 隐私声明
@@ -40,10 +43,11 @@
C# + .NET6.0 + WPF <br/>
- [x] ~~新版本UI界面开发~~
- [x] 完善各类消息支持(已经初步完成)
- [x] ~~词云~~
- [ ] 性能优化
- [ ] 词云
- [ ] 打包资源文件夹
- [ ] 手动模式(合适离线分析)
- [ ] 年度报告类分析(等美术资源中,没有资源不做)
<br/>
### 部分问题
@@ -53,10 +57,12 @@ A<b>在手机端</b>使用迁移功能即可,路径:我->设置->聊天->
Q怎么导出全部的记录<br/>
A工作区->右键->管理,就见了。<br/>
<br/>
Q解密工作区提示no such teble:MSG怎么办<br/>
A基本上都是因为刚迁移完缓存没写入到数据库导致的建议迁移完重启一次微信后再创建工作区<br/>
<br/>
### 使用说明
**本说明为新版本说明,即将发版**<br/>
0.安装.NET Desktop Runtime(如已经安装忽略)<br/>
0.安装.NET Desktop Runtime(注意是6.0版本的Desktop Runtime如已经安装忽略)<br/>
1.打开微信,并登录。<br/>
2.在软件左侧下方点击**新建工作区**<br/>
3.在**新建工作区界面**,选择要创建工作区的微信进程,并**确认下方微信号是否正确**<br/>
@@ -68,12 +74,13 @@ A工作区->右键->管理,就见了。<br/>
### 参考/引用
项目在开发过程中参考了以下项目或资料,有引用相关代码,如有需要,推荐您可以去参考下相关资料:
1. C#使用OpenSSL解密微信数据库这里注意一下64位适配问题注意dll引用 [Mr0x01/WXDBDecrypt.NET](https://github.com/Mr0x01/WXDBDecrypt.NET)<br/>
1. C#使用OpenSSL解密微信数据库这里注意一下64位适配问题注意dll引用另外解密的资源优化不是很好可以参考一下我改写的C#还需要注意一下超大文件的问题 [Mr0x01/WXDBDecrypt.NET](https://github.com/Mr0x01/WXDBDecrypt.NET)<br/>
2. C#使用地址获取微信Key [AdminTest0/SharpWxDump](https://github.com/AdminTest0/SharpWxDump)
3. 解密微信语音我是直接调用解密反正都要ffmpeg多一个也是多多两个也是多懒得头铁实现 [kn007/silk-v3-decoder](https://github.com/kn007/silk-v3-decoder)
4. 解密微信图片 [吾爱破解chenhahacjl/微信 DAT 图片解密 C#](https://www.52pojie.cn/forum.php?mod=viewthread&tid=1507922)
5. 参考了句柄名称实现,注意获取句柄别看这里,#10 这个issue就是血泪 [huiyadanli/RevokeMsgPatcher](https://github.com/huiyadanli/RevokeMsgPatcher)
6. 参考了句柄获取 [FuzzySecurity/Sharp-Suite](https://github.com/FuzzySecurity/Sharp-Suite)
7. 这个获取秘钥更通用一些 [SnowMeteors/GetWeChatKey](https://github.com/SnowMeteors/GetWeChatKey) ,用户名不是很稳定
### 其他声明
[1] 理论支持所有64位版本指用户名推断和公钥头推断地址直接获取方式需要version.json支持更新不是很及时。

79460
Resources/char_state_tab.json Normal file

File diff suppressed because it is too large Load Diff

17817
Resources/cn_synonym.txt Normal file

File diff suppressed because one or more lines are too long

349046
Resources/dict.txt Normal file

File diff suppressed because it is too large Load Diff

270132
Resources/idf.txt Normal file

File diff suppressed because it is too large Load Diff

89711
Resources/pos_prob_emit.json Normal file

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

35234
Resources/prob_emit.json Normal file

File diff suppressed because it is too large Load Diff

18
Resources/prob_trans.json Normal file
View File

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

653
Resources/stopwords.txt Normal file
View File

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

View File

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

View File

@@ -16,6 +16,9 @@ namespace WechatBakTool.ViewModel
[NotifyPropertyChangedFor(nameof(LabelStatus))]
private WXContact? wXContact = null;
[ObservableProperty]
private ObservableCollection<WXMsg> wXMsgs = new ObservableCollection<WXMsg>();
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(LabelStatus))]
private string exportCount = "";

View File

@@ -162,7 +162,7 @@ namespace WechatBakTool
}
else
{
string path = Path.Combine(UserBakConfig.UserWorkspacePath, "Emoji", md5 + ".gif");
string path = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "Emoji", md5 + ".gif");
try
{
HttpResponseMessage res = httpClient.GetAsync(url).Result;
@@ -330,6 +330,25 @@ namespace WechatBakTool
}
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>();
@@ -356,98 +375,107 @@ namespace WechatBakTool
wXMsgs = con.Query<WXMsg>(query, uid, string.Format("%{0}%", msg));
}
foreach (WXMsg w in wXMsgs)
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))
{
if (UserNameCache.ContainsKey(w.StrTalker))
WXContact? contact = UserNameCache[w.StrTalker] as WXContact;
if (contact != null)
{
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 (contact.Remark != "")
w.NickName = contact.Remark;
else
w.NickName = contact.NickName;
if (_tmp.Type == 1)
userId = _tmp.TypeValue;
}
}
// 群聊处理
if (uid.Contains("@chatroom"))
if (!w.IsSender)
{
string userId = "";
if (w.BytesExtra == null)
continue;
string sl = BitConverter.ToString(w.BytesExtra).Replace("-", "");
ProtoMsg protoMsg;
using (MemoryStream stream = new MemoryStream(w.BytesExtra))
if (UserNameCache.ContainsKey(userId))
{
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);
}
WXContact? contact = UserNameCache[userId] as WXContact;
if (contact != null)
w.NickName = contact.Remark == "" ? contact.NickName : contact.Remark;
}
else
{
w.DisplayContent = "[界面未支持格式]Type=" + w.Type;
w.NickName = userId;
}
}
tmp.Add(w);
}
// 发送人名字处理
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 tmp;
return msgs;
}
public List<WXSessionAttachInfo>? GetWXMsgAtc()
{
@@ -511,7 +539,7 @@ namespace WechatBakTool
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);
@@ -576,15 +604,15 @@ namespace WechatBakTool
// 获取到原路径后,开始进行解密转移,只有图片和语音需要解密,解密后是直接归档目录
if (type == WXMsgType.Image || type == WXMsgType.Audio)
{
path = DecryptAttachment(type, path);
path = DecryptAttachment(type, path, msg.StrTalker);
}
else if (type == WXMsgType.Video || type == WXMsgType.File)
{
string to_dir;
if (type == WXMsgType.Video)
to_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "Video");
to_dir = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "Video");
else
to_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "File");
to_dir = Path.Combine(UserBakConfig.UserWorkspacePath, msg.StrTalker, "File");
if (!Directory.Exists(to_dir))
Directory.CreateDirectory(to_dir);
FileInfo fileInfo = new FileInfo(path);
@@ -615,7 +643,7 @@ namespace WechatBakTool
return path;
}
public string? DecryptAttachment(WXMsgType type, string path)
public string? DecryptAttachment(WXMsgType type, string path,string username)
{
if (UserBakConfig == null)
return null;
@@ -624,11 +652,13 @@ namespace WechatBakTool
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("解密失败,可能是未支持的格式");
@@ -636,7 +666,7 @@ namespace WechatBakTool
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);

View File

@@ -18,7 +18,7 @@ namespace WechatBakTool
{
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);
}
@@ -28,7 +28,7 @@ namespace WechatBakTool
UserBakConfig = userBakConfig;
}
public void DecryptDB(string pid,int type,CreateWorkViewModel viewModel)
public void DecryptDB(string pid,int type,CreateWorkViewModel viewModel,string pwd = "")
{
if (UserBakConfig == null)
{
@@ -39,7 +39,20 @@ namespace WechatBakTool
{
byte[]? key = null;
viewModel.LabelStatus = "正在获取秘钥需要1 - 10秒左右";
key = DecryptionHelper.GetWechatKey(pid, type, UserBakConfig.Account);
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("获取到的密钥为空,获取失败");
@@ -104,7 +117,7 @@ namespace WechatBakTool
}
}
}
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);

View File

@@ -6,17 +6,27 @@
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyVersion>0.9.5.0</AssemblyVersion>
<FileVersion>0.9.5.0</FileVersion>
<Version>0.9.5.0</Version>
<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>
@@ -26,6 +36,39 @@
<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>
@@ -37,8 +80,4 @@
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="YearReport\" />
</ItemGroup>
</Project>

25
WordCloudSetting.xaml Normal file
View File

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

48
WordCloudSetting.xaml.cs Normal file
View File

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

View File

@@ -1,11 +0,0 @@
<Window x:Class="WechatBakTool.YearReport"
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="YearReport" Height="660" Width="330" WindowStyle="None" WindowStartupLocation="CenterScreen" >
<Grid>
</Grid>
</Window>

View File

@@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace WechatBakTool
{
/// <summary>
/// YearReport.xaml 的交互逻辑
/// </summary>
public partial class YearReport : Window
{
public YearReport()
{
InitializeComponent();
}
}
}

BIN
mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

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