天天看點

QQ聊天記錄快速備份 v0.9

QQ聊天記錄快速備份 v0.9

本程式由愛頁工作室(www.ayeah.net)使用C#在VS2008上開發

目前釋出第一版v0.9

有以下功能:

1、輸入QQ号碼提取所有聊天記錄為TXT檔案(包括普通聊天記錄、群聊天記錄、臨時會話。。。)

2、一鍵打包成rar(自動調用rar.exe)

3、發送到郵箱中做備份(使用SMTP)

其它說明:

本程式需要.net framework 2.0以上版本運作庫

本程式為綠色版,不用安裝,不寫系統資料庫,解壓到任意目錄即用,可自動識别QQ所在目錄

聊天記錄導出不需要密碼,對于本地消息加密的MsgEX.DB暫未測試。

本程式隻作聊天記錄備份之用,請勿用于偷窺他人隐私。

點選這裡下載下傳

ayeah

2008.12.03

愛頁工作室

www.ayeah.net

參考了以下内容:

1、解密QQ消息檔案格式

http://blog.csdn.net/vbvan/archive/2007/12/14/1937440.aspx

2、Decompiling CHM (help) files with C#

http://www.codeproject.com/csharp/decompilingchm.asp?print=true

3、Google,CSDN,Google....

Storage.cs(從RelatedObjects.Storage.dll反編譯而來)

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Collections;
  6. using System.IO;
  7. using System.Runtime.InteropServices;
  8. using System.Security;
  9. namespace RelatedObjects.Storage
  10. {
  11.     [ComImport, SuppressUnmanagedCodeSecurity, Guid("0000000B-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  12.     public interface IStorage
  13.     {
  14.         [return: MarshalAs(UnmanagedType.Interface)]
  15.         UCOMIStream CreateStream([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved1, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
  16.         [return: MarshalAs(UnmanagedType.Interface)]
  17.         UCOMIStream OpenStream([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, IntPtr reserved1, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
  18.         [return: MarshalAs(UnmanagedType.Interface)]
  19.         IStorage CreateStorage([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved1, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
  20.         [return: MarshalAs(UnmanagedType.Interface)]
  21.         IStorage OpenStorage([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, IntPtr pstgPriority, [In, MarshalAs(UnmanagedType.U4)] int grfMode, IntPtr snbExclude, [In, MarshalAs(UnmanagedType.U4)] int reserved);
  22.         void CopyTo(int ciidExclude, [In, MarshalAs(UnmanagedType.LPArray)] Guid[] rgiidExclude, IntPtr snbExclude, [In, MarshalAs(UnmanagedType.Interface)] IStorage pstgDest);
  23.         void MoveElementTo([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.Interface)] IStorage pstgDest, [In, MarshalAs(UnmanagedType.BStr)] string pwcsNewName, [In, MarshalAs(UnmanagedType.U4)] int grfFlags);
  24.         void Commit(int grfCommitFlags);
  25.         void Revert();
  26.         int EnumElements([In, MarshalAs(UnmanagedType.U4)] int reserved1, IntPtr reserved2, [In, MarshalAs(UnmanagedType.U4)] int reserved3, [MarshalAs(UnmanagedType.Interface)] out IEnumSTATSTG ppenum);
  27.         void DestroyElement([In, MarshalAs(UnmanagedType.BStr)] string pwcsName);
  28.         void RenameElement([In, MarshalAs(UnmanagedType.BStr)] string pwcsOldName, [In, MarshalAs(UnmanagedType.BStr)] string pwcsNewName);
  29.         void SetElementTimes([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In] FILETIME pctime, [In] FILETIME patime, [In] FILETIME pmtime);
  30.         void SetClass(ref Guid clsid);
  31.         void SetStateBits(int grfStateBits, int grfMask);
  32.         int Stat(out STATSTG pStatStg, int grfStatFlag);
  33.     }
  34.     [ComImport, Guid("0000000D-0000-0000-C000-000000000046"), SuppressUnmanagedCodeSecurity, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  35.     public interface IEnumSTATSTG
  36.     {
  37.         [PreserveSig]
  38.         int Next(int celt, out STATSTG rgVar, out int pceltFetched);
  39.         [PreserveSig]
  40.         int Skip(int celt);
  41.         [PreserveSig]
  42.         int Reset();
  43.         int Clone(out IEnumSTATSTG newEnum);
  44.     }
  45.     public class Ole32
  46.     {
  47.         // Methods
  48.         [DllImport("Ole32.dll")]
  49.         public static extern int StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string wcsName, IStorage pstgPriority, int grfMode, IntPtr snbExclude, int reserved, out IStorage storage);
  50.     }
  51.     public class IStorageWrapper : IBaseStorageWrapper
  52.     {
  53.         // Methods
  54.         public IStorageWrapper(string workPath)
  55.         {
  56.             Ole32.StgOpenStorage(workPath, null, 0x10, IntPtr.Zero, 0, out this.storage);
  57.             IBaseStorageWrapper.BaseUrl = workPath;
  58.             STATSTG pStatStg = new STATSTG();
  59.             base.storage.Stat(out pStatStg, 1);
  60.             base.EnumIStorageObject(base.storage);
  61.         }
  62.     }
  63.     public class IBaseStorageWrapper
  64.     {
  65.         // Fields
  66.         private static string baseUrl;
  67.         public FileObjects foCollection = new FileObjects();
  68.         protected IStorage storage;
  69.         // Methods
  70.         protected void EnumIStorageObject(IStorage stgArgument)
  71.         {
  72.             this.EnumIStorageObject(stgArgument, "");
  73.         }
  74.         protected void EnumIStorageObject(IStorage stgArgument, string BasePath)
  75.         {
  76.             IEnumSTATSTG mstatstg;
  77.             STATSTG statstg;
  78.             int num;
  79.             stgArgument.EnumElements(0, IntPtr.Zero, 0, out mstatstg);
  80.             mstatstg.Reset();
  81.             while (mstatstg.Next(1, out statstg, out num) == 0)
  82.             {
  83.                 FileObjects.FileObject fo = new FileObjects.FileObject();
  84.                 fo.FileType = statstg.type;
  85.                 switch (statstg.type)
  86.                 {
  87.                     case 1:
  88.                         {
  89.                             IStorage storage = stgArgument.OpenStorage(statstg.pwcsName, IntPtr.Zero, 0x10, IntPtr.Zero, 0);
  90.                             if (storage != null)
  91.                             {
  92.                                 string basePath = BasePath + statstg.pwcsName.ToString();
  93.                                 fo.FileStorage = storage;
  94.                                 fo.FilePath = BasePath;
  95.                                 fo.FileName = statstg.pwcsName.ToString();
  96.                                 this.foCollection.Add(fo);
  97.                                 this.EnumIStorageObject(storage, basePath);
  98.                             }
  99.                             break;
  100.                         }
  101.                     case 2:
  102.                         {
  103.                             UCOMIStream stream = stgArgument.OpenStream(statstg.pwcsName, IntPtr.Zero, 0x10, 0);
  104.                             fo.FilePath = BasePath;
  105.                             fo.FileName = statstg.pwcsName.ToString();
  106.                             fo.FileStream = stream;
  107.                             this.foCollection.Add(fo);
  108.                             break;
  109.                         }
  110.                     case 3:
  111.                         Console.WriteLine("[Property:ILockBytes] Ignoring...");
  112.                         break;
  113.                     case 4:
  114.                         Console.WriteLine("[Property:IProperty] Ignoring...");
  115.                         break;
  116.                     default:
  117.                         Console.WriteLine("Unknown object, skipping and continuing...");
  118.                         break;
  119.                 }
  120.                 if (statstg.type == 1)
  121.                 {
  122.                     Console.WriteLine("Type: STORAGE");
  123.                 }
  124.             }
  125.         }
  126.         // Properties
  127.         public static string BaseUrl
  128.         {
  129.             get
  130.             {
  131.                 return baseUrl;
  132.             }
  133.             set
  134.             {
  135.                 baseUrl = "mk:@MSITStore:" + value + "::/";
  136.             }
  137.         }
  138.         // Nested Types
  139.         public class FileObjects : CollectionBase
  140.         {
  141.             // Methods
  142.             public void Add(FileObject fo)
  143.             {
  144.                 base.List.Add(fo);
  145.             }
  146.             public FileObject Item(int index)
  147.             {
  148.                 return (FileObject)base.List[index];
  149.             }
  150.             public void Remove(int index)
  151.             {
  152.                 if ((index < (base.Count - 1)) && (index > 0))
  153.                 {
  154.                     base.List.RemoveAt(index);
  155.                 }
  156.             }
  157.             // Nested Types
  158.             public class FileObject : Stream
  159.             {
  160.                 // Fields
  161.                 private string fileName;
  162.                 private string filePath;
  163.                 private IStorage fileStorage;
  164.                 private UCOMIStream fileStream;
  165.                 private int fileType;
  166.                 private string fileUrl;
  167.                 // Methods
  168.                 public override void Close()
  169.                 {
  170.                     if (this.fileStream != null)
  171.                     {
  172.                         this.fileStream.Commit(0);
  173.                         Marshal.ReleaseComObject(this.fileStream);
  174.                         this.fileStream = null;
  175.                         GC.SuppressFinalize(this);
  176.                     }
  177.                 }
  178.                 public override void Flush()
  179.                 {
  180.                     if (this.fileStream == null)
  181.                     {
  182.                         throw new ObjectDisposedException("theStream");
  183.                     }
  184.                     this.fileStream.Commit(0);
  185.                 }
  186.                 public override int Read(byte[] buffer, int offset, int count)
  187.                 {
  188.                     if (this.fileStream == null)
  189.                     {
  190.                         throw new ObjectDisposedException("theStream");
  191.                     }
  192.                     int length = 0;
  193.                     object obj2 = length;
  194.                     GCHandle handle = new GCHandle();
  195.                     try
  196.                     {
  197.                         handle = GCHandle.Alloc(obj2, GCHandleType.Pinned);
  198.                         IntPtr pcbRead = handle.AddrOfPinnedObject();
  199.                         if (offset != 0)
  200.                         {
  201.                             byte[] pv = new byte[count - 1];
  202.                             this.fileStream.Read(pv, count, pcbRead);
  203.                             length = (int)obj2;
  204.                             Array.Copy(pv, 0, buffer, offset, length);
  205.                             return length;
  206.                         }
  207.                         this.fileStream.Read(buffer, count, pcbRead);
  208.                         length = (int)obj2;
  209.                     }
  210.                     finally
  211.                     {
  212.                         if (handle.IsAllocated)
  213.                         {
  214.                             handle.Free();
  215.                         }
  216.                     }
  217.                     return length;
  218.                 }
  219.                 public string ReadFromFile()
  220.                 {
  221.                     int num;
  222.                     if (this.fileStream == null)
  223.                     {
  224.                         throw new ObjectDisposedException("theStream");
  225.                     }
  226.                     Stream stream = new MemoryStream();
  227.                     byte[] buffer = new byte[this.Length];
  228.                     this.Seek(0L, SeekOrigin.Begin);
  229.                     while ((num = this.Read(buffer, 0, 0x400)) > 0)
  230.                     {
  231.                         stream.Write(buffer, 0, num);
  232.                     }
  233.                     stream.Seek(0L, SeekOrigin.Begin);
  234.                     StreamReader reader = new StreamReader(stream);
  235.                     return reader.ReadToEnd().ToString();
  236.                 }
  237.                 public void Save(string FileName)
  238.                 {
  239.                     int num;
  240.                     if (this.fileStream == null)
  241.                     {
  242.                         throw new ObjectDisposedException("theStream");
  243.                     }
  244.                     byte[] buffer = new byte[this.Length];
  245.                     this.Seek(0L, SeekOrigin.Begin);
  246.                     Stream stream = File.OpenWrite(FileName);
  247.                     while ((num = this.Read(buffer, 0, 0x400)) > 0)
  248.                     {
  249.                         stream.Write(buffer, 0, num);
  250.                     }
  251.                     stream.Close();
  252.                 }
  253.                 public override long Seek(long offset, SeekOrigin origin)
  254.                 {
  255.                     if (this.fileStream == null)
  256.                     {
  257.                         throw new ObjectDisposedException("theStream");
  258.                     }
  259.                     long num = 0L;
  260.                     object obj2 = num;
  261.                     GCHandle handle = new GCHandle();
  262.                     try
  263.                     {
  264.                         handle = GCHandle.Alloc(obj2, GCHandleType.Pinned);
  265.                         IntPtr plibNewPosition = handle.AddrOfPinnedObject();
  266.                         this.fileStream.Seek(offset, (int)origin, plibNewPosition);
  267.                         num = (long)obj2;
  268.                     }
  269.                     finally
  270.                     {
  271.                         if (handle.IsAllocated)
  272.                         {
  273.                             handle.Free();
  274.                         }
  275.                     }
  276.                     return num;
  277.                 }
  278.                 public override void SetLength(long Value)
  279.                 {
  280.                     if (this.fileStream == null)
  281.                     {
  282.                         throw new ObjectDisposedException("theStream");
  283.                     }
  284.                     this.fileStream.SetSize(Value);
  285.                 }
  286.                 public override void Write(byte[] buffer, int offset, int count)
  287.                 {
  288.                     if (this.fileStream == null)
  289.                     {
  290.                         throw new ObjectDisposedException("theStream");
  291.                     }
  292.                     if (offset != 0)
  293.                     {
  294.                         int length = buffer.Length - offset;
  295.                         byte[] destinationArray = new byte[length];
  296.                         Array.Copy(buffer, offset, destinationArray, 0, length);
  297.                         this.fileStream.Write(destinationArray, length, IntPtr.Zero);
  298.                     }
  299.                     else
  300.                     {
  301.                         this.fileStream.Write(buffer, count, IntPtr.Zero);
  302.                     }
  303.                 }
  304.                 // Properties
  305.                 public override bool CanRead
  306.                 {
  307.                     get
  308.                     {
  309.                         return (this.fileStream != null);
  310.                     }
  311.                 }
  312.                 public override bool CanSeek
  313.                 {
  314.                     get
  315.                     {
  316.                         return true;
  317.                     }
  318.                 }
  319.                 public override bool CanWrite
  320.                 {
  321.                     get
  322.                     {
  323.                         return true;
  324.                     }
  325.                 }
  326.                 public string FileName
  327.                 {
  328.                     get
  329.                     {
  330.                         return this.fileName;
  331.                     }
  332.                     set
  333.                     {
  334.                         this.fileName = value;
  335.                     }
  336.                 }
  337.                 public string FilePath
  338.                 {
  339.                     get
  340.                     {
  341.                         return this.filePath;
  342.                     }
  343.                     set
  344.                     {
  345.                         this.filePath = value;
  346.                     }
  347.                 }
  348.                 public IStorage FileStorage
  349.                 {
  350.                     get
  351.                     {
  352.                         return this.fileStorage;
  353.                     }
  354.                     set
  355.                     {
  356.                         this.fileStorage = value;
  357.                     }
  358.                 }
  359.                 public UCOMIStream FileStream
  360.                 {
  361.                     get
  362.                     {
  363.                         return this.fileStream;
  364.                     }
  365.                     set
  366.                     {
  367.                         this.fileStream = value;
  368.                     }
  369.                 }
  370.                 public int FileType
  371.                 {
  372.                     get
  373.                     {
  374.                         return this.fileType;
  375.                     }
  376.                     set
  377.                     {
  378.                         this.fileType = value;
  379.                     }
  380.                 }
  381.                 public string FileUrl
  382.                 {
  383.                     get
  384.                     {
  385.                         return (IBaseStorageWrapper.BaseUrl + this.FilePath.Replace(@"/", "/") + "/" + this.FileName);
  386.                     }
  387.                     set
  388.                     {
  389.                         this.fileUrl = value;
  390.                     }
  391.                 }
  392.                 public override long Length
  393.                 {
  394.                     get
  395.                     {
  396.                         STATSTG statstg;
  397.                         if (this.fileStream == null)
  398.                         {
  399.                             throw new ObjectDisposedException("theStream");
  400.                         }
  401.                         this.fileStream.Stat(out statstg, 1);
  402.                         return statstg.cbSize;
  403.                     }
  404.                 }
  405.                 public override long Position
  406.                 {
  407.                     get
  408.                     {
  409.                         return this.Seek(0L, SeekOrigin.Current);
  410.                     }
  411.                     set
  412.                     {
  413.                         this.Seek(value, SeekOrigin.Begin);
  414.                     }
  415.                 }
  416.             }
  417.         }
  418.     }
  419. }

QQMsgMgr.cs -- 從vbvan處修改而來,主要修改了一直占用MsgEx.db的毛病

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.IO;
  5. using System.Text;
  6. using RelatedObjects.Storage;
  7. namespace QQBackup
  8. {
  9.     public enum QQMsgType
  10.     {
  11.         BIM, C2C, Group, Sys, Mobile, TempSession //Disc
  12.     }
  13.     class QQMsgMgr
  14.     {
  15.         private static readonly int s_MsgTypeNum = (int)QQMsgType.TempSession + 1;
  16.         private static readonly string[] s_MsgName = new string[] {
  17.             "BIMMsg", "C2CMsg", "GroupMsg", "SysMsg", "MobileMsg", "TempSessionMsg"
  18.         };
  19.         private IStorageWrapper m_Storage;
  20.         private byte[] m_Password;
  21.         private List<string>[] m_MsgList = new List<string>[s_MsgTypeNum];
  22.         public void Open(string QQID)
  23.         {
  24.             Open(QQID, null);
  25.         }
  26.         public void Open(string QQID, string QQPath)
  27.         {
  28.             if (QQPath == null)
  29.             {
  30.                 using (Microsoft.Win32.RegistryKey reg = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"Software/Tencent/QQ"))
  31.                 {
  32.                     QQPath = reg.GetValue("Install") as string;
  33.                 }
  34.                 if (QQPath == null) return;
  35.             }
  36.             for (int i = 0; i < m_MsgList.Length; ++i)
  37.             {
  38.                 m_MsgList[i] = new List<string>();
  39.             }
  40.             m_Storage = null;
  41.             m_Password = null;
  42.             m_Storage = new IStorageWrapper(QQPath + QQID + @"/MsgEx.db");
  43.             m_Password = QQMsgMgr.GetGlobalPass(m_Storage, QQID);
  44.             if (m_Password == null) m_Storage = null;
  45.             foreach (IBaseStorageWrapper.FileObjects.FileObject fileObject in m_Storage.foCollection)
  46.             {
  47.                 if (fileObject.FileType == 1)
  48.                 {
  49.                     for (int i = 0; i < m_MsgList.Length; ++i)
  50.                     {
  51.                         if (fileObject.FilePath == s_MsgName[i])
  52.                         {
  53.                             m_MsgList[i].Add(fileObject.FileName);
  54.                         }
  55.                     }
  56.                 }
  57.             }
  58.         }
  59.         public void OutputMsg()
  60.         {
  61.             for (int i = 0; i < s_MsgTypeNum; ++i)
  62.             {
  63.                 OutputMsg((QQMsgType)i);
  64.             }
  65.         }
  66.         public void OutputMsg(QQMsgType type)
  67.         {
  68.             if (m_Storage == null) return;
  69.             if (m_Password == null) return;
  70.             int typeIndex = (int)type;
  71.             if (typeIndex < 0 || typeIndex >= s_MsgTypeNum)
  72.             {
  73.                 throw new ArgumentException("Invalid QQMsgType", "type");
  74.             }
  75.             string filePath = s_MsgName[typeIndex] + "//";
  76.             Directory.CreateDirectory(filePath);
  77.             foreach (string QQID in m_MsgList[typeIndex])
  78.             {
  79.                 string fileName = filePath + QQID + ".txt";
  80.                 OutputMsg(type, QQID, fileName);
  81.             }
  82.         }
  83.         public void OutputMsg(QQMsgType type, string QQID)
  84.         {
  85.             if (m_Storage == null) return;
  86.             if (m_Password == null) return;
  87.             int typeIndex = (int)type;
  88.             if (typeIndex < 0 || typeIndex >= s_MsgTypeNum)
  89.             {
  90.                 throw new ArgumentException("Invalid QQMsgType", "type");
  91.             }
  92.             string filePath = s_MsgName[typeIndex] + "//";
  93.             Directory.CreateDirectory(filePath);
  94.             string fileName = filePath + QQID + ".txt";
  95.             OutputMsg(type, QQID, fileName);
  96.         }
  97.         private void OutputMsg(QQMsgType type, string QQID, string fileName)
  98.         {
  99.             string msgPath = s_MsgName[(int)type] + QQID;
  100.             IList<byte[]> msgList = QQMsgMgr.DecryptMsg(m_Storage, msgPath, m_Password);
  101.             Encoding encoding = Encoding.GetEncoding(936);
  102.             using (FileStream fs = new FileStream(fileName, FileMode.Create))
  103.             {
  104.                 using (StreamWriter sw = new StreamWriter(fs))
  105.                 {
  106.                     for (int i = 0; i < msgList.Count; ++i)
  107.                     {
  108.                         using (MemoryStream ms = new MemoryStream(msgList[i]))
  109.                         {
  110.                             using (BinaryReader br = new BinaryReader(ms, Encoding.GetEncoding(936)))
  111.                             {
  112. #if false
  113.                                 fs.Write(msgList[i], 0, msgList[i].Length);
  114. #else
  115.                                 int ticks = br.ReadInt32();
  116.                                 DateTime time = new DateTime(1970, 1, 1) + new TimeSpan(0, 0, ticks);
  117.                                 switch (type)
  118.                                 {
  119.                                     case QQMsgType.BIM:
  120.                                     case QQMsgType.C2C:
  121.                                     case QQMsgType.Mobile:
  122.                                         ms.Seek(1, SeekOrigin.Current);
  123.                                         break;
  124.                                     case QQMsgType.Group:
  125.                                         ms.Seek(8, SeekOrigin.Current);
  126.                                         break;
  127.                                     case QQMsgType.Sys:
  128.                                         ms.Seek(4, SeekOrigin.Current);
  129.                                         break;
  130.                                     case QQMsgType.TempSession: //?
  131.                                         ms.Seek(9, SeekOrigin.Current);
  132.                                         break;
  133.                                 }
  134.                                 if (type == QQMsgType.TempSession)
  135.                                 {
  136.                                     int gLen = br.ReadInt32();
  137.                                     string groupName = encoding.GetString(br.ReadBytes(gLen));
  138.                                     if (groupName.Length > 0) sw.WriteLine("{0}", groupName);
  139.                                 }
  140.                                 int nLen = br.ReadInt32();
  141.                                 string id = encoding.GetString(br.ReadBytes(nLen));
  142.                                 sw.WriteLine("{0}: {1}", id, time.ToString());
  143.                                 int cLen = br.ReadInt32();
  144.                                 string msg = encoding.GetString(br.ReadBytes(cLen));
  145.                                 //msg=msg.Replace("/n", Environment.NewLine);
  146.                                 msg = msg.Replace(new string(new char[] { (char)21 }), "(圖檔)");
  147.                                 msg = msg.Replace(new string(new char[] { (char)20 }), "(表情)");
  148.                                 char[] t = msg.ToCharArray();
  149.                                 msg=msg.Split((char)19)[0];
  150.                                 msg = msg.Replace("/n", Environment.NewLine);
  151.                                 sw.WriteLine(msg);
  152.                                 sw.WriteLine();
  153. #endif
  154.                             }
  155.                         }
  156.                     }
  157.                     sw.Close();
  158.                 }
  159.                 fs.Close();
  160.             }
  161.         }
  162.         public void OutputFileList()
  163.         {
  164.             if (m_Storage == null) return;
  165.             Dictionary<string, long> dic = new Dictionary<string, long>();
  166.             foreach (IBaseStorageWrapper.FileObjects.FileObject fileObject in m_Storage.foCollection)
  167.             {
  168.                 if (fileObject.FileType == 2 && fileObject.FileName == "Index.msj")
  169.                 {
  170.                     dic[fileObject.FilePath] = fileObject.Length / 4;
  171.                 }
  172.             }
  173.             for (int i = 0; i < m_MsgList.Length; ++i)
  174.             {
  175.                 Console.WriteLine("{0}", s_MsgName[i]);
  176.                 foreach (string ID in m_MsgList[i])
  177.                 {
  178.                     Console.WriteLine("/t{0}: {1}", ID, dic[s_MsgName[i] + ID]);
  179.                 }
  180.             }
  181.         }
  182.         private static IBaseStorageWrapper.FileObjects.FileObject GetStorageFileObject(IStorageWrapper iw, string path, string fileName)
  183.         {
  184.             foreach (IBaseStorageWrapper.FileObjects.FileObject fileObject in iw.foCollection)
  185.             {
  186.                 if (fileObject.CanRead)
  187.                 {
  188.                     if (fileObject.FilePath == path && fileObject.FileName == fileName) return fileObject;
  189.                 }
  190.             }
  191.             return null;
  192.         }
  193.         private static byte[] Decrypt(byte[] src, byte[] pass, long offset)
  194.         {
  195.             RedQ.QQCrypt decryptor = new RedQ.QQCrypt();
  196.             return decryptor.QQ_Decrypt(src, pass, offset);
  197.         }
  198.         private static IList<byte[]> DecryptMsg(IStorageWrapper iw, string path, byte[] pass)
  199.         {
  200.             List<byte[]> msgList = new List<byte[]>();
  201.             int num = 0;
  202.             int[] pos = null;
  203.             int[] len = null;
  204.             using (IBaseStorageWrapper.FileObjects.FileObject fileObject = GetStorageFileObject(iw, path, "Index.msj"))
  205.             {
  206.                 if (fileObject == null) return msgList;
  207.                 int fileLen = (int)fileObject.Length;
  208.                 num = fileLen / 4;
  209.                 pos = new int[num + 1];
  210.                 using (BinaryReader br = new BinaryReader(fileObject))
  211.                 {
  212.                     for (int i = 0; i < num; ++i)
  213.                     {
  214.                         pos[i] = br.ReadInt32();
  215.                     }
  216.                 }
  217.             }
  218.             using (IBaseStorageWrapper.FileObjects.FileObject fileObject = GetStorageFileObject(iw, path, "Data.msj"))
  219.             {
  220.                 if (fileObject != null)
  221.                 {
  222.                     int fileLen = (int)fileObject.Length;
  223.                     len = new int[num];
  224.                     pos[num] = fileLen;
  225.                     for (int i = 0; i < num; ++i)
  226.                     {
  227.                         len[i] = pos[i + 1] - pos[i];
  228.                     }
  229.                     using (BinaryReader br = new BinaryReader(fileObject))
  230.                     {
  231.                         for (int i = 0; i < num; ++i)
  232.                         {
  233.                             fileObject.Seek(pos[i], SeekOrigin.Begin);
  234.                             byte[] data = br.ReadBytes(len[i]);
  235.                             byte[] msg = Decrypt(data, pass, 0);
  236.                             msgList.Add(msg);
  237.                         }
  238.                     }
  239.                 }
  240.             }
  241.             return msgList;
  242.         }
  243.         private static byte[] GetGlobalPass(IStorageWrapper iw, string QQID)
  244.         {
  245.             System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
  246.             byte[] dataID = new byte[QQID.Length];
  247.             for (int i = 0; i < QQID.Length; ++i) dataID[i] = (byte)(QQID[i]);
  248.             byte[] hashID = md5.ComputeHash(dataID);
  249.             IBaseStorageWrapper.FileObjects.FileObject fileObject = GetStorageFileObject(iw, "Matrix", "Matrix.db");
  250.             if (fileObject != null)
  251.             {
  252.                 using (BinaryReader br = new BinaryReader(fileObject))
  253.                 {
  254.                     byte[] data = br.ReadBytes((int)fileObject.Length);
  255.                     long len = data.Length;
  256.                     if (len < 6 || data[0] != 0x51 || data[1] != 0x44) return null;
  257.                     if (len >= 32768) return null;
  258.                     bool bl = false;
  259.                     int i = 6;
  260.                     while (i < len)
  261.                     {
  262.                         bl = false;
  263.                         byte type = data[i++];
  264.                         if (i + 2 > len) break;
  265.                         int len1 = data[i] + data[i + 1] * 256;
  266.                         byte xor1 = (byte)(data[i] ^ data[i + 1]);
  267.                         i += 2;
  268.                         if (i + len1 > len) break;
  269.                         for (int j = 0; j < len1; ++j) data[i + j] = (byte)(~(data[i + j] ^ xor1));
  270.                         if (len1 == 3 && data[i] == 0x43 && data[i + 1] == 0x52 && data[i + 2] == 0x4B)
  271.                         {
  272.                             bl = true;
  273.                         }
  274.                         i += len1;
  275.                         if (type > 7) break;
  276.                         if (i + 4 > len) break;
  277.                         int len2 = data[i] + data[i + 1] * 256 + data[i + 2] * 256 * 256 + data[i + 3] * 256 * 256 * 256;
  278.                         byte xor2 = (byte)(data[i] ^ data[i + 1]);
  279.                         i += 4;
  280.                         if (i + len2 > len) break;
  281.                         if (type == 6 || type == 7)
  282.                         {
  283.                             for (int j = 0; j < len2; ++j) data[i + j] = (byte)(~(data[i + j] ^ xor2));
  284.                         }
  285.                         if (bl && len2 == 0x20)
  286.                         {
  287.                             byte[] dataT = new byte[len2];
  288.                             for (int j = 0; j < len2; ++j) dataT[j] = data[i + j];
  289.                             return Decrypt(dataT, hashID, 0);
  290.                         }
  291.                         i += len2;
  292.                     }
  293.                     if (i != len) return null;
  294.                 }
  295.             }
  296.             return null;
  297.         }
  298.         public void Close(string QQID) {
  299.             if (m_Storage == null)
  300.             {
  301.                 return;
  302.             }
  303.             else {
  304.                 m_Storage = null;
  305.             }
  306.         }
  307.     }
  308. }

QQCrypt.cs -- 原封未動,直接copy過來的

  1. using System;
  2. namespace RedQ
  3. {
  4.     /// <summary>
  5.     /// QQ Msg En/DeCrypt Class
  6.     /// Writen By Red_angelX On 2006.9.13
  7.     /// </summary>
  8.     public class QQCrypt
  9.     {    
  10.         //QQ TEA-16 Encrypt/Decrypt Class 
  11.         // 
  12.         // 
  13.         //And also LumaQQ//s source code 
  14.         //  CopyRight:No CopyRight^_^ 
  15.         //  Author : Red_angelX     
  16.         //  NetWork is Free,Tencent is ****!
  17.         // 
  18.         //Class Begin 
  19.         //AD:Find Job!!,If you Want Give me a Job,Content Me!!
  20.         //Copied & translated from LumaQQ//s source code          `From LumaQQ///s source code: 
  21.         private byte[] Plain;                                   //指向目前的明文塊 
  22.         private byte[] prePlain ;                               //指向前面一個明文塊 
  23.         private byte[] Out;                                     //輸出的密文或者明文 
  24.         private long Crypt, preCrypt;                           //目前加密的密文位置和上一次加密的密文塊位置,他們相差8 
  25.         private long Pos;                                       //目前處理的加密解密塊的位置 
  26.         private long padding;                                   //填充數 
  27.         private byte[] Key = new byte[16];                      //密鑰 
  28.         private bool Header;                                    //用于加密時,表示目前是否是第一個8位元組塊,因為加密算法 
  29.                                                                 //是回報的,但是最開始的8個位元組沒有回報可用,所有需要标 
  30.                                                                 //明這種情況 
  31.         private long contextStart;                              //這個表示目前解密開始的位置,之是以要這麼一個變量是為了 
  32.                                                                 //避免當解密到最後時後面已經沒有資料,這時候就會出錯,這 
  33.                                                                 //個變量就是用來判斷這種情況免得出錯 
  34.         public QQCrypt()
  35.         {
  36.             //
  37.             // TODO: 在此處添加構造函數邏輯
  38.             //
  39.         }
  40.         //Push 資料
  41.         byte[] CopyMemory(byte[] arr,int arr_index,long input)  //lenth = 4
  42.         {
  43.             if(arr_index+4 > arr.Length)
  44.             {
  45.                 // 不能執行
  46.                 return arr;
  47.             }
  48.             arr[arr_index+3]=(byte)((input & 0xff000000) >> 24);
  49.             arr[arr_index+2]=(byte)((input & 0x00ff0000) >> 16);
  50.             arr[arr_index+1]=(byte)((input & 0x0000ff00) >> 8);
  51.             arr[arr_index]=(byte)(input & 0x000000ff);
  52.             arr[arr_index] &= 0xff;
  53.             arr[arr_index+1] &= 0xff;
  54.             arr[arr_index+2] &= 0xff;
  55.             arr[arr_index+3] &= 0xff;
  56.             return arr;
  57.         }
  58.         long CopyMemory(long Out,byte[] arr,int arr_index)
  59.         {
  60.             if(arr_index+4 > arr.Length)
  61.             {
  62.                 return Out;
  63.                 //不能執行
  64.             }
  65.             long x1 = arr[arr_index+3] << 24;
  66.             long x2 = arr[arr_index+2] << 16;
  67.             long x3 = arr[arr_index+1] << 8;
  68.             long x4 = arr[arr_index];
  69.             long o = x1 | x2 | x3 | x4;
  70.             o &= 0xffffffff;
  71.             return o;
  72.         }
  73.         long getUnsignedInt(byte[] arrayIn, int offset,int len ) 
  74.         {
  75.             long ret = 0;
  76.             int end = 0;
  77.             if (len > 8)
  78.                 end = offset + 8;
  79.             else
  80.                 end = offset + len;
  81.             for (int i = offset; i < end; i++) 
  82.             {
  83.                 ret <<= 8;
  84.                 ret |= arrayIn[i] & 0xff;
  85.             }
  86.             return (ret & 0xffffffff) | (ret >> 32);
  87.         }
  88.         long Rand()
  89.         {
  90.             Random rd = new Random();
  91.             long ret;
  92.             ret = rd.Next() + (rd.Next() % 1024);
  93.             return ret;
  94.         }
  95.         private byte[] Decipher(byte[] arrayIn,byte[] arrayKey,long offset)
  96.         {
  97.             //long Y,z,a,b,c,d;
  98.             long sum,delta;
  99.             //Y=z=a=b=c=d=0;
  100.             byte[] tmpArray = new byte[24];
  101.             byte[] tmpOut = new byte[8];
  102.             if(arrayIn.Length < 8)
  103.             {
  104.                 // Error:return
  105.                 return tmpOut;
  106.             }
  107.             if(arrayKey.Length < 16)
  108.             {
  109.                 // Error:return
  110.                 return tmpOut;
  111.             }
  112.             sum = 0xE3779B90; 
  113.             sum = sum & 0xFFFFFFFF; 
  114.             delta = 0x9E3779B9; 
  115.             delta = delta & 0xFFFFFFFF; 
  116.             long Y = getUnsignedInt(arrayIn, (int)offset, 4);
  117.             long z = getUnsignedInt(arrayIn, (int)offset + 4, 4);
  118.             long a = getUnsignedInt(arrayKey, 0, 4);
  119.             long b = getUnsignedInt(arrayKey, 4, 4);
  120.             long c = getUnsignedInt(arrayKey, 8, 4);
  121.             long d = getUnsignedInt(arrayKey, 12, 4);
  122.             for(int i=1;i<=16;i++)
  123.             {
  124.                 z -= ((Y<<4)+c) ^ (Y+sum) ^ ((Y>>5)+d);
  125.                 z &= 0xFFFFFFFF;
  126.                 Y -= ((z<<4)+a) ^ (z+sum) ^ ((z>>5)+b);
  127.                 Y &= 0xFFFFFFFF;
  128.                 sum -= delta;
  129.                 sum &= 0xFFFFFFFF; 
  130.             }
  131.             tmpArray = CopyMemory(tmpArray,0,Y);
  132.             tmpArray = CopyMemory(tmpArray,4,z);
  133.             tmpOut[0] = tmpArray[3]; 
  134.             tmpOut[1] = tmpArray[2]; 
  135.             tmpOut[2] = tmpArray[1]; 
  136.             tmpOut[3] = tmpArray[0]; 
  137.             tmpOut[4] = tmpArray[7]; 
  138.             tmpOut[5] = tmpArray[6]; 
  139.             tmpOut[6] = tmpArray[5]; 
  140.             tmpOut[7] = tmpArray[4]; 
  141.             return tmpOut;    
  142.         }
  143.         private byte[] Decipher(byte[] arrayIn,byte[] arrayKey)
  144.         {
  145.             return Decipher(arrayIn,arrayKey,0);
  146.         }
  147.         private byte[] Encipher(byte[] arrayIn,byte[] arrayKey,long offset)
  148.         {
  149.             byte[] tmpOut = new byte[8];
  150.             byte[] tmpArray = new byte[24];
  151.             //long Y,z,a,b,c,d;
  152.             //Y=z=a=b=c=d=0;
  153.             long sum,delta;
  154.             if(arrayIn.Length < 8)
  155.             {
  156.                 // Error:
  157.                 return tmpOut;
  158.             }
  159.             if(arrayKey.Length < 16)
  160.             {
  161.                 // Error:
  162.                 return tmpOut;
  163.             }
  164.             sum = 0;
  165.             delta = 0x9E3779B9;
  166.             delta &= 0xFFFFFFFF;
  167.             long Y = getUnsignedInt(arrayIn, (int)offset, 4);
  168.             long z = getUnsignedInt(arrayIn, (int)offset + 4, 4);
  169.             long a = getUnsignedInt(arrayKey, 0, 4);
  170.             long b = getUnsignedInt(arrayKey, 4, 4);
  171.             long c = getUnsignedInt(arrayKey, 8, 4);
  172.             long d = getUnsignedInt(arrayKey, 12, 4);
  173.             for(int i=1;i<=16;i++)
  174.             {
  175.                 sum += delta;
  176.                 sum &= 0xFFFFFFFF;
  177.                 Y += ((z<<4)+a) ^ (z+sum) ^ ((z>>5)+b);
  178.                 Y &= 0xFFFFFFFF;
  179.                 z += ((Y<<4)+c) ^ (Y+sum) ^ ((Y>>5)+d);
  180.                 z &= 0xFFFFFFFF;
  181.             }
  182.             tmpArray = CopyMemory(tmpArray,0,Y);
  183.             tmpArray = CopyMemory(tmpArray,4,z);
  184.             tmpOut[0] = tmpArray[3]; 
  185.             tmpOut[1] = tmpArray[2]; 
  186.             tmpOut[2] = tmpArray[1];
  187.             tmpOut[3] = tmpArray[0]; 
  188.             tmpOut[4] = tmpArray[7]; 
  189.             tmpOut[5] = tmpArray[6]; 
  190.             tmpOut[6] = tmpArray[5]; 
  191.             tmpOut[7] = tmpArray[4]; 
  192.             return tmpOut;
  193.         }
  194.         private byte[] Encipher(byte[] arrayIn,byte[] arrayKey)
  195.         {
  196.             return Encipher(arrayIn,arrayKey,0);
  197.         }
  198.         private void Encrypt8Bytes()
  199.         {
  200.             byte[] Crypted;
  201.             for(Pos=0;Pos<=7;Pos++)
  202.             {
  203.                 if(this.Header == true)
  204.                 {
  205.                     Plain[Pos] = (byte)(Plain[Pos] ^ prePlain[Pos]);
  206.                 }
  207.                 else
  208.                 {
  209.                     Plain[Pos] = (byte)(Plain[Pos] ^ Out[preCrypt + Pos]);
  210.                 }
  211.             }
  212.             Crypted = Encipher(Plain,Key);
  213.             for(int i=0;i<=7;i++)
  214.             {
  215.                 Out[Crypt + i] = (byte)Crypted[i];
  216.             }
  217.             for(Pos=0;Pos<=7;Pos++)
  218.             {
  219.                 Out[Crypt + Pos] = (byte)(Out[Crypt + Pos] ^ prePlain[Pos]);
  220.             }            
  221.             Plain.CopyTo(prePlain,0);
  222.             preCrypt = Crypt;
  223.             Crypt = Crypt + 8;
  224.             Pos = 0;
  225.             Header = false;
  226.         }
  227.         private bool Decrypt8Bytes(byte[] arrayIn,long offset)
  228.         {
  229.             long lngTemp;
  230.             for(Pos=0;Pos<=7;Pos++)
  231.             {
  232.                 if(this.contextStart+Pos > arrayIn.Length-1) 
  233.                 {
  234.                     return true;
  235.                 }
  236.                 prePlain[Pos] = (byte)(prePlain[Pos] ^ arrayIn[offset+Crypt+Pos]);
  237.             }
  238.             try
  239.             {
  240.                 prePlain = this.Decipher(prePlain,Key);
  241.             }
  242.             catch
  243.             {
  244.                 return false;
  245.             }
  246.             lngTemp = prePlain.Length - 1;
  247.             contextStart += 8;
  248.             Crypt+=8;
  249.             Pos = 0;
  250.             return true;
  251.         }
  252.         private bool Decrypt8Bytes(byte[] arrayIn)
  253.         {
  254.             return Decrypt8Bytes(arrayIn,0);
  255.         }
  256.         #region Public Methods!
  257.         /// <summary>
  258.         /// QQ TEA 加密函數
  259.         /// </summary>
  260.         /// <param name="arrayIn">要加密的字串</param>
  261.         /// <param name="arrayKey">密鑰</param>
  262.         /// <param name="offset">偏移</param>
  263.         /// <returns></returns>
  264.         public byte[] QQ_Encrypt(byte[] arrayIn,byte[] arrayKey,long offset)
  265.         {
  266.             Plain = new byte[8];
  267.             prePlain = new byte[8];
  268.             long l;
  269.             Pos = 1;
  270.             padding = 0;
  271.             Crypt = preCrypt = 0;
  272.             arrayKey.CopyTo(Key,0);    // Key Must Be 16 Length!
  273.             Header = true;
  274.             Pos = 2;
  275.             //計算頭部填充位元組數
  276.             Pos = (arrayIn.Length+10) % 8;
  277.             if(Pos != 0)
  278.                 Pos = 8-Pos;
  279.             //輸出長度
  280.             Out = new byte[arrayIn.Length+Pos+10];
  281.             //把POS存到PLAIN的第一個位元組
  282.             //0xf8後面3位是空的,正好給Pos
  283.             Plain[0] = (byte)((Rand() & 0xf8) | Pos);
  284.             //用随機數填充1到Pos的内容
  285.             for(int i=1;i<=Pos;i++)
  286.             {
  287.                 Plain[i] = (byte)(Rand() & 0xff);
  288.             }
  289.             Pos++;
  290.             padding = 1;
  291.             //繼續填充兩個位元組随機數,滿8位元組就加密
  292.             while(padding < 3)
  293.             {
  294.                 if( Pos < 8)
  295.                 {
  296.                     Plain[Pos] = (byte)(Rand() & 0xff);
  297.                     padding++;
  298.                     Pos++;
  299.                 }
  300.                 else if(Pos == 8)
  301.                 {
  302.                     this.Encrypt8Bytes();
  303.                 }
  304.             }
  305.             int I = (int)offset;
  306.             l = 0;
  307.             //明文内容,滿8位元組加密到讀完
  308.             l = arrayIn.Length;
  309.             while ( l > 0)
  310.             {
  311.                 if(Pos<8)
  312.                 {
  313.                     Plain[Pos] = arrayIn[I];
  314.                     I++;
  315.                     Pos++;
  316.                     l--;
  317.                 }
  318.                 else if(Pos == 8)
  319.                 {
  320.                     this.Encrypt8Bytes();
  321.                 }
  322.             }
  323.             //末尾填充0,保證是8的倍數
  324.             padding = 1;
  325.             while(padding < 9)      
  326.             {
  327.                 if(Pos<8)
  328.                 {
  329.                     Plain[Pos] = 0;
  330.                     Pos++;
  331.                     padding++;
  332.                 }
  333.                 else if(Pos == 8)
  334.                 {
  335.                     this.Encrypt8Bytes();
  336.                 }
  337.             }
  338.             return Out;
  339.         }
  340.         public byte[] QQ_Encrypt(byte[] arrayIn,byte[] arrayKey)
  341.         {
  342.             return QQ_Encrypt(arrayIn,arrayKey,0);
  343.         }
  344.         /// <summary>
  345.         ///  QQ TEA 解密函數
  346.         /// </summary>
  347.         /// <param name="arrayIn">要解密字串</param>
  348.         /// <param name="arrayKey">密鑰</param>
  349.         /// <param name="offset">偏移</param>
  350.         /// <returns></returns>
  351.         public byte[] QQ_Decrypt(byte[] arrayIn,byte[] arrayKey,long offset)
  352.         {
  353.             byte[] error = new byte[0];
  354.             //檢查是否是8的倍數至少16位元組
  355.             if(arrayIn.Length < 16 || (arrayIn.Length % 8 != 0))
  356.             {
  357.                 //Return What?
  358.                 return error;
  359.             }
  360.             if(arrayKey.Length != 16)
  361.             {
  362.                 //Return What?
  363.                 return error;
  364.             }
  365.             byte[] m;
  366.             long I,Count;
  367.             m= new byte[offset+8];
  368.             arrayKey.CopyTo(Key,0);
  369.             Crypt = preCrypt = 0;
  370.             //計算消息頭部,明文開始的偏移,解密第一位元組和7相與得到
  371.             prePlain = this.Decipher(arrayIn,arrayKey,offset);
  372.             Pos = prePlain[0] & 7;
  373.             //計算明文長度
  374.             Count = arrayIn.Length - Pos - 10;
  375.             if(Count <= 0)
  376.             {
  377.                 //Return What?
  378.                 return error;
  379.             }
  380.             Out = new byte[Count];
  381.             preCrypt = 0;
  382.             Crypt = 8;
  383.             this.contextStart = 8;
  384.             Pos++;
  385.             padding = 1;
  386.             //跳過頭部
  387.             while(padding < 3)
  388.             {
  389.                 if(Pos<8)
  390.                 {
  391.                     Pos++;
  392.                     padding++;
  393.                 }
  394.                 else if(Pos==8)
  395.                 {
  396.                     for(int i=0;i<m.Length;i++)
  397.                         m[i]=arrayIn[i];
  398.                     if(this.Decrypt8Bytes(arrayIn,offset) == false)
  399.                     {
  400.                         //Return What?
  401.                         return error;
  402.                     }
  403.                 }
  404.             }
  405.             //解密明文
  406.             I=0;
  407.             while(Count != 0)
  408.             {
  409.                 if(Pos<8)
  410.                 {
  411.                     Out[I] = (byte)(m[offset+preCrypt+Pos] ^ prePlain[Pos]);
  412.                     I++;
  413.                     Count--;
  414.                     Pos++;
  415.                 }
  416.                 else if(Pos == 8)
  417.                 {
  418.                     m = arrayIn;
  419.                     preCrypt = Crypt - 8;
  420.                     if(this.Decrypt8Bytes(arrayIn,offset) == false)
  421.                     {
  422.                         //Return What?
  423.                         return error;
  424.                     }
  425.                 }
  426.             }
  427.             //最後的解密部分,檢查尾部是不是0
  428.             for(padding=1;padding<=7;padding++)
  429.             {
  430.                 if(Pos<8)
  431.                 {
  432.                     if( (m[offset+preCrypt+Pos] ^ prePlain[Pos]) != 0 )
  433.                     {
  434.                         //Return What?
  435.                         return error;
  436.                     }
  437.                     Pos++;
  438.                 }
  439.                 else if(Pos == 8)
  440.                 {
  441.                     for(int i=0;i<m.Length;i++)
  442.                         m[i] = arrayIn[i];
  443.                     preCrypt = Crypt;
  444.                     if(this.Decrypt8Bytes(arrayIn,offset) == false)
  445.                     {
  446.                         //Return What?
  447.                         return error;
  448.                     }
  449.                 }
  450.             }
  451.             return Out;
  452.         }
  453.         public byte[] QQ_Decrypt(byte[] arrayIn,byte[] arrayKey)
  454.         {
  455.             return QQ_Decrypt(arrayIn,arrayKey,0);
  456.         }
  457.         #endregion
  458.     }
  459. }

另外還有發郵件的代碼就不發了,簡單的調用cdosys.dll發的,還有調用rar.exe的也很簡單,就幾行代碼,不貼了,自己反編譯吧,我沒混淆~~

不想直接發源碼是讨厭很多人拿去改掉我的名字和網址就當自己的

“原創”

了。。。