关于QQ2009 CustomFace.db的探究
(没想研究,只向把东西弄出来的可以直接用7-zip解压)
UE直接打开看了下文件头~~D0 CF 11 E0….(正好对应DOC File….)
“劳拉”– 微软复试文档二进制储存结构~
先了解点资料~
结构化存储机制是COM的数据存储的基础,其核心思想是在一个文件内部建立一个类似于文件系统的完整的存储结构,并以存储对象或流对象构成了此类文件系统中树状结构的各个节点,这个包含了类似于文件系统的存储结构的文件也被称为复合文件。
形象点说,结构化存储就是用一个文件来模拟了一个硬盘~~
访问它,就比较像XML文档的节点式访问~~~
好啦,看看微软给我们的接口吧~~
(摘抄的,原作者看到的话麻烦留下言,因为我忘记当时是在谁那里摘抄的了……..)
原帖地址:http://blog.csdn.net/jh_zzz/archive/2007/02/27/1515657.aspx
public sealed class NativeMethods { private NativeMethods() { } [DllImport("ole32.dll", PreserveSig = false)] [return: MarshalAs(UnmanagedType.Interface)] internal static extern IStorage StgCreateDocfile([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, uint grfMode, uint reserved); [DllImport("ole32.dll", PreserveSig = false)] [return: MarshalAs(UnmanagedType.Interface)] internal static extern IStorage StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IntPtr pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved); }
这两个函数成功执行后都会返回一个 IStorage 接口指针,调用相应的接口的函数便可以对复合文件进行操作,以下是对 IStorage,ISteam 接口以及相关常量的声明:
public enum StorageMode
{
Read = 0x0,
Write = 0x1,
ReadWrite = 0x2,
ShareDenyNone = 0x40,
ShareDenyRead = 0x30,
ShareDenyWrite = 0x20,
ShareExclusive = 0x10,
Priority = 0x40000,
Create = 0x1000,
}
[ComImport, Guid(“0000000d-0000-0000-C000-000000000046”), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IEnumSTATSTG
{
[PreserveSig]
uint Next(uint celt, [MarshalAs(UnmanagedType.LPArray), Out] STATSTG[] rgelt, out uint pceltFetched);
void Skip(uint celt);
void Reset();
[return: MarshalAs(UnmanagedType.Interface)]
IEnumSTATSTG Clone();
}
[ComImport, Guid(“0000000b-0000-0000-C000-000000000046”), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IStorage
{
void CreateStream(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStream ppstm);
void OpenStream(string pwcsName, IntPtr reserved1, uint grfMode, uint reserved2, out IStream ppstm);
void CreateStorage(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStorage ppstg);
void OpenStorage(string pwcsName, IStorage pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved, out IStorage ppstg);
void CopyTo(uint ciidExclude, IntPtr rgiidExclude, IntPtr snbExclude, IStorage pstgDest);
void MoveElementTo(string pwcsName, IStorage pstgDest, string pwcsNewName, uint grfFlags);
void Commit(uint grfCommitFlags);
void Revert();
void EnumElements(uint reserved1, IntPtr reserved2, uint reserved3, out IEnumSTATSTG ppenum);
void DestroyElement(string pwcsName);
void RenameElement(string pwcsOldName, string pwcsNewName);
void SetElementTimes(string pwcsName, FILETIME pctime, FILETIME patime, FILETIME pmtime);
void SetClass(Guid clsid);
void SetStateBits(uint grfStateBits, uint grfMask);
void Stat(out STATSTG pstatstg, uint grfStatFlag);
}
[ComImport, Guid(“0000000c-0000-0000-C000-000000000046″), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IStream
{
void Read([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbRead);
void Write([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbWritten);
void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition);
void SetSize(long libNewSize);
void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten);
void Commit(int grfCommitFlags);
void Revert();
void LockRegion(long libOffset, long cb, int dwLockType);
void UnlockRegion(long libOffset, long cb, int dwLockType);
void Stat(out STATSTG pstatstg, int grfStatFlag);
void Clone(out IStream ppstm);
}
[/csharp” line=”1]
为了使用方便,我们又另外包装了 Storage 类:
public sealed class Storage : IDisposable
{
private bool disposed;
private IStorage storage;
public Storage(IStorage storage)
{
this.storage = storage;
}
~Storage()
{
//this.Dispose();
}
public static Storage CreateDocFile(string storageFile, StorageMode mode)
{
IStorage storage = NativeMethods.StgCreateDocfile(storageFile, (uint)mode, 0);
return new Storage(storage);
}
public static Storage Open(string storageFile, StorageMode mode)
{
IStorage storage = NativeMethods.StgOpenStorage(storageFile, IntPtr.Zero, (uint)mode, IntPtr.Zero, 0);
return new Storage(storage);
}
public void CopyTo(Storage destinationStorage)
{
this.storage.CopyTo(0, IntPtr.Zero, IntPtr.Zero, destinationStorage.storage);
}
public Storage OpenStorage(string name, bool autoCreate)
{
IStorage subStorage;
try
{
this.storage.OpenStorage(name, null, (uint)(StorageMode.ReadWrite | StorageMode.ShareExclusive), IntPtr.Zero, 0, out subStorage);
}
catch (COMException)
{
subStorage = null;
}
if (subStorage == null)
{
if (autoCreate)
return CreateStorage(name);
return null;
}
return new Storage(subStorage);
}
public Storage RecurOpenStorage(string name, bool autoCreate)
{
string pwcsName;
int pos = name.IndexOf(”);
if (pos > 0)
{
pwcsName = name.Substring(0, pos);
name = name.Substring(pos + 1);
}
else
{
pwcsName = name;
name = “”;
}
Storage subStorage = OpenStorage(pwcsName, autoCreate);
if (subStorage != null && name.Length > 0)
{
return subStorage.RecurOpenStorage(name, autoCreate);
}
return subStorage;
}
public void Dispose()
{
if (!this.disposed)
{
Marshal.ReleaseComObject(this.storage);
this.storage = null;
this.disposed = true;
}
GC.SuppressFinalize(this);
}
public Storage CreateStorage(string name)
{
IStorage subStorage = null;
try
{
//this.storage.OpenStorage(name, null,
// (uint)(StorageMode.ReadWrite | StorageMode.ShareExclusive),
// IntPtr.Zero, 0, out subStorage);
this.storage.CreateStorage(name,
(uint)(StorageMode.Create | StorageMode.ReadWrite | StorageMode.ShareExclusive),
0, 0, out subStorage);
this.storage.Commit(0);
return new Storage(subStorage);
}
catch (COMException)
{
if (subStorage != null)
Marshal.ReleaseComObject(subStorage);
}
return null;
}
public Stream CreateStream(string name)
{
IStream subStream = null;
try
{
//this.storage.OpenStream(name, IntPtr.Zero,
// (uint)(StorageMode.ReadWrite | StorageMode.ShareExclusive),
// 0, out subStream);
//if (subStream != null)
// this.storage.DestroyElement(name);
//Now create the element
this.storage.CreateStream(name,
(uint)(StorageMode.Create | StorageMode.ReadWrite | StorageMode.ShareExclusive),
0, 0, out subStream);
this.storage.Commit(0);
return new Stream(subStream);
}
catch (COMException)
{
if (subStream != null)
Marshal.ReleaseComObject(subStream);
return null;
}
}
public Stream OpenStream(string name)
{
IStream subStream;
try
{
this.storage.OpenStream(name, IntPtr.Zero,
(uint)(StorageMode.ReadWrite | StorageMode.ShareExclusive),
0, out subStream);
return new Stream(subStream);
}
catch (COMException)
{
return null;
}
}
public void Commit(uint grfCommitFlags)
{
this.storage.Commit(grfCommitFlags);
}
}
[/csharp” line=”1]
C# 在进行输入输出流操作的时候,都是使用 Stream 对象的,所以我们另外包装两个类做 IStream 接口到 Stream 对象的相互转换,这样在 C# 中用起来就很爽啦:
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class AxMemoryStream : MemoryStream, IStream
{
void IStream.Clone(out IStream clone)
{
clone = this.MemberwiseClone() as IStream;
}
void IStream.Commit(int flags)
{
throw new NotImplementedException(“AxMemoryStream is not transactional”);
}
void IStream.CopyTo(IStream destination, long count, IntPtr pcbRead, IntPtr pcbWritten)
{
/**///////////
// Copy the lot using 4k chunks
/**///////////
byte[] _buffer = new byte[4096];
int _cbRead = 0;
int _cbWritten = 0;
while (count > 0)
{
int _chunk = (int)Math.Min(count, _buffer.Length);
int _chunkRead = this.Read(_buffer, _cbRead, _chunk);
destination.Write(_buffer, _chunk, IntPtr.Zero);
_cbRead += _chunkRead;
_cbWritten += _chunkRead;
}
/**///////////
// Update the counts, if they were provided
/**///////////
if (pcbRead != IntPtr.Zero)
{
Marshal.WriteInt64(pcbRead, _cbRead);
}
if (pcbWritten != IntPtr.Zero)
{
Marshal.WriteInt64(pcbWritten, _cbWritten);
}
}
void IStream.LockRegion(long offset, long count, int lockType)
{
throw new NotImplementedException(“AxMemoryStream does not support locking”);
}
void IStream.Read(byte[] buffer, int count, IntPtr pcbRead)
{
int _cbRead = this.Read(buffer, 0, count);
if (pcbRead != IntPtr.Zero)
{
Marshal.WriteInt32(pcbRead, _cbRead);
}
}
void IStream.Revert()
{
throw new NotImplementedException(“AxMemoryStream is not transactional”);
}
void IStream.Seek(long offset, int origin, IntPtr pcbPos)
{
long _position = this.Seek(offset, (SeekOrigin)origin);
if (pcbPos != IntPtr.Zero)
{
Marshal.WriteInt64(pcbPos, _position);
}
}
void IStream.SetSize(long newSize)
{
this.SetLength(newSize);
}
void IStream.Stat(out STATSTG stat, int flags)
{
stat = new STATSTG();
stat.cbSize = Marshal.SizeOf(stat);
stat.grfLocksSupported = 0;
}
void IStream.UnlockRegion(long offset, long count, int lockType)
{
throw new NotImplementedException(“AxMemoryStream does not support locking”);
}
void IStream.Write(byte[] buffer, int count, IntPtr pcbWritten)
{
this.Write(buffer, 0, count);
if (pcbWritten != IntPtr.Zero)
{
Marshal.WriteInt32(pcbWritten, count);
}
}
}
public sealed class Stream : System.IO.Stream
{
private bool disposed;
private IStream stream;
public Stream(IStream stream)
{
this.stream = stream;
}
~Stream()
{
//this.Dispose();
}
public void Dispose()
{
if (!this.disposed)
{
Marshal.ReleaseComObject(this.stream);
this.stream = null;
this.disposed = true;
}
GC.SuppressFinalize(this);
}
public IStream UnderlyingStream
{
get
{
return this.stream;
}
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush()
{
this.stream.Commit(0);
}
public override long Length
{
get
{
if (this.stream == null)
throw new ObjectDisposedException(“Invalid stream object.”);
STATSTG statstg;
this.stream.Stat(out statstg, 1 /**//* STATSFLAG_NONAME*/ );
return statstg.cbSize;
}
}
public override long Position
{
get { return Seek(0, SeekOrigin.Current); }
set { Seek(value, SeekOrigin.Begin); }
}
public override int Read(byte[] buffer, int offset, int count)
{
if (stream == null)
throw new ObjectDisposedException(“Invalid stream object.”);
if (offset != 0)
{
throw new NotSupportedException(“Only 0 offset is supported”);
}
int bytesRead;
unsafe
{
IntPtr address = new IntPtr(&bytesRead);
stream.Read(buffer, count, address);
}
return bytesRead;
}
public override long Seek(long offset, SeekOrigin origin)
{
if (stream == null)
throw new ObjectDisposedException(“Invalid stream object.”);
long position = 0;
unsafe
{
IntPtr address = new IntPtr(&position);
stream.Seek(offset, (int)origin, address);
}
return position;
}
public override void SetLength(long value)
{
if (stream == null)
throw new ObjectDisposedException(“Invalid stream object.”);
stream.SetSize(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
if (stream == null)
throw new ObjectDisposedException(“Invalid stream object.”);
if (offset != 0)
{
throw new NotSupportedException(“Only 0 offset is supported”);
}
stream.Write(buffer, count, IntPtr.Zero);
stream.Commit(0);
}
//Convenience method for writing Strings to the stream
public void Write(string s)
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] pv = encoding.GetBytes(s);
Write(pv, 0, pv.GetLength(0));
}
public override void Close()
{
if (this.stream != null)
{
stream.Commit(0);
GC.SuppressFinalize(this);
}
}
}
[/csharp” line=”1]
值得一提的是 IStorage COM 复合文件的实现只支持 STGM_DIRECT 模式,所以文件的存取模式只能有以下三种组合:
STGM_READ | STGM_SHARE_DENY_WRITE
STGM_READWRITE | STGM_SHARE_EXCLUSIVE
STGM_READ | STGM_PRIORITY
所以在进行存取操作时都是独占式的访问,由于.Net 本身的垃圾回收机制,在使用 Storage 对象打开一个存储对象后,一定要记住操作完成后调用 Dispose 释放对象,否则下次再打开其他存储对象将会碰到访问被拒绝。
头疼不?,就知道你头疼了~~,还是我人品好~~
我封装成类库了~~~
好了下一步就是导出的过程了~
详细的我就不写了~~,我写主要步骤
经过探究 知道CustomFace.db有两层(CustomFaceRec.db只有一层,全都是文件)
一个目录层代表表情的分组
分组下就是实际内容了~
Storage file = StorageFile.OpenStorageFile(“CustomFace.db”);//定义一个Storage
List<StgElementInfo> DoFList = file.GetChildElementsInfo();//获取子节点
foreach (StgElementInfo DoF in DoFList)//遍历子节点
{
if (DoF.StgType == StgElementType.Storage)//子目录
{
dirname[dir] = DoF.Name;
Array.Resize(ref dirname, dirname.Length + 1);
dir++;//目录数加一
}
else
{//因为里面还存放了缩略图,因此在这里过滤下~~
if (((DoF.Name.ToLower().IndexOf(“.bmp”) != -1)||(DoF.Name.ToLower().IndexOf(“.jpg”) != -1)||(DoF.Name.ToLower().IndexOf(“.jpeg”) != -1)||(DoF.Name.ToLower().IndexOf(“.gif”) != -1))&&(DoF.Size!=1544))
{
face++;
SaveImage(file,DoF.Name,face);
}
//textBox1.Text += “文件:” + stgg.Name + “t大小:” + stgg.Size + “rn”;
}
}
//GetContent(stg);
file.Dispose();
[/csharp” line=”1]
至于保存我就不写了 ~
和“流”的保存方法一致
crayon
2009-03-04 下午 5:45
你好,看到你关于db文件保存和读取图片,能否发一段源码,到itcrayon@hotmail.com
目前研究这个到了一个瓶颈,所以希望你能帮忙
谢谢
harde
2009-03-09 上午 1:21
很抱歉,因为硬盘坏了,所以没有备份了
不过文章中的代码已经很全面了啊~~
crayon
2009-03-09 上午 10:41
谢谢!因为对图片和文件存储到DB文件中,ExpertLib.dll没有写的write和read的方法,所以我在寻找这样的方法,取是可以取了,自己写方法也实现了,可是不够稳定,还有对存储文件到DB里面,还是一个瓶颈。
harde
2009-03-09 下午 12:58
图片和文件的储存我是用流来实现的
同样其他文件也适用这个方法
另外说下
我没有试过直接存文本进复试文档中~
都是以文件的形式用流读取和写入的
等有空我试试
harde
2009-03-09 下午 1:00
这几天有事,又正好赶上更换IDC服务商
所以回复有点晚,请见谅
(因为换成了美国的主机,因此访问速度有所下降)
crayon
2009-03-11 下午 5:42
没有关系,我也只是对复合文档感兴趣,所以稍微研究一下!在对QQ2009的解析中,发现QQ不仅仅只是放入图片,还是有文件,文字,等等
harde
2009-03-11 下午 11:48
按我的理解
复合文档其实很像磁盘结构
在这块“硬盘”上存什么就看自己的爱好了
crayon
2009-03-11 下午 5:43
所以自己实现了一下!网络上C#对复合文档的研究文章太少,只有一点点,而且被转载的太多
harde
2009-03-11 下午 11:50
复试文档国内研究不多,外国能找到的资料不少
不过毕竟是微软的东西,不开源的东西研究起来很麻烦~~
Jaylin
2009-06-24 下午 7:32
好像用7-zip可以直接解压出来。
harde
2009-06-24 下午 8:09
的确,7-zip直接就可以解压出来
对之前的话我道歉
qwqew
2009-07-04 下午 3:14
试过了,可以打开,不过,打开后是一堆的文件
harde
2009-07-04 下午 3:34
这就对了啊,不然为什么叫结构化储存啊
畅翔宇
2010-03-01 下午 3:12
高手,请教你一下,网络上说的用ZIP能直接解压成功,但是在按照用DB合并后,确看不到聊天记录怎么回事情啊
harde
2010-03-01 下午 3:33
限于精力
msg那个DB我没研究
但就CustomFace.db是可以使用7-zip直接解压缩的
得到的直接是QQ表情
至于MSG,理论上腾讯是肯定加密的
所以本来就没什么精力,我就更懒得跟腾讯硬耗…而且还没什么好处….
174
2010-03-16 上午 4:34
http://blog.csdn.net/jh_zzz/archive/2007/02/27/1515657.aspx
原贴是这?
harde
2010-03-17 下午 10:47
应该是,不过印象中原帖主人的博客风格好像不是这个
可能是换了吧
地址我已经添加到文章顶部了