liuyuqi-dellpc 4 years ago
commit
809dd4e44b
91 changed files with 4233 additions and 0 deletions
  1. 25 0
      .gitignore
  2. BIN
      Images/give_a_star.png
  3. BIN
      Images/logo.png
  4. BIN
      Images/logo_old.jpg
  5. BIN
      Images/revoke.jpg
  6. BIN
      Images/screenshot.png
  7. BIN
      Images/screenshot.v0.4.png
  8. BIN
      Images/screenshot.v0.5.png
  9. BIN
      Images/watch_release.png
  10. BIN
      Images/wiki/qq/1.png
  11. BIN
      Images/wiki/qq/10.png
  12. BIN
      Images/wiki/qq/11.png
  13. BIN
      Images/wiki/qq/12.png
  14. BIN
      Images/wiki/qq/13.png
  15. BIN
      Images/wiki/qq/2.png
  16. BIN
      Images/wiki/qq/3.png
  17. BIN
      Images/wiki/qq/4.png
  18. BIN
      Images/wiki/qq/5.png
  19. BIN
      Images/wiki/qq/6.png
  20. BIN
      Images/wiki/qq/7.png
  21. BIN
      Images/wiki/qq/8.png
  22. BIN
      Images/wiki/qq/9.png
  23. BIN
      Images/wiki/wechat/10_mutex.png
  24. BIN
      Images/wiki/wechat/11_push_ebp_to_ret.png
  25. BIN
      Images/wiki/wechat/12_push_ebp_to_ret.png
  26. BIN
      Images/wiki/wechat/13_push_ebp_to_ret.png
  27. BIN
      Images/wiki/wechat/14_patch_dll.png
  28. BIN
      Images/wiki/wechat/1_start.png
  29. BIN
      Images/wiki/wechat/2_click_attach.png
  30. BIN
      Images/wiki/wechat/3_attach_wechat_exe.png
  31. BIN
      Images/wiki/wechat/4_wechatwin_dll.png
  32. BIN
      Images/wiki/wechat/5_search_string.png
  33. BIN
      Images/wiki/wechat/6_revokemsg_1.png
  34. BIN
      Images/wiki/wechat/7_je_to_jmp.png
  35. BIN
      Images/wiki/wechat/8_je_to_jmp.png
  36. BIN
      Images/wiki/wechat/9_je_to_jmp.png
  37. 65 0
      README.md
  38. 6 0
      RevokeMsgPatcher.Assistant/App.config
  39. 48 0
      RevokeMsgPatcher.Assistant/FormAssisant.Designer.cs
  40. 25 0
      RevokeMsgPatcher.Assistant/FormAssisant.cs
  41. 120 0
      RevokeMsgPatcher.Assistant/FormAssisant.resx
  42. 138 0
      RevokeMsgPatcher.Assistant/JsonData.cs
  43. 22 0
      RevokeMsgPatcher.Assistant/Program.cs
  44. 36 0
      RevokeMsgPatcher.Assistant/Properties/AssemblyInfo.cs
  45. 71 0
      RevokeMsgPatcher.Assistant/Properties/Resources.Designer.cs
  46. 117 0
      RevokeMsgPatcher.Assistant/Properties/Resources.resx
  47. 30 0
      RevokeMsgPatcher.Assistant/Properties/Settings.Designer.cs
  48. 7 0
      RevokeMsgPatcher.Assistant/Properties/Settings.settings
  49. 112 0
      RevokeMsgPatcher.Assistant/RevokeMsgPatcher.Assistant.csproj
  50. 6 0
      RevokeMsgPatcher.MultiInstance/App.config
  51. 186 0
      RevokeMsgPatcher.MultiInstance/FormMultiInstance.Designer.cs
  52. 111 0
      RevokeMsgPatcher.MultiInstance/FormMultiInstance.cs
  53. 120 0
      RevokeMsgPatcher.MultiInstance/FormMultiInstance.resx
  54. 22 0
      RevokeMsgPatcher.MultiInstance/Program.cs
  55. 36 0
      RevokeMsgPatcher.MultiInstance/Properties/AssemblyInfo.cs
  56. 71 0
      RevokeMsgPatcher.MultiInstance/Properties/Resources.Designer.cs
  57. 117 0
      RevokeMsgPatcher.MultiInstance/Properties/Resources.resx
  58. 30 0
      RevokeMsgPatcher.MultiInstance/Properties/Settings.Designer.cs
  59. 7 0
      RevokeMsgPatcher.MultiInstance/Properties/Settings.settings
  60. 82 0
      RevokeMsgPatcher.MultiInstance/RevokeMsgPatcher.MultiInstance.csproj
  61. 37 0
      RevokeMsgPatcher.sln
  62. 6 0
      RevokeMsgPatcher/App.config
  63. 14 0
      RevokeMsgPatcher/BusinessException.cs
  64. 265 0
      RevokeMsgPatcher/FormMain.Designer.cs
  65. 359 0
      RevokeMsgPatcher/FormMain.cs
  66. 408 0
      RevokeMsgPatcher/FormMain.resx
  67. 45 0
      RevokeMsgPatcher/Model/App.cs
  68. 17 0
      RevokeMsgPatcher/Model/Bag.cs
  69. 23 0
      RevokeMsgPatcher/Model/Change.cs
  70. 40 0
      RevokeMsgPatcher/Model/ModifyInfo.cs
  71. 26 0
      RevokeMsgPatcher/Model/TargetInfo.cs
  72. 215 0
      RevokeMsgPatcher/Modifier/AppModifier.cs
  73. 74 0
      RevokeMsgPatcher/Modifier/FileHexEditor.cs
  74. 56 0
      RevokeMsgPatcher/Modifier/QQLiteModifier.cs
  75. 56 0
      RevokeMsgPatcher/Modifier/QQModifier.cs
  76. 56 0
      RevokeMsgPatcher/Modifier/TIMModifier.cs
  77. 72 0
      RevokeMsgPatcher/Modifier/WechatModifier.cs
  78. 80 0
      RevokeMsgPatcher/Program.cs
  79. 36 0
      RevokeMsgPatcher/Properties/AssemblyInfo.cs
  80. 72 0
      RevokeMsgPatcher/Properties/Resources.Designer.cs
  81. 120 0
      RevokeMsgPatcher/Properties/Resources.resx
  82. 30 0
      RevokeMsgPatcher/Properties/Settings.Designer.cs
  83. 7 0
      RevokeMsgPatcher/Properties/Settings.settings
  84. 108 0
      RevokeMsgPatcher/RevokeMsgPatcher.csproj
  85. 151 0
      RevokeMsgPatcher/Utils/Device.cs
  86. 78 0
      RevokeMsgPatcher/Utils/FileUtil.cs
  87. 75 0
      RevokeMsgPatcher/Utils/GAHelper.cs
  88. 14 0
      RevokeMsgPatcher/Utils/HttpUtil.cs
  89. 81 0
      RevokeMsgPatcher/Utils/PathUtil.cs
  90. BIN
      RevokeMsgPatcher/icon.ico
  91. 2 0
      appveyor.yml

+ 25 - 0
.gitignore

@@ -0,0 +1,25 @@
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+.vs/
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Mine
+Tmp/
+/packages/

BIN
Images/give_a_star.png


BIN
Images/logo.png


BIN
Images/logo_old.jpg


BIN
Images/revoke.jpg


BIN
Images/screenshot.png


BIN
Images/screenshot.v0.4.png


BIN
Images/screenshot.v0.5.png


BIN
Images/watch_release.png


BIN
Images/wiki/qq/1.png


BIN
Images/wiki/qq/10.png


BIN
Images/wiki/qq/11.png


BIN
Images/wiki/qq/12.png


BIN
Images/wiki/qq/13.png


BIN
Images/wiki/qq/2.png


BIN
Images/wiki/qq/3.png


BIN
Images/wiki/qq/4.png


BIN
Images/wiki/qq/5.png


BIN
Images/wiki/qq/6.png


BIN
Images/wiki/qq/7.png


BIN
Images/wiki/qq/8.png


BIN
Images/wiki/qq/9.png


BIN
Images/wiki/wechat/10_mutex.png


BIN
Images/wiki/wechat/11_push_ebp_to_ret.png


BIN
Images/wiki/wechat/12_push_ebp_to_ret.png


BIN
Images/wiki/wechat/13_push_ebp_to_ret.png


BIN
Images/wiki/wechat/14_patch_dll.png


BIN
Images/wiki/wechat/1_start.png


BIN
Images/wiki/wechat/2_click_attach.png


BIN
Images/wiki/wechat/3_attach_wechat_exe.png


BIN
Images/wiki/wechat/4_wechatwin_dll.png


BIN
Images/wiki/wechat/5_search_string.png


BIN
Images/wiki/wechat/6_revokemsg_1.png


BIN
Images/wiki/wechat/7_je_to_jmp.png


BIN
Images/wiki/wechat/8_je_to_jmp.png


BIN
Images/wiki/wechat/9_je_to_jmp.png


+ 65 - 0
README.md

@@ -0,0 +1,65 @@
+
+<p align="center">
+	<a><img width="100px" src="https://raw.githubusercontent.com/huiyadanli/RevokeMsgPatcher/master/Images/logo.png"/></a>
+</p>
+<p align="center">
+	<a href="https://www.microsoft.com/download/details.aspx?id=30653">
+		<img src="https://img.shields.io/badge/platform-windows-lightgrey.svg?style=flat-square"/>
+	</a>
+	<a href="https://github.com/huiyadanli/RevokeMsgPatcher/releases">
+		<img src="https://img.shields.io/github/downloads/huiyadanli/RevokeMsgPatcher/total.svg?style=flat-square"/>
+	</a>
+	<a href="http://hits.dwyl.io/huiyadanli/RevokeMsgPatcher">
+		<img src="http://hits.dwyl.io/huiyadanli/RevokeMsgPatcher.svg"/>
+	</a>
+	<a href="https://ci.appveyor.com/project/huiyadanli/RevokeMsgPatcher">
+		<img src="https://img.shields.io/appveyor/ci/huiyadanli/RevokeMsgPatcher.svg?style=flat-square"/>
+	</a>
+</p>
+# 👀微信/QQ/TIM防撤回补丁
+
+适用于 Windows 下 PC 版微信/QQ/TIM的防撤回补丁。
+
+<img width="180px" src="https://raw.githubusercontent.com/huiyadanli/RevokeMsgPatcher/master/Images/revoke.jpg"/>
+
+下载地址:
+**[⚡️点我下载最新版本](https://github.com/huiyadanli/RevokeMsgPatcher/releases/download/0.6/RevokeMsgPatcher.v0.6.zip)** |
+[☁备用下载-蓝奏云](https://www.lanzous.com/i75gffi) | 
+[☁备用下载-百度云](https://pan.baidu.com/s/1M8rNYrhS85pFjpAASZqKlw) 提取码:coco
+
+相关文档:
+**[✔支持哪些版本](https://github.com/huiyadanli/RevokeMsgPatcher/wiki/%E7%89%88%E6%9C%AC%E6%94%AF%E6%8C%81)** | 
+[❓常见问题](https://github.com/huiyadanli/RevokeMsgPatcher/wiki#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98) | 
+[📖查看完整文档](https://github.com/huiyadanli/RevokeMsgPatcher/wiki)
+
+原理与方法:
+[📗微信](https://github.com/huiyadanli/RevokeMsgPatcher/wiki/%E5%BE%AE%E4%BF%A1%E9%98%B2%E6%92%A4%E5%9B%9E%E4%B8%8E%E5%A4%9A%E5%BC%80%E6%95%99%E7%A8%8B) |
+[📕QQ](https://github.com/huiyadanli/RevokeMsgPatcher/wiki/QQ%E6%88%96TIM%E9%98%B2%E6%92%A4%E5%9B%9E%E6%95%99%E7%A8%8B) |
+[📘TIM](https://github.com/huiyadanli/RevokeMsgPatcher/wiki/QQ%E6%88%96TIM%E9%98%B2%E6%92%A4%E5%9B%9E%E6%95%99%E7%A8%8B)
+
+## 📷截图
+![Screenshot](https://raw.githubusercontent.com/huiyadanli/RevokeMsgPatcher/master/Images/screenshot.png)
+
+## 🔨使用方法
+
+1. 首先,你的系统需要满足以下条件:
+
+    * Windows 7 或更高版本,**不支持XP**。
+    * [.NET Framework 4.5](https://www.microsoft.com/zh-cn/download/details.aspx?id=30653) 或更高版本。**低于此版本在打开程序时可能无反应,或者直接报错**。
+
+2. 使用本程序前,先关闭微信/QQ/TIM。
+
+3. **以管理员身份运行本程序**,等待右下角获取最新的补丁信息。
+
+4. 选择微信/QQ/TIM的安装路径。如果你用的安装版的微信/QQ/TIM,正常情况下本程序会自动从注册表中获取安装路径,绿色版需要手动选择路径。
+
+5. 点击防撤回。界面可能会出现一段时间的无响应,请耐心等待。**由于修改了微信的 WeChatWin.dll 文件、QQ/TIM的 IM.dll 文件,杀毒软件可能会弹出警告,放行即可。**
+
+## ❤️Thanks
+
+[wechat_anti_revoke](https://github.com/36huo/wechat_anti_revoke)
+
+## 📄License
+GPLv3
+
+![](https://raw.githubusercontent.com/huiyadanli/RevokeMsgPatcher/master/Images/give_a_star.png)

+ 6 - 0
RevokeMsgPatcher.Assistant/App.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
+    </startup>
+</configuration>

+ 48 - 0
RevokeMsgPatcher.Assistant/FormAssisant.Designer.cs

@@ -0,0 +1,48 @@
+namespace RevokeMsgPatcher.Assistant
+{
+    partial class FormAssisant
+    {
+        /// <summary>
+        /// 必需的设计器变量。
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// 清理所有正在使用的资源。
+        /// </summary>
+        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows 窗体设计器生成的代码
+
+        /// <summary>
+        /// 设计器支持所需的方法 - 不要修改
+        /// 使用代码编辑器修改此方法的内容。
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.SuspendLayout();
+            // 
+            // FormMain
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(508, 262);
+            this.Name = "FormMain";
+            this.Text = "冷血无情的助手界面";
+            this.Load += new System.EventHandler(this.FormMain_Load);
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+    }
+}
+

+ 25 - 0
RevokeMsgPatcher.Assistant/FormAssisant.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace RevokeMsgPatcher.Assistant
+{
+    public partial class FormAssisant : Form
+    {
+        public FormAssisant()
+        {
+            InitializeComponent();
+        }
+
+        private void FormMain_Load(object sender, EventArgs e)
+        {
+            Console.WriteLine(new JsonData().BagJson());
+        }
+    }
+}

+ 120 - 0
RevokeMsgPatcher.Assistant/FormAssisant.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

File diff suppressed because it is too large
+ 138 - 0
RevokeMsgPatcher.Assistant/JsonData.cs


+ 22 - 0
RevokeMsgPatcher.Assistant/Program.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace RevokeMsgPatcher.Assistant
+{
+    static class Program
+    {
+        /// <summary>
+        /// 应用程序的主入口点。
+        /// </summary>
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            Application.Run(new FormAssisant());
+        }
+    }
+}

+ 36 - 0
RevokeMsgPatcher.Assistant/Properties/AssemblyInfo.cs

@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("RevokeMsgPatcher.Assistant")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("RevokeMsgPatcher.Assistant")]
+[assembly: AssemblyCopyright("Copyright ©  2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("6992004f-17e6-45bf-8d72-180a31e9c23c")]
+
+// 程序集的版本信息由下列四个值组成: 
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
+// 方法是按如下所示使用“*”: :
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 71 - 0
RevokeMsgPatcher.Assistant/Properties/Resources.Designer.cs

@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     此代码由工具生成。
+//     运行时版本: 4.0.30319.42000
+//
+//     对此文件的更改可能导致不正确的行为,如果
+//     重新生成代码,则所做更改将丢失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace RevokeMsgPatcher.Assistant.Properties
+{
+
+
+    /// <summary>
+    ///   强类型资源类,用于查找本地化字符串等。
+    /// </summary>
+    // 此类是由 StronglyTypedResourceBuilder
+    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+    // 若要添加或删除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+    // (以 /str 作为命令选项),或重新生成 VS 项目。
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources
+    {
+
+        private static global::System.Resources.ResourceManager resourceMan;
+
+        private static global::System.Globalization.CultureInfo resourceCulture;
+
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources()
+        {
+        }
+
+        /// <summary>
+        ///   返回此类使用的缓存 ResourceManager 实例。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager
+        {
+            get
+            {
+                if ((resourceMan == null))
+                {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RevokeMsgPatcher.Assistant.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+
+        /// <summary>
+        ///   覆盖当前线程的 CurrentUICulture 属性
+        ///   使用此强类型的资源类的资源查找。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture
+        {
+            get
+            {
+                return resourceCulture;
+            }
+            set
+            {
+                resourceCulture = value;
+            }
+        }
+    }
+}

+ 117 - 0
RevokeMsgPatcher.Assistant/Properties/Resources.resx

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 30 - 0
RevokeMsgPatcher.Assistant/Properties/Settings.Designer.cs

@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace RevokeMsgPatcher.Assistant.Properties
+{
+
+
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}

+ 7 - 0
RevokeMsgPatcher.Assistant/Properties/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 112 - 0
RevokeMsgPatcher.Assistant/RevokeMsgPatcher.Assistant.csproj

@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{6992004F-17E6-45BF-8D72-180A31E9C23C}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>RevokeMsgPatcher.Assistant</RootNamespace>
+    <AssemblyName>RevokeMsgPatcher.Assistant</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+    <PublishUrl>publish\</PublishUrl>
+    <Install>true</Install>
+    <InstallFrom>Disk</InstallFrom>
+    <UpdateEnabled>false</UpdateEnabled>
+    <UpdateMode>Foreground</UpdateMode>
+    <UpdateInterval>7</UpdateInterval>
+    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+    <UpdatePeriodically>false</UpdatePeriodically>
+    <UpdateRequired>false</UpdateRequired>
+    <MapFileExtensions>true</MapFileExtensions>
+    <ApplicationRevision>0</ApplicationRevision>
+    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+    <IsWebBootstrapper>false</IsWebBootstrapper>
+    <UseApplicationTrust>false</UseApplicationTrust>
+    <BootstrapperEnabled>true</BootstrapperEnabled>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Web.Extensions" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="FormAssisant.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="FormAssisant.Designer.cs">
+      <DependentUpon>FormAssisant.cs</DependentUpon>
+    </Compile>
+    <Compile Include="JsonData.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <EmbeddedResource Include="FormAssisant.resx">
+      <DependentUpon>FormAssisant.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\RevokeMsgPatcher\RevokeMsgPatcher.csproj">
+      <Project>{977bf781-ced8-4389-9404-0fa08fdf21df}</Project>
+      <Name>RevokeMsgPatcher</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 6 - 0
RevokeMsgPatcher.MultiInstance/App.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
+    </startup>
+</configuration>

+ 186 - 0
RevokeMsgPatcher.MultiInstance/FormMultiInstance.Designer.cs

@@ -0,0 +1,186 @@
+namespace RevokeMsgPatcher.MultiInstance
+{
+    partial class FormMultiInstance
+    {
+        /// <summary>
+        /// 必需的设计器变量。
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// 清理所有正在使用的资源。
+        /// </summary>
+        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows 窗体设计器生成的代码
+
+        /// <summary>
+        /// 设计器支持所需的方法 - 不要修改
+        /// 使用代码编辑器修改此方法的内容。
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.txtPath = new System.Windows.Forms.TextBox();
+            this.lblPathTag = new System.Windows.Forms.Label();
+            this.btnChoosePath = new System.Windows.Forms.Button();
+            this.label1 = new System.Windows.Forms.Label();
+            this.label2 = new System.Windows.Forms.Label();
+            this.startNum = new System.Windows.Forms.NumericUpDown();
+            this.btnStart = new System.Windows.Forms.Button();
+            this.linkLabel1 = new System.Windows.Forms.LinkLabel();
+            this.label3 = new System.Windows.Forms.Label();
+            this.label4 = new System.Windows.Forms.Label();
+            ((System.ComponentModel.ISupportInitialize)(this.startNum)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // txtPath
+            // 
+            this.txtPath.Location = new System.Drawing.Point(86, 60);
+            this.txtPath.Name = "txtPath";
+            this.txtPath.Size = new System.Drawing.Size(332, 21);
+            this.txtPath.TabIndex = 7;
+            // 
+            // lblPathTag
+            // 
+            this.lblPathTag.AutoSize = true;
+            this.lblPathTag.Location = new System.Drawing.Point(15, 63);
+            this.lblPathTag.Name = "lblPathTag";
+            this.lblPathTag.Size = new System.Drawing.Size(65, 12);
+            this.lblPathTag.TabIndex = 6;
+            this.lblPathTag.Text = "微信路径:";
+            // 
+            // btnChoosePath
+            // 
+            this.btnChoosePath.Location = new System.Drawing.Point(424, 59);
+            this.btnChoosePath.Name = "btnChoosePath";
+            this.btnChoosePath.Size = new System.Drawing.Size(36, 23);
+            this.btnChoosePath.TabIndex = 8;
+            this.btnChoosePath.Text = "...";
+            this.btnChoosePath.UseVisualStyleBackColor = true;
+            this.btnChoosePath.Click += new System.EventHandler(this.btnChoosePath_Click);
+            // 
+            // label1
+            // 
+            this.label1.AutoSize = true;
+            this.label1.Location = new System.Drawing.Point(84, 151);
+            this.label1.Name = "label1";
+            this.label1.Size = new System.Drawing.Size(293, 24);
+            this.label1.TabIndex = 9;
+            this.label1.Text = "只要我打开微信的速度足够快,我就多开无限个微信。\r\n我为什么能手动双开微信,别问,问就是手速快。";
+            // 
+            // label2
+            // 
+            this.label2.AutoSize = true;
+            this.label2.Location = new System.Drawing.Point(15, 95);
+            this.label2.Name = "label2";
+            this.label2.Size = new System.Drawing.Size(65, 12);
+            this.label2.TabIndex = 11;
+            this.label2.Text = "多开个数:";
+            // 
+            // startNum
+            // 
+            this.startNum.Location = new System.Drawing.Point(86, 91);
+            this.startNum.Maximum = new decimal(new int[] {
+            1000,
+            0,
+            0,
+            0});
+            this.startNum.Minimum = new decimal(new int[] {
+            2,
+            0,
+            0,
+            0});
+            this.startNum.Name = "startNum";
+            this.startNum.Size = new System.Drawing.Size(55, 21);
+            this.startNum.TabIndex = 12;
+            this.startNum.Value = new decimal(new int[] {
+            2,
+            0,
+            0,
+            0});
+            // 
+            // btnStart
+            // 
+            this.btnStart.Location = new System.Drawing.Point(369, 91);
+            this.btnStart.Name = "btnStart";
+            this.btnStart.Size = new System.Drawing.Size(91, 23);
+            this.btnStart.TabIndex = 13;
+            this.btnStart.Text = "点击启动!";
+            this.btnStart.UseVisualStyleBackColor = true;
+            this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
+            // 
+            // linkLabel1
+            // 
+            this.linkLabel1.AutoSize = true;
+            this.linkLabel1.Location = new System.Drawing.Point(284, 95);
+            this.linkLabel1.Name = "linkLabel1";
+            this.linkLabel1.Size = new System.Drawing.Size(41, 12);
+            this.linkLabel1.TabIndex = 15;
+            this.linkLabel1.TabStop = true;
+            this.linkLabel1.Text = "GitHub";
+            this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked);
+            // 
+            // label3
+            // 
+            this.label3.AutoSize = true;
+            this.label3.Location = new System.Drawing.Point(177, 95);
+            this.label3.Name = "label3";
+            this.label3.Size = new System.Drawing.Size(101, 12);
+            this.label3.TabIndex = 14;
+            this.label3.Text = "软件主页(开源):";
+            // 
+            // label4
+            // 
+            this.label4.AutoSize = true;
+            this.label4.Location = new System.Drawing.Point(12, 9);
+            this.label4.Name = "label4";
+            this.label4.Size = new System.Drawing.Size(323, 36);
+            this.label4.TabIndex = 16;
+            this.label4.Text = "注意:\r\n1. 使用本程序多开前不能存在正在运行的微信进程。\r\n2. 多开的成功率取决于你的机器性能,不保证每次都能成功";
+            // 
+            // FormMultiInstance
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(472, 133);
+            this.Controls.Add(this.label4);
+            this.Controls.Add(this.linkLabel1);
+            this.Controls.Add(this.label3);
+            this.Controls.Add(this.btnStart);
+            this.Controls.Add(this.startNum);
+            this.Controls.Add(this.label2);
+            this.Controls.Add(this.label1);
+            this.Controls.Add(this.txtPath);
+            this.Controls.Add(this.lblPathTag);
+            this.Controls.Add(this.btnChoosePath);
+            this.Name = "FormMultiInstance";
+            this.Text = "微信多开小工具";
+            ((System.ComponentModel.ISupportInitialize)(this.startNum)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.TextBox txtPath;
+        private System.Windows.Forms.Label lblPathTag;
+        private System.Windows.Forms.Button btnChoosePath;
+        private System.Windows.Forms.Label label1;
+        private System.Windows.Forms.Label label2;
+        private System.Windows.Forms.NumericUpDown startNum;
+        private System.Windows.Forms.Button btnStart;
+        private System.Windows.Forms.LinkLabel linkLabel1;
+        private System.Windows.Forms.Label label3;
+        private System.Windows.Forms.Label label4;
+    }
+}
+

+ 111 - 0
RevokeMsgPatcher.MultiInstance/FormMultiInstance.cs

@@ -0,0 +1,111 @@
+using Microsoft.Win32;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Diagnostics;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace RevokeMsgPatcher.MultiInstance
+{
+    public partial class FormMultiInstance : Form
+    {
+        public FormMultiInstance()
+        {
+            InitializeComponent();
+            string installFolder = FindInstallPathFromRegistry("Wechat");
+            if (!string.IsNullOrEmpty(installFolder))
+            {
+                string wechatPath = Path.Combine(installFolder, "WeChat.exe");
+                if (File.Exists(wechatPath))
+                {
+                    txtPath.Text = wechatPath;
+                }
+            }
+        }
+
+        private void btnChoosePath_Click(object sender, EventArgs e)
+        {
+            OpenFileDialog dialog = new OpenFileDialog
+            {
+                Multiselect = false,
+                Title = "请选择微信启动主程序",
+                Filter = "微信主程序|WeChat.exe"
+            };
+            if (dialog.ShowDialog() == DialogResult.OK)
+            {
+                txtPath.Text = dialog.FileName;
+            }
+        }
+
+        private void btnStart_Click(object sender, EventArgs e)
+        {
+            if (File.Exists(txtPath.Text))
+            {
+                // 检测微信进程是否存在
+                Process[] ps = Process.GetProcessesByName("WeChat");
+                if (ps.Length > 0)
+                {
+                    DialogResult result = MessageBox.Show("当前存在运行中的微信进程,请先关闭当前微信才能使用该功能。点击【确定】强制关闭当前所有微信进程并进行多开,点击【取消】不做任何处理。", "当前存在运行中的微信进程", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
+                    if (result == DialogResult.OK)
+                    {
+                        foreach (Process p in ps)
+                            p.Kill();
+                    }
+                    else
+                    {
+                        return;
+                    }
+                }
+                // 启动多个实例
+                for (int i = 0; i < startNum.Value; i++)
+                {
+                    Process.Start(txtPath.Text);
+                }
+            }
+        }
+
+        private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
+        {
+            Process.Start("https://github.com/huiyadanli/RevokeMsgPatcher");
+        }
+
+        /// <summary>
+        /// 从注册表中寻找安装路径
+        /// </summary>
+        /// <param name="uninstallKeyName">
+        /// 安装信息的注册表键名
+        /// 微信:WeChat
+        /// QQ:{052CFB79-9D62-42E3-8A15-DE66C2C97C3E} 
+        /// TIM:TIM
+        /// </param>
+        /// <returns>安装路径</returns>
+        public static string FindInstallPathFromRegistry(string uninstallKeyName)
+        {
+            try
+            {
+                RegistryKey key = Registry.LocalMachine.OpenSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{uninstallKeyName}");
+                if (key == null)
+                {
+                    return null;
+                }
+                object installLocation = key.GetValue("InstallLocation");
+                key.Close();
+                if (installLocation != null && !string.IsNullOrEmpty(installLocation.ToString()))
+                {
+                    return installLocation.ToString();
+                }
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e.Message);
+            }
+            return null;
+        }
+    }
+}

+ 120 - 0
RevokeMsgPatcher.MultiInstance/FormMultiInstance.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 22 - 0
RevokeMsgPatcher.MultiInstance/Program.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace RevokeMsgPatcher.MultiInstance
+{
+    static class Program
+    {
+        /// <summary>
+        /// 应用程序的主入口点。
+        /// </summary>
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            Application.Run(new FormMultiInstance());
+        }
+    }
+}

+ 36 - 0
RevokeMsgPatcher.MultiInstance/Properties/AssemblyInfo.cs

@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("RevokeMsgPatcher.MultiInstance")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("RevokeMsgPatcher.MultiInstance")]
+[assembly: AssemblyCopyright("Copyright ©  2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("73043ca8-af54-4591-9174-40fb6e0a3d36")]
+
+// 程序集的版本信息由下列四个值组成: 
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
+// 方法是按如下所示使用“*”: :
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 71 - 0
RevokeMsgPatcher.MultiInstance/Properties/Resources.Designer.cs

@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     此代码由工具生成。
+//     运行时版本: 4.0.30319.42000
+//
+//     对此文件的更改可能导致不正确的行为,如果
+//     重新生成代码,则所做更改将丢失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace RevokeMsgPatcher.MultiInstance.Properties
+{
+
+
+    /// <summary>
+    ///   强类型资源类,用于查找本地化字符串等。
+    /// </summary>
+    // 此类是由 StronglyTypedResourceBuilder
+    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+    // 若要添加或删除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+    // (以 /str 作为命令选项),或重新生成 VS 项目。
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources
+    {
+
+        private static global::System.Resources.ResourceManager resourceMan;
+
+        private static global::System.Globalization.CultureInfo resourceCulture;
+
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources()
+        {
+        }
+
+        /// <summary>
+        ///   返回此类使用的缓存 ResourceManager 实例。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager
+        {
+            get
+            {
+                if ((resourceMan == null))
+                {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RevokeMsgPatcher.MultiInstance.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+
+        /// <summary>
+        ///   覆盖当前线程的 CurrentUICulture 属性
+        ///   使用此强类型的资源类的资源查找。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture
+        {
+            get
+            {
+                return resourceCulture;
+            }
+            set
+            {
+                resourceCulture = value;
+            }
+        }
+    }
+}

+ 117 - 0
RevokeMsgPatcher.MultiInstance/Properties/Resources.resx

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 30 - 0
RevokeMsgPatcher.MultiInstance/Properties/Settings.Designer.cs

@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace RevokeMsgPatcher.MultiInstance.Properties
+{
+
+
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}

+ 7 - 0
RevokeMsgPatcher.MultiInstance/Properties/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 82 - 0
RevokeMsgPatcher.MultiInstance/RevokeMsgPatcher.MultiInstance.csproj

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{73043CA8-AF54-4591-9174-40FB6E0A3D36}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>RevokeMsgPatcher.MultiInstance</RootNamespace>
+    <AssemblyName>RevokeMsgPatcher.MultiInstance</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="FormMultiInstance.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="FormMultiInstance.Designer.cs">
+      <DependentUpon>FormMultiInstance.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <EmbeddedResource Include="FormMultiInstance.resx">
+      <DependentUpon>FormMultiInstance.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 37 - 0
RevokeMsgPatcher.sln

@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28010.2016
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RevokeMsgPatcher", "RevokeMsgPatcher\RevokeMsgPatcher.csproj", "{977BF781-CED8-4389-9404-0FA08FDF21DF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RevokeMsgPatcher.Assistant", "RevokeMsgPatcher.Assistant\RevokeMsgPatcher.Assistant.csproj", "{6992004F-17E6-45BF-8D72-180A31E9C23C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RevokeMsgPatcher.MultiInstance", "RevokeMsgPatcher.MultiInstance\RevokeMsgPatcher.MultiInstance.csproj", "{73043CA8-AF54-4591-9174-40FB6E0A3D36}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{977BF781-CED8-4389-9404-0FA08FDF21DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{977BF781-CED8-4389-9404-0FA08FDF21DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{977BF781-CED8-4389-9404-0FA08FDF21DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{977BF781-CED8-4389-9404-0FA08FDF21DF}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6992004F-17E6-45BF-8D72-180A31E9C23C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6992004F-17E6-45BF-8D72-180A31E9C23C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6992004F-17E6-45BF-8D72-180A31E9C23C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6992004F-17E6-45BF-8D72-180A31E9C23C}.Release|Any CPU.Build.0 = Release|Any CPU
+		{73043CA8-AF54-4591-9174-40FB6E0A3D36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{73043CA8-AF54-4591-9174-40FB6E0A3D36}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{73043CA8-AF54-4591-9174-40FB6E0A3D36}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{73043CA8-AF54-4591-9174-40FB6E0A3D36}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {56DAEA8C-77F7-4E55-A7AF-CE9F23F2C1A6}
+	EndGlobalSection
+EndGlobal

+ 6 - 0
RevokeMsgPatcher/App.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
+    </startup>
+</configuration>

+ 14 - 0
RevokeMsgPatcher/BusinessException.cs

@@ -0,0 +1,14 @@
+using System;
+
+namespace RevokeMsgPatcher
+{
+    class BusinessException : ApplicationException
+    {
+        public string ErrorCode { get; protected set; }
+
+        public BusinessException(string errcode, string message) : base(message)
+        {
+            ErrorCode = errcode;
+        }
+    }
+}

+ 265 - 0
RevokeMsgPatcher/FormMain.Designer.cs

@@ -0,0 +1,265 @@
+namespace RevokeMsgPatcher
+{
+    partial class FormMain
+    {
+        /// <summary>
+        /// 必需的设计器变量。
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// 清理所有正在使用的资源。
+        /// </summary>
+        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows 窗体设计器生成的代码
+
+        /// <summary>
+        /// 设计器支持所需的方法 - 不要修改
+        /// 使用代码编辑器修改此方法的内容。
+        /// </summary>
+        private void InitializeComponent()
+        {
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormMain));
+            this.lblPathTag = new System.Windows.Forms.Label();
+            this.btnPatch = new System.Windows.Forms.Button();
+            this.txtPath = new System.Windows.Forms.TextBox();
+            this.btnChoosePath = new System.Windows.Forms.Button();
+            this.label1 = new System.Windows.Forms.Label();
+            this.linkLabel1 = new System.Windows.Forms.LinkLabel();
+            this.btnRestore = new System.Windows.Forms.Button();
+            this.lblUpdatePachJson = new System.Windows.Forms.Label();
+            this.lblVersion = new System.Windows.Forms.Label();
+            this.lblVersionTag = new System.Windows.Forms.Label();
+            this.rbtWechat = new System.Windows.Forms.RadioButton();
+            this.rbtQQ = new System.Windows.Forms.RadioButton();
+            this.rbtTIM = new System.Windows.Forms.RadioButton();
+            this.label5 = new System.Windows.Forms.Label();
+            this.panelMask = new System.Windows.Forms.Panel();
+            this.rbtQQLite = new System.Windows.Forms.RadioButton();
+            this.SuspendLayout();
+            // 
+            // lblPathTag
+            // 
+            this.lblPathTag.AutoSize = true;
+            this.lblPathTag.Location = new System.Drawing.Point(12, 34);
+            this.lblPathTag.Name = "lblPathTag";
+            this.lblPathTag.Size = new System.Drawing.Size(65, 12);
+            this.lblPathTag.TabIndex = 1;
+            this.lblPathTag.Text = "应用路径:";
+            // 
+            // btnPatch
+            // 
+            this.btnPatch.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.btnPatch.Location = new System.Drawing.Point(372, 56);
+            this.btnPatch.Name = "btnPatch";
+            this.btnPatch.Size = new System.Drawing.Size(102, 23);
+            this.btnPatch.TabIndex = 3;
+            this.btnPatch.Text = "点我防撤回!";
+            this.btnPatch.UseVisualStyleBackColor = true;
+            this.btnPatch.Click += new System.EventHandler(this.btnPatch_Click);
+            // 
+            // txtPath
+            // 
+            this.txtPath.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.txtPath.Location = new System.Drawing.Point(82, 29);
+            this.txtPath.Name = "txtPath";
+            this.txtPath.Size = new System.Drawing.Size(352, 21);
+            this.txtPath.TabIndex = 4;
+            this.txtPath.TextChanged += new System.EventHandler(this.txtPath_TextChanged);
+            // 
+            // btnChoosePath
+            // 
+            this.btnChoosePath.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.btnChoosePath.Location = new System.Drawing.Point(440, 27);
+            this.btnChoosePath.Name = "btnChoosePath";
+            this.btnChoosePath.Size = new System.Drawing.Size(34, 23);
+            this.btnChoosePath.TabIndex = 5;
+            this.btnChoosePath.Text = "...";
+            this.btnChoosePath.UseVisualStyleBackColor = true;
+            this.btnChoosePath.Click += new System.EventHandler(this.btnChoosePath_Click);
+            // 
+            // label1
+            // 
+            this.label1.AutoSize = true;
+            this.label1.Location = new System.Drawing.Point(12, 87);
+            this.label1.Name = "label1";
+            this.label1.Size = new System.Drawing.Size(101, 12);
+            this.label1.TabIndex = 6;
+            this.label1.Text = "软件主页(开源):";
+            // 
+            // linkLabel1
+            // 
+            this.linkLabel1.AutoSize = true;
+            this.linkLabel1.Location = new System.Drawing.Point(119, 87);
+            this.linkLabel1.Name = "linkLabel1";
+            this.linkLabel1.Size = new System.Drawing.Size(41, 12);
+            this.linkLabel1.TabIndex = 7;
+            this.linkLabel1.TabStop = true;
+            this.linkLabel1.Text = "GitHub";
+            this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked);
+            // 
+            // btnRestore
+            // 
+            this.btnRestore.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.btnRestore.Location = new System.Drawing.Point(264, 56);
+            this.btnRestore.Name = "btnRestore";
+            this.btnRestore.Size = new System.Drawing.Size(102, 23);
+            this.btnRestore.TabIndex = 8;
+            this.btnRestore.Text = "备份还原";
+            this.btnRestore.UseVisualStyleBackColor = true;
+            this.btnRestore.Click += new System.EventHandler(this.btnRestore_Click);
+            // 
+            // lblUpdatePachJson
+            // 
+            this.lblUpdatePachJson.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.lblUpdatePachJson.Cursor = System.Windows.Forms.Cursors.Hand;
+            this.lblUpdatePachJson.ForeColor = System.Drawing.SystemColors.Highlight;
+            this.lblUpdatePachJson.Location = new System.Drawing.Point(166, 89);
+            this.lblUpdatePachJson.Name = "lblUpdatePachJson";
+            this.lblUpdatePachJson.Size = new System.Drawing.Size(308, 12);
+            this.lblUpdatePachJson.TabIndex = 9;
+            this.lblUpdatePachJson.Text = "[ 获取最新补丁信息中... ]";
+            this.lblUpdatePachJson.TextAlign = System.Drawing.ContentAlignment.TopRight;
+            this.lblUpdatePachJson.Click += new System.EventHandler(this.lblUpdatePachJson_Click);
+            // 
+            // lblVersion
+            // 
+            this.lblVersion.AutoSize = true;
+            this.lblVersion.Location = new System.Drawing.Point(83, 61);
+            this.lblVersion.Name = "lblVersion";
+            this.lblVersion.Size = new System.Drawing.Size(0, 12);
+            this.lblVersion.TabIndex = 10;
+            // 
+            // lblVersionTag
+            // 
+            this.lblVersionTag.AutoSize = true;
+            this.lblVersionTag.Location = new System.Drawing.Point(12, 61);
+            this.lblVersionTag.Name = "lblVersionTag";
+            this.lblVersionTag.Size = new System.Drawing.Size(65, 12);
+            this.lblVersionTag.TabIndex = 9;
+            this.lblVersionTag.Text = "当前版本:";
+            // 
+            // rbtWechat
+            // 
+            this.rbtWechat.AutoSize = true;
+            this.rbtWechat.Checked = true;
+            this.rbtWechat.Location = new System.Drawing.Point(82, 7);
+            this.rbtWechat.Name = "rbtWechat";
+            this.rbtWechat.Size = new System.Drawing.Size(47, 16);
+            this.rbtWechat.TabIndex = 12;
+            this.rbtWechat.TabStop = true;
+            this.rbtWechat.Text = "微信";
+            this.rbtWechat.UseVisualStyleBackColor = true;
+            this.rbtWechat.CheckedChanged += new System.EventHandler(this.radioButtons_CheckedChanged);
+            // 
+            // rbtQQ
+            // 
+            this.rbtQQ.AutoSize = true;
+            this.rbtQQ.Location = new System.Drawing.Point(140, 7);
+            this.rbtQQ.Name = "rbtQQ";
+            this.rbtQQ.Size = new System.Drawing.Size(35, 16);
+            this.rbtQQ.TabIndex = 13;
+            this.rbtQQ.Text = "QQ";
+            this.rbtQQ.UseVisualStyleBackColor = true;
+            this.rbtQQ.CheckedChanged += new System.EventHandler(this.radioButtons_CheckedChanged);
+            // 
+            // rbtTIM
+            // 
+            this.rbtTIM.AutoSize = true;
+            this.rbtTIM.Location = new System.Drawing.Point(186, 7);
+            this.rbtTIM.Name = "rbtTIM";
+            this.rbtTIM.Size = new System.Drawing.Size(41, 16);
+            this.rbtTIM.TabIndex = 14;
+            this.rbtTIM.Text = "TIM";
+            this.rbtTIM.UseVisualStyleBackColor = true;
+            this.rbtTIM.CheckedChanged += new System.EventHandler(this.radioButtons_CheckedChanged);
+            // 
+            // label5
+            // 
+            this.label5.AutoSize = true;
+            this.label5.Location = new System.Drawing.Point(12, 9);
+            this.label5.Name = "label5";
+            this.label5.Size = new System.Drawing.Size(65, 12);
+            this.label5.TabIndex = 16;
+            this.label5.Text = "选择应用:";
+            // 
+            // panelMask
+            // 
+            this.panelMask.Location = new System.Drawing.Point(140, 127);
+            this.panelMask.Name = "panelMask";
+            this.panelMask.Size = new System.Drawing.Size(157, 58);
+            this.panelMask.TabIndex = 17;
+            this.panelMask.Visible = false;
+            // 
+            // rbtQQLite
+            // 
+            this.rbtQQLite.AutoSize = true;
+            this.rbtQQLite.Location = new System.Drawing.Point(235, 7);
+            this.rbtQQLite.Name = "rbtQQLite";
+            this.rbtQQLite.Size = new System.Drawing.Size(71, 16);
+            this.rbtQQLite.TabIndex = 18;
+            this.rbtQQLite.Text = "QQ轻聊版";
+            this.rbtQQLite.UseVisualStyleBackColor = true;
+            // 
+            // FormMain
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(490, 110);
+            this.Controls.Add(this.rbtQQLite);
+            this.Controls.Add(this.lblUpdatePachJson);
+            this.Controls.Add(this.panelMask);
+            this.Controls.Add(this.lblVersion);
+            this.Controls.Add(this.lblVersionTag);
+            this.Controls.Add(this.label5);
+            this.Controls.Add(this.txtPath);
+            this.Controls.Add(this.rbtTIM);
+            this.Controls.Add(this.lblPathTag);
+            this.Controls.Add(this.rbtQQ);
+            this.Controls.Add(this.btnRestore);
+            this.Controls.Add(this.rbtWechat);
+            this.Controls.Add(this.btnPatch);
+            this.Controls.Add(this.btnChoosePath);
+            this.Controls.Add(this.linkLabel1);
+            this.Controls.Add(this.label1);
+            this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
+            this.MinimumSize = new System.Drawing.Size(506, 149);
+            this.Name = "FormMain";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "微信/QQ/TIM防撤回补丁";
+            this.Load += new System.EventHandler(this.FormMain_Load);
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+        private System.Windows.Forms.Label lblPathTag;
+        private System.Windows.Forms.Button btnPatch;
+        private System.Windows.Forms.TextBox txtPath;
+        private System.Windows.Forms.Button btnChoosePath;
+        private System.Windows.Forms.Label label1;
+        private System.Windows.Forms.LinkLabel linkLabel1;
+        private System.Windows.Forms.Button btnRestore;
+        private System.Windows.Forms.Label lblUpdatePachJson;
+        private System.Windows.Forms.Label lblVersion;
+        private System.Windows.Forms.Label lblVersionTag;
+        private System.Windows.Forms.RadioButton rbtWechat;
+        private System.Windows.Forms.RadioButton rbtQQ;
+        private System.Windows.Forms.RadioButton rbtTIM;
+        private System.Windows.Forms.Label label5;
+        private System.Windows.Forms.Panel panelMask;
+        private System.Windows.Forms.RadioButton rbtQQLite;
+    }
+}
+

+ 359 - 0
RevokeMsgPatcher/FormMain.cs

@@ -0,0 +1,359 @@
+using RevokeMsgPatcher.Model;
+using RevokeMsgPatcher.Modifier;
+using RevokeMsgPatcher.Utils;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+using System.Web.Script.Serialization;
+using System.Windows.Forms;
+
+namespace RevokeMsgPatcher
+{
+    public partial class FormMain : Form
+    {
+        // 当前使用的修改者
+        private AppModifier modifier = null;
+
+        private WechatModifier wechatModifier = null;
+        private QQModifier qqModifier = null;
+        private TIMModifier timModifier = null;
+        private QQLiteModifier qqLiteModifier = null;
+
+        private string thisVersion;
+        private bool needUpdate = false;
+
+        private GAHelper ga = new GAHelper(); // Google Analytics 记录
+
+        public void InitModifier()
+        {
+            // 从配置文件中读取配置
+            JavaScriptSerializer serializer = new JavaScriptSerializer();
+            Bag bag = serializer.Deserialize<Bag>(Properties.Resources.PatchJson);
+
+            // 初始化每个应用对应的修改者
+            wechatModifier = new WechatModifier(bag.Apps["Wechat"]);
+            qqModifier = new QQModifier(bag.Apps["QQ"]);
+            timModifier = new TIMModifier(bag.Apps["TIM"]);
+            qqLiteModifier = new QQLiteModifier(bag.Apps["QQLite"]);
+
+            rbtWechat.Tag = wechatModifier;
+            rbtQQ.Tag = qqModifier;
+            rbtTIM.Tag = timModifier;
+            rbtQQLite.Tag = qqLiteModifier;
+
+            // 默认微信
+            rbtWechat.Enabled = true;
+            modifier = wechatModifier;
+        }
+
+        public FormMain()
+        {
+            InitializeComponent();
+
+            // 标题加上版本号
+            string currentVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
+            if (currentVersion.Length > 3)
+            {
+                thisVersion = currentVersion.Substring(0, 3);
+                currentVersion = " v" + thisVersion;
+            }
+            this.Text += currentVersion;
+
+            InitModifier();
+            InitControls();
+
+            ga.RequestPageView($"/main/{thisVersion}", $"进入{thisVersion}版本主界面");
+        }
+
+        private void InitControls()
+        {
+            // 自动获取应用安装路径
+            txtPath.Text = modifier.FindInstallPath();
+            btnRestore.Enabled = false;
+            // 显示是否能够备份还原
+            if (!string.IsNullOrEmpty(txtPath.Text))
+            {
+                modifier.InitEditors(txtPath.Text);
+                lblVersion.Text = modifier.GetVersion();
+                btnRestore.Enabled = modifier.BackupExists();
+            }
+        }
+
+        private void btnPatch_Click(object sender, EventArgs e)
+        {
+            if (!modifier.IsAllFilesExist(txtPath.Text))
+            {
+                MessageBox.Show("请选择正确的安装路径!");
+                return;
+            }
+
+            // 记录点了什么应用的防撤回
+            string enName = GetCheckedRadioButtonNameEn(); // 应用英文名
+            string version = modifier.GetVersion(); // 应用版本
+            ga.RequestPageView($"{enName}/{version}/patch", "点击防撤回");
+
+            EnableAllButton(false);
+            // a.重新初始化编辑器
+            modifier.InitEditors(txtPath.Text);
+            // b.计算SHA1,验证文件完整性,寻找对应的补丁信息
+            try
+            {
+                modifier.ValidateAndFindModifyInfo();
+            }
+            catch (BusinessException ex)
+            {
+                ga.RequestPageView($"{enName}/{version}/patch/sha1/ex/{ex.ErrorCode}", ex.Message);
+                MessageBox.Show(ex.Message);
+                return;
+            }
+            catch (IOException ex)
+            {
+                ga.RequestPageView($"{enName}/{version}/patch/sha1/ex/{ex.HResult.ToString("x4")}", ex.Message);
+                MessageBox.Show(ex.Message + " 请以管理员权限启动本程序,并确认当前应用(微信/QQ/TIM)处于关闭状态。");
+                return;
+            }
+            catch (Exception ex)
+            {
+                ga.RequestPageView($"{enName}/{version}/patch/sha1/ex/{ex.HResult.ToString("x4")}", ex.Message);
+                MessageBox.Show(ex.Message);
+                return;
+            }
+            finally
+            {
+                EnableAllButton(true);
+                btnRestore.Enabled = modifier.BackupExists();
+            }
+            // c.打补丁
+            try
+            {
+                modifier.Patch();
+                ga.RequestPageView($"{enName}/{version}/patch/succ", "防撤回成功");
+                MessageBox.Show("补丁安装成功!");
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex.Message);
+                ga.RequestPageView($"{enName}/{version}/patch/ex/{ex.HResult.ToString("x4")}", ex.Message);
+                MessageBox.Show(ex.Message + " 请以管理员权限启动本程序,并确认当前应用(微信/QQ/TIM)处于关闭状态。");
+            }
+            finally
+            {
+                EnableAllButton(true);
+                btnRestore.Enabled = modifier.BackupExists();
+            }
+
+        }
+
+        private void txtPath_TextChanged(object sender, EventArgs e)
+        {
+            if (modifier.IsAllFilesExist(txtPath.Text))
+            {
+                modifier.InitEditors(txtPath.Text);
+                btnRestore.Enabled = modifier.BackupExists();
+            }
+            else
+            {
+                btnPatch.Enabled = false;
+                btnRestore.Enabled = false;
+            }
+        }
+
+        private void btnChoosePath_Click(object sender, EventArgs e)
+        {
+            FolderBrowserDialog dialog = new FolderBrowserDialog();
+            dialog.Description = "请选择安装路径";
+            if (dialog.ShowDialog() == DialogResult.OK)
+            {
+                if (string.IsNullOrEmpty(dialog.SelectedPath) || !modifier.IsAllFilesExist(dialog.SelectedPath))
+                {
+                    MessageBox.Show("无法找到此应用的关键文件,请选择正确的安装路径!");
+                }
+                else
+                {
+                    txtPath.Text = dialog.SelectedPath;
+                    btnRestore.Enabled = false;
+                    // 显示是否能够备份还原
+                    if (!string.IsNullOrEmpty(txtPath.Text))
+                    {
+                        modifier.InitEditors(txtPath.Text);
+                        lblVersion.Text = modifier.GetVersion();
+                        btnRestore.Enabled = modifier.BackupExists();
+                    }
+                }
+            }
+        }
+
+        private void btnRestore_Click(object sender, EventArgs e)
+        {
+            EnableAllButton(false);
+            try
+            {
+                modifier.Restore();
+                MessageBox.Show("还原成功!");
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex.Message);
+                MessageBox.Show(ex.Message);
+            }
+            EnableAllButton(true);
+            btnRestore.Enabled = modifier.BackupExists();
+        }
+
+        private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
+        {
+            System.Diagnostics.Process.Start("https://github.com/huiyadanli/RevokeMsgPatcher");
+        }
+
+        private async void FormMain_Load(object sender, EventArgs e)
+        {
+            // 异步获取最新的补丁信息
+            string json = await GetPathJsonAsync();
+            if (string.IsNullOrEmpty(json))
+            {
+                lblUpdatePachJson.Text = "[ 获取最新补丁信息失败 ]";
+
+            }
+            else
+            {
+                try
+                {
+                    JavaScriptSerializer serializer = new JavaScriptSerializer();
+                    Bag bag = serializer.Deserialize<Bag>(json);
+
+                    wechatModifier.Config = bag.Apps["Wechat"];
+                    qqModifier.Config = bag.Apps["QQ"];
+                    timModifier.Config = bag.Apps["TIM"];
+                    qqLiteModifier.Config = bag.Apps["QQLite"];
+
+                    if (Convert.ToDecimal(bag.LatestVersion) > Convert.ToDecimal(thisVersion))
+                    {
+                        needUpdate = true;
+                        lblUpdatePachJson.Text = $"[ 存在最新版本 {bag.LatestVersion} ]";
+                    }
+                    else
+                    {
+                        needUpdate = false;
+                        lblUpdatePachJson.Text = "[ 获取成功,点击查看更多信息 ]";
+                    }
+                }
+                catch (Exception ex)
+                {
+                    Console.WriteLine(ex.Message);
+                    lblUpdatePachJson.Text = "[ 更换新配置时异常 ]";
+                }
+            }
+        }
+
+        private async Task<string> GetPathJsonAsync()
+        {
+            string downStr = null;
+            try
+            {
+                downStr = await HttpUtil.Client.GetStringAsync("https://huiyadanli.coding.me/i/revokemsg/05.json");
+            }
+            catch (Exception ex1)
+            {
+                Console.WriteLine(ex1.Message);
+                try
+                {
+                    downStr = await HttpUtil.Client.GetStringAsync("https://www.huiyadan.com/i/revokemsg/05.json");
+                }
+                catch (Exception ex2)
+                {
+                    Console.WriteLine(ex2.Message);
+                }
+            }
+            return downStr;
+        }
+
+        private void lblUpdatePachJson_Click(object sender, EventArgs e)
+        {
+            string tips = "";
+            if (needUpdate)
+            {
+                tips += "【当前存在最新版本,点击确定进入软件主页下载最新版本。】" + Environment.NewLine + Environment.NewLine;
+            }
+            tips += "支持以下版本" + Environment.NewLine;
+            tips += " ➯ 微信:" + wechatModifier.Config.GetSupportVersionStr() + Environment.NewLine;
+            tips += " ➯ QQ:" + qqModifier.Config.GetSupportVersionStr() + Environment.NewLine;
+            tips += " ➯ QQ轻聊版:" + qqLiteModifier.Config.GetSupportVersionStr() + Environment.NewLine;
+            tips += " ➯ TIM:" + timModifier.Config.GetSupportVersionStr() + Environment.NewLine;
+
+            DialogResult dr = MessageBox.Show(tips, "当前支持防撤回的版本", MessageBoxButtons.OKCancel);
+            if (dr == DialogResult.OK && needUpdate)
+            {
+                System.Diagnostics.Process.Start("https://github.com/huiyadanli/RevokeMsgPatcher/releases");
+            }
+        }
+
+        private void radioButtons_CheckedChanged(object sender, EventArgs e)
+        {
+            EnableAllButton(false);
+            RadioButton radioButton = sender as RadioButton;
+            // 切换使用不同的防撤回对象
+            if (rbtWechat.Checked)
+            {
+                modifier = (WechatModifier)rbtWechat.Tag;
+            }
+            else if (rbtQQ.Checked)
+            {
+                modifier = (QQModifier)rbtQQ.Tag;
+            }
+            else if (rbtTIM.Checked)
+            {
+                modifier = (TIMModifier)rbtTIM.Tag;
+            }
+            else if (rbtQQLite.Checked)
+            {
+                modifier = (QQLiteModifier)rbtQQLite.Tag;
+            }
+            txtPath.Text = modifier.FindInstallPath();
+            ga.RequestPageView($"{GetCheckedRadioButtonNameEn()}/{lblVersion.Text}/switch", "切换标签页");
+            EnableAllButton(true);
+            lblVersion.Text = "";
+            btnRestore.Enabled = false;
+            // 显示是否能够备份还原
+            if (!string.IsNullOrEmpty(txtPath.Text))
+            {
+                modifier.InitEditors(txtPath.Text);
+                lblVersion.Text = modifier.GetVersion();
+                btnRestore.Enabled = modifier.BackupExists();
+            }
+        }
+
+        private string GetCheckedRadioButtonNameEn()
+        {
+            if (rbtWechat.Checked)
+            {
+                return "wechat";
+            }
+            else if (rbtQQ.Checked)
+            {
+                return "qq";
+            }
+            else if (rbtTIM.Checked)
+            {
+                return "tim";
+            }
+            else if (rbtQQLite.Checked)
+            {
+                return "qqlite";
+            }
+            return "none";
+        }
+
+        private void EnableAllButton(bool state)
+        {
+            foreach (Control c in this.Controls)
+            {
+                if (c is Button)
+                {
+                    c.Enabled = state;
+                }
+            }
+        }
+    }
+}

+ 408 - 0
RevokeMsgPatcher/FormMain.resx

@@ -0,0 +1,408 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+  <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAE9FOAJPRTgOT0U4KE5EN1BPRTh2T0U4jU9FN5FPRTiRT0U4kU9F
+        OJFPRTiRT0Q4j09FN35PRTdcT0U4ME9FOBRPRTgET0U4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPRTcATkQ4Ek9FOEZORTiHT0U3yU9FN+VPRDf3T0Q3/09F
+        Nv9PRDb/T0Q1/09ENf9PRDX/T0Q1/09FNv9PRDf/T0Q3+09FN+tPRTfTTkU4m09FOFhPRTgcT0U4Ak9F
+        OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE9FOAJPRTcqT0U4eE9FOM1ORDf1T0Q2/09E
+        Nv9ORTn/TkZD/05IVf9NSmP/TUxt/01Ndv9NTnr/TU13/01Mb/9NS2X/TklY/05HR/9ORTr/T0U2/09E
+        Nv9PRTf7T0U4209FOJVPRTg8T0Q4CE9FOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT0U4Ak5FOCRPRTiNT0Q4409F
+        N/1PRDX/TkU8/01JVv9MTn//S1Ol/0pWvP9KWM7/SlnU/0pZ1/9KWtj/SlrZ/0pa2P9KWtf/SlnV/0pZ
+        0f9KV8L/S1Ss/0xQi/9NSl//TkZC/09ENv9PRDb/T0U48U9FOKtPRTg6T0U4BAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPRTcATkU3Ek5F
+        OHJPRTjfT0Q3/05FN/9OR0n/TE59/0tUrv9KWdD/SlrY/0pa2P9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa2P9KWtj/SlnU/0pWuv9MUIz/TUhV/05FOf9PRDb/T0Q3709F
+        OJdPRTgkT0U4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AABPRTgCT0U4Mk9FOLtPRDf7T0Q2/05HR/9MT4f/SlfC/0pZ1/9KWtj/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtj/SlnY/0pY
+        y/9MUZj/TUhV/05FN/9PRDf9T0U4105FN1RPRTgGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAABPRTgGTkU3XE9FN+FPRDf/TkU8/01Mc/9KVr3/SlnX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrY/0pYyv9MT4j/TkZF/05ENv9ORTfxTkU3h09FOA5PRTcAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPRTgIT0U4ek9FN/FPRTb/TkdM/0tSn/9KWdT/SlrY/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlnX/0tVtf9NSl//TkU3/09F
+        N/tPRTelT0U4GE5ENwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPRTgKT0U4iU9FN/dORTj/TUpg/0pW
+        vP9KWdj/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtj/SljK/01NeP9ORTn/T0Q3/U9FOLdPRTgcT0U4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPRTgIT0U4hU9F
+        N/lPRTf/TkdJ/0pWv/9KWtn/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWdP/TE+L/05FO/9PRDf/T0U4s09FNxgAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AABPRTgCT0U4bE9FN/VPRTf/T0U3/05FOP9NTXP/SljO/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pZ1f9MT4z/TkU7/09E
+        N/1PRTihT0U3DgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAABPRTgAT0U4Rk9FN+1ORTb/TUtn/0xNe/9ORTn/TkU3/01Mcf9KWM3/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlnV/0xPiP9ORTj/T0U3+09FOHxPRTgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT0U4Ik9FONFPRTb/TUlX/0pXxP9KWdL/TE6A/05F
+        Of9ORTf/TUxv/0pYzf9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWdL/TUxz/05FN/9PRTftT0U4SE9FOAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATkU4Bk5FOJ1PRDf9TkZD/0tU
+        sP9KWtj/SlrX/0pZ0v9MToD/TkU5/05FN/9NTHD/SljN/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pXx/9NSVn/T0Q2/09F
+        OM1PRTgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT0U4AE9F
+        OEhPRTfzTkU4/0xQjP9KWdf/SlrX/0pa1/9KWtf/SlnS/0xOgP9ORTn/TkU3/01McP9KWM3/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtj/S1St/05GQP9PRTf9T0U4g09FOAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAE9FOA5PRTi9T0Q2/01JWv9KWM3/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWdL/TE6B/05F
+        OP9ORTf/TUxx/0pYzf9KWdf/SlrX/0pa1/9KWtf/SlrX/0pZ1P9KWcz/SlrW/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlnW/0pZzf9MTXv/TkQ2/09FN+VPRDgsT0U4AAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAE9FOABORThYT0U4905FPP9LU6X/SlrY/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pZ0/9MToL/TkU5/05FN/9NTG//SljN/0pa1/9KWtf/SlrX/0pZ1P9LToL/TlOQ/0pa
+        1v9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pYzf9QUHH/Sla//05HTP9PRDf/T0U4k09F
+        OAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPRTgKT0U4vU9ENv9NSmT/SlnS/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlnS/0xOgv9ORTj/TkU2/01Mb/9KWMz/SlrX/0pZ
+        0/9LToP/TkQ1/01Tlv9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pZ0v9NTXL/T0xh/0pa
+        1v9MT4n/TkU3/09FN+VPRTgmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPRTgAT0U4PE9F
+        N/FORTr/S1Ol/0pZ2P9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWdP/TE+C/05F
+        Of9ORTf/TUxt/0pXyf9LTX//TkQ2/05ENP9NUpb/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrW/0pZ
+        0v9PUoX/T0U2/05Mav9KWtf/SlfC/05HSv9PRTf9T0U4eE9FOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAATkU3Ak9FOI1PRTf/TkhV/0pYzv9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pZ0v9MToL/TkU5/05FNv9NR0//TkQ1/09FNf9ORDT/TVKW/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pYzv9OUHr/T0U3/05ENf9MTXX/SlrX/0pZ1/9NTXX/T0Q2/05FOMVORTgOAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAE9FNxRPRTfRTkQ2/0xOgP9KWtj/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlnS/0xOgP9NRDb/TkQ1/09FNf9PRTX/TkQ0/01S
+        lv9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlnW/0tYw/9NSmH/TkU1/09FNf9ORDX/T1Wb/0pa1/9KWtj/S1Oq/05F
+        Ov9PRTfzT0U4OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPRTg8T0U38U5FPP9LVLH/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pZ0v9MTHT/TkQ1/09F
+        Nf9PRTX/T0U1/05ENP9NU5b/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtb/SlnP/01SlP9PR0b/TkQ0/09FNf9PRTX/TkQ3/0xX
+        uf9KWtf/SlrX/0pYyv9OSE7/T0Q3/U9FOHpPRTcAAAAAAAAAAAAAAAAAAAAAAAAAAABPRTgAT0U4eE5E
+        N/1OSFD/SljK/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pZ
+        0v9MTHP/TkQ1/09FNf9PRTX/T0U1/09FNf9ORDT/TVKV/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWc3/TFOc/09KVv9PRTb/T0U1/09F
+        Nf9PRTX/T0U1/09KUv9LWcz/SlrX/0pa1/9KWdf/TExz/09ENv9PRTixT0U4CAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAT0U4Bk9FOKVPRTb/TUxt/0pZ1v9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pZ0P9MS2//TkQ1/09FNf9PRTX/T0U1/09FNf9PRTX/TkQ0/09MZP9KWMv/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1v9KWdP/S1nO/0tUp/9UVof/iYaO/7u3
+        sv9ORDT/T0U1/2phVP/Mycb/YllL/05ENP9OTnP/SlrW/0pa1/9KWtf/SlrZ/0tQkP9ORTb/T0U41U9F
+        OBoAAAAAAAAAAAAAAAAAAAAAAAAAAE5FOBBPRTjFTkQ1/0xPhP9KWtn/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pZz/9MS27/TkQ1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/05E
+        NP9PRTf/UU1g/0xRlP9LUpv/TFat/01Yuv9NWcH/TVnB/01Zvv9NV7L/S1Oh/0tRlv9PUon/Tkpb/09I
+        Sf9iWk7/zsvH/9jW0/+qpZ7/T0U1/8K/u//Kx8T/0c/M/1NKOv9ORDT/TVex/0pa1/9KWtf/SlrX/0pa
+        2P9LVLD/TkU8/09FN/FPRTg4AAAAAAAAAAAAAAAAAAAAAAAAAABPRTgkTkU35U5FN/9LU6H/SlrY/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pYz/9MS2r/TkQ1/09FNf9PRTX/T0U1/09F
+        Nf9PRTX/T0U1/09FNf9PRTX/T0Q0/05ENP9ORDT/TkQ1/09GOf9PRj7/T0dA/09HQP9PRz//T0Y7/05F
+        Nv9ORDT/TkQ0/05ENP9PRTX/mJKJ/6unoP+HgXb/zcrG/2ZeUP/Ewb3/nJeP/66qo/9waFz/T0pX/0pZ
+        zv9KWtf/SlrX/0pa1/9KWtf/SlfC/05GRP9PRTf5TkU4TgAAAAAAAAAAAAAAAAAAAAAAAAAAT0U4Mk9F
+        N+9ORTv/S1W0/0pa2P9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pYzv9MSmf/TkQ1/09F
+        Nf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09F
+        Nf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/6Kdlf9waFv/T0U1/8TBvP9vZ1r/o56X/09F
+        Nf+gm5P/cmpf/0xSl/9KWtb/SlrX/0pa1/9KWtf/SlrX/0pYyv9OR0z/T0U3+09FOFgAAAAAAAAAAAAA
+        AAAAAAAAAAAAAE9FODpPRTfzTkU+/0tVuv9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pY
+        zf9MSmX/TkQ1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09F
+        Nf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf+QioD/jIZ8/09F
+        Nf/DwLv/YFdI/7y5s/9PRTX/sa2m/2FcZv9KWMr/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWM7/TkhT/09E
+        NvtPRThYAAAAAAAAAAAAAAAAAAAAAAAAAABPRTdCT0U39U5GQP9KVr7/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pYzP94eJT/XFNE/09FNf9+d2z/VEo7/09FNf9SSTn/f3ht/4mDeP9bUkP/T0U1/1NJ
+        Of+AeW7/gntw/09FNf9yal3/YFdJ/09FNf91bmH/XVRF/1ZNPv+HgHb/c2tf/3t0af9PRTX/Ukk5/4iB
+        d/9qYVT/WVBB/7Wxq/+uqaL/enJn/09FNf+inZX/qaWe/5yXkf9MU5z/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlnQ/01JV/9PRDb7T0U4WAAAAAAAAAAAAAAAAAAAAAAAAAAAT0U3Qk9FN/VORkD/Sla+/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pYy/9UUWn/9PT0/6GclP9dU0X/+Pj4/4mDeP9PRTX/raih/9XT
+        0P+3s63/wL24/09FNf/LyMT/1NHO/8fEwP95cWX/trKt/4N8cf9PRTX/wb25/3lxZf+tqaL/zszI/9nX
+        1f/T0c7/T0U1/4uEev/d3Nr/nJaO/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/05ENP9OTGX/SlnT/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pZ0P9NSVf/T0Q2+09FOFgAAAAAAAAAAAAAAAAAAAAAAAAAAE9F
+        ODpPRTfzTkY+/0tWuv9KWtf/SlrX/0pa1/9KWtf/SlrX/0pYyf9MSVz/h4B2/8XCvv/PzMn/koyC/9LQ
+        zf/Bvrn/T0U1/+Xj4v+2sq3/qqWf/6mknf9kW03/5+bl/1BGNv9PRTX/T0U1/7ayrf+DfHH/T0U1/8G9
+        uf95cWX/rKeg/7ayrf+dmI//09HO/09FNf+Ykon/opyV/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09E
+        NP9PSUr/S1jD/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWM7/TkhT/09ENvtPRThYAAAAAAAA
+        AAAAAAAAAAAAAAAAAABPRTcyT0Q3705FO/9LVbT/SlrY/0pa1/9KWtf/SlrX/0pXyP9NSVv/TkQ1/8G+
+        uf+IgXf/ycbC/97c2/94cWX/6Ofm/1VMPP/f3dv/qaSd/5SOhf/x8fH/YFdJ/+rp6f9WTD3/T0U1/09F
+        Nf+2sq3/mZSL/09FNf/IxcD/eXFl/1hPQP+WkIf/wLy3/9PRzv9PRTX/mJKJ/6Kclf9PRTX/T0U1/09F
+        Nf9PRTX/T0U1/09FNf9ORTr/TFOg/0pa1v9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SljK/05H
+        TP9PRTf7T0U4WAAAAAAAAAAAAAAAAAAAAAAAAAAAT0U4JE9FOOdORDj/S1Kj/0pa2f9KWtf/SlrX/0pZ
+        z/9NSVj/TkQ1/1hOP//o5+b/U0o6/4uEev/r6un/UEY2/7y4s/+Ignf/hn90/+fl5P/b2db/sq2n/09F
+        Nf+uqqP/5uXk/9LQzf95cWX/trKt/+vq6f/Qzcr/7e3s/1xSRP9kXE7/5ePi/93b2f+rpp//W1JD/+De
+        3P/i4d//m5WN/09FNf9PRTX/T0U1/09FNf9ORTn/TlOX/0pZ1v9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pXw/9ORkT/T0U3+U9FOE4AAAAAAAAAAAAAAAAAAAAAAAAAAE9EOBBPRTjHTkQ1/0xP
+        hf9KWtn/SlrX/0pa1/9KWdP/TE5+/05FNv9SSDn/aF9S/09FNf9USjv/Zl1Q/09FNf9dVEX/XVRF/09F
+        Nf9fVkf/aWFT/09FNf9PRTX/T0U1/2deUf9uZln/UEY2/7ayrf+EfXL/bmZZ/1lQQf9PRTX/T0U1/15V
+        R/9uZln/UUc4/1JIOP+qpZ7/sq6o/2JZS/9PRTX/T0U1/09FNf9ORTj/T1ON/0pa1f9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa2P9LVbL/TkU9/09FN/NPRTg6AAAAAAAAAAAAAAAAAAAAAAAA
+        AABPRTgGT0U4pU9FNv9NTG7/SlrX/0pa1/9KWtf/SlrX/0pZ0/9LToL/TkU2/09FNf9PRTX/T0U1/09F
+        Nf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf+2sqz/g3xx/09F
+        Nf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/XVRF/2lgU/9PRTX/T0U1/09FNf9ORTr/T1SU/0pZ
+        1P9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtn/TFGR/05FN/9PRTjXT0U4HAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAT0U4AE9FOHpPRDf9TkhS/0pYyv9KWtf/SlrX/0pa1/9KWtf/SlnV/0tP
+        hv9ORTf/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09F
+        Nf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/05E
+        NP9ORkH/TVSh/0pa1v9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlnX/01N
+        dP9PRDb/T0U4tU9FOAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPRTg+TkQ3805FPf9LVbP/SlrY/0pa
+        1/9KWtf/SlrX/0pa1/9KWdT/S0+J/05FN/9PRTX/T0U1/09FNf9PRTX/xMG9/2JZS/9WTD3/2dfU/46H
+        fv+EfXL/gHlu/5+akv+Nh33/m5aN/4uFev9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/09F
+        Nf9PRTX/T0U1/05ENP9QTF3/S1a3/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pYy/9OSFD/T0Q2/U9FOHxPRTgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT0U3Fk9F
+        N9VPRDb/TE+E/0pa2P9KWtf/SlrX/0pa1/9KWtf/SlrX/0pZ1f9LT4v/TkU4/09FNf9PRTX/T0U1/9XT
+        0P9lXE7/T0U1/7i0r/9aUUL/i4V6/4qDef/g393/1NLP/6ijnP+UjoT/T0U1/09FNf9PRTX/T0U1/09F
+        Nf9PRTX/T0U1/09FNf9ORDT/TkQ0/05ENf9QSk7/S1e//0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa2P9LVK7/TkU7/09FN/VPRTg6AAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAE9FOAJPRTiRT0U2/05JVv9KWM7/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlnV/0tQ
+        j/9ORTj/T0U1/09FNf/V09D/ZVxO/09FNf+4tK//WlFC/4uFev/Fwr7/p6Ka/7Svqf/V0s//lI6E/09F
+        Nf9PRTX/T0U1/09FNf9PRTX/T0U1/09FNf9ORDX/TkdB/05MYf9PRjz/TkU2/01LZv9KWMn/SlrY/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWdf/TU13/09ENv9PRTjJT0U4EAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT0U4QE9FN/NORTv/S1Oo/0pa2P9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWdX/S1CS/05FOP9TSjr/1tTR/2lhU/9QRjb/uLSv/1pRQv+LhXr/+Pj4/2Rb
+        Tf9xaV3/+vn6/5SOhP9PRTX/T0U1/05ENP9ORDT/TkU1/05ENv9PSlX/TVCF/0tXwv9KWdT/TE+J/05F
+        Ov9ORTb/TUto/0pXyf9KWtj/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlfE/05H
+        TP9PRTf9T0U4fE9FOAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE9FOAxPRTjBT0Q2/01L
+        Z/9KWdL/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pZ1f9LUZT/19bY/+Xk4//l5OP/fXZq/9jW
+        1P+Oh37/g3xy/7OvqP9PRTX/TkU1/7y4s/+LhXz/TkU4/05FOv9ORkL/T01o/01PgP9MV7b/SlnP/0pa
+        1/9KWtf/SlrX/0pZ1P9MUIv/TkU7/09FNv9NS2b/SlfK/0pa2P9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrY/0xQjf9ORTf/T0U3509FOCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AABPRTgAT0U4Xk9FN/lORT3/S1Op/0pa2P9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlnV/0tR
+        mP9ORTn/T0U1/09FNf9PRTX/T0U1/09FNf9PRTX/T0U1/0xLZf9NUYv/TlSY/05Ysf9KV8H/SljH/0pZ
+        1P9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlnU/0xPiv9ORTr/TkQ2/01LaP9KV8n/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pXxf9OSE7/T0U3/09FOJtPRTgGAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAE9FOBBPRTjBTkQ2/01JXf9KWc7/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWdb/S1Gb/05FO/9PRTX/T0U1/09FNf9PRTX/T0U1/05ENP9NUpf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWdX/TFCM/05F
+        PP9ORTb/TUpl/0pXyP9KWtf/SlrX/0pa1/9KWtf/SlrX/0pZ1/9MTn//TkU2/09FN+lPRTgwT0U4AAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPRTcAT0U4Tk9FN/VORTn/TFCR/0pa
+        2P9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pZ1v9KUp3/TkU7/09FNf9PRTX/T0U1/09F
+        Nf9ORDT/TVKW/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pZ1P9MUIv/TkU7/09FNv9NSmb/SlfH/0pa2P9KWtf/SlrX/0pa2P9LVbL/TkZC/09F
+        N/1PRDiJT0U4AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE5F
+        OAhPRDihT0Q3/05GRf9LVbP/SlnY/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlnW/0tS
+        oP9ORT3/T0U1/09FNf9PRTX/TkQ0/01Slv9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlnV/0xPjP9ORTz/TkU2/01KZf9KV8j/SlrX/0pa
+        1/9KWMn/TUlb/05FNv9PRTjRT0U4HgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAT0U3Jk9FONdORDb/TUlb/0pXyP9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWdb/SlOj/05FPv9PRTX/T0U1/05ENP9NUpb/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWdT/TFCK/05F
+        O/9ORTb/TUpl/0pXyP9KWdT/TE15/05FN/9PRTfxT0U4UE9FOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE9FNwBPRTdOT0U3705FN/9NS2z/SljO/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pZ1v9KU6b/TkU+/05FNf9ORDT/TVKW/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pZ1f9MUI3/TkU7/05FNv9NS2b/TE+D/05FOv9PRTf7T0U4g09FOAQAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT0U4BE9F
+        N3RPRTf3TkU3/01Ndf9KWM//SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlnW/0pT
+        qP9ORkD/TkQ0/01Slv9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlnV/0xQjf9ORj7/T0Q3/09FN/9PRTf9T0U4qU9F
+        OBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAABPRTgIT0U4jU9FN/tORTj/TUxy/0pYzP9KWtj/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWdb/SlSr/01FP/9NU5b/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWdH/TUtn/09F
+        Nv9PRTf/T0U3u09FOBxPRTcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE9FOA5PRTiVT0U3+U5FNv9NS2b/SlbA/0pZ
+        2P9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pZ1v9KU6f/TlOT/0pa1v9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        2P9KWM3/TE6A/05FPP9PRDf9T0U4v09FOCBPRTgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT0U4Ck9F
+        OINPRTf1TkU2/05IT/9LU6X/SlnW/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa2P9KVrr/TUpk/05EOP9PRDf9T0U3r09FNx5ORDcAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAABPRTgGT0U4ZE9FN+VPRDb/TkU+/0xNe/9KV8H/SlrY/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrY/0pYzf9MUJD/TkdI/05ENv9PRTfzT0U4kU9FOBJPRTcAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE9FNwJPRTc6T0U4w09EN/tPRDb/TkdK/0xQ
+        jf9KV8X/SlrY/0pa2P9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtj/SljO/0tSn/9OSVr/TkU4/09EN/9PRTjdT0U3XE9F
+        OAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT0U4AE9F
+        NxRPRTd+T0U35U9FN/9ORDf/TkdO/0xOg/9KVbf/SlnR/0pa2f9KWtj/SlrX/0pa1/9KWtf/SlrX/0pa
+        1/9KWtf/SlrX/0pa1/9KWtf/SlrX/0pa1/9KWtj/SlrZ/0pZ1f9KV8D/S1GU/01JWv9ORTn/T0Q2/09F
+        N/FPRTehT0U4Kk9FOAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAT0U4Ak9FOCxORTeVT0U36U9FN/9PRDb/TkY+/01KXf9MT4P/S1St/0pX
+        xP9KWM7/SlnV/0pZ2P9KWtj/SlnY/0pZ2P9KWtj/SlnW/0pZ0P9KV8f/S1W2/0xQjv9NS2j/TkZD/09E
+        N/9PRTf/T0U3809FN7NPRThET0U3BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT0U4BE9FODBPRTiHT0U31U9F
+        N/dPRDb/T0U2/05FO/9ORkb/TUhV/01Mbf9NTXr/TE6A/0xPhP9MToH/TE58/01Mcf9NSVz/TkdJ/05G
+        Pf9ORTf/T0Q2/09EN/tORDflT0U4nU9EOEZPRTgKT0U4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAT0U4Ak9FNxZPRTdMT0U4mU9FOMtPRTfvT0U3+U9ENv1PRDb/T0U2/09FNv9PRTb/T0U2/09F
+        Nv9PRDb/T0Q2/09EN/lPRTfzT0U32U9FOKdPRTdgT0U4Ik9FOARPRTgAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE9FOARPRTgST0U4ME9FN1JPRTh+TkU3o09F
+        OLFPRTi9T0U4wU9FOL9PRTi1T0U4qU9FOItPRThaT0U4Ok5FNxhPRTcGT0U4AAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AABPRTgAT0U4Ak9FOARPRTgET0U4Bk9FOAZPRTgGT0U4BE9FOARPRTgCT0U4AE9FOAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAA////////////////////////////////////8B////////4AAP//////+AAAH//////AAAAH
+        /////4AAAAH////+AAAAAP////wAAAAAP///+AAAAAAf///gAAAAAA///8AAAAAAB///wAAAAAAD//+A
+        AAAAAAP//wAAAAAAAf/+AAAAAAAA//4AAAAAAAB//AAAAAAAAH/8AAAAAAAAP/gAAAAAAAA/+AAAAAAA
+        AD/wAAAAAAAAH/AAAAAAAAAf8AAAAAAAAB/wAAAAAAAAD+AAAAAAAAAP4AAAAAAAAA/gAAAAAAAAD+AA
+        AAAAAAAP4AAAAAAAAA/gAAAAAAAAD+AAAAAAAAAP4AAAAAAAAA/gAAAAAAAAD+AAAAAAAAAP4AAAAAAA
+        AA/gAAAAAAAAD/AAAAAAAAAP8AAAAAAAAB/wAAAAAAAAH/AAAAAAAAAf+AAAAAAAAD/4AAAAAAAAP/wA
+        AAAAAAA//AAAAAAAAH/+AAAAAAAAf/4AAAAAAAD//wAAAAAAAf//gAAAAAAB///AAAAAAAP//8AAAAAA
+        B///4AAAAAAP///wAAAAAB////wAAAAAP////gAAAAD/////gAAAAf/////AAAAH//////AAAB//////
+        /gAA////////8A////////////////////////////////////8=
+</value>
+  </data>
+</root>

+ 45 - 0
RevokeMsgPatcher/Model/App.cs

@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RevokeMsgPatcher.Model
+{
+    public class App
+    {
+        public string Name { get; set; }
+
+        public Dictionary<string, TargetInfo> FileTargetInfos { get; set; }
+
+        public Dictionary<string, List<ModifyInfo>> FileModifyInfos { get; set; }
+
+        public HashSet<string> GetSupportVersions()
+        {
+            // 使用 HashSet 防重
+            HashSet<string> versions = new HashSet<string>();
+            foreach (List<ModifyInfo> modifyInfos in FileModifyInfos.Values)
+            {
+                foreach (ModifyInfo modifyInfo in modifyInfos)
+                {
+                    versions.Add(modifyInfo.Version);
+                }
+            }
+            return versions;
+        }
+
+        public string GetSupportVersionStr()
+        {
+            string str = "";
+            foreach (string v in GetSupportVersions())
+            {
+                str += v + "、";
+            }
+            if (str.Length > 1)
+            {
+                str = str.Substring(0, str.Length - 1);
+            }
+            return str;
+        }
+    }
+}

+ 17 - 0
RevokeMsgPatcher/Model/Bag.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RevokeMsgPatcher.Model
+{
+    public class Bag
+    {
+        public Dictionary<string, App> Apps { get; set; }
+
+        public string LatestVersion { get; set; }
+
+        public string Notice { get; set; }
+    }
+}

+ 23 - 0
RevokeMsgPatcher/Model/Change.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RevokeMsgPatcher.Model
+{
+    public class Change
+    {
+        public long Position { get; set; }
+
+        public byte[] Content { get; set; }
+
+        public Change Clone()
+        {
+            Change o = new Change();
+            o.Position = Position;
+            o.Content = Content;
+            return o;
+        }
+    }
+}

+ 40 - 0
RevokeMsgPatcher/Model/ModifyInfo.cs

@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RevokeMsgPatcher.Model
+{
+    public class ModifyInfo
+    {
+        public string Name { get; set; }
+
+        //public string RelativePath { get; set; }
+
+        public string Version { get; set; }
+
+        public string SHA1Before { get; set; }
+
+        public string SHA1After { get; set; }
+
+        public List<Change> Changes { get; set; }
+
+        public ModifyInfo Clone()
+        {
+            ModifyInfo o = new ModifyInfo();
+            o.Name = Name;
+            o.Version = Version;
+            o.SHA1Before = SHA1Before;
+            o.SHA1After = SHA1After;
+            List<Change> cs = new List<Change>();
+            foreach(Change c in Changes)
+            {
+                cs.Add(c.Clone());
+            }
+            o.Changes = cs;
+            return o;
+        }
+    }
+}

+ 26 - 0
RevokeMsgPatcher/Model/TargetInfo.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RevokeMsgPatcher.Model
+{
+    public class TargetInfo
+    {
+        public string Name { get; set; }
+
+        public string RelativePath { get; set; }
+
+        public string Memo { get; set; }
+
+        public TargetInfo Clone()
+        {
+            TargetInfo o = new TargetInfo();
+            o.Name = Name;
+            o.RelativePath = RelativePath;
+            o.Memo = Memo;
+            return o;
+        }
+    }
+}

+ 215 - 0
RevokeMsgPatcher/Modifier/AppModifier.cs

@@ -0,0 +1,215 @@
+using RevokeMsgPatcher.Model;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RevokeMsgPatcher.Modifier
+{
+    /// <summary>
+    /// 1. 自动获取安装目录(已验证) 或者手动填写安装目录
+    /// 2. 验证安装目录 验证要修改的 dll 是否存在
+    /// 3. 判断所有 dll 是否符合防撤回要求
+    /// 4. 备份所有 dll  // *.h.bak
+    /// 5. 根据每个 dll 匹配的 修改信息 循环修改
+    /// </summary>
+    public abstract class AppModifier
+    {
+        protected App config;
+
+        public App Config { set { config = value; } get { return config; } }
+
+        protected List<FileHexEditor> editors;
+
+        public string InstallPath { get; set; }
+
+        /// <summary>
+        /// 自动搜索应用安装路径
+        /// </summary>
+        /// <returns>应用安装路径</returns>
+        public abstract string FindInstallPath();
+
+        //public abstract bool ValidateAndInitialize(string installPath);
+
+        /// <summary>
+        /// 获取版本号
+        /// </summary>
+        /// <returns></returns>
+        public abstract string GetVersion();
+
+        /// <summary>
+        /// 判断APP安装路径内是否都存在要修改的文件
+        /// </summary>
+        /// <param name="installPath">APP安装路径</param>
+        /// <returns></returns>
+        public bool IsAllFilesExist(string installPath)
+        {
+            if (string.IsNullOrEmpty(installPath))
+            {
+                return false;
+            }
+            int success = 0;
+            foreach (TargetInfo info in config.FileTargetInfos.Values)
+            {
+                string filePath = Path.Combine(installPath, info.RelativePath);
+                if (File.Exists(filePath))
+                {
+                    success++;
+                }
+            }
+            if (success == config.FileTargetInfos.Count)
+            {
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// a.初始化修改器
+        /// </summary>
+        /// <param name="installPath">APP安装路径</param>
+        public void InitEditors(string installPath)
+        {
+            // 初始化文件修改器
+            editors = new List<FileHexEditor>();
+            foreach (TargetInfo info in config.FileTargetInfos.Values)
+            {
+                FileHexEditor editor = new FileHexEditor(installPath, info);
+                editors.Add(editor);
+            }
+
+        }
+
+        /// <summary>
+        /// b.验证文件完整性,寻找对应的补丁信息
+        /// </summary>
+        public void ValidateAndFindModifyInfo()
+        {
+            // 寻找对应文件版本与SHA1的修改信息
+            foreach (FileHexEditor editor in editors) // 多种文件
+            {
+                // 通过SHA1和文件版本判断是否可以打补丁 根据不同结果返回不同的提示
+                ModifyInfo matchingSHA1Before = null, matchingSHA1After = null, matchingVersion = null;
+                foreach (ModifyInfo modifyInfo in config.FileModifyInfos[editor.FileName]) // 多个版本信息
+                {
+                    if (modifyInfo.Name == editor.FileName) // 保险用的无用判断
+                    {
+                        if (editor.FileSHA1 == modifyInfo.SHA1After)
+                        {
+                            matchingSHA1After = modifyInfo;
+                        }
+                        else if (editor.FileSHA1 == modifyInfo.SHA1Before)
+                        {
+                            matchingSHA1Before = modifyInfo;
+                        }
+
+                        if (editor.FileVersion == modifyInfo.Version)
+                        {
+                            matchingVersion = modifyInfo;
+                        }
+                    }
+                }
+
+                // 补丁前SHA1匹配上,肯定是正确的dll
+                if (matchingSHA1Before != null)
+                {
+                    editor.FileModifyInfo = matchingSHA1Before;
+                    continue;
+                }
+                // 补丁后SHA1匹配上,肯定已经打过补丁
+                if (matchingSHA1After != null)
+                {
+                    throw new BusinessException("installed", $"你已经安装过此补丁,文件路径:{editor.FilePath}");
+                }
+                // 全部不匹配,说明不支持
+                if (matchingSHA1Before == null && matchingSHA1After == null && matchingVersion == null)
+                {
+                    throw new BusinessException("not_support", $"不支持此版本:{editor.FileVersion},文件路径:{editor.FilePath}");
+                }
+                // SHA1不匹配,版本匹配,可能dll已经被其他补丁程序修改过
+                if ((matchingSHA1Before == null && matchingSHA1After == null) && matchingVersion != null)
+                {
+                    throw new BusinessException("maybe_modified", $"程序支持此版本:{editor.FileVersion}。但是文件校验不通过,请确认是否使用过其他补丁程序。文件路径:{editor.FilePath}");
+                }
+            }
+        }
+
+        /// <summary>
+        /// c.根据补丁信息,安装补丁
+        /// </summary>
+        /// <returns></returns>
+        public bool Patch()
+        {
+            // 首先验证文件修改器是否没问题
+            foreach (FileHexEditor editor in editors)
+            {
+                if (editor.FileModifyInfo == null)
+                {
+                    throw new Exception("补丁安装失败,原因:文件修改器初始化失败!");
+                }
+            }
+            // 再备份所有文件
+            foreach (FileHexEditor editor in editors)
+            {
+                editor.Backup();
+            }
+            // 打补丁!
+            List<FileHexEditor> done = new List<FileHexEditor>(); // 已经打上补丁的
+            try
+            {
+                foreach (FileHexEditor editor in editors)
+                {
+                    bool success = editor.Patch();
+                    if (!success)
+                    {
+                        editor.Restore();
+                    }
+                    done.Add(editor);
+                }
+            }
+            catch (Exception ex)
+            {
+                // 恢复所有已经打上补丁的文件
+                foreach (FileHexEditor editor in done)
+                {
+                    editor.Restore();
+                }
+                throw ex;
+            }
+            return true;
+        }
+
+        public bool BackupExists()
+        {
+            foreach (FileHexEditor editor in editors)
+            {
+                if (!File.Exists(editor.FileBakPath))
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public bool Restore()
+        {
+            if (BackupExists())
+            {
+                foreach (FileHexEditor editor in editors)
+                {
+                    editor.Restore();
+                }
+                return true;
+            }
+            else
+            {
+                throw new Exception("备份文件不存在,还原失败!");
+            }
+        }
+    }
+}

+ 74 - 0
RevokeMsgPatcher/Modifier/FileHexEditor.cs

@@ -0,0 +1,74 @@
+using RevokeMsgPatcher.Model;
+using RevokeMsgPatcher.Utils;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RevokeMsgPatcher.Modifier
+{
+    public class FileHexEditor
+    {
+        public string FileName { get; set; }
+
+        public string FilePath { get; set; }
+
+        public string FileBakPath { get; set; }
+
+        private string version;
+        public string FileVersion
+        {
+            get
+            {
+                if (version == null)
+                {
+                    version = FileUtil.GetFileVersion(FilePath);
+                }
+                return version;
+            }
+        }
+
+        public string sha1;
+        public string FileSHA1
+        {
+            get
+            {
+                if (sha1 == null)
+                {
+                    sha1 = FileUtil.ComputeFileSHA1(FilePath);
+                }
+                return sha1;
+            }
+        }
+
+        public TargetInfo FileTargetInfo { get; set; }
+
+        public ModifyInfo FileModifyInfo { get; set; }
+
+        public FileHexEditor(string installPath, TargetInfo target)
+        {
+            FileTargetInfo = target.Clone();
+            FileName = FileTargetInfo.Name;
+            FilePath = Path.Combine(installPath, FileTargetInfo.RelativePath);
+            FileBakPath = FilePath + ".h.bak";
+        }
+
+        public void Backup()
+        {
+            File.Copy(FilePath, FileBakPath, true);
+        }
+
+        public bool Patch()
+        {
+            FileUtil.EditMultiHex(FilePath, FileModifyInfo.Changes);
+            return true;
+        }
+
+        public void Restore()
+        {
+            File.Copy(FileBakPath, FilePath, true);
+        }
+    }
+}

+ 56 - 0
RevokeMsgPatcher/Modifier/QQLiteModifier.cs

@@ -0,0 +1,56 @@
+using RevokeMsgPatcher.Model;
+using RevokeMsgPatcher.Utils;
+
+namespace RevokeMsgPatcher.Modifier
+{
+    class QQLiteModifier : AppModifier
+    {
+        public QQLiteModifier(App config)
+        {
+            this.config = config;
+        }
+
+        /// <summary>
+        /// 自动寻找获取微信安装路径
+        /// </summary>
+        /// <returns></returns>
+        public override string FindInstallPath()
+        {
+            string installPath = PathUtil.FindInstallPathFromRegistry("QQLite");
+            if (!IsAllFilesExist(installPath))
+            {
+                foreach (string defaultPath in PathUtil.GetDefaultInstallPaths(@"Tencent\QQLite"))
+                {
+                    if (IsAllFilesExist(defaultPath))
+                    {
+                        return defaultPath;
+                    }
+                }
+            }
+            else
+            {
+                return installPath;
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// 获取整个APP的当前版本
+        /// </summary>
+        /// <returns></returns>
+        public override string GetVersion()
+        {
+            if (editors != null && editors.Count > 0)
+            {
+                foreach (FileHexEditor editor in editors)
+                {
+                    if (editor.FileName == "IM.dll")
+                    {
+                        return editor.FileVersion;
+                    }
+                }
+            }
+            return "";
+        }
+    }
+}

+ 56 - 0
RevokeMsgPatcher/Modifier/QQModifier.cs

@@ -0,0 +1,56 @@
+using RevokeMsgPatcher.Model;
+using RevokeMsgPatcher.Utils;
+
+namespace RevokeMsgPatcher.Modifier
+{
+    class QQModifier : AppModifier
+    {
+        public QQModifier(App config)
+        {
+            this.config = config;
+        }
+
+        /// <summary>
+        /// 自动寻找获取微信安装路径
+        /// </summary>
+        /// <returns></returns>
+        public override string FindInstallPath()
+        {
+            string installPath = PathUtil.FindInstallPathFromRegistry("{052CFB79-9D62-42E3-8A15-DE66C2C97C3E}");
+            if (!IsAllFilesExist(installPath))
+            {
+                foreach (string defaultPath in PathUtil.GetDefaultInstallPaths(@"Tencent\QQ"))
+                {
+                    if (IsAllFilesExist(defaultPath))
+                    {
+                        return defaultPath;
+                    }
+                }
+            }
+            else
+            {
+                return installPath;
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// 获取整个APP的当前版本
+        /// </summary>
+        /// <returns></returns>
+        public override string GetVersion()
+        {
+            if (editors != null && editors.Count > 0)
+            {
+                foreach (FileHexEditor editor in editors)
+                {
+                    if (editor.FileName == "IM.dll")
+                    {
+                        return editor.FileVersion;
+                    }
+                }
+            }
+            return "";
+        }
+    }
+}

+ 56 - 0
RevokeMsgPatcher/Modifier/TIMModifier.cs

@@ -0,0 +1,56 @@
+using RevokeMsgPatcher.Model;
+using RevokeMsgPatcher.Utils;
+
+namespace RevokeMsgPatcher.Modifier
+{
+    class TIMModifier : AppModifier
+    {
+
+        public TIMModifier(App config)
+        {
+            this.config = config;
+        }
+        /// <summary>
+        /// 自动寻找获取微信安装路径
+        /// </summary>
+        /// <returns></returns>
+        public override string FindInstallPath()
+        {
+            string installPath = PathUtil.FindInstallPathFromRegistry("TIM");
+            if (!IsAllFilesExist(installPath))
+            {
+                foreach (string defaultPath in PathUtil.GetDefaultInstallPaths(@"Tencent\TIM"))
+                {
+                    if (IsAllFilesExist(defaultPath))
+                    {
+                        return defaultPath;
+                    }
+                }
+            }
+            else
+            {
+                return installPath;
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// 获取整个APP的当前版本
+        /// </summary>
+        /// <returns></returns>
+        public override string GetVersion()
+        {
+            if (editors != null && editors.Count > 0)
+            {
+                foreach (FileHexEditor editor in editors)
+                {
+                    if (editor.FileName == "IM.dll")
+                    {
+                        return editor.FileVersion;
+                    }
+                }
+            }
+            return "";
+        }
+    }
+}

+ 72 - 0
RevokeMsgPatcher/Modifier/WechatModifier.cs

@@ -0,0 +1,72 @@
+using RevokeMsgPatcher.Model;
+using RevokeMsgPatcher.Utils;
+
+namespace RevokeMsgPatcher.Modifier
+{
+    class WechatModifier : AppModifier
+    {
+
+        public WechatModifier(App config)
+        {
+            this.config = config;
+        }
+
+        /// <summary>
+        /// 自动寻找获取微信安装路径
+        /// </summary>
+        /// <returns></returns>
+        public override string FindInstallPath()
+        {
+            string installPath = PathUtil.FindInstallPathFromRegistry("Wechat");
+            if (!IsAllFilesExist(installPath))
+            {
+                foreach (string defaultPath in PathUtil.GetDefaultInstallPaths(@"Tencent\Wechat"))
+                {
+                    if (IsAllFilesExist(defaultPath))
+                    {
+                        return defaultPath;
+                    }
+                }
+            }
+            else
+            {
+                return installPath;
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// 获取整个APP的当前版本
+        /// </summary>
+        /// <returns></returns>
+        public override string GetVersion()
+        {
+            if (editors != null && editors.Count > 0)
+            {
+                foreach (FileHexEditor editor in editors)
+                {
+                    if (editor.FileName == "WeChatWin.dll")
+                    {
+                        return editor.FileVersion;
+                    }
+                }
+            }
+            return "";
+        }
+
+        //public override bool ValidateAndInitialize(string installPath)
+        //{
+        //    // 判断是否是安装路径
+        //    if (!IsAllBinaryFilesExist(installPath))
+        //    {
+        //        return false;
+        //    }
+
+        //    // 初始化十六进制文件编辑器
+        //    // 并寻找与之配对的版本修改信息
+        //    InitEditors(installPath);
+
+        //    return true;
+        //}
+    }
+}

+ 80 - 0
RevokeMsgPatcher/Program.cs

@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace RevokeMsgPatcher
+{
+    static class Program
+    {
+        /// <summary>
+        /// 应用程序的主入口点。
+        /// </summary>
+        [STAThread]
+        static void Main()
+        {
+
+
+#if DEBUG
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            Application.Run(new FormMain());
+#else
+            try
+            {
+                Application.EnableVisualStyles();
+                Application.SetCompatibleTextRenderingDefault(false);
+
+                //当前用户是管理员的时候,直接启动应用程序
+                //如果不是管理员,则使用启动对象启动程序,以确保使用管理员身份运行
+                //获得当前登录的Windows用户标示
+                System.Security.Principal.WindowsIdentity identity = System.Security.Principal.WindowsIdentity.GetCurrent();
+                System.Security.Principal.WindowsPrincipal principal = new System.Security.Principal.WindowsPrincipal(identity);
+                //判断当前登录用户是否为管理员
+                if (principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator))
+                {
+                    //如果是管理员,则直接运行
+                    Application.Run(new FormMain());
+                }
+                else
+                {
+                    //创建启动对象
+                    System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
+                    startInfo.UseShellExecute = true;
+                    startInfo.WorkingDirectory = Environment.CurrentDirectory;
+                    startInfo.FileName = Application.ExecutablePath;
+                    //设置启动动作,确保以管理员身份运行
+                    startInfo.Verb = "runas";
+                    try
+                    {
+                        System.Diagnostics.Process.Start(startInfo);
+                    }
+                    catch
+                    {
+                        return;
+                    }
+                    //退出
+                    Application.Exit();
+                }
+
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show(ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+#endif
+        }
+
+
+        static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
+        {
+            MessageBox.Show(e.Exception.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
+        }
+
+        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+        {
+            MessageBox.Show((e.ExceptionObject as Exception).Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
+        }
+    }
+}

+ 36 - 0
RevokeMsgPatcher/Properties/AssemblyInfo.cs

@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("RevokeMsgPatcher")]
+[assembly: AssemblyDescription("微信防撤回补丁")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("huiyadanli")]
+[assembly: AssemblyProduct("RevokeMsgPatcher")]
+[assembly: AssemblyCopyright("Copyright ©  2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("977bf781-ced8-4389-9404-0fa08fdf21df")]
+
+// 程序集的版本信息由下列四个值组成: 
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
+// 方法是按如下所示使用“*”: :
+//[assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("0.6")]
+[assembly: AssemblyFileVersion("0.6")]

+ 72 - 0
RevokeMsgPatcher/Properties/Resources.Designer.cs

@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     此代码由工具生成。
+//     运行时版本:4.0.30319.42000
+//
+//     对此文件的更改可能会导致不正确的行为,并且如果
+//     重新生成代码,这些更改将会丢失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace RevokeMsgPatcher.Properties {
+    using System;
+    
+    
+    /// <summary>
+    ///   一个强类型的资源类,用于查找本地化的字符串等。
+    /// </summary>
+    // 此类是由 StronglyTypedResourceBuilder
+    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+    // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+    // (以 /str 作为命令选项),或重新生成 VS 项目。
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   返回此类使用的缓存的 ResourceManager 实例。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RevokeMsgPatcher.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   重写当前线程的 CurrentUICulture 属性
+        ///   重写当前线程的 CurrentUICulture 属性。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+        
+        /// <summary>
+        ///   查找类似 {&quot;Apps&quot;:{&quot;Wechat&quot;:{&quot;Name&quot;:&quot;Wechat&quot;,&quot;FileTargetInfos&quot;:{&quot;WeChatWin.dll&quot;:{&quot;Name&quot;:&quot;WeChatWin.dll&quot;,&quot;RelativePath&quot;:&quot;WeChatWin.dll&quot;,&quot;Memo&quot;:null}},&quot;FileModifyInfos&quot;:{&quot;WeChatWin.dll&quot;:[{&quot;Name&quot;:&quot;WeChatWin.dll&quot;,&quot;Version&quot;:&quot;2.7.1.85&quot;,&quot;SHA1Before&quot;:&quot;de0df4e138b72460450f66c029e33f4510f5e2df&quot;,&quot;SHA1After&quot;:&quot;fbd35720aaff3cdcfd3ff18ea503dc06450e5c99&quot;,&quot;Changes&quot;:[{&quot;Position&quot;:2499465,&quot;Content&quot;:[235]},{&quot;Position&quot;:7952000,&quot;Content&quot;:[195]}]},{&quot;Name&quot;:&quot;WeChatWin.dll&quot;,&quot;Version&quot;:&quot;2.7.1.82&quot;,&quot;SHA1Before&quot;:&quot;20e111a18872bf6c7148a897c11da26c1ec [字符串的其余部分被截断]&quot;; 的本地化字符串。
+        /// </summary>
+        internal static string PatchJson {
+            get {
+                return ResourceManager.GetString("PatchJson", resourceCulture);
+            }
+        }
+    }
+}

File diff suppressed because it is too large
+ 120 - 0
RevokeMsgPatcher/Properties/Resources.resx


+ 30 - 0
RevokeMsgPatcher/Properties/Settings.Designer.cs

@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace RevokeMsgPatcher.Properties
+{
+
+
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}

+ 7 - 0
RevokeMsgPatcher/Properties/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 108 - 0
RevokeMsgPatcher/RevokeMsgPatcher.csproj

@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{977BF781-CED8-4389-9404-0FA08FDF21DF}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>RevokeMsgPatcher</RootNamespace>
+    <AssemblyName>RevokeMsgPatcher</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationIcon>icon.ico</ApplicationIcon>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Management" />
+    <Reference Include="System.Web.Extensions" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="BusinessException.cs" />
+    <Compile Include="FormMain.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="FormMain.Designer.cs">
+      <DependentUpon>FormMain.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Model\App.cs" />
+    <Compile Include="Model\Bag.cs" />
+    <Compile Include="Model\Change.cs" />
+    <Compile Include="Model\ModifyInfo.cs" />
+    <Compile Include="Model\TargetInfo.cs" />
+    <Compile Include="Modifier\AppModifier.cs" />
+    <Compile Include="Modifier\FileHexEditor.cs" />
+    <Compile Include="Modifier\QQLiteModifier.cs" />
+    <Compile Include="Modifier\QQModifier.cs" />
+    <Compile Include="Modifier\TIMModifier.cs" />
+    <Compile Include="Modifier\WechatModifier.cs" />
+    <Compile Include="Utils\Device.cs" />
+    <Compile Include="Utils\FileUtil.cs" />
+    <Compile Include="Utils\GAHelper.cs" />
+    <Compile Include="Utils\HttpUtil.cs" />
+    <Compile Include="Utils\PathUtil.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <EmbeddedResource Include="FormMain.resx">
+      <DependentUpon>FormMain.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+      <DesignTime>True</DesignTime>
+    </Compile>
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="icon.ico" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 151 - 0
RevokeMsgPatcher/Utils/Device.cs

@@ -0,0 +1,151 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management;
+using System.Net;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace RevokeMsgPatcher.Utils
+{
+    public class Device
+    {
+        private static string macID = null;
+        private static string osVersion = null;
+
+        private static string fingerPrint = null;
+
+        #region PROP, get it only once
+
+        public static string MacID
+        {
+            get
+            {
+                if (macID == null)
+                {
+                    macID = ObtainMacID();
+                }
+                return macID;
+            }
+        }
+
+        public static string OSVersion
+        {
+            get
+            {
+                if (osVersion == null)
+                {
+                    var name = (from x in new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem").Get().Cast<ManagementObject>()
+                                select x.GetPropertyValue("Caption")).FirstOrDefault();
+                    osVersion = name != null ? name.ToString() : "Unknown";
+                }
+                return osVersion;
+            }
+        }
+        #endregion
+
+        /// <summary>
+        /// Calculate GUID
+        /// </summary>
+        /// <returns>GUID</returns>
+        public static string Value()
+        {
+            if (fingerPrint == null)
+            {
+                fingerPrint = GetHash(
+                    "MAC >> " + MacID
+                    );
+            }
+            return fingerPrint;
+        }
+
+        private static string GetHash(string s)
+        {
+            MD5 sec = new MD5CryptoServiceProvider();
+            ASCIIEncoding enc = new ASCIIEncoding();
+            byte[] bt = enc.GetBytes(s);
+            return GetHexString(sec.ComputeHash(bt));
+        }
+
+        private static string GetHexString(byte[] bt)
+        {
+            string s = string.Empty;
+            for (int i = 0; i < bt.Length; i++)
+            {
+                byte b = bt[i];
+                int n, n1, n2;
+                n = (int)b;
+                n1 = n & 15;
+                n2 = (n >> 4) & 15;
+                if (n2 > 9)
+                    s += ((char)(n2 - 10 + (int)'A')).ToString();
+                else
+                    s += n2.ToString();
+                if (n1 > 9)
+                    s += ((char)(n1 - 10 + (int)'A')).ToString();
+                else
+                    s += n1.ToString();
+                if ((i + 1) != bt.Length && (i + 1) % 2 == 0) s += "-";
+            }
+            return s;
+        }
+
+
+        #region Original Device ID Getting Code
+
+        public static string ObtainMacID()
+        {
+            return Identifier("Win32_NetworkAdapterConfiguration", "MACAddress", "IPEnabled");
+        }
+
+        private static string Identifier(string wmiClass, string wmiProperty, string wmiMustBeTrue)
+        {
+            string result = "";
+            try
+            {
+                ManagementClass mc = new ManagementClass(wmiClass);
+                ManagementObjectCollection moc = mc.GetInstances();
+                foreach (ManagementObject mo in moc)
+                {
+                    if (mo[wmiMustBeTrue].ToString() == "True")
+                    {
+                        //Only get the first one
+                        if (result == "")
+                        {
+                            result = mo[wmiProperty].ToString();
+                            break;
+                        }
+                    }
+                }
+            }
+            catch
+            {
+            }
+            return result;
+        }
+
+        private static string Identifier(string wmiClass, string wmiProperty)
+        {
+            string result = "";
+            try
+            {
+                ManagementClass mc = new ManagementClass(wmiClass);
+                ManagementObjectCollection moc = mc.GetInstances();
+                foreach (ManagementObject mo in moc)
+                {
+                    //Only get the first one
+                    if (result == "")
+                    {
+                        result = mo[wmiProperty].ToString();
+                        break;
+                    }
+                }
+            }
+            catch
+            {
+            }
+            return result;
+        }
+        #endregion
+    }
+}

+ 78 - 0
RevokeMsgPatcher/Utils/FileUtil.cs

@@ -0,0 +1,78 @@
+using RevokeMsgPatcher.Model;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace RevokeMsgPatcher.Utils
+{
+    public class FileUtil
+    {
+        /// <summary>
+        /// 获取文件版本
+        /// </summary>
+        /// <param name="path"></param>
+        /// <returns></returns>
+        public static string GetFileVersion(string path)
+        {
+            FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(path);
+            return fileVersionInfo.FileVersion;
+        }
+
+        /// <summary>
+        /// 计算文件SHA1
+        /// </summary>
+        /// <param name="s">文件路径</param>
+        /// <returns></returns>
+        public static string ComputeFileSHA1(string s)
+        {
+            FileStream file = new FileStream(s, FileMode.Open);
+            SHA1 sha1 = new SHA1CryptoServiceProvider();
+            byte[] retval = sha1.ComputeHash(file);
+            file.Close();
+
+            StringBuilder sc = new StringBuilder();
+            for (int i = 0; i < retval.Length; i++)
+            {
+                sc.Append(retval[i].ToString("x2"));
+            }
+            return sc.ToString();
+        }
+
+        /// <summary>
+        /// 修改文件指定位置的字节
+        /// </summary>
+        /// <param name="path">文件对象的路径</param>
+        /// <param name="position">偏移位置</param>
+        /// <param name="after">修改后的值</param>
+        /// <returns></returns>
+        public static bool EditHex(string path, long position, byte after)
+        {
+            using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite))
+            {
+                stream.Position = position;
+                stream.WriteByte(after);
+            }
+            return true;
+
+        }
+
+        /// <summary>
+        /// 修改文件多个指定位置的多个字节
+        /// </summary>
+        /// <param name="path">文件对象的路径</param>
+        /// <param name="changes">需要修改的位置和内容</param>
+        public static void EditMultiHex(string path, List<Change> changes)
+        {
+            using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite))
+            {
+                foreach (Change change in changes)
+                {
+                    stream.Seek(change.Position, SeekOrigin.Begin);
+                    stream.Write(change.Content, 0, change.Content.Length);
+                }
+            }
+        }
+    }
+}

+ 75 - 0
RevokeMsgPatcher/Utils/GAHelper.cs

@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace RevokeMsgPatcher.Utils
+{
+    /// <summary>
+    /// 用于软件的 Google Analytics 实现 By huiyadanli
+    /// 相关文档:
+    /// 指南 https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide
+    /// 参数 https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters
+    /// 测试 https://ga-dev-tools.appspot.com/hit-builder/
+    /// </summary>
+    public class GAHelper
+    {
+        // 根据实际情况修改
+        private static readonly HttpClient client = HttpUtil.Client;
+
+        private const string GAUrl = "https://www.google-analytics.com/collect";
+
+        // 根据实际使用分析账号设置
+        private const string tid = "UA-80358493-2"; // GA Tracking ID / Property ID.
+
+        private static readonly string cid = Device.Value(); // Anonymous Client ID. // Guid.NewGuid().ToString()
+
+        // 屏幕分辨率(可选)
+        private static readonly string sr = Screen.PrimaryScreen.Bounds.Width + "x" + Screen.PrimaryScreen.Bounds.Height;
+
+        public string UserAgent { get; set; }
+
+        public GAHelper()
+        {
+            UserAgent = string.Format("Hui Google Analytics Tracker/1.0 ({0}; {1}; {2})", Environment.OSVersion.Platform.ToString(), Environment.OSVersion.Version.ToString(), Environment.OSVersion.VersionString);
+        }
+
+        public async Task RequestPageViewAsync(string page, string title = null)
+        {
+            try
+            {
+                if (!page.StartsWith("/"))
+                {
+                    page = "/" + page;
+                }
+                // 请求参数
+                var values = new Dictionary<string, string>
+                {
+                    { "v", "1" }, // 当前必填1
+                    { "tid", tid },
+                    { "cid", cid },
+                    { "ua", UserAgent },
+                    { "t", "pageview" },
+                    { "sr", sr },
+                    { "dp", page },
+                    { "dt", title },
+                };
+                var content = new FormUrlEncodedContent(values);
+                var response = await client.PostAsync(GAUrl, content);
+            }
+            catch (Exception ex)
+            {
+
+                Console.WriteLine("GAHelper:" + ex.Message);
+            }
+        }
+
+        public void RequestPageView(string page, string title = null)
+        {
+            Task.Run(() => RequestPageViewAsync(page, title));
+        }
+    }
+}

+ 14 - 0
RevokeMsgPatcher/Utils/HttpUtil.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RevokeMsgPatcher.Utils
+{
+    public class HttpUtil
+    {
+        public static HttpClient Client { get; } = new HttpClient();
+    }
+}

+ 81 - 0
RevokeMsgPatcher/Utils/PathUtil.cs

@@ -0,0 +1,81 @@
+using Microsoft.Win32;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RevokeMsgPatcher.Utils
+{
+    public class PathUtil
+    {
+        public static void DisplayAllProgram()
+        {
+            RegistryKey uninstallKey, programKey;
+            uninstallKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall");
+            string[] programKeys = uninstallKey.GetSubKeyNames();
+            foreach (string keyName in programKeys)
+            {
+                programKey = uninstallKey.OpenSubKey(keyName);
+                Console.WriteLine(keyName + " , " + programKey.GetValue("DisplayName") + " , " + programKey.GetValue("InstallLocation"));
+                programKey.Close();
+            }
+            uninstallKey.Close();
+        }
+
+        /// <summary>
+        /// 从注册表中寻找安装路径
+        /// </summary>
+        /// <param name="uninstallKeyName">
+        /// 安装信息的注册表键名
+        /// 微信:WeChat
+        /// QQ:{052CFB79-9D62-42E3-8A15-DE66C2C97C3E} 
+        /// TIM:TIM
+        /// </param>
+        /// <returns>安装路径</returns>
+        public static string FindInstallPathFromRegistry(string uninstallKeyName)
+        {
+            try
+            {
+                RegistryKey key = Registry.LocalMachine.OpenSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{uninstallKeyName}");
+                if (key == null)
+                {
+                    return null;
+                }
+                object installLocation = key.GetValue("InstallLocation");
+                key.Close();
+                if (installLocation != null && !string.IsNullOrEmpty(installLocation.ToString()))
+                {
+                    return installLocation.ToString();
+                }
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e.Message);
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// 获取所有可能的默认安装路径
+        /// </summary>
+        /// <param name="relativePath">Tencent\*</param>
+        /// <returns></returns>
+        public static List<string> GetDefaultInstallPaths(string relativePath)
+        {
+            List<string> list = new List<string>();
+            // 从默认安装目录查找
+            string[] drives = Environment.GetLogicalDrives(); //获取当前计算机逻辑磁盘名称列表
+            foreach (string d in drives)
+            {
+                string path = Path.Combine(d, $@"Program Files (x86)\{relativePath}");
+                if (Directory.Exists(path))
+                {
+                    list.Add(path);
+                }
+            }
+            return list;
+        }
+    }
+}

BIN
RevokeMsgPatcher/icon.ico


+ 2 - 0
appveyor.yml

@@ -0,0 +1,2 @@
+image:
+- Visual Studio 2017

Some files were not shown because too many files changed in this diff