Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03074e75a3 | ||
|
|
77d3d3628d | ||
|
|
b0d4210c04 | ||
|
|
ef2024f90e |
2
App.xaml
2
App.xaml
@@ -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>
|
||||
|
||||
@@ -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,20 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string json = File.ReadAllText("version.json");
|
||||
List<VersionInfo>? 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 +151,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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace WechatPCMsgBakTool.Helpers
|
||||
{
|
||||
@@ -15,6 +16,18 @@ namespace WechatPCMsgBakTool.Helpers
|
||||
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];
|
||||
}
|
||||
|
||||
166
Helpers/ToolsHelper.cs
Normal file
166
Helpers/ToolsHelper.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WechatPCMsgBakTool.Helpers
|
||||
{
|
||||
public class ToolsHelper
|
||||
{
|
||||
public static TaskFactory factory = new TaskFactory(new LimitedConcurrencyLevelTaskScheduler(10));
|
||||
public static string DecodeVoice(string source,string pcm,string to)
|
||||
{
|
||||
string ffmpeg = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Tools", "ffmpeg.exe");
|
||||
string silk_decoder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Tools", "silk_v3_decoder.exe");
|
||||
|
||||
Task task = factory.StartNew(() =>
|
||||
{
|
||||
Process silk = new Process();
|
||||
silk.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
silk.StartInfo.UseShellExecute = false;
|
||||
silk.StartInfo.CreateNoWindow = true;
|
||||
silk.StartInfo.FileName = silk_decoder;
|
||||
silk.StartInfo.Arguments = string.Format("\"{0}\" \"{1}\"", source, pcm);
|
||||
silk.Start();
|
||||
silk.WaitForExit();
|
||||
|
||||
if (File.Exists(pcm))
|
||||
{
|
||||
Process ff = new Process();
|
||||
ff.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
ff.StartInfo.UseShellExecute= false;
|
||||
ff.StartInfo.CreateNoWindow = true;
|
||||
ff.StartInfo.FileName = ffmpeg;
|
||||
ff.StartInfo.Arguments = string.Format(" -y -f s16le -ar 24000 -ac 1 -i \"{0}\" -ar 24000 -b:a 320k \"{1}\"", pcm, to);
|
||||
ff.Start();
|
||||
ff.WaitForExit();
|
||||
}
|
||||
});
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
|
||||
{
|
||||
// Indicates whether the current thread is processing work items.
|
||||
[ThreadStatic]
|
||||
private static bool _currentThreadIsProcessingItems;
|
||||
|
||||
// The list of tasks to be executed
|
||||
private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); // protected by lock(_tasks)
|
||||
|
||||
// The maximum concurrency level allowed by this scheduler.
|
||||
private readonly int _maxDegreeOfParallelism;
|
||||
|
||||
// Indicates whether the scheduler is currently processing work items.
|
||||
private int _delegatesQueuedOrRunning = 0;
|
||||
|
||||
// Creates a new instance with the specified degree of parallelism.
|
||||
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
|
||||
{
|
||||
if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism");
|
||||
_maxDegreeOfParallelism = maxDegreeOfParallelism;
|
||||
}
|
||||
|
||||
// Queues a task to the scheduler.
|
||||
protected sealed override void QueueTask(Task task)
|
||||
{
|
||||
// Add the task to the list of tasks to be processed. If there aren't enough
|
||||
// delegates currently queued or running to process tasks, schedule another.
|
||||
lock (_tasks)
|
||||
{
|
||||
_tasks.AddLast(task);
|
||||
if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
|
||||
{
|
||||
++_delegatesQueuedOrRunning;
|
||||
NotifyThreadPoolOfPendingWork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inform the ThreadPool that there's work to be executed for this scheduler.
|
||||
private void NotifyThreadPoolOfPendingWork()
|
||||
{
|
||||
ThreadPool.UnsafeQueueUserWorkItem(_ =>
|
||||
{
|
||||
// Note that the current thread is now processing work items.
|
||||
// This is necessary to enable inlining of tasks into this thread.
|
||||
_currentThreadIsProcessingItems = true;
|
||||
try
|
||||
{
|
||||
// Process all available items in the queue.
|
||||
while (true)
|
||||
{
|
||||
Task item;
|
||||
lock (_tasks)
|
||||
{
|
||||
// When there are no more items to be processed,
|
||||
// note that we're done processing, and get out.
|
||||
if (_tasks.Count == 0)
|
||||
{
|
||||
--_delegatesQueuedOrRunning;
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the next item from the queue
|
||||
item = _tasks.First.Value;
|
||||
_tasks.RemoveFirst();
|
||||
}
|
||||
|
||||
// Execute the task we pulled out of the queue
|
||||
base.TryExecuteTask(item);
|
||||
}
|
||||
}
|
||||
// We're done processing items on the current thread
|
||||
finally { _currentThreadIsProcessingItems = false; }
|
||||
}, null);
|
||||
}
|
||||
|
||||
// Attempts to execute the specified task on the current thread.
|
||||
protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
|
||||
{
|
||||
// If this thread isn't already processing a task, we don't support inlining
|
||||
if (!_currentThreadIsProcessingItems) return false;
|
||||
|
||||
// If the task was previously queued, remove it from the queue
|
||||
if (taskWasPreviouslyQueued)
|
||||
// Try to run the task.
|
||||
if (TryDequeue(task))
|
||||
return base.TryExecuteTask(task);
|
||||
else
|
||||
return false;
|
||||
else
|
||||
return base.TryExecuteTask(task);
|
||||
}
|
||||
|
||||
// Attempt to remove a previously scheduled task from the scheduler.
|
||||
protected sealed override bool TryDequeue(Task task)
|
||||
{
|
||||
lock (_tasks) return _tasks.Remove(task);
|
||||
}
|
||||
|
||||
// Gets the maximum concurrency level supported by this scheduler.
|
||||
public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } }
|
||||
|
||||
// Gets an enumerable of the tasks currently scheduled on this scheduler.
|
||||
protected sealed override IEnumerable<Task> GetScheduledTasks()
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
Monitor.TryEnter(_tasks, ref lockTaken);
|
||||
if (lockTaken) return _tasks;
|
||||
else throw new NotSupportedException();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) Monitor.Exit(_tasks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -22,6 +22,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 +49,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 +76,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,13 +86,23 @@ 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)
|
||||
{
|
||||
string? path = reader.GetAttachment(WXMsgType.Audio, msg);
|
||||
if (path == null)
|
||||
{
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "语音不存在");
|
||||
continue;
|
||||
}
|
||||
HtmlBody += string.Format("<p class=\"content\"><audio controls src=\"{0}\"></audio></p></div>", path);
|
||||
}
|
||||
else
|
||||
{
|
||||
HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "暂未支持的消息");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private static DateTime TimeStampToDateTime(long timeStamp, bool inMilli = false)
|
||||
{
|
||||
DateTimeOffset dateTimeOffset = inMilli ? DateTimeOffset.FromUnixTimeMilliseconds(timeStamp) : DateTimeOffset.FromUnixTimeSeconds(timeStamp);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
38
Main.xaml
Normal file
38
Main.xaml
Normal file
@@ -0,0 +1,38 @@
|
||||
<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="142" 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="120" Height="20"/>
|
||||
<Button Name="btn_search" Content="搜索" HorizontalAlignment="Left" Margin="451,96,0,0" VerticalAlignment="Top" Width="43" Click="btn_search_Click"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
184
Main.xaml.cs
Normal file
184
Main.xaml.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
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()
|
||||
{
|
||||
InitializeComponent();
|
||||
LoadWorkspace();
|
||||
}
|
||||
|
||||
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 = JsonConvert.DeserializeObject<UserBakConfig>(jsonString);
|
||||
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 = DecryptionHelper.GetWechatKey();
|
||||
if (key == null)
|
||||
{
|
||||
MessageBox.Show("微信密钥获取失败,请检查微信是否打开,或者版本不兼容");
|
||||
return;
|
||||
}
|
||||
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)
|
||||
{
|
||||
MessageBox.Show("解密过程出现错误,请检查是否秘钥是否正确,如果有多开微信,请确保当前微信是选择的用户");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
list_sessions.ItemsSource = UserReader.GetWXContacts(find_user.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,26 @@ 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 WXUserInfo
|
||||
{
|
||||
public string UserName { get; set; } = "";
|
||||
@@ -85,6 +102,14 @@ namespace WechatPCMsgBakTool.Model
|
||||
public string StrContent { get; set; } = "";
|
||||
}
|
||||
|
||||
[Table("Media")]
|
||||
public class WXMediaMsg
|
||||
{
|
||||
public int Key { get; set; }
|
||||
public byte[]? Buf { get; set; }
|
||||
public string Reserved0 { get; set; } = "";
|
||||
}
|
||||
|
||||
[Table("Contact")]
|
||||
public class WXContact
|
||||
{
|
||||
|
||||
26
README.md
26
README.md
@@ -1,8 +1,8 @@
|
||||
# WechatPCMsgBakTool
|
||||
微信PC聊天记录备份工具,仅支持Windows
|
||||
|
||||
- 支持3.9.6.33版本
|
||||
- 导出图片、视频
|
||||
- 支持3.9.6.33版本后,若版本更新可在version.json添加版本号和地址即可完成新版本支持
|
||||
- 导出图片、视频、音频
|
||||
- 导出Html文件
|
||||
|
||||
本项目仅做学习使用,供个人备份自己的微信,请勿做其他用途使用。
|
||||
@@ -11,15 +11,19 @@
|
||||
|
||||
#### 使用
|
||||
<p>1.打开微信,并登录。</p>
|
||||
<p>2.将微信设置内的个人目录,填入用户文件夹的文本框,注意,需要带账号,你从微信设置里面点点打开文件夹那个路径就是对的了。</p>
|
||||
<p>3.依次点击,确定,解密,读取,即可在左侧见到会话列表了。>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><b>尽情使用吧!</b></p>
|
||||
|
||||
#### 注意
|
||||
<p>如果使用过程中发生崩溃,请删除工作区试一下,工作区即根据用户名在运行目录下生成的md5文件夹。</p>
|
||||
<p>已解谜的工作区可以直接读取</p>
|
||||
<p>再次强调,仅供个人备份自己微信使用 </p>
|
||||
|
||||
#### 参考/引用
|
||||
都是站在大佬们的肩膀上完成的项目,本项目 参考/引用 了以下 项目/文章 内代码。
|
||||
##### [Mr0x01/WXDBDecrypt.NET](https://github.com/Mr0x01/WXDBDecrypt.NET")
|
||||
##### [AdminTest0/SharpWxDump](https://github.com/AdminTest0/SharpWxDump")
|
||||
##### [吾爱破解chenhahacjl/微信 DAT 图片解密 (C#)](https://www.52pojie.cn/forum.php?mod=viewthread&tid=1507922")
|
||||
##### [Mr0x01/WXDBDecrypt.NET](https://github.com/Mr0x01/WXDBDecrypt.NET)
|
||||
##### [AdminTest0/SharpWxDump](https://github.com/AdminTest0/SharpWxDump)
|
||||
##### [kn007/silk-v3-decoder](https://github.com/kn007/silk-v3-decoder)
|
||||
##### [吾爱破解chenhahacjl/微信 DAT 图片解密 (C#)](https://www.52pojie.cn/forum.php?mod=viewthread&tid=1507922)
|
||||
|
||||
24
SelectWechat.xaml
Normal file
24
SelectWechat.xaml
Normal 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>
|
||||
85
SelectWechat.xaml.cs
Normal file
85
SelectWechat.xaml.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using 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();
|
||||
}
|
||||
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Tools/ffmpeg.exe
Normal file
BIN
Tools/ffmpeg.exe
Normal file
Binary file not shown.
BIN
Tools/handle64.exe
Normal file
BIN
Tools/handle64.exe
Normal file
Binary file not shown.
BIN
Tools/silk_v3_decoder.exe
Normal file
BIN
Tools/silk_v3_decoder.exe
Normal file
Binary file not shown.
259
WXReader.cs
259
WXReader.cs
@@ -1,259 +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.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 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);
|
||||
return savePath;
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
229
WXUserReader.cs
Normal file
229
WXUserReader.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
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 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 enum WXMsgType
|
||||
{
|
||||
Image = 0,
|
||||
Video = 1,
|
||||
Audio = 2,
|
||||
File = 3,
|
||||
}
|
||||
}
|
||||
125
WXWorkspace.cs
Normal file
125
WXWorkspace.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,12 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<AssemblyVersion>0.2.1.0</AssemblyVersion>
|
||||
<FileVersion>0.2.1.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -19,6 +22,18 @@
|
||||
<None Update="libssl-1_1-x64.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Tools\ffmpeg.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Tools\handle64.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Tools\silk_v3_decoder.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="version.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
9
version.json
Normal file
9
version.json
Normal file
@@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"Version": "3.9.6.33",
|
||||
"BaseAddr": 62031872
|
||||
},{
|
||||
"Version":"3.9.7.25",
|
||||
"BaseAddr": 63484032
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user