Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c66a539d48 | ||
|
|
51da1fd0ae | ||
|
|
4213395c9f | ||
|
|
b43a517322 | ||
|
|
5637af45f3 | ||
|
|
5cb9cd922b | ||
|
|
dca42a7849 | ||
|
|
76def416e6 | ||
|
|
affafe324f | ||
|
|
ef3c84a724 | ||
|
|
0f70591cdb | ||
|
|
029403e8ae |
25
Dialog/MsgDatetimePicker.xaml
Normal file
25
Dialog/MsgDatetimePicker.xaml
Normal file
@@ -0,0 +1,25 @@
|
||||
<Window x:Class="WechatBakTool.Dialog.MsgDatetimePicker"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:WechatBakTool.Dialog"
|
||||
xmlns:local2="clr-namespace:WechatBakTool.ViewModel"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d"
|
||||
Title="选择导出日期" Height="350" Width="330">
|
||||
<Window.Resources>
|
||||
<local2:DateTypeConverter x:Key="dateTypeConverter" />
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<RadioButton GroupName="Date" Content="全部" HorizontalAlignment="Left" Margin="45,60,0,0" VerticalAlignment="Top" IsChecked="{Binding DateType, Converter={StaticResource ResourceKey=dateTypeConverter}, ConverterParameter=1}"/>
|
||||
<RadioButton GroupName="Date" Content="昨天" HorizontalAlignment="Left" Margin="45,90,0,0" VerticalAlignment="Top" IsChecked="{Binding DateType, Converter={StaticResource ResourceKey=dateTypeConverter}, ConverterParameter=2}"/>
|
||||
<RadioButton GroupName="Date" Content="指定日期" HorizontalAlignment="Left" Margin="45,120,0,0" VerticalAlignment="Top" IsChecked="{Binding DateType, Converter={StaticResource ResourceKey=dateTypeConverter}, ConverterParameter=3}"/>
|
||||
<DatePicker HorizontalAlignment="Left" Margin="45,140,0,0" VerticalAlignment="Top" SelectedDate="{Binding PickDate}"/>
|
||||
<RadioButton GroupName="Date" Content="指定范围日期" HorizontalAlignment="Left" Margin="45,180,0,0" VerticalAlignment="Top" IsChecked="{Binding DateType, Converter={StaticResource ResourceKey=dateTypeConverter}, ConverterParameter=4}"/>
|
||||
<DatePicker Margin="45,200,0,0" VerticalAlignment="Top" SelectedDate="{Binding StartDate}" HorizontalAlignment="Left" Width="100"/>
|
||||
<DatePicker Margin="185,200,0,0" VerticalAlignment="Top" SelectedDate="{Binding EndDate}" HorizontalAlignment="Left" Width="100"/>
|
||||
<Label Content="至" Margin="0,200,0,0" VerticalAlignment="Top" HorizontalAlignment="Center" Width="22"/>
|
||||
<Button Content="提交" Margin="100,265,0,0" VerticalAlignment="Top" Height="40" Click="Button_Click" HorizontalAlignment="Left" Width="140"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
36
Dialog/MsgDatetimePicker.xaml.cs
Normal file
36
Dialog/MsgDatetimePicker.xaml.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using WechatBakTool.ViewModel;
|
||||
|
||||
namespace WechatBakTool.Dialog
|
||||
{
|
||||
/// <summary>
|
||||
/// MsgDatetimePicker.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class MsgDatetimePicker : Window
|
||||
{
|
||||
|
||||
public MsgDatetimePicker(DatetimePickerViewModel viewModel)
|
||||
{
|
||||
DataContext = viewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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, DatetimePickerViewModel dateModel);
|
||||
void SetEnd();
|
||||
void Save(string path = "");
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ using Newtonsoft.Json;
|
||||
using WechatBakTool.ViewModel;
|
||||
using System.Security.Policy;
|
||||
using System.Windows;
|
||||
using System.Xml.Linq;
|
||||
using WechatBakTool.Helpers;
|
||||
|
||||
namespace WechatBakTool.Export
|
||||
{
|
||||
@@ -27,7 +29,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)
|
||||
@@ -50,20 +51,25 @@ 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, DatetimePickerViewModel dateModel)
|
||||
{
|
||||
if (Session == null)
|
||||
throw new Exception("请初始化模版:Not Use InitTemplate");
|
||||
|
||||
List<WXMsg>? msgList = reader.GetWXMsgs(contact.UserName);
|
||||
List<WXMsg>? msgList = reader.GetWXMsgs(contact.UserName, dateModel);
|
||||
if (msgList == null)
|
||||
throw new Exception("获取消息失败,请确认数据库读取正常");
|
||||
|
||||
if(msgList.Count == 0)
|
||||
{
|
||||
viewModel.ExportCount = "没有消息,忽略";
|
||||
return false;
|
||||
}
|
||||
msgList.Sort((x, y) => x.CreateTime.CompareTo(y.CreateTime));
|
||||
|
||||
bool err = false;
|
||||
int msgCount = 0;
|
||||
HtmlBody = "";
|
||||
|
||||
StreamWriter streamWriter = new StreamWriter(Path, true);
|
||||
foreach (var msg in msgList)
|
||||
{
|
||||
@@ -139,7 +145,7 @@ namespace WechatBakTool.Export
|
||||
string xml = Encoding.UTF8.GetString(data);
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
xml = xml.Replace("\n", "");
|
||||
xml = StringHelper.CleanInvalidXmlChars(xml);
|
||||
XmlDocument xmlObj = new XmlDocument();
|
||||
xmlObj.LoadXml(xml);
|
||||
if (xmlObj.DocumentElement != null)
|
||||
@@ -157,29 +163,88 @@ namespace WechatBakTool.Export
|
||||
}
|
||||
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p>", title);
|
||||
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/recorditem");
|
||||
try
|
||||
{
|
||||
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/recorditem");
|
||||
if (findNode != null)
|
||||
{
|
||||
if (findNode.Count > 0)
|
||||
{
|
||||
XmlDocument itemObj = new XmlDocument();
|
||||
itemObj.LoadXml(findNode[0]!.InnerText);
|
||||
XmlNodeList? itemNode = itemObj.DocumentElement.SelectNodes("/recordinfo/datalist/dataitem");
|
||||
if (itemNode.Count > 0)
|
||||
{
|
||||
foreach (XmlNode node in itemNode)
|
||||
{
|
||||
string nodeMsg;
|
||||
string name = node["sourcename"].InnerText;
|
||||
if (node.Attributes["datatype"].InnerText == "1")
|
||||
nodeMsg = node["datadesc1"].InnerText;
|
||||
else if (node.Attributes["datatype"].InnerText == "2")
|
||||
nodeMsg = "不支持的消息";
|
||||
else
|
||||
nodeMsg = node["datatitle"].InnerText;
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}:{1}</p>", name, nodeMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p>", "解析异常");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (msg.SubType == 57)
|
||||
{
|
||||
using (var decoder = LZ4Decoder.Create(true, 64))
|
||||
{
|
||||
byte[] target = new byte[10240];
|
||||
int res = 0;
|
||||
if (msg.CompressContent != null)
|
||||
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
|
||||
|
||||
byte[] data = target.Skip(0).Take(res).ToArray();
|
||||
string xml = Encoding.UTF8.GetString(data);
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
xml = StringHelper.CleanInvalidXmlChars(xml);
|
||||
XmlDocument xmlObj = new XmlDocument();
|
||||
xmlObj.LoadXml(xml);
|
||||
if (xmlObj.DocumentElement != null)
|
||||
{
|
||||
string title = "";
|
||||
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
|
||||
if (findNode != null)
|
||||
{
|
||||
if (findNode.Count > 0)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
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>");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,7 +264,7 @@ namespace WechatBakTool.Export
|
||||
string xml = Encoding.UTF8.GetString(data);
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
xml = xml.Replace("\n", "");
|
||||
xml = StringHelper.CleanInvalidXmlChars(xml);
|
||||
XmlDocument xmlObj = new XmlDocument();
|
||||
xmlObj.LoadXml(xml);
|
||||
if (xmlObj.DocumentElement != null)
|
||||
@@ -280,7 +345,7 @@ namespace WechatBakTool.Export
|
||||
}
|
||||
streamWriter.Close();
|
||||
streamWriter.Dispose();
|
||||
|
||||
return true;
|
||||
}
|
||||
private static DateTime TimeStampToDateTime(long timeStamp, bool inMilli = false)
|
||||
{
|
||||
|
||||
@@ -40,12 +40,12 @@ namespace WechatBakTool.Export
|
||||
|
||||
}
|
||||
|
||||
public void SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel)
|
||||
public bool SetMsg(WXUserReader reader, WXContact session, WorkspaceViewModel viewModel, DatetimePickerViewModel dateModel)
|
||||
{
|
||||
if (Contact == null)
|
||||
throw new Exception("请初始化模版:Not Use InitTemplate");
|
||||
|
||||
List<WXMsg>? msgList = reader.GetWXMsgs(Contact.UserName);
|
||||
List<WXMsg>? msgList = reader.GetWXMsgs(Contact.UserName, dateModel);
|
||||
if (msgList == null)
|
||||
throw new Exception("获取消息失败,请确认数据库读取正常");
|
||||
|
||||
@@ -146,6 +146,7 @@ namespace WechatBakTool.Export
|
||||
msgCount++;
|
||||
viewModel.ExportCount = msgCount.ToString();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static DateTime TimeStampToDateTime(long timeStamp, bool inMilli = false)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -136,41 +136,127 @@ namespace WechatBakTool.Helpers
|
||||
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 = page_bytes.Take(4).ToArray();
|
||||
page_bytes.CopyTo(going_to_hashed, DEFAULT_PAGESIZE - reserved - offset + IV_SIZE);
|
||||
var hash_mac_compute = hmac_sha1.ComputeHash(going_to_hashed, 0, going_to_hashed.Length);
|
||||
|
||||
// 取分页hash
|
||||
byte[] hash_mac_cached = new byte[hash_mac_compute.Length];
|
||||
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + DEFAULT_PAGESIZE - reserved + IV_SIZE, SeekOrigin.Begin);
|
||||
fileStream.Read(hash_mac_cached, 0, hash_mac_compute.Length);
|
||||
|
||||
if (!hash_mac_compute.SequenceEqual(hash_mac_cached) && page_no == 0)
|
||||
{
|
||||
Console.WriteLine("Hash错误...");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (page_no == 0)
|
||||
{
|
||||
var header_bytes = Encoding.ASCII.GetBytes(SQLITE_HEADER);
|
||||
header_bytes.CopyTo(decryped_page_bytes, 0);
|
||||
}
|
||||
|
||||
// 加密内容
|
||||
byte[] page_content = new byte[DEFAULT_PAGESIZE - reserved - offset];
|
||||
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + offset, SeekOrigin.Begin);
|
||||
fileStream.Read(page_content, 0, Convert.ToInt32(DEFAULT_PAGESIZE - reserved - offset));
|
||||
|
||||
// iv
|
||||
byte[] iv = new byte[16];
|
||||
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + (DEFAULT_PAGESIZE - reserved), SeekOrigin.Begin);
|
||||
fileStream.Read(iv, 0, 16);
|
||||
|
||||
var decrypted_content = AESDecrypt(page_content, key, iv);
|
||||
decrypted_content.CopyTo(decryped_page_bytes, offset);
|
||||
|
||||
// 保留
|
||||
byte[] reserved_byte = new byte[reserved];
|
||||
fileStream.Seek((page_no * DEFAULT_PAGESIZE) + DEFAULT_PAGESIZE - reserved, SeekOrigin.Begin);
|
||||
fileStream.Read(reserved_byte, 0, Convert.ToInt32(reserved));
|
||||
reserved_byte.CopyTo(decryped_page_bytes, DEFAULT_PAGESIZE - reserved);
|
||||
|
||||
tofileStream.Write(decryped_page_bytes, 0, decryped_page_bytes.Length);
|
||||
|
||||
}
|
||||
page_no++;
|
||||
offset = 0;
|
||||
}
|
||||
}catch(Exception ex)
|
||||
{
|
||||
File.AppendAllText("err.log", "page=>" + page_no.ToString() + "\r\n");
|
||||
File.AppendAllText("err.log", "size=>" + fileStream.Length.ToString() + "\r\n");
|
||||
File.AppendAllText("err.log", "postion=>" + ((page_no * DEFAULT_PAGESIZE) + offset).ToString() + "\r\n");
|
||||
File.AppendAllText("err.log", ex.ToString() + "\r\n");
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 旧版解密
|
||||
while (page_no < fileStream.Length / DEFAULT_PAGESIZE)
|
||||
{
|
||||
byte[] decryped_page_bytes = new byte[DEFAULT_PAGESIZE];
|
||||
byte[] going_to_hashed = new byte[DEFAULT_PAGESIZE - reserved - offset + IV_SIZE + 4];
|
||||
@@ -179,7 +265,6 @@ namespace WechatBakTool.Helpers
|
||||
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.Length);
|
||||
//取出分页中存储的Hash
|
||||
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 +293,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)
|
||||
{
|
||||
@@ -297,16 +383,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,18 @@ namespace WechatBakTool.Helpers
|
||||
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern uint QueryDosDevice([In] string lpDeviceName, [Out] StringBuilder lpTargetPath, [In] int ucchMax);
|
||||
|
||||
public static string FromDevicePath(string devicePath)
|
||||
public static string? FromDevicePath(string devicePath)
|
||||
{
|
||||
var drive = Array.Find(DriveInfo.GetDrives(), d => devicePath.StartsWith(d.GetDevicePath(), StringComparison.InvariantCultureIgnoreCase));
|
||||
var drive = Array.Find(
|
||||
DriveInfo.GetDrives(), d =>
|
||||
devicePath.StartsWith(d.GetDevicePath() + "\\", StringComparison.InvariantCultureIgnoreCase)
|
||||
);
|
||||
return drive != null ?
|
||||
devicePath.ReplaceFirst(drive.GetDevicePath(), drive.GetDriveLetter()) :
|
||||
devicePath.ReplaceFirst(drive.GetDevicePath()!, drive.GetDriveLetter()) :
|
||||
null;
|
||||
}
|
||||
|
||||
private static string GetDevicePath(this DriveInfo driveInfo)
|
||||
private static string? GetDevicePath(this DriveInfo driveInfo)
|
||||
{
|
||||
var devicePathBuilder = new StringBuilder(128);
|
||||
return QueryDosDevice(driveInfo.GetDriveLetter(), devicePathBuilder, devicePathBuilder.Capacity + 1) != 0 ?
|
||||
|
||||
46
Helpers/StringHelper.cs
Normal file
46
Helpers/StringHelper.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace WechatBakTool.Helpers
|
||||
{
|
||||
public static class StringHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 清理XML中的非法字符
|
||||
/// </summary>
|
||||
/// <param name="input">需要清理的字符串</param>
|
||||
/// <returns>清理后的字符串</returns>
|
||||
public static string CleanInvalidXmlChars(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return input;
|
||||
|
||||
// #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
|
||||
// 这里使用正则表达式匹配非法字符并替换
|
||||
return Regex.Replace(input, @"[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD]", "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 替换文件名中的非法字符为指定字符
|
||||
/// </summary>
|
||||
/// <param name="fileName">原始文件名</param>
|
||||
/// <param name="replacement">用于替换非法字符的字符,默认为 "-"</param>
|
||||
/// <returns>清理后的文件名</returns>
|
||||
public static string SanitizeFileName(string fileName, char replacement = '-')
|
||||
{
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
return fileName;
|
||||
|
||||
// 处理Windows系统中文件名不允许的特殊字符
|
||||
char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
|
||||
|
||||
foreach (char invalidChar in invalidFileNameChars)
|
||||
{
|
||||
fileName = fileName.Replace(invalidChar, '-');
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,8 @@ namespace WechatBakTool
|
||||
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Welcome.xaml?datatime=" + DateTime.Now.Ticks));
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentUserBakConfig = config;
|
||||
if (!config.Decrypt)
|
||||
{
|
||||
MessageBox.Show("请先到创建工作区进行解密");
|
||||
@@ -87,7 +89,6 @@ namespace WechatBakTool
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentUserBakConfig = config;
|
||||
MainFrame.Navigate(new Uri("pack://application:,,,/Pages/Workspace.xaml?datatime=" + DateTime.Now.Ticks));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace WechatBakTool.Model
|
||||
public string Account { get; set; } = "";
|
||||
public string Friends_Number { get; set; } = "-";
|
||||
public string Msg_Number { get; set; } = "-";
|
||||
public string Key { get; set; } = "";
|
||||
public bool Manual { get; set; } = false;
|
||||
}
|
||||
|
||||
public class WXCount
|
||||
@@ -82,8 +84,6 @@ namespace WechatBakTool.Model
|
||||
public string Content { get; set; } = "";
|
||||
[Column("nTime")]
|
||||
public int LastTime { get; set; }
|
||||
public int ReadCount { get; set; }
|
||||
public int LastMsgId { get; set; }
|
||||
}
|
||||
|
||||
[Table("SessionAttachInfo")]
|
||||
@@ -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
|
||||
{
|
||||
|
||||
7
NuGet.config
Normal file
7
NuGet.config
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
@@ -26,9 +26,9 @@
|
||||
<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,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}"/>
|
||||
<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>
|
||||
@@ -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>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using JiebaNet.Segmenter.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -13,7 +14,6 @@ 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;
|
||||
@@ -29,9 +29,20 @@ namespace WechatBakTool.Pages
|
||||
public CreateWork()
|
||||
{
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
GetWechatProcessInfos();
|
||||
isManualProcess();
|
||||
}
|
||||
|
||||
private void isManualProcess()
|
||||
{
|
||||
if(Main2.CurrentUserBakConfig!= null)
|
||||
{
|
||||
cb_manual.IsChecked = Main2.CurrentUserBakConfig.Manual;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void GetWechatProcessInfos()
|
||||
{
|
||||
@@ -55,7 +66,7 @@ namespace WechatBakTool.Pages
|
||||
ProcessInfo info = new ProcessInfo();
|
||||
info.ProcessId = p.Id.ToString();
|
||||
info.ProcessName = p.ProcessName;
|
||||
info.DBPath = DevicePathMapper.FromDevicePath(name);
|
||||
info.DBPath = DevicePathMapper.FromDevicePath(name)!;
|
||||
ViewModel.ProcessInfos.Add(info);
|
||||
}
|
||||
}
|
||||
@@ -97,8 +108,9 @@ namespace WechatBakTool.Pages
|
||||
private void btn_create_worksapce_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.IsEnable = false;
|
||||
bool m = (bool)cb_manual.IsChecked!;
|
||||
Task.Run(() => {
|
||||
if (ViewModel.KeyType != -1)
|
||||
if (ViewModel.KeyType != -1 && !m)
|
||||
{
|
||||
if (ViewModel.SelectProcess != null)
|
||||
{
|
||||
@@ -140,6 +152,17 @@ namespace WechatBakTool.Pages
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m)
|
||||
{
|
||||
WXWorkspace wXWorkspace = new WXWorkspace(Main2.CurrentUserBakConfig!);
|
||||
ViewModel.LabelStatus = "开始解密数据库";
|
||||
wXWorkspace.DecryptDB("", -1, ViewModel,Main2.CurrentUserBakConfig!.Key);
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
MessageBox.Show("解密完成");
|
||||
((Main2)Window.GetWindow(this)).LoadWorkspace();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("请选择Key获取方式", "错误");
|
||||
@@ -147,5 +170,55 @@ namespace WechatBakTool.Pages
|
||||
ViewModel.IsEnable = true;
|
||||
});
|
||||
}
|
||||
|
||||
private void cb_manual_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
MessageBox.Show("该功能仅限用于网络安全研究用途使用,红队同学请在合规授权下进行相关操作","重要提醒!!!!!!!!!");
|
||||
if(Main2.CurrentUserBakConfig != null)
|
||||
{
|
||||
if (Main2.CurrentUserBakConfig.Manual)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (MessageBox.Show("我确认获取到合规授权,仅用于网络安全用途使用", "信息确认", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
if (File.Exists("auth.txt"))
|
||||
{
|
||||
string auth = File.ReadAllText("auth.txt");
|
||||
/*
|
||||
*
|
||||
* pwd:
|
||||
* 我已知晓手动模式可能潜在的法律及道德风险,我明白非法使用将要承担相关法律责任。
|
||||
* tips:
|
||||
* 请不要公开宣传手动模式,不提供任何使用解答,谢谢。
|
||||
* 不要编写任何关于手动模式的教程,避免非法传播使用。
|
||||
*
|
||||
*/
|
||||
if (DecryptionHelper.GetMD5(auth) == "295f634af60d61dfa52a5f35849ac42b")
|
||||
{
|
||||
string genHash = DateTime.Now.ToString();
|
||||
string md5 = DecryptionHelper.GetMD5(genHash);
|
||||
UserBakConfig config = new UserBakConfig();
|
||||
config.Hash = md5;
|
||||
string workspacePath = Path.Combine(Directory.GetCurrentDirectory(), "workspace");
|
||||
config.UserWorkspacePath = Path.Combine(workspacePath, md5);
|
||||
|
||||
WXWorkspace workspace = new WXWorkspace(config);
|
||||
workspace.ManualInit();
|
||||
|
||||
MessageBox.Show("已经创建空的配置文件,请完善该配置文件后,点击开始解密","提示");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("未完成声明文件,请先确认声明", "错误");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cb_manual.IsChecked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -17,7 +18,9 @@ using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Xml;
|
||||
using WechatBakTool.Dialog;
|
||||
using WechatBakTool.Export;
|
||||
using WechatBakTool.Helpers;
|
||||
using WechatBakTool.Model;
|
||||
using WechatBakTool.ViewModel;
|
||||
|
||||
@@ -30,6 +33,9 @@ namespace WechatBakTool.Pages
|
||||
{
|
||||
private WorkspaceViewModel workspaceViewModel = new WorkspaceViewModel();
|
||||
public WXUserReader? UserReader;
|
||||
private List<WXContact>? ExpContacts;
|
||||
private bool Suspend = false;
|
||||
private int Status = 0;
|
||||
public Manager()
|
||||
{
|
||||
DataContext = workspaceViewModel;
|
||||
@@ -48,6 +54,32 @@ namespace WechatBakTool.Pages
|
||||
|
||||
private void btn_export_all_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DatetimePickerViewModel datePickViewModel = new DatetimePickerViewModel();
|
||||
if (Status == 0)
|
||||
{
|
||||
MsgDatetimePicker picker = new MsgDatetimePicker(datePickViewModel);
|
||||
datePickViewModel.DateType = 1;
|
||||
datePickViewModel.PickDate = DateTime.Now.AddDays(-1);
|
||||
if (picker.ShowDialog() != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 0 未开始
|
||||
if(Status == 0 || Status == 2)
|
||||
{
|
||||
Suspend = false;
|
||||
btn_export_all.Content = "暂停";
|
||||
}
|
||||
// 1 进行中
|
||||
else if (Status == 1)
|
||||
{
|
||||
// 开启暂停
|
||||
Suspend = true;
|
||||
Status = 2;
|
||||
btn_export_all.Content = "继续";
|
||||
return;
|
||||
}
|
||||
Task.Run(() =>
|
||||
{
|
||||
bool group = false, user = false;
|
||||
@@ -61,34 +93,67 @@ namespace WechatBakTool.Pages
|
||||
});
|
||||
if (UserReader != null)
|
||||
{
|
||||
List<WXContact>? contacts = UserReader.GetWXContacts().ToList();
|
||||
foreach (var contact in contacts)
|
||||
if (Status == 0)
|
||||
ExpContacts = UserReader.GetWXContacts().ToList();
|
||||
else
|
||||
Suspend = false;
|
||||
|
||||
List<WXContact> process = new List<WXContact>();
|
||||
foreach (var contact in ExpContacts!)
|
||||
{
|
||||
|
||||
if (Suspend)
|
||||
{
|
||||
foreach(WXContact p in process)
|
||||
{
|
||||
ExpContacts.Remove(p);
|
||||
}
|
||||
workspaceViewModel.ExportCount = "已暂停";
|
||||
return;
|
||||
}
|
||||
|
||||
Status = 1;
|
||||
if (group && contact.UserName.Contains("@chatroom"))
|
||||
{
|
||||
workspaceViewModel.WXContact = contact;
|
||||
ExportMsg(contact);
|
||||
ExportMsg(contact, datePickViewModel);
|
||||
}
|
||||
if (user)
|
||||
if (user && !contact.UserName.Contains("@chatroom") && !contact.UserName.Contains("gh_"))
|
||||
{
|
||||
workspaceViewModel.WXContact = contact;
|
||||
ExportMsg(contact);
|
||||
ExportMsg(contact, datePickViewModel);
|
||||
}
|
||||
process.Add(contact);
|
||||
}
|
||||
Status = 0;
|
||||
btn_export_all.Content = "导出";
|
||||
MessageBox.Show("批量导出完成", "提示");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ExportMsg(WXContact contact)
|
||||
private void ExportMsg(WXContact contact, DatetimePickerViewModel dt)
|
||||
{
|
||||
workspaceViewModel.ExportCount = "";
|
||||
string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, contact.UserName + ".html");
|
||||
// string path = Path.Combine(Main2.CurrentUserBakConfig!.UserWorkspacePath, contact.UserName + ".html");
|
||||
string fileName = StringHelper.SanitizeFileName(string.Format(
|
||||
"{0}-{1}.html",
|
||||
contact.UserName,
|
||||
contact.Remark == "" ? contact.NickName : contact.Remark
|
||||
));
|
||||
string path = Path.Combine(
|
||||
Main2.CurrentUserBakConfig!.UserWorkspacePath,
|
||||
fileName
|
||||
);
|
||||
|
||||
IExport export = new HtmlExport();
|
||||
export.InitTemplate(contact, path);
|
||||
export.SetMsg(UserReader!, contact, workspaceViewModel);
|
||||
export.SetEnd();
|
||||
export.Save(path);
|
||||
if (export.SetMsg(UserReader!, contact, workspaceViewModel, dt))
|
||||
{
|
||||
export.SetEnd();
|
||||
export.Save(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void btn_emoji_download_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -25,6 +25,11 @@ using WordCloudSharp;
|
||||
using System.Drawing;
|
||||
using System.Windows.Controls;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Threading;
|
||||
using System.Runtime.CompilerServices;
|
||||
using WechatBakTool.Dialog;
|
||||
|
||||
namespace WechatBakTool.Pages
|
||||
{
|
||||
@@ -35,6 +40,9 @@ 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> {
|
||||
@@ -54,7 +62,11 @@ namespace WechatBakTool.Pages
|
||||
UserReader = new WXUserReader(config);
|
||||
if (config.Decrypt)
|
||||
{
|
||||
ViewModel.Contacts = UserReader.GetWXContacts();
|
||||
ViewModel.Contacts = null;
|
||||
Task.Run(() => {
|
||||
ViewModel.Contacts = UserReader.GetWXContacts();
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,14 +82,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)
|
||||
@@ -89,7 +130,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)
|
||||
@@ -100,29 +159,6 @@ namespace WechatBakTool.Pages
|
||||
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)
|
||||
{
|
||||
File.AppendAllText("1.log", ex.Message);
|
||||
MessageBox.Show(ex.Message);
|
||||
}
|
||||
|
||||
MessageBox.Show("导出完成");
|
||||
}
|
||||
|
||||
private void btn_open_workspace_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
@@ -143,7 +179,20 @@ namespace WechatBakTool.Pages
|
||||
MessageBox.Show("请选择导出方式", "错误");
|
||||
return;
|
||||
}
|
||||
if(ViewModel.SelectExportItem.Value == 3)
|
||||
|
||||
DatetimePickerViewModel datePickViewModel = new DatetimePickerViewModel();
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
MsgDatetimePicker picker = new MsgDatetimePicker(datePickViewModel);
|
||||
datePickViewModel.DateType = 1;
|
||||
datePickViewModel.PickDate = DateTime.Now.AddDays(-1);
|
||||
if (picker.ShowDialog() != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (ViewModel.SelectExportItem.Value == 3)
|
||||
{
|
||||
if(UserReader != null && ViewModel.WXContact != null)
|
||||
{
|
||||
@@ -164,20 +213,30 @@ namespace WechatBakTool.Pages
|
||||
var jieba = new JiebaSegmenter();
|
||||
Counter<string> counter = new Counter<string>();
|
||||
|
||||
ViewModel.ExportCount = "词频统计ing...";
|
||||
List<WXMsg>? msgs = UserReader.GetWXMsgs(ViewModel.WXContact.UserName);
|
||||
if(msgs!= null)
|
||||
try
|
||||
{
|
||||
foreach(WXMsg msg in msgs)
|
||||
ViewModel.ExportCount = "词频统计ing...";
|
||||
List<WXMsg>? msgs = UserReader.GetWXMsgs(ViewModel.WXContact.UserName);
|
||||
if (msgs != null)
|
||||
{
|
||||
if(msg.Type == 1)
|
||||
foreach (WXMsg msg in msgs)
|
||||
{
|
||||
List<string> list = jieba.Cut(msg.StrContent).ToList();
|
||||
counter.Add(list);
|
||||
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(",");
|
||||
@@ -211,16 +270,14 @@ namespace WechatBakTool.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
string name = ViewModel.WXContact.NickName;
|
||||
name = name.Replace(@"\", "");
|
||||
name = Regex.Replace(name, "[ \\[ \\] \\^ \\-_*×――(^)$%~!/@#$…&%¥—+=<>《》|!!???::•`·、。,;,.;\"‘’“”-]", "");
|
||||
string fileName = StringHelper.SanitizeFileName(string.Format(
|
||||
"{0}-{1}",
|
||||
ViewModel.WXContact.UserName,
|
||||
ViewModel.WXContact.Remark == "" ? ViewModel.WXContact.NickName : ViewModel.WXContact.Remark
|
||||
));
|
||||
string path = Path.Combine(
|
||||
Main2.CurrentUserBakConfig!.UserWorkspacePath,
|
||||
string.Format(
|
||||
"{0}-{1}",
|
||||
ViewModel.WXContact.UserName,
|
||||
ViewModel.WXContact.Remark == "" ? name : ViewModel.WXContact.Remark
|
||||
)
|
||||
fileName
|
||||
);
|
||||
IExport export;
|
||||
|
||||
@@ -239,7 +296,7 @@ namespace WechatBakTool.Pages
|
||||
export = new HtmlExport();
|
||||
}
|
||||
export.InitTemplate(ViewModel.WXContact, path);
|
||||
export.SetMsg(UserReader, ViewModel.WXContact, ViewModel);
|
||||
export.SetMsg(UserReader, ViewModel.WXContact, ViewModel, datePickViewModel);
|
||||
export.SetEnd();
|
||||
export.Save(path);
|
||||
#if DEBUG
|
||||
@@ -260,6 +317,73 @@ namespace WechatBakTool.Pages
|
||||
MessageBox.Show("用户所有表情预下载完毕");
|
||||
});
|
||||
}
|
||||
/*
|
||||
if (UserReader != null && ViewModel.WXContact != null)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
List<WXMsg> msgs = UserReader.GetWXMsgs(ViewModel.WXContact.UserName).ToList();
|
||||
List<WXContactHT> users = new List<WXContactHT>();
|
||||
if (File.Exists("WXContact.json"))
|
||||
{
|
||||
string text = File.ReadAllText("WXContact.json");
|
||||
text = text.Substring(8, text.Length - 8);
|
||||
users = JsonConvert.DeserializeObject<List<WXContactHT>>(text);
|
||||
}
|
||||
|
||||
int i = 1; int all = 1;
|
||||
List<WXMsg> tmp = new List<WXMsg>();
|
||||
foreach (WXMsg m in msgs)
|
||||
{
|
||||
m.BytesExtra = null;
|
||||
tmp.Add(m);
|
||||
if (all % 10000 == 0)
|
||||
{
|
||||
File.WriteAllText(ViewModel.WXContact.UserName + "-" + i.ToString() + ".json", string.Format("showMsg({0})", JsonConvert.SerializeObject(tmp)));
|
||||
tmp.Clear();
|
||||
i++;
|
||||
}
|
||||
all++;
|
||||
}
|
||||
|
||||
if (users!.Find(x => x.UserName == ViewModel.WXContact.UserName) == null)
|
||||
{
|
||||
WXContactHT html = new WXContactHT();
|
||||
html.NickName = ViewModel.WXContact.NickName;
|
||||
html.UserName = ViewModel.WXContact.UserName;
|
||||
html.LastMsg = ViewModel.WXContact.LastMsg;
|
||||
if (ViewModel.WXContact.Avatar != null)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
ViewModel.WXContact.Avatar.StreamSource.CopyTo(ms);
|
||||
byte[] bytes = new byte[ms.Length];
|
||||
ms.Write(bytes, 0, bytes.Length);
|
||||
html.AvatarString = Convert.ToBase64String(bytes);
|
||||
}
|
||||
}
|
||||
html.FileCount = i;
|
||||
users.Add(html);
|
||||
}
|
||||
|
||||
File.WriteAllText(ViewModel.WXContact.UserName + "-" + i.ToString() + ".json", string.Format("showMsg({0})", JsonConvert.SerializeObject(tmp)));
|
||||
File.WriteAllText("WXContact.json", string.Format("getUser({0})", JsonConvert.SerializeObject(users)));
|
||||
MessageBox.Show("json已导出");
|
||||
});
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private void list_msg_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
if (ViewModel.WXMsgs.Count == 0)
|
||||
return;
|
||||
|
||||
if (e.VerticalOffset + e.ViewportHeight == e.ExtentHeight && !Loading)
|
||||
{
|
||||
// 滚动条到达底部的处理逻辑
|
||||
loadMsg();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
README.md
20
README.md
@@ -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/>
|
||||
|
||||
### 隐私声明
|
||||
@@ -44,6 +47,7 @@ C# + .NET6.0 + WPF <br/>
|
||||
- [ ] 性能优化
|
||||
- [ ] 打包资源文件夹
|
||||
- [ ] 手动模式(合适离线分析)
|
||||
- [ ] 年度报告类分析(等美术资源中,没有资源不做)
|
||||
<br/>
|
||||
|
||||
### 部分问题
|
||||
@@ -56,10 +60,15 @@ A:工作区->右键->管理,就见了。<br/>
|
||||
Q:解密工作区提示no such teble:MSG怎么办<br/>
|
||||
A:基本上都是因为刚迁移完,缓存没写入到数据库导致的,建议迁移完重启一次微信后再创建工作区<br/>
|
||||
<br/>
|
||||
Q:解密工作区提示no such teble:XXXXXXX怎么办<br/>
|
||||
A:这个原因基本上是因为解密失败导致的,回落使用固定地址查找方式解密,请确保你的微信版本在version.json内支持<br/>
|
||||
<br/>
|
||||
Q:解密时提示,Unable to load DLL 'libcrypto-1_1' or one of its dependencies怎么办<br/>
|
||||
A:这个是因为加解密库的运行环境不满足,安装vc++2015 x64运行库后再尝试<br/>
|
||||
<br/>
|
||||
|
||||
### 使用说明
|
||||
**本说明为新版本说明,即将发版**<br/>
|
||||
0.安装.NET Desktop Runtime(如已经安装忽略)<br/>
|
||||
0.安装.NET Desktop Runtime(注意是6.0版本的Desktop Runtime,如已经安装忽略)<br/>
|
||||
1.打开微信,并登录。<br/>
|
||||
2.在软件左侧下方点击**新建工作区**,<br/>
|
||||
3.在**新建工作区界面**,选择要创建工作区的微信进程,并**确认下方微信号是否正确**<br/>
|
||||
@@ -71,12 +80,13 @@ A:基本上都是因为刚迁移完,缓存没写入到数据库导致的,
|
||||
### 参考/引用
|
||||
项目在开发过程中参考了以下项目或资料,有引用相关代码,如有需要,推荐您可以去参考下相关资料:
|
||||
|
||||
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支持,更新不是很及时。
|
||||
|
||||
39
ViewModel/DatetimePickerViewModel.cs
Normal file
39
ViewModel/DatetimePickerViewModel.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using WechatBakTool.Model;
|
||||
|
||||
namespace WechatBakTool.ViewModel
|
||||
{
|
||||
public partial class DatetimePickerViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private DateTime startDate = DateTime.Now.AddMonths(-1);
|
||||
|
||||
[ObservableProperty]
|
||||
private DateTime endDate = DateTime.Now;
|
||||
|
||||
[ObservableProperty]
|
||||
private DateTime pickDate = DateTime.Now.AddDays(-1);
|
||||
|
||||
[ObservableProperty]
|
||||
private int dateType = 1;
|
||||
}
|
||||
|
||||
public class DateTypeConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return (int.Parse(parameter.ToString()!) == (int)value);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return (bool)value ? parameter : Binding.DoNothing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = "";
|
||||
|
||||
219
WXUserReader.cs
219
WXUserReader.cs
@@ -22,6 +22,7 @@ using System.Net.Http;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json;
|
||||
using WechatBakTool.ViewModel;
|
||||
|
||||
namespace WechatBakTool
|
||||
{
|
||||
@@ -51,6 +52,10 @@ namespace WechatBakTool
|
||||
continue;
|
||||
SQLiteConnection con = new SQLiteConnection(item);
|
||||
string dbName = fileInfo.Name.Split('.')[0];
|
||||
if (DBInfo.ContainsKey(dbName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
DBInfo.Add(dbName, con);
|
||||
}
|
||||
}
|
||||
@@ -330,6 +335,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 +380,141 @@ namespace WechatBakTool
|
||||
wXMsgs = con.Query<WXMsg>(query, uid, string.Format("%{0}%", msg));
|
||||
}
|
||||
|
||||
foreach (WXMsg w in wXMsgs)
|
||||
tmp.AddRange(ProcessMsg(wXMsgs, uid));
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
public List<WXMsg>? GetWXMsgs(string uid, DatetimePickerViewModel dateModel)
|
||||
{
|
||||
List<WXMsg> tmp = new List<WXMsg>();
|
||||
for (int i = 0; i <= 99; i++)
|
||||
{
|
||||
SQLiteConnection? con = getCon("MSG" + i.ToString());
|
||||
if (con == null)
|
||||
return tmp;
|
||||
|
||||
List<WXMsg>? wXMsgs = null;
|
||||
string query = "";
|
||||
|
||||
if (dateModel.DateType == 2 || dateModel.DateType == 3)
|
||||
{
|
||||
if (UserNameCache.ContainsKey(w.StrTalker))
|
||||
query = "select * from MSG where StrTalker=? and date(createtime,'unixepoch') = ?";
|
||||
wXMsgs = con.Query<WXMsg>(query, uid, dateModel.PickDate.ToString("yyyy-MM-dd"));
|
||||
}
|
||||
else if(dateModel.DateType == 4 )
|
||||
{
|
||||
query = "select * from MSG where StrTalker=? and date(createtime,'unixepoch') >= ? and date(createtime,'unixepoch') <= ?";
|
||||
wXMsgs = con.Query<WXMsg>(query, uid, dateModel.StartDate.ToString("yyyy-MM-dd"), dateModel.EndDate.ToString("yyyy-MM-dd"));
|
||||
}
|
||||
else
|
||||
{
|
||||
query = "select * from MSG where StrTalker=?";
|
||||
wXMsgs = con.Query<WXMsg>(query, uid);
|
||||
}
|
||||
|
||||
tmp.AddRange(ProcessMsg(wXMsgs, uid));
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
private List<WXMsg> ProcessMsg(List<WXMsg> msgs,string uid)
|
||||
{
|
||||
foreach (WXMsg w in msgs)
|
||||
{
|
||||
if (UserNameCache.ContainsKey(w.StrTalker))
|
||||
{
|
||||
WXContact? contact = UserNameCache[w.StrTalker] as WXContact;
|
||||
if (contact != null)
|
||||
{
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -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("获取到的密钥为空,获取失败");
|
||||
@@ -91,32 +104,42 @@ namespace WechatBakTool
|
||||
{
|
||||
return UserBakConfig;
|
||||
}
|
||||
public static void SaveConfig(UserBakConfig userBakConfig)
|
||||
public static void SaveConfig(UserBakConfig userBakConfig, bool manual = false)
|
||||
{
|
||||
if(userBakConfig.UserWorkspacePath != "")
|
||||
{
|
||||
DirectoryInfo directoryInfo = new DirectoryInfo(userBakConfig.UserWorkspacePath);
|
||||
if(directoryInfo.Parent != null)
|
||||
{
|
||||
string json_path = Path.Combine(directoryInfo.Parent.FullName, userBakConfig.UserName + ".json");
|
||||
string json_path = Path.Combine(directoryInfo.Parent.FullName, userBakConfig.Manual ? userBakConfig.Hash + ".json" : userBakConfig.UserName + ".json");
|
||||
string json = JsonConvert.SerializeObject(userBakConfig);
|
||||
File.WriteAllText(json_path, json);
|
||||
}
|
||||
}
|
||||
}
|
||||
private string Init(string path,string account = "")
|
||||
|
||||
public void ManualInit()
|
||||
{
|
||||
Init("", true, "");
|
||||
}
|
||||
private string Init(string path,bool manual = false,string account = "")
|
||||
{
|
||||
string curPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||
string md5 = GetMd5Hash(path);
|
||||
string[] paths = path.Split(new string[] { "/", "\\" }, StringSplitOptions.None);
|
||||
string username = paths[paths.Length - 1];
|
||||
UserBakConfig.UserResPath = path;
|
||||
UserBakConfig.UserWorkspacePath = Path.Combine(curPath, "workspace", md5);
|
||||
UserBakConfig.Hash = md5;
|
||||
UserBakConfig.UserName = username;
|
||||
UserBakConfig.Account = account;
|
||||
if (!manual)
|
||||
{
|
||||
string md5 = GetMd5Hash(path);
|
||||
string[] paths = path.Split(new string[] { "/", "\\" }, StringSplitOptions.None);
|
||||
string username = paths[paths.Length - 1];
|
||||
UserBakConfig.UserResPath = path;
|
||||
UserBakConfig.UserWorkspacePath = Path.Combine(curPath, "workspace", md5);
|
||||
UserBakConfig.Hash = md5;
|
||||
UserBakConfig.UserName = username;
|
||||
UserBakConfig.Account = account;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(UserBakConfig.UserResPath))
|
||||
UserBakConfig.Manual = manual;
|
||||
|
||||
if (!Directory.Exists(UserBakConfig.UserResPath) && !manual)
|
||||
{
|
||||
return "用户资源文件夹不存在,如需使用离线数据,请从工作区读取";
|
||||
}
|
||||
@@ -128,6 +151,7 @@ namespace WechatBakTool
|
||||
|
||||
string db = Path.Combine(UserBakConfig.UserWorkspacePath, "OriginalDB");
|
||||
string decDb = Path.Combine(UserBakConfig.UserWorkspacePath, "DecDB");
|
||||
|
||||
if (!Directory.Exists(db))
|
||||
{
|
||||
Directory.CreateDirectory (db);
|
||||
@@ -136,7 +160,7 @@ namespace WechatBakTool
|
||||
{
|
||||
Directory.CreateDirectory(decDb);
|
||||
}
|
||||
SaveConfig(UserBakConfig);
|
||||
SaveConfig(UserBakConfig, manual);
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<AssemblyVersion>0.9.6.2</AssemblyVersion>
|
||||
<FileVersion>0.9.6.2</FileVersion>
|
||||
<Version>0.9.6.2</Version>
|
||||
<AssemblyVersion>0.9.7.7</AssemblyVersion>
|
||||
<FileVersion>0.9.7.7</FileVersion>
|
||||
<Version>0.9.7.7</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
75
version.json
75
version.json
@@ -2,14 +2,77 @@
|
||||
{
|
||||
"Version": "3.9.6.33",
|
||||
"BaseAddr": 62031872
|
||||
},{
|
||||
"Version":"3.9.7.25",
|
||||
},
|
||||
{
|
||||
"Version": "3.9.7.25",
|
||||
"BaseAddr": 63484032
|
||||
},{
|
||||
"Version":"3.9.7.29",
|
||||
},
|
||||
{
|
||||
"Version": "3.9.7.29",
|
||||
"BaseAddr": 63488256
|
||||
},{
|
||||
"Version":"3.9.8.15",
|
||||
},
|
||||
{
|
||||
"Version": "3.9.8.15",
|
||||
"BaseAddr": 64997904
|
||||
},
|
||||
{
|
||||
"Version": "3.9.8.25",
|
||||
"BaseAddr": 65002192
|
||||
},
|
||||
{
|
||||
"Version": "3.9.9.27",
|
||||
"BaseAddr": 68066576
|
||||
},
|
||||
{
|
||||
"Version": "3.9.9.35",
|
||||
"BaseAddr": 68066576
|
||||
},
|
||||
{
|
||||
"Version": "3.9.9.43",
|
||||
"BaseAddr": 68067216
|
||||
},
|
||||
{
|
||||
"Version": "3.9.10.19",
|
||||
"BaseAddr": 95131040
|
||||
},
|
||||
{
|
||||
"Version": "3.9.10.27",
|
||||
"BaseAddr": 95126928
|
||||
},
|
||||
{
|
||||
"Version": "3.9.11.19",
|
||||
"BaseAddr": 93551568
|
||||
},
|
||||
{
|
||||
"Version": "3.9.11.23",
|
||||
"BaseAddr": 93700920
|
||||
},
|
||||
{
|
||||
"Version": "3.9.11.25",
|
||||
"BaseAddr": 93702352
|
||||
},
|
||||
{
|
||||
"Version": "3.9.12.15",
|
||||
"BaseAddr": 93814816
|
||||
},
|
||||
{
|
||||
"Version": "3.9.12.17",
|
||||
"BaseAddr": 93836256
|
||||
},
|
||||
{
|
||||
"Version": "3.9.12.31",
|
||||
"BaseAddr": 94518176
|
||||
},
|
||||
{
|
||||
"Version": "3.9.12.37",
|
||||
"BaseAddr": 94522080
|
||||
},
|
||||
{
|
||||
"Version": "3.9.12.45",
|
||||
"BaseAddr": 94505056
|
||||
},
|
||||
{
|
||||
"Version": "3.9.12.51",
|
||||
"BaseAddr": 94556448
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user