8 Commits
v0.2 ... v0.5

Author SHA1 Message Date
Suxue
cd9095859e 1.替换handle64,使用windows api直接获取文件句柄
2.新增了分享链接的支持
2023-11-01 16:17:07 +08:00
Suxue
97277c5e3e 新增错误处理,未处理异常留log 2023-10-20 10:29:34 +08:00
Suxue
ddb82e14ec 支持3.9.7.29 2023-10-16 10:18:51 +08:00
Suxue
5ccf928be8 新增分析模块:聊天频率分析,全消息库内容搜索
新增强制搜索已删除人员(从消息库搜索)
2023-09-27 16:16:42 +08:00
Suxue
11df32451a 更新openssl库,解决部分系统下解密失败的问题。
完善解密失败的提示。
2023-09-26 23:01:02 +08:00
Suxue
03074e75a3 重构工作区,理顺工作区概念。
新增微信选择功能,实现多开微信时也能方便选择创建/解密。
UI更新,更贴合现在的想法和概念,更易于理解。
添加3.9.7.25版本信息。
2023-09-26 14:19:30 +08:00
Suxue
77d3d3628d 忘了更新md了 2023-09-08 14:32:47 +08:00
Suxue
b0d4210c04 版本支持拆分 2023-09-08 14:26:43 +08:00
29 changed files with 1508 additions and 559 deletions

3
.gitignore vendored
View File

@@ -360,4 +360,5 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
/WechatPCMsgBakTool.csproj

36
Analyse.xaml Normal file
View File

@@ -0,0 +1,36 @@
<Window x:Class="WechatPCMsgBakTool.Analyse"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WechatPCMsgBakTool"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="溯雪微信备份工具-分析" Height="450" Width="900">
<Grid>
<ListView Name="list_msg_group" Margin="41,75,0,19" HorizontalAlignment="Left" Width="420" SelectionChanged="list_msg_group_SelectionChanged">
<ListView.View>
<GridView>
<GridViewColumn Header="昵称" Width="120" DisplayMemberBinding="{Binding NickName}" />
<GridViewColumn Header="原始ID" Width="120" DisplayMemberBinding="{Binding UserName}" />
<GridViewColumn Header="数量" Width="140" DisplayMemberBinding="{Binding MsgCount}" />
</GridView>
</ListView.View>
</ListView>
<Button x:Name="btn_analyse" Content="分析" HorizontalAlignment="Left" Margin="42,43,0,0" VerticalAlignment="Top" Width="72" Click="btn_analyse_Click"/>
<Button x:Name="btn_copy_id" Content="复制id" HorizontalAlignment="Left" Margin="366,43,0,0" VerticalAlignment="Top" Width="94" Click="btn_copy_id_Click"/>
<ListView Name="list_msg_search" Margin="500,75,0,19" HorizontalAlignment="Left" Width="350">
<ListView.View>
<GridView>
<GridViewColumn Header="原始ID" Width="120" DisplayMemberBinding="{Binding StrTalker}" />
<GridViewColumn Header="消息" Width="200" DisplayMemberBinding="{Binding StrContent}" />
</GridView>
</ListView.View>
</ListView>
<TextBox Name="txt_search_text" HorizontalAlignment="Left" Margin="574,43,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120" Height="20"/>
<Label Content="消息搜索:" HorizontalAlignment="Left" Margin="504,41,0,0" VerticalAlignment="Top"/>
<Button x:Name="btn_search" Content="搜索" HorizontalAlignment="Left" Margin="708,43,0,0" VerticalAlignment="Top" Width="65" Click="btn_search_Click" />
<Button x:Name="btn_search_copy_id" Content="复制id" HorizontalAlignment="Left" Margin="784,43,0,0" VerticalAlignment="Top" Width="65" Click="btn_search_copy_id_Click" />
</Grid>
</Window>

106
Analyse.xaml.cs Normal file
View File

@@ -0,0 +1,106 @@
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 WechatPCMsgBakTool.Model;
namespace WechatPCMsgBakTool
{
/// <summary>
/// Analyse.xaml 的交互逻辑
/// </summary>
public partial class Analyse : Window
{
private UserBakConfig UserBakConfig;
private WXUserReader UserReader;
public Analyse(UserBakConfig userBakConfig,WXUserReader reader)
{
UserBakConfig = userBakConfig;
UserReader = reader;
InitializeComponent();
}
private void btn_analyse_Click(object sender, RoutedEventArgs e)
{
List<WXContact>? contacts = UserReader.GetWXContacts();
List<WXMsgGroup> list = UserReader.GetWXMsgGroup().OrderByDescending(x => x.MsgCount).ToList();
if(contacts == null)
contacts = new List<WXContact>();
foreach (WXMsgGroup item in list)
{
WXContact? contact = contacts.Find(x => x.UserName == item.UserName);
if (contact != null)
{
item.NickName = contact.NickName;
}
else
item.NickName = "已删除人员:" + item.UserName;
}
list_msg_group.ItemsSource = list;
}
private void btn_copy_id_Click(object sender, RoutedEventArgs e)
{
WXMsgGroup? msgGroup = list_msg_group.SelectedItem as WXMsgGroup;
if(msgGroup == null)
{
MessageBox.Show("请先选择数据");
return;
}
else
{
Clipboard.SetDataObject(msgGroup.UserName);
}
}
private void list_msg_group_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
WXMsgGroup? wXMsgGroup = list_msg_group.SelectedItem as WXMsgGroup;
if(wXMsgGroup != null)
{
List<WXMsg>? wXMsgs = UserReader.GetWXMsgs(wXMsgGroup.UserName);
if(wXMsgs != null)
{
wXMsgs = wXMsgs.OrderByDescending(x => x.CreateTime).ToList();
list_msg_search.ItemsSource = wXMsgs;
}
}
}
private void btn_search_Click(object sender, RoutedEventArgs e)
{
List<WXMsg>? wXMsgs = UserReader.GetWXMsgs("",txt_search_text.Text);
if (wXMsgs != null)
{
wXMsgs = wXMsgs.OrderByDescending(x => x.CreateTime).ToList();
list_msg_search.ItemsSource = wXMsgs;
}
}
private void btn_search_copy_id_Click(object sender, RoutedEventArgs e)
{
WXMsg? wxMsg = list_msg_search.SelectedItem as WXMsg;
if (wxMsg == null)
{
MessageBox.Show("请先选择数据");
return;
}
else
{
Clipboard.SetDataObject(wxMsg.StrTalker);
}
}
}
}

View File

@@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WechatPCMsgBakTool"
StartupUri="MainWindow.xaml">
StartupUri="Main.xaml">
<Application.Resources>
</Application.Resources>

View File

@@ -1,10 +1,16 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using WechatPCMsgBakTool.Model;
namespace WechatPCMsgBakTool.Helpers
{
public class DecryptionHelper
@@ -33,8 +39,23 @@ namespace WechatPCMsgBakTool.Helpers
{
return null;
}
List<VersionInfo>? info = null;
string json = File.ReadAllText("version.json");
info = JsonConvert.DeserializeObject<List<VersionInfo>?>(json);
if (info == null)
return null;
if (info.Count == 0)
return null;
VersionInfo? cur = info.Find(x => x.Version == version);
if (cur == null)
return null;
//这里加的是版本偏移量,兼容不同版本把这个加给改了
long baseAddress = (long)module.BaseAddress + 62031872;
long baseAddress = (long)module.BaseAddress + cur.BaseAddr;
byte[]? bytes = ProcessHelper.ReadMemoryDate(process.Handle, (IntPtr)baseAddress, 8);
if (bytes != null)
{
@@ -133,6 +154,112 @@ namespace WechatPCMsgBakTool.Helpers
{
return BitConverter.ToString(bytes, 0).Replace("-", string.Empty).ToLower().ToUpper();
}
public static byte[] DecImage(string source)
{
//读取数据
byte[] fileBytes = File.ReadAllBytes(source);
//算差异转换
byte key = GetImgKey(fileBytes);
fileBytes = ConvertData(fileBytes, key);
return fileBytes;
}
public static string CheckFileType(byte[] data)
{
switch (data[0])
{
case 0XFF: //byte[] jpg = new byte[] { 0xFF, 0xD8, 0xFF };
{
if (data[1] == 0xD8 && data[2] == 0xFF)
{
return ".jpg";
}
break;
}
case 0x89: //byte[] png = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
{
if (data[1] == 0x50 && data[2] == 0x4E && data[7] == 0x0A)
{
return ".png";
}
break;
}
case 0x42: //byte[] bmp = new byte[] { 0x42, 0x4D };
{
if (data[1] == 0X4D)
{
return ".bmp";
}
break;
}
case 0x47: //byte[] gif = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39(0x37), 0x61 };
{
if (data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x38 && data[5] == 0x61)
{
return ".gif";
}
break;
}
case 0x49: // byte[] tif = new byte[] { 0x49, 0x49, 0x2A, 0x00 };
{
if (data[1] == 0x49 && data[2] == 0x2A && data[3] == 0x00)
{
return ".tif";
}
break;
}
case 0x4D: //byte[] tif = new byte[] { 0x4D, 0x4D, 0x2A, 0x00 };
{
if (data[1] == 0x4D && data[2] == 0x2A && data[3] == 0x00)
{
return ".tif";
}
break;
}
}
return ".dat";
}
private static byte GetImgKey(byte[] fileRaw)
{
byte[] raw = new byte[8];
for (int i = 0; i < 8; i++)
{
raw[i] = fileRaw[i];
}
for (byte key = 0x01; key < 0xFF; key++)
{
byte[] buf = new byte[8];
raw.CopyTo(buf, 0);
if (CheckFileType(ConvertData(buf, key)) != ".dat")
{
return key;
}
}
return 0x00;
}
private static byte[] ConvertData(byte[] data, byte key)
{
for (int i = 0; i < data.Length; i++)
{
data[i] ^= key;
}
return data;
}
public static string SaveDecImage(byte[] fileRaw,string source,string to_dir,string type)
{
FileInfo fileInfo = new FileInfo(source);
string fileName = fileInfo.Name.Substring(0, fileInfo.Name.Length - 4);
string saveFilePath = Path.Combine(to_dir, fileName + type);
using (FileStream fileStream = File.OpenWrite(saveFilePath))
{
fileStream.Write(fileRaw, 0, fileRaw.Length);
fileStream.Flush();
}
return saveFilePath;
}
}
}

View File

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

View File

@@ -7,7 +7,7 @@ namespace WechatPCMsgBakTool.Helpers
{
public class OpenSSLInterop
{
private const string Lib = "libcrypto-1_1-x64";
private const string Lib = "libcrypto-1_1";
internal static unsafe int HMAC_Init(out HMAC_CTX ctx, byte[] key, int key_len, IntPtr md)
{
return HMAC_InitNative(out ctx, key, key_len, md);

View File

@@ -2,23 +2,43 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace WechatPCMsgBakTool.Helpers
{
public class ProcessHelper
{
public static Process? GetProcess(string ProcessName)
private const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
private const int DUPLICATE_CLOSE_SOURCE = 0x1;
private const int DUPLICATE_SAME_ACCESS = 0x2;
private const int CNST_SYSTEM_HANDLE_INFORMATION = 0x10;
private const int OBJECT_TYPE_MUTANT = 17;
public static Process GetProcess(string ProcessName)
{
Process[] processes = Process.GetProcessesByName(ProcessName);
if (processes.Length == 0)
return null;
else if(processes.Length > 1) {
SelectWechat selectWechat = new SelectWechat();
MessageBox.Show("检测到有多个微信,请选择本工作区对应的微信");
selectWechat.ShowDialog();
if (selectWechat.SelectProcess == null)
return null;
Process? p = processes.ToList().Find(x => x.Id.ToString() == selectWechat.SelectProcess.ProcessId);
if (p == null)
return null;
return p;
}
else
return processes[0];
}
public static ProcessModule? FindProcessModule(int ProcessId, string ModuleName)
{
Process process = Process.GetProcessById(ProcessId);
@@ -30,6 +50,100 @@ namespace WechatPCMsgBakTool.Helpers
return null;
}
public static List<SYSTEM_HANDLE_INFORMATION> GetHandles(Process process)
{
List<SYSTEM_HANDLE_INFORMATION> aHandles = new List<SYSTEM_HANDLE_INFORMATION>();
int handle_info_size = Marshal.SizeOf(new SYSTEM_HANDLE_INFORMATION()) * 20000;
IntPtr ptrHandleData = IntPtr.Zero;
try
{
ptrHandleData = Marshal.AllocHGlobal(handle_info_size);
int nLength = 0;
while (NtQuerySystemInformation(CNST_SYSTEM_HANDLE_INFORMATION, ptrHandleData, handle_info_size, ref nLength) == STATUS_INFO_LENGTH_MISMATCH)
{
handle_info_size = nLength;
Marshal.FreeHGlobal(ptrHandleData);
ptrHandleData = Marshal.AllocHGlobal(nLength);
}
long handle_count = Marshal.ReadIntPtr(ptrHandleData).ToInt64();
IntPtr ptrHandleItem = ptrHandleData + Marshal.SizeOf(ptrHandleData);
for (long lIndex = 0; lIndex < handle_count; lIndex++)
{
SYSTEM_HANDLE_INFORMATION? oSystemHandleInfo = new SYSTEM_HANDLE_INFORMATION();
oSystemHandleInfo = Marshal.PtrToStructure(ptrHandleItem, oSystemHandleInfo.GetType()) as SYSTEM_HANDLE_INFORMATION?;
if (oSystemHandleInfo == null)
throw new Exception("获取SYSTEM_HANDLE_INFORMATION失败");
ptrHandleItem += Marshal.SizeOf(new SYSTEM_HANDLE_INFORMATION());
if (oSystemHandleInfo.Value.ProcessID != process.Id) { continue; }
aHandles.Add(oSystemHandleInfo.Value);
}
}
catch (Exception)
{
throw;
}
finally
{
Marshal.FreeHGlobal(ptrHandleData);
}
return aHandles;
}
public static string FindHandleName(SYSTEM_HANDLE_INFORMATION systemHandleInformation, Process process)
{
IntPtr ipHandle = IntPtr.Zero;
IntPtr openProcessHandle = IntPtr.Zero;
IntPtr hObjectName = IntPtr.Zero;
try
{
PROCESS_ACCESS_FLAGS flags = PROCESS_ACCESS_FLAGS.DupHandle | PROCESS_ACCESS_FLAGS.VMRead;
openProcessHandle = OpenProcess(flags, false, process.Id);
// 通过 DuplicateHandle 访问句柄
if (!DuplicateHandle(openProcessHandle, new IntPtr(systemHandleInformation.Handle), GetCurrentProcess(), out ipHandle, 0, false, DUPLICATE_SAME_ACCESS))
{
return "";
}
int nLength = 0;
hObjectName = Marshal.AllocHGlobal(256 * 1024);
// 查询句柄名称
while ((uint)(NtQueryObject(ipHandle, (int)OBJECT_INFORMATION_CLASS.ObjectNameInformation, hObjectName, nLength, ref nLength)) == STATUS_INFO_LENGTH_MISMATCH)
{
Marshal.FreeHGlobal(hObjectName);
if (nLength == 0)
{
Console.WriteLine("Length returned at zero!");
return "";
}
hObjectName = Marshal.AllocHGlobal(nLength);
}
OBJECT_NAME_INFORMATION? objObjectName = new OBJECT_NAME_INFORMATION();
objObjectName = Marshal.PtrToStructure(hObjectName, objObjectName.GetType()) as OBJECT_NAME_INFORMATION?;
if (objObjectName == null)
return "";
if (objObjectName.Value.Name.Buffer != IntPtr.Zero)
{
string? strObjectName = Marshal.PtrToStringUni(objObjectName.Value.Name.Buffer);
if (strObjectName != null)
return strObjectName;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Marshal.FreeHGlobal(hObjectName);
CloseHandle(ipHandle);
CloseHandle(openProcessHandle);
}
return "";
}
// 这里开始下面是对Windows API引用声明
public static byte[]? ReadMemoryDate(IntPtr hProcess, IntPtr lpBaseAddress, int nSize = 100)
{
byte[] array = new byte[nSize];
@@ -41,5 +155,74 @@ namespace WechatPCMsgBakTool.Helpers
[DllImport("kernel32.dll")]
public static extern int ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, int lpNumberOfBytesRead);
[DllImport("ntdll.dll")]
private static extern uint NtQuerySystemInformation(int SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, ref int returnLength);
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(PROCESS_ACCESS_FLAGS dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);
[DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentProcess();
[DllImport("ntdll.dll")]
private static extern int NtQueryObject(IntPtr ObjectHandle, int ObjectInformationClass, IntPtr ObjectInformation, int ObjectInformationLength, ref int returnLength);
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll")]
private static extern bool GetHandleInformation(IntPtr hObject, out uint lpdwFlags);
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SYSTEM_HANDLE_INFORMATION
{ // Information Class 16
public ushort ProcessID;
public ushort CreatorBackTrackIndex;
public byte ObjectType;
public byte HandleAttribute;
public ushort Handle;
public IntPtr Object_Pointer;
public IntPtr AccessMask;
}
private enum OBJECT_INFORMATION_CLASS : int
{
ObjectBasicInformation = 0,
ObjectNameInformation = 1,
ObjectTypeInformation = 2,
ObjectAllTypesInformation = 3,
ObjectHandleInformation = 4
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct OBJECT_NAME_INFORMATION
{
public UNICODE_STRING Name;
}
[StructLayout(LayoutKind.Sequential)]
private struct UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public IntPtr Buffer;
}
[Flags]
private enum PROCESS_ACCESS_FLAGS : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000
}
}
}

View File

@@ -136,10 +136,10 @@ namespace WechatPCMsgBakTool.Helpers
}
return "请复制目录至文本框内";
}
public static void DecryUserData(byte[] key)
public static void DecryUserData(byte[] key,string source,string to)
{
string dbPath = Path.Combine(UserWorkPath, "DB");
string decPath = Path.Combine(UserWorkPath, "DecDB");
string dbPath = source;
string decPath = to;
if(!Directory.Exists(decPath))
Directory.CreateDirectory(decPath);

View File

@@ -1,4 +1,6 @@
using System;
using K4os.Compression.LZ4.Encoders;
using K4os.Compression.LZ4;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -6,6 +8,7 @@ using System.Text;
using System.Threading.Tasks;
using WechatPCMsgBakTool.Interface;
using WechatPCMsgBakTool.Model;
using System.Xml;
namespace WechatPCMsgBakTool
{
@@ -22,6 +25,14 @@ namespace WechatPCMsgBakTool
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\"><b>导出时间:{0}</b></p><hr/>", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
}
public void InitTemplate(WXContact contact)
{
WXSession session = new WXSession();
session.NickName = contact.NickName;
session.UserName = contact.UserName;
InitTemplate(session);
}
public void Save(string path = "",bool append = false)
{
if (!append)
@@ -41,21 +52,26 @@ namespace WechatPCMsgBakTool
HtmlBody += "</body></html>";
}
public void SetMsg(WXReader reader, WXSession session)
public void SetMsg(WXUserReader reader,WXContact contact)
{
List<WXMsg> msgList = reader.GetMsgs(session.UserName);
if (Session == null)
throw new Exception("请初始化模版Not Use InitTemplate");
List<WXMsg>? msgList = reader.GetWXMsgs(contact.UserName);
if (msgList == null)
throw new Exception("获取消息失败,请确认数据库读取正常");
msgList.Sort((x, y) => x.CreateTime.CompareTo(y.CreateTime));
foreach (var msg in msgList)
{
if (Session == null)
throw new Exception("请初始化模版Not Use InitTemplate");
HtmlBody += string.Format("<div class=\"msg\"><p class=\"nickname\">{0} <span style=\"padding-left:10px;\">{1}</span></p>", msg.IsSender ? "我" : Session.NickName, TimeStampToDateTime(msg.CreateTime).ToString("yyyy-MM-dd HH:mm:ss"));
if (msg.Type == 1)
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", msg.StrContent);
else if(msg.Type == 3)
else if (msg.Type == 3)
{
string? path = reader.GetImage(msg);
string? path = reader.GetAttachment(WXMsgType.Image, msg);
if (path == null)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "图片转换出现错误或文件不存在");
@@ -63,9 +79,9 @@ namespace WechatPCMsgBakTool
}
HtmlBody += string.Format("<p class=\"content\"><img src=\"{0}\" style=\"max-height:1000px;max-width:1000px;\"/></p></div>", path);
}
else if(msg.Type == 43)
else if (msg.Type == 43)
{
string? path = reader.GetVideo(msg);
string? path = reader.GetAttachment(WXMsgType.Video, msg);
if (path == null)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "视频不存在");
@@ -73,12 +89,65 @@ namespace WechatPCMsgBakTool
}
HtmlBody += string.Format("<p class=\"content\"><video controls style=\"max-height:300px;max-width:300px;\"><source src=\"{0}\" type=\"video/mp4\" /></video></p></div>", path);
}
else if(msg.Type == 34)
else if(msg.Type== 49)
{
string? path = reader.GetVoice(msg);
using (var decoder = LZ4Decoder.Create(true, 64))
{
byte[] target = new byte[10240];
int res = 0;
if(msg.CompressContent != null)
res = LZ4Codec.Decode(msg.CompressContent, 0, msg.CompressContent.Length, target, 0, target.Length);
byte[] data = target.Skip(0).Take(res).ToArray();
string xml = Encoding.UTF8.GetString(data);
if (!string.IsNullOrEmpty(xml))
{
xml = xml.Replace("\n", "");
XmlDocument xmlObj = new XmlDocument();
xmlObj.LoadXml(xml);
if(xmlObj.DocumentElement != null)
{
string title = "";
string appName = "";
string url = "";
XmlNodeList? findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/title");
if(findNode != null)
{
if(findNode.Count > 0)
{
title = findNode[0]!.InnerText;
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/sourcedisplayname");
if (findNode != null)
{
if (findNode.Count > 0)
{
appName = findNode[0]!.InnerText;
}
}
findNode = xmlObj.DocumentElement.SelectNodes("/msg/appmsg/url");
if (findNode != null)
{
if (findNode.Count > 0)
{
url = findNode[0]!.InnerText;
}
}
HtmlBody += string.Format("<p class=\"content\">{0}|{1}</p><p><a href=\"{2}\">点击访问</a></p></div>", appName, title, url);
}
}
}
}
else if (msg.Type == 34)
{
string? path = reader.GetAttachment(WXMsgType.Audio, msg);
if (path == null)
{
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "视频不存在");
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "语音不存在");
continue;
}
HtmlBody += string.Format("<p class=\"content\"><audio controls src=\"{0}\"></audio></p></div>", path);
@@ -88,8 +157,8 @@ namespace WechatPCMsgBakTool
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "暂未支持的消息");
}
}
}
}
private static DateTime TimeStampToDateTime(long timeStamp, bool inMilli = false)
{
DateTimeOffset dateTimeOffset = inMilli ? DateTimeOffset.FromUnixTimeMilliseconds(timeStamp) : DateTimeOffset.FromUnixTimeSeconds(timeStamp);

View File

@@ -10,7 +10,8 @@ namespace WechatPCMsgBakTool.Interface
public interface IExport
{
void InitTemplate(WXSession session);
void SetMsg(WXReader reader, WXSession session);
void InitTemplate(WXContact session);
void SetMsg(WXUserReader reader, WXContact session);
void SetEnd();
void Save(string path = "", bool append = false);

40
Main.xaml Normal file
View File

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

249
Main.xaml.cs Normal file
View File

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

View File

@@ -1,36 +0,0 @@
<Window x:Class="WechatPCMsgBakTool.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WechatPCMsgBakTool"
mc:Ignorable="d"
Title="溯雪PC微信备份工具" Height="450" Width="900">
<Grid>
<Label Content="用户文件夹:" HorizontalAlignment="Left" Margin="30,27,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="txt_user_msg_path" HorizontalAlignment="Left" Margin="110,34,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="400"/>
<Button Name="select_user_msg_path" Content="确定" HorizontalAlignment="Left" Width="60" Margin="528,32,0,0" VerticalAlignment="Top" Click="select_user_msg_path_Click"/>
<Button x:Name="decryption_user_msg_db" Content="解密" HorizontalAlignment="Left" Width="60" Margin="610,32,0,0" VerticalAlignment="Top" Click="decryption_user_msg_db_Click"/>
<Button x:Name="read_user_msg_db" Content="读取" HorizontalAlignment="Left" Width="60" Margin="686,32,0,0" VerticalAlignment="Top" Click="read_user_msg_db_Click"/>
<Label Content="会话列表:" HorizontalAlignment="Left" Margin="30,60,0,0" VerticalAlignment="Top"/>
<ListView Name="list_sessions" Margin="30,100,0,20" HorizontalAlignment="Left" Width="380" MouseDoubleClick="list_sessions_MouseDoubleClick">
<ListView.View>
<GridView>
<GridViewColumn Header="昵称" Width="100" DisplayMemberBinding="{Binding NickName}" />
<GridViewColumn Header="原始id" Width="120" DisplayMemberBinding="{Binding UserName}" />
<GridViewColumn Header="最后消息" Width="150" DisplayMemberBinding="{Binding Content}" />
</GridView>
</ListView.View>
</ListView>
<Label Content="操作:" HorizontalAlignment="Left" Margin="440,60,0,0" VerticalAlignment="Top"/>
<Label Content="记录预览:" HorizontalAlignment="Left" Margin="440,150,0,0" VerticalAlignment="Top"/>
<ScrollViewer Margin="440,180,20,20" ScrollChanged="ScrollViewer_ScrollChanged" >
<Grid Name="msg_list" />
</ScrollViewer>
<Button x:Name="export_record" Content="导出选中人员记录" HorizontalAlignment="Left" Width="160" Margin="440,105,0,0" VerticalAlignment="Top" Click="export_record_Click" />
<TextBox Name="txt_find_session" HorizontalAlignment="Left" Margin="110,67,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120"/>
<Button Name="find_session_person" Content="查找" HorizontalAlignment="Left" Margin="246,67,0,0" VerticalAlignment="Top" Width="70" Click="find_session_person_Click"/>
<CheckBox Name="cb_use_local_decdb" Content="使用已解密的工作区读取" HorizontalAlignment="Left" Margin="642,108,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>

View File

@@ -1,174 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using WechatPCMsgBakTool.Helpers;
using WechatPCMsgBakTool.Interface;
using WechatPCMsgBakTool.Model;
namespace WechatPCMsgBakTool
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public string UserMsgPath { get; set; } = "";
public MainWindow()
{
InitializeComponent();
}
private void select_user_msg_path_Click(object sender, RoutedEventArgs e)
{
if (Directory.Exists(txt_user_msg_path.Text))
{
UserMsgPath = txt_user_msg_path.Text;
if (UserMsgPath.Substring(UserMsgPath.Length - 1, 1) == "\\") {
UserMsgPath = UserMsgPath.Substring(0, UserMsgPath.Length - 1);
}
//判定数据目录是否存在
if (Directory.Exists(UserMsgPath + "\\Msg"))
{
//MessageBox.Show("微信目录存在");
}
//复制数据DB
WechatDBHelper.CreateUserWorkPath(UserMsgPath);
string err = WechatDBHelper.MoveUserData(UserMsgPath);
if(err != "")
{
MessageBox.Show(err);
return;
}
else
{
MessageBox.Show("用户目录创建成功请打开PC微信并登录获取数据库秘钥解密");
}
}
}
private void decryption_user_msg_db_Click(object sender, RoutedEventArgs e)
{
byte[]? key = DecryptionHelper.GetWechatKey();
if(key == null)
{
MessageBox.Show("微信密钥获取失败,请检查微信是否打开,或者版本不兼容");
return;
}
WechatDBHelper.DecryUserData(key);
MessageBox.Show("解密完成,请点击读取数据");
}
WXReader? Reader = null;
private void read_user_msg_db_Click(object sender, RoutedEventArgs e)
{
list_sessions.Items.Clear();
if (cb_use_local_decdb.IsChecked == true)
{
DBInfo info = WechatDBHelper.GetDBinfoOnLocal(txt_user_msg_path.Text);
Reader = new WXReader(info);
}
else
{
Reader = new WXReader();
}
List<WXSession>? sessions = new List<WXSession>();
sessions = Reader.GetWXSessions();
if (sessions == null)
{
MessageBox.Show("咩都厶啊");
return;
}
foreach (WXSession session in sessions)
{
list_sessions.Items.Add(session);
}
}
private bool loading = false;
private bool end = false;
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (sender == null)
return;
ScrollViewer scrollViewer = (ScrollViewer)sender;
if (scrollViewer.ScrollableHeight == 0)
return;
if (scrollViewer.ScrollableHeight - scrollViewer.ContentVerticalOffset < 10)
{
if (!loading && !end)
{
loading = true;
//GetMsg();
}
}
}
private void list_sessions_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
}
private void export_record_Click(object sender, RoutedEventArgs e)
{
WXSession selectItem = (WXSession)list_sessions.SelectedValue;
if (selectItem != null)
{
IExport export = new HtmlExport();
export.InitTemplate(selectItem);
if(Reader == null)
{
MessageBox.Show("请先读取用户数据");
return;
}
export.SetMsg(Reader, selectItem);
export.SetEnd();
string path = Reader.GetSavePath(selectItem);
export.Save(path);
MessageBox.Show("导出完成");
}
}
private void find_session_person_Click(object sender, RoutedEventArgs e)
{
list_sessions.Items.Clear();
if (Reader == null)
Reader = new WXReader();
List<WXContact>? sessions = new List<WXContact>();
sessions = Reader.GetUser(txt_find_session.Text);
if (sessions == null)
{
MessageBox.Show("咩都厶啊");
return;
}
foreach (WXContact session in sessions)
{
WXSession session1 = new WXSession();
session1.NickName = session.NickName;
session1.UserName = session.UserName;
list_sessions.Items.Add(session1);
}
}
}
}

View File

@@ -6,6 +6,12 @@ using System.Threading.Tasks;
namespace WechatPCMsgBakTool.Model
{
public class ProcessInfo
{
public string ProcessName { get; set; } = "";
public string ProcessId { get; set; } = "";
public string DBPath { get; set; } = "";
}
public class DBInfo
{
public int MaxMsgDBCount { get; set; }
@@ -19,4 +25,10 @@ namespace WechatPCMsgBakTool.Model
public string UserName { get; set; } = "";
public string NickName { get; set; } = "";
}
public class VersionInfo
{
public string Version { get; set; } = "";
public int BaseAddr { get; set; }
}
}

View File

@@ -4,9 +4,36 @@ using SQLite;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
namespace WechatPCMsgBakTool.Model
{
public class UserBakConfig : INotifyPropertyChanged
{
public string UserResPath { get; set; } = "";
public string UserWorkspacePath { get; set; } = "";
public bool Decrypt { get; set; } = false;
public string Hash { get; set; } = "";
public string NickName { get; set; } = "";
public string UserName { get; set; } = "";
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class WXMsgGroup
{
[Column("StrTalker")]
public string UserName { get; set; } = "";
[Column("MsgCount")]
public int MsgCount { get; set; }
public string NickName { get; set; } = "";
}
public class WXUserInfo
{
public string UserName { get; set; } = "";
@@ -83,6 +110,8 @@ namespace WechatPCMsgBakTool.Model
public string StrTalker { get; set; } = "";
[Column("StrContent")]
public string StrContent { get; set; } = "";
[Column("CompressContent")]
public byte[]? CompressContent { get; set; }
}
[Table("Media")]

View File

@@ -1,22 +1,32 @@
# WechatPCMsgBakTool
微信PC聊天记录备份工具仅支持Windows
- 当前仅支持3.9.6.33版本,后续将版本文件拆分出来
- 导出图片、视频、音频
- 支持3.9.6.33版本若版本更新可在version.json添加版本号和地址即可完成新版本支持
- 导出图片、视频、音频、分享链接
- 导出Html文件
- 支持聊天频率分析,全消息库内容搜索
本项目仅做学习使用,供个人备份自己的微信,请勿做其他用途使用。
** 本项目仅做学习使用,主要供个人备份自己的微信记录,请勿用于非法用途。 **
** 本项目严禁商用 **
本项目严禁商用
如果有什么好的建议或意见或者遇到什么问题欢迎提issue看到会回
> [!NOTE]
> 反馈群815054692
> 如果觉得不错欢迎右上角点个star这是对作者的鼓励谢谢
#### 使用
<p>1.打开微信,并登录。</p>
<p>2.将微信设置内的个人目录,填入用户文件夹的文本框,注意,需要带账号,你从微信设置里面点点打开文件夹那个路径就是对的了。</p>
<p>3.依次点击,确定,解密,读取,即可在左侧见到会话列表了。</p>
<p>4.如果会话列表内没有这个人,你可以按账号搜索。</p>
<p>5.如果使用过程中发生崩溃请删除工作区试一下工作区即根据用户名在运行目录下生成的md5文件夹。</p>
<p>6.如果你已经读取过一次数据了,想离线使用,请填入微信个人目录后,选择使用已解密的工作区读取,即可本地离线加载。</p>
<p>7.再次强调,仅供个人备份自己微信使用 </p>
<p>2.在工作区上方点击新增,选择要创建的工作区微信</p>
<p>3.如同时运行多个微信,请选择微信,请注意通过路径进行区别</p>
<p>4.选中刚刚创建的工作区,点击解密。(如当前多开微信,请选择对应的微信进行解密)</p>
<p>5.选中刚刚创建的工作区,点击读取</p>
<p><b>尽情使用吧!</b></p>
#### 注意
<p>本项目基于.NET开发需要安装.NET Desktop Runtime如未安装双击EXE时会提示。</p>
<p>如果使用过程中发生崩溃请删除工作区试一下工作区即根据用户名在运行目录下生成的md5文件夹。</p>
<p>已解密的工作区可以直接读取。</p>
<p>再次强调,主要用于个人备份自己微信使用,请勿用于非法用途,严禁商用!</p>
#### 参考/引用
都是站在大佬们的肩膀上完成的项目,本项目 参考/引用 了以下 项目/文章 内代码。
@@ -24,3 +34,4 @@
##### [AdminTest0/SharpWxDump](https://github.com/AdminTest0/SharpWxDump)
##### [kn007/silk-v3-decoder](https://github.com/kn007/silk-v3-decoder)
##### [吾爱破解chenhahacjl/微信 DAT 图片解密 C#](https://www.52pojie.cn/forum.php?mod=viewthread&tid=1507922)
##### [huiyadanli/RevokeMsgPatcher](https://github.com/huiyadanli/RevokeMsgPatcher)

24
SelectWechat.xaml Normal file
View File

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

125
SelectWechat.xaml.cs Normal file
View File

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

View File

@@ -1,311 +0,0 @@
using SQLite;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Interop;
using WechatPCMsgBakTool.Helpers;
using WechatPCMsgBakTool.Model;
namespace WechatPCMsgBakTool
{
public class WXReader
{
private DBInfo DecDBInfo;
private Dictionary<string, SQLiteConnection> DBInfo = new Dictionary<string, SQLiteConnection>();
public WXReader(DBInfo? info = null) {
if (info == null)
DecDBInfo = WechatDBHelper.GetDBInfo();
else
DecDBInfo = info;
string[] dbFileList = Directory.GetFiles(Path.Combine(DecDBInfo.UserPath, "DecDB"));
foreach (var item in dbFileList)
{
FileInfo fileInfo = new FileInfo(item);
if (fileInfo.Extension != ".db")
continue;
SQLiteConnection con = new SQLiteConnection(item);
string dbName = fileInfo.Name.Split('.')[0];
DBInfo.Add(dbName, con);
}
}
public List<WXSession>? GetWXSessions(string? name = null)
{
SQLiteConnection con = DBInfo["MicroMsg"];
if (con == null)
return null;
string query = "select * from session";
if(name != null)
{
query = "select * from session where strUsrName = ?";
return con.Query<WXSession>(query, name);
}
return con.Query<WXSession>(query);
}
public List<WXContact>? GetUser(string? name = null)
{
SQLiteConnection con = DBInfo["MicroMsg"];
if (con == null)
return null;
string query = "select * from contact";
if (name != null)
{
query = "select * from contact where username = ? or alias = ?";
return con.Query<WXContact>(query, name, name);
}
return con.Query<WXContact>(query);
}
public WXSessionAttachInfo? GetWXMsgAtc(WXMsg msg)
{
SQLiteConnection con = DBInfo["MultiSearchChatMsg"];
if (con == null)
return null;
string query = "select * from SessionAttachInfo where msgId = ? order by attachsize desc";
List<WXSessionAttachInfo> list = con.Query<WXSessionAttachInfo>(query, msg.MsgSvrID);
if (list.Count != 0)
return list[0];
else
return null;
}
public List<WXMsg> GetMsgs(string uid)
{
List<WXMsg> tmp = new List<WXMsg>();
for(int i = 0; i <= DecDBInfo.MaxMsgDBCount; i++)
{
SQLiteConnection con = DBInfo["MSG" + i.ToString()];
if (con == null)
continue;
string query = "select * from MSG where StrTalker=?";
List<WXMsg> wXMsgs = con.Query<WXMsg>(query, uid);
foreach(WXMsg w in wXMsgs)
{
tmp.Add(w);
}
}
return tmp;
}
public WXMediaMsg? GetVoiceMsg(string msgid)
{
for (int i = 0; i <= DecDBInfo.MaxMediaDBCount; i++)
{
SQLiteConnection con = DBInfo["MediaMSG" + i.ToString()];
if (con == null)
continue;
string query = "select * from Media where Reserved0=?";
List<WXMediaMsg> wXMsgs = con.Query<WXMediaMsg>(query, msgid);
if(wXMsgs.Count != 0)
return wXMsgs[0];
}
return null;
}
public string? GetVideo(WXMsg msg)
{
WXSessionAttachInfo? attachInfo = GetWXMsgAtc(msg);
if (attachInfo == null)
return null;
string resBasePath = Path.Combine(DecDBInfo.ResPath, attachInfo.attachPath);
if (!File.Exists(resBasePath))
return null;
string videoPath = Path.Combine(DecDBInfo.UserPath, msg.StrTalker, "Video");
if (!Directory.Exists(videoPath))
Directory.CreateDirectory(videoPath);
FileInfo fileInfo = new FileInfo(resBasePath);
string savePath = Path.Combine(videoPath, fileInfo.Name);
if(!File.Exists(savePath))
File.Copy(resBasePath, savePath, false);
savePath = savePath.Replace(DecDBInfo.UserPath + "\\", "");
return savePath;
}
public string? GetVoice(WXMsg msg) {
string tmp = Path.Combine(DecDBInfo.UserPath, msg.StrTalker, "tmp");
if (!Directory.Exists(tmp))
{
Directory.CreateDirectory(tmp);
}
WXMediaMsg? voiceMsg = GetVoiceMsg(msg.MsgSvrID);
if(voiceMsg != null)
{
if (voiceMsg.Buf == null)
return null;
string voicePath = Path.Combine(DecDBInfo.UserPath, msg.StrTalker, "Voice");
if (!Directory.Exists(voicePath))
Directory.CreateDirectory(voicePath);
// 从DB取音频文件到临时目录
string tmp_file_path = Path.Combine(tmp, voiceMsg.Key + ".arm");
using (FileStream stream = new FileStream(tmp_file_path,FileMode.OpenOrCreate))
{
stream.Write(voiceMsg.Buf, 0, voiceMsg.Buf.Length);
}
// 调用silk_v3_decoder解码成pcm
string tmp_pcm_file_path = Path.Combine(tmp, voiceMsg.Key + ".pcm");
// 调用ffmpeg转换成mp3
string mp3_file_path = Path.Combine(voicePath, voiceMsg.Key + ".mp3");
ToolsHelper.DecodeVoice(tmp_file_path, tmp_pcm_file_path, mp3_file_path);
mp3_file_path = mp3_file_path.Replace(DecDBInfo.UserPath + "\\", "");
return mp3_file_path;
}
return null;
}
public string GetSavePath(WXSession session)
{
string savePath = Path.Combine(DecDBInfo.UserPath, session.UserName + ".html");
return savePath;
}
public string? GetImage(WXMsg msg)
{
WXSessionAttachInfo? attachInfo = GetWXMsgAtc(msg);
if (attachInfo == null)
return null;
string resBasePath = Path.Combine(DecDBInfo.ResPath, attachInfo.attachPath);
//部分attachpath可能会附加md5校验这里做处理
int index = attachInfo.attachPath.IndexOf(".dat");
if (attachInfo.attachPath.Length - index > 10)
{
resBasePath = resBasePath.Substring(0, resBasePath.Length - 32);
}
if (!File.Exists(resBasePath))
return null;
string imgPath = Path.Combine(DecDBInfo.UserPath, msg.StrTalker, "Image");
if (!Directory.Exists(imgPath))
Directory.CreateDirectory(imgPath);
string img = DecImage(resBasePath, imgPath);
img = img.Replace(DecDBInfo.UserPath + "\\", "");
return img;
}
private string DecImage(string source,string toPath)
{
//读取数据
byte[] fileBytes = File.ReadAllBytes(source);
//算差异转换
byte key = getImgKey(fileBytes);
fileBytes = ConvertData(fileBytes, key);
//取文件类型
string type = CheckFileType(fileBytes);
//
FileInfo fileInfo = new FileInfo(source);
string fileName = fileInfo.Name.Substring(0, fileInfo.Name.Length - 4);
string saveFilePath = Path.Combine(toPath, fileName + type);
using (FileStream fileStream = File.OpenWrite(saveFilePath))
{
fileStream.Write(fileBytes, 0, fileBytes.Length);
fileStream.Flush();
}
return saveFilePath;
}
private string CheckFileType(byte[] data)
{
switch (data[0])
{
case 0XFF: //byte[] jpg = new byte[] { 0xFF, 0xD8, 0xFF };
{
if (data[1] == 0xD8 && data[2] == 0xFF)
{
return ".jpg";
}
break;
}
case 0x89: //byte[] png = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
{
if (data[1] == 0x50 && data[2] == 0x4E && data[7] == 0x0A)
{
return ".png";
}
break;
}
case 0x42: //byte[] bmp = new byte[] { 0x42, 0x4D };
{
if (data[1] == 0X4D)
{
return ".bmp";
}
break;
}
case 0x47: //byte[] gif = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39(0x37), 0x61 };
{
if (data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x38 && data[5] == 0x61)
{
return ".gif";
}
break;
}
case 0x49: // byte[] tif = new byte[] { 0x49, 0x49, 0x2A, 0x00 };
{
if (data[1] == 0x49 && data[2] == 0x2A && data[3] == 0x00)
{
return ".tif";
}
break;
}
case 0x4D: //byte[] tif = new byte[] { 0x4D, 0x4D, 0x2A, 0x00 };
{
if (data[1] == 0x4D && data[2] == 0x2A && data[3] == 0x00)
{
return ".tif";
}
break;
}
}
return ".dat";
}
private byte getImgKey(byte[] fileRaw)
{
byte[] raw = new byte[8];
for (int i = 0; i < 8; i++)
{
raw[i] = fileRaw[i];
}
for (byte key = 0x01; key < 0xFF; key++)
{
byte[] buf = new byte[8];
raw.CopyTo(buf, 0);
if (CheckFileType(ConvertData(buf, key)) != ".dat")
{
return key;
}
}
return 0x00;
}
private byte[] ConvertData(byte[] data, byte key)
{
for (int i = 0; i < data.Length; i++)
{
data[i] ^= key;
}
return data;
}
}
}

266
WXUserReader.cs Normal file
View File

@@ -0,0 +1,266 @@
using SQLite;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Interop;
using System.Xml.Linq;
using WechatPCMsgBakTool.Helpers;
using WechatPCMsgBakTool.Model;
namespace WechatPCMsgBakTool
{
public class WXUserReader
{
private Dictionary<string, SQLiteConnection> DBInfo = new Dictionary<string, SQLiteConnection>();
private UserBakConfig? UserBakConfig = null;
public WXUserReader(UserBakConfig userBakConfig) {
string path = Path.Combine(userBakConfig.UserWorkspacePath, "DecDB");
UserBakConfig = userBakConfig;
LoadDB(path);
}
public void LoadDB(string path)
{
string[] dbFileList = Directory.GetFiles(path);
foreach (var item in dbFileList)
{
FileInfo fileInfo = new FileInfo(item);
if (fileInfo.Extension != ".db")
continue;
SQLiteConnection con = new SQLiteConnection(item);
string dbName = fileInfo.Name.Split('.')[0];
DBInfo.Add(dbName, con);
}
}
public List<WXContact>? GetWXContacts(string? name = null)
{
SQLiteConnection con = DBInfo["MicroMsg"];
if (con == null)
return null;
string query = "select * from contact";
if (name != null)
{
query = "select * from contact where username = ? or alias = ?";
return con.Query<WXContact>(query, name, name);
}
return con.Query<WXContact>(query);
}
public List<WXMsg>? GetWXMsgs(string uid,string msg = "")
{
List<WXMsg> tmp = new List<WXMsg>();
for (int i = 0; i <= 99; i++)
{
if(DBInfo.ContainsKey("MSG" + i.ToString()))
{
SQLiteConnection con = DBInfo["MSG" + i.ToString()];
if (con == null)
return tmp;
List<WXMsg>? wXMsgs = null;
if (msg == "")
{
string query = "select * from MSG where StrTalker=?";
wXMsgs = con.Query<WXMsg>(query, uid);
}
else if(uid == "")
{
string query = "select * from MSG where StrContent like ?";
wXMsgs = con.Query<WXMsg>(query, string.Format("%{0}%", msg));
}
else
{
string query = "select * from MSG where StrTalker=? and StrContent like ?";
wXMsgs = con.Query<WXMsg>(query, uid, string.Format("%{0}%", msg));
}
foreach (WXMsg w in wXMsgs)
{
tmp.Add(w);
}
}
}
return tmp;
}
public WXSessionAttachInfo? GetWXMsgAtc(WXMsg msg)
{
SQLiteConnection con = DBInfo["MultiSearchChatMsg"];
if (con == null)
return null;
string query = "select * from SessionAttachInfo where msgId = ? order by attachsize desc";
List<WXSessionAttachInfo> list = con.Query<WXSessionAttachInfo>(query, msg.MsgSvrID);
if (list.Count != 0)
{
//部分附件可能有md5校验这里移除校验给的是正确路径
WXSessionAttachInfo acc = list[0];
int index = acc.attachPath.IndexOf(".dat");
int index2 = acc.attachPath.IndexOf(".dat");
if (acc.attachPath.Length - index > 10 && index != -1)
{
acc.attachPath = acc.attachPath.Substring(0, acc.attachPath.Length - 32);
}
if (acc.attachPath.Length - index2 > 10 && index2 != -1)
{
acc.attachPath = acc.attachPath.Substring(0, acc.attachPath.Length - 32);
}
return acc;
}
else
return null;
}
public WXMediaMsg? GetVoiceMsg(WXMsg msg)
{
for (int i = 0; i <= 99; i++)
{
if(DBInfo.ContainsKey("MediaMSG" + i.ToString()))
{
SQLiteConnection con = DBInfo["MediaMSG" + i.ToString()];
if (con == null)
continue;
string query = "select * from Media where Reserved0=?";
List<WXMediaMsg> wXMsgs = con.Query<WXMediaMsg>(query, msg.MsgSvrID);
if (wXMsgs.Count != 0)
return wXMsgs[0];
}
}
return null;
}
public string? GetAttachment(WXMsgType type, WXMsg msg)
{
if (UserBakConfig == null)
return null;
string? tmpPath = Path.Combine(UserBakConfig.UserWorkspacePath, "Temp");
if (!Directory.Exists(tmpPath))
Directory.CreateDirectory(tmpPath);
// 如果是图片和视频,从附件库中搜索
string? path = null;
if (type == WXMsgType.Image || type == WXMsgType.Video)
{
WXSessionAttachInfo? atcInfo = GetWXMsgAtc(msg);
if (atcInfo == null)
return null;
path = atcInfo.attachPath;
}
// 如果是从语音,从媒体库查找
else if (type == WXMsgType.Audio)
{
WXMediaMsg? voiceMsg = GetVoiceMsg(msg);
if (voiceMsg == null)
return null;
if (voiceMsg.Buf == null)
return null;
// 从DB取音频文件到临时目录
string tmp_file_path = Path.Combine(tmpPath, voiceMsg.Key + ".arm");
using (FileStream stream = new FileStream(tmp_file_path, FileMode.OpenOrCreate))
{
stream.Write(voiceMsg.Buf, 0, voiceMsg.Buf.Length);
}
path = tmp_file_path;
}
if (path == null)
return null;
// 获取到原路径后,开始进行解密转移,只有图片和语音需要解密,解密后是直接归档目录
if(type == WXMsgType.Image || type== WXMsgType.Audio)
{
path = DecryptAttachment(type, path);
}
else if (type == WXMsgType.Video)
{
string video_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "Video");
if(!Directory.Exists(video_dir))
Directory.CreateDirectory(video_dir);
FileInfo fileInfo = new FileInfo(path);
string video_file_path = Path.Combine(video_dir, fileInfo.Name);
// 视频的路径是相对路径,需要加上资源目录
path = Path.Combine(UserBakConfig.UserResPath, path);
if(!File.Exists(video_file_path))
File.Move(path, video_file_path);
path = video_file_path;
}
if (path == null)
return null;
// 改相对路径
path = path.Replace(UserBakConfig.UserWorkspacePath + "\\", "");
return path;
}
public string? DecryptAttachment(WXMsgType type, string path)
{
if (UserBakConfig == null)
return null;
string? file_path = null;
switch (type)
{
case WXMsgType.Image:
string img_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "Image");
if (!Directory.Exists(img_dir))
Directory.CreateDirectory(img_dir);
// 图片的路径是相对路径,需要加上资源目录
path = Path.Combine(UserBakConfig.UserResPath, path);
byte[] decFileByte = DecryptionHelper.DecImage(path);
string decFiletype = DecryptionHelper.CheckFileType(decFileByte);
file_path = DecryptionHelper.SaveDecImage(decFileByte, path, img_dir, decFiletype);
break;
case WXMsgType.Audio:
string audio_dir = Path.Combine(UserBakConfig.UserWorkspacePath, "Audio");
if (!Directory.Exists(audio_dir))
Directory.CreateDirectory(audio_dir);
FileInfo fileInfo = new FileInfo(path);
string audio_file_dir = Path.Combine(audio_dir, fileInfo.Name + ".mp3");
ToolsHelper.DecodeVoice(path, path + ".pcm", audio_file_dir);
file_path = audio_file_dir;
break;
}
return file_path;
}
public List<WXMsgGroup> GetWXMsgGroup()
{
List<WXMsgGroup> g = new List<WXMsgGroup>();
for (int i = 0; i <= 99; i++)
{
if (DBInfo.ContainsKey("MSG" + i.ToString()))
{
SQLiteConnection con = DBInfo["MSG" + i.ToString()];
if (con == null)
return g;
string query = "select StrTalker,Count(localId) as MsgCount from MSG GROUP BY StrTalker";
List<WXMsgGroup> wXMsgs = con.Query<WXMsgGroup>(query);
foreach (WXMsgGroup w in wXMsgs)
{
WXMsgGroup? tmp = g.Find(x => x.UserName == w.UserName);
if (tmp == null)
g.Add(w);
else
tmp.MsgCount += g.Count;
}
}
}
return g;
}
}
public enum WXMsgType
{
Image = 0,
Video = 1,
Audio = 2,
File = 3,
}
}

125
WXWorkspace.cs Normal file
View File

@@ -0,0 +1,125 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using WechatPCMsgBakTool.Model;
namespace WechatPCMsgBakTool
{
public class WXWorkspace
{
private UserBakConfig UserBakConfig = new UserBakConfig();
public WXWorkspace(string path) {
string checkResult = Init(path);
if (checkResult != "")
new Exception(checkResult);
}
public WXWorkspace(UserBakConfig userBakConfig)
{
UserBakConfig = userBakConfig;
}
public void MoveDB()
{
string sourceBase = Path.Combine(UserBakConfig.UserResPath, "Msg");
string sourceMulit = Path.Combine(UserBakConfig.UserResPath, "Msg/Multi");
string[] files = Directory.GetFiles(sourceBase);
foreach (string file in files)
{
FileInfo fileInfo = new FileInfo(file);
if(fileInfo.Extension == ".db")
{
string to_path = Path.Combine(UserBakConfig.UserWorkspacePath, "OriginalDB", fileInfo.Name);
File.Copy(file, to_path, true);
}
}
files = Directory.GetFiles(sourceMulit);
foreach (string file in files)
{
FileInfo fileInfo = new FileInfo(file);
if (fileInfo.Extension == ".db")
{
string to_path = Path.Combine(UserBakConfig.UserWorkspacePath, "OriginalDB", fileInfo.Name);
File.Copy(file, to_path, true);
}
}
}
public static void SaveConfig(UserBakConfig userBakConfig)
{
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 = JsonConvert.SerializeObject(userBakConfig);
File.WriteAllText(json_path, json);
}
}
}
private string Init(string path)
{
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;
if (!Directory.Exists(UserBakConfig.UserResPath))
{
return "用户资源文件夹不存在,如需使用离线数据,请从工作区读取";
}
if (!Directory.Exists(UserBakConfig.UserWorkspacePath))
{
Directory.CreateDirectory(UserBakConfig.UserWorkspacePath);
}
string db = Path.Combine(UserBakConfig.UserWorkspacePath, "OriginalDB");
string decDb = Path.Combine(UserBakConfig.UserWorkspacePath, "DecDB");
if (!Directory.Exists(db))
{
Directory.CreateDirectory (db);
}
if (!Directory.Exists(decDb))
{
Directory.CreateDirectory(decDb);
}
SaveConfig(UserBakConfig);
return "";
}
private static string GetMd5Hash(string input)
{
using (MD5 md5Hash = MD5.Create())
{
// Convert the input string to a byte array and compute the hash.
byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
// Create a new Stringbuilder to collect the bytes
// and create a string.
StringBuilder sBuilder = new StringBuilder();
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
for (int i = 0; i < data.Length; i++)
{
sBuilder.Append(data[i].ToString("x2"));
}
// Return the hexadecimal string.
return sBuilder.ToString();
}
}
}
}

View File

@@ -6,19 +6,23 @@
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyVersion>0.2.0.0</AssemblyVersion>
<FileVersion>0.2.0.0</FileVersion>
<AssemblyVersion>0.5.0.0</AssemblyVersion>
<FileVersion>0.5.0.0</FileVersion>
<Version>0.5.0.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
<PackageReference Include="System.Management" Version="7.0.2" />
</ItemGroup>
<ItemGroup>
<None Update="libcrypto-1_1-x64.dll">
<None Update="libcrypto-1_1.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="libssl-1_1-x64.dll">
<None Update="libssl-1_1.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Tools\ffmpeg.exe">
@@ -27,6 +31,9 @@
<None Update="Tools\silk_v3_decoder.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="version.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

Binary file not shown.

BIN
libcrypto-1_1.dll Normal file

Binary file not shown.

Binary file not shown.

BIN
libssl-1_1.dll Normal file

Binary file not shown.

12
version.json Normal file
View File

@@ -0,0 +1,12 @@
[
{
"Version": "3.9.6.33",
"BaseAddr": 62031872
},{
"Version":"3.9.7.25",
"BaseAddr": 63484032
},{
"Version":"3.9.7.29",
"BaseAddr": 63488256
}
]