Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef2024f90e |
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,16 @@ 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.GetVoice(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>", "暂未支持的消息");
|
||||
|
||||
@@ -85,6 +85,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
|
||||
{
|
||||
|
||||
13
README.md
13
README.md
@@ -1,8 +1,8 @@
|
||||
# WechatPCMsgBakTool
|
||||
微信PC聊天记录备份工具,仅支持Windows
|
||||
|
||||
- 支持3.9.6.33版本
|
||||
- 导出图片、视频
|
||||
- 当前仅支持3.9.6.33版本,后续将版本文件拆分出来
|
||||
- 导出图片、视频、音频
|
||||
- 导出Html文件
|
||||
|
||||
本项目仅做学习使用,供个人备份自己的微信,请勿做其他用途使用。
|
||||
@@ -12,7 +12,7 @@
|
||||
#### 使用
|
||||
<p>1.打开微信,并登录。</p>
|
||||
<p>2.将微信设置内的个人目录,填入用户文件夹的文本框,注意,需要带账号,你从微信设置里面点点打开文件夹那个路径就是对的了。</p>
|
||||
<p>3.依次点击,确定,解密,读取,即可在左侧见到会话列表了。>3.依次点击,确定,解密,读取,即可在左侧见到会话列表了。</p>
|
||||
<p>3.依次点击,确定,解密,读取,即可在左侧见到会话列表了。</p>
|
||||
<p>4.如果会话列表内没有这个人,你可以按账号搜索。</p>
|
||||
<p>5.如果使用过程中发生崩溃,请删除工作区试一下,工作区即根据用户名在运行目录下生成的md5文件夹。</p>
|
||||
<p>6.如果你已经读取过一次数据了,想离线使用,请填入微信个人目录后,选择使用已解密的工作区读取,即可本地离线加载。</p>
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#### 参考/引用
|
||||
都是站在大佬们的肩膀上完成的项目,本项目 参考/引用 了以下 项目/文章 内代码。
|
||||
##### [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)
|
||||
|
||||
BIN
Tools/ffmpeg.exe
Normal file
BIN
Tools/ffmpeg.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.
52
WXReader.cs
52
WXReader.cs
@@ -6,6 +6,7 @@ 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;
|
||||
@@ -96,6 +97,22 @@ namespace WechatPCMsgBakTool
|
||||
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);
|
||||
@@ -115,9 +132,43 @@ namespace WechatPCMsgBakTool
|
||||
|
||||
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");
|
||||
@@ -147,6 +198,7 @@ namespace WechatPCMsgBakTool
|
||||
Directory.CreateDirectory(imgPath);
|
||||
|
||||
string img = DecImage(resBasePath, imgPath);
|
||||
img = img.Replace(DecDBInfo.UserPath + "\\", "");
|
||||
return img;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<AssemblyVersion>0.2.0.0</AssemblyVersion>
|
||||
<FileVersion>0.2.0.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -19,6 +21,12 @@
|
||||
<None Update="libssl-1_1-x64.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Tools\ffmpeg.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Tools\silk_v3_decoder.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user