| /* | 
 |  * Compound Storage (32 bit version) | 
 |  * Storage implementation | 
 |  * | 
 |  * This file contains the compound file implementation | 
 |  * of the storage interface. | 
 |  * | 
 |  * Copyright 1999 Francis Beaudet | 
 |  * Copyright 1999 Sylvain St-Germain | 
 |  * Copyright 1999 Thuy Nguyen | 
 |  * Copyright 2005 Mike McCormack | 
 |  * Copyright 2005 Juan Lang | 
 |  * | 
 |  * This library is free software; you can redistribute it and/or | 
 |  * modify it under the terms of the GNU Lesser General Public | 
 |  * License as published by the Free Software Foundation; either | 
 |  * version 2.1 of the License, or (at your option) any later version. | 
 |  * | 
 |  * This library is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
 |  * Lesser General Public License for more details. | 
 |  * | 
 |  * You should have received a copy of the GNU Lesser General Public | 
 |  * License along with this library; if not, write to the Free Software | 
 |  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
 |  * | 
 |  * TODO: | 
 |  * - I don't honor the maximum property set size. | 
 |  * - Certain bogus files could result in reading past the end of a buffer. | 
 |  * - Mac-generated files won't be read correctly, even if they're little | 
 |  *   endian, because I disregard whether the generator was a Mac.  This means | 
 |  *   strings will probably be munged (as I don't understand Mac scripts.) | 
 |  * - Not all PROPVARIANT types are supported. | 
 |  * - User defined properties are not supported, see comment in | 
 |  *   PropertyStorage_ReadFromStream | 
 |  */ | 
 |  | 
 | #include <assert.h> | 
 | #include <stdarg.h> | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 |  | 
 | #define COBJMACROS | 
 | #define NONAMELESSUNION | 
 | #define NONAMELESSSTRUCT | 
 |  | 
 | #include "windef.h" | 
 | #include "winbase.h" | 
 | #include "winnls.h" | 
 | #include "winuser.h" | 
 | #include "wine/unicode.h" | 
 | #include "wine/debug.h" | 
 | #include "dictionary.h" | 
 | #include "storage32.h" | 
 | #include "enumx.h" | 
 |  | 
 | WINE_DEFAULT_DEBUG_CHANNEL(storage); | 
 |  | 
 | static inline StorageImpl *impl_from_IPropertySetStorage( IPropertySetStorage *iface ) | 
 | { | 
 |     return (StorageImpl *)((char*)iface - FIELD_OFFSET(StorageImpl, base.pssVtbl)); | 
 | } | 
 |  | 
 | /* These are documented in MSDN, | 
 |  * but they don't seem to be in any header file. | 
 |  */ | 
 | #define PROPSETHDR_BYTEORDER_MAGIC      0xfffe | 
 | #define PROPSETHDR_OSVER_KIND_WIN16     0 | 
 | #define PROPSETHDR_OSVER_KIND_MAC       1 | 
 | #define PROPSETHDR_OSVER_KIND_WIN32     2 | 
 |  | 
 | #define CP_UNICODE 1200 | 
 |  | 
 | #define MAX_VERSION_0_PROP_NAME_LENGTH 256 | 
 |  | 
 | #define CFTAG_WINDOWS   (-1L) | 
 | #define CFTAG_MACINTOSH (-2L) | 
 | #define CFTAG_FMTID     (-3L) | 
 | #define CFTAG_NODATA      0L | 
 |  | 
 | typedef struct tagPROPERTYSETHEADER | 
 | { | 
 |     WORD  wByteOrder; /* always 0xfffe */ | 
 |     WORD  wFormat;    /* can be zero or one */ | 
 |     DWORD dwOSVer;    /* OS version of originating system */ | 
 |     CLSID clsid;      /* application CLSID */ | 
 |     DWORD reserved;   /* always 1 */ | 
 | } PROPERTYSETHEADER; | 
 |  | 
 | typedef struct tagFORMATIDOFFSET | 
 | { | 
 |     FMTID fmtid; | 
 |     DWORD dwOffset; /* from beginning of stream */ | 
 | } FORMATIDOFFSET; | 
 |  | 
 | typedef struct tagPROPERTYSECTIONHEADER | 
 | { | 
 |     DWORD cbSection; | 
 |     DWORD cProperties; | 
 | } PROPERTYSECTIONHEADER; | 
 |  | 
 | typedef struct tagPROPERTYIDOFFSET | 
 | { | 
 |     DWORD propid; | 
 |     DWORD dwOffset; /* from beginning of section */ | 
 | } PROPERTYIDOFFSET; | 
 |  | 
 | typedef struct tagPropertyStorage_impl PropertyStorage_impl; | 
 |  | 
 | /* Initializes the property storage from the stream (and undoes any uncommitted | 
 |  * changes in the process.)  Returns an error if there is an error reading or | 
 |  * if the stream format doesn't match what's expected. | 
 |  */ | 
 | static HRESULT PropertyStorage_ReadFromStream(PropertyStorage_impl *); | 
 |  | 
 | static HRESULT PropertyStorage_WriteToStream(PropertyStorage_impl *); | 
 |  | 
 | /* Creates the dictionaries used by the property storage.  If successful, all | 
 |  * the dictionaries have been created.  If failed, none has been.  (This makes | 
 |  * it a bit easier to deal with destroying them.) | 
 |  */ | 
 | static HRESULT PropertyStorage_CreateDictionaries(PropertyStorage_impl *); | 
 |  | 
 | static void PropertyStorage_DestroyDictionaries(PropertyStorage_impl *); | 
 |  | 
 | /* Copies from propvar to prop.  If propvar's type is VT_LPSTR, copies the | 
 |  * string using PropertyStorage_StringCopy. | 
 |  */ | 
 | static HRESULT PropertyStorage_PropVariantCopy(PROPVARIANT *prop, | 
 |  const PROPVARIANT *propvar, LCID targetCP, LCID srcCP); | 
 |  | 
 | /* Copies the string src, which is encoded using code page srcCP, and returns | 
 |  * it in *dst, in the code page specified by targetCP.  The returned string is | 
 |  * allocated using CoTaskMemAlloc. | 
 |  * If srcCP is CP_UNICODE, src is in fact an LPCWSTR.  Similarly, if targetCP | 
 |  * is CP_UNICODE, the returned string is in fact an LPWSTR. | 
 |  * Returns S_OK on success, something else on failure. | 
 |  */ | 
 | static HRESULT PropertyStorage_StringCopy(LPCSTR src, LCID srcCP, LPSTR *dst, | 
 |  LCID targetCP); | 
 |  | 
 | static const IPropertyStorageVtbl IPropertyStorage_Vtbl; | 
 | static const IEnumSTATPROPSETSTGVtbl IEnumSTATPROPSETSTG_Vtbl; | 
 | static const IEnumSTATPROPSTGVtbl IEnumSTATPROPSTG_Vtbl; | 
 | static HRESULT create_EnumSTATPROPSETSTG(StorageImpl *, IEnumSTATPROPSETSTG**); | 
 | static HRESULT create_EnumSTATPROPSTG(PropertyStorage_impl *, IEnumSTATPROPSTG**); | 
 |  | 
 | /*********************************************************************** | 
 |  * Implementation of IPropertyStorage | 
 |  */ | 
 | struct tagPropertyStorage_impl | 
 | { | 
 |     IPropertyStorage IPropertyStorage_iface; | 
 |     LONG ref; | 
 |     CRITICAL_SECTION cs; | 
 |     IStream *stm; | 
 |     BOOL  dirty; | 
 |     FMTID fmtid; | 
 |     CLSID clsid; | 
 |     WORD  format; | 
 |     DWORD originatorOS; | 
 |     DWORD grfFlags; | 
 |     DWORD grfMode; | 
 |     UINT  codePage; | 
 |     LCID  locale; | 
 |     PROPID highestProp; | 
 |     struct dictionary *name_to_propid; | 
 |     struct dictionary *propid_to_name; | 
 |     struct dictionary *propid_to_prop; | 
 | }; | 
 |  | 
 | static inline PropertyStorage_impl *impl_from_IPropertyStorage(IPropertyStorage *iface) | 
 | { | 
 |     return CONTAINING_RECORD(iface, PropertyStorage_impl, IPropertyStorage_iface); | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnQueryInterface (IPropertyStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertyStorage_fnQueryInterface( | 
 |     IPropertyStorage *iface, | 
 |     REFIID riid, | 
 |     void** ppvObject) | 
 | { | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |  | 
 |     TRACE("(%p, %s, %p)\n", This, debugstr_guid(riid), ppvObject); | 
 |  | 
 |     if (!ppvObject) | 
 |         return E_INVALIDARG; | 
 |  | 
 |     *ppvObject = 0; | 
 |  | 
 |     if (IsEqualGUID(&IID_IUnknown, riid) || | 
 |         IsEqualGUID(&IID_IPropertyStorage, riid)) | 
 |     { | 
 |         IPropertyStorage_AddRef(iface); | 
 |         *ppvObject = iface; | 
 |         return S_OK; | 
 |     } | 
 |  | 
 |     return E_NOINTERFACE; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnAddRef (IPropertyStorage) | 
 |  */ | 
 | static ULONG WINAPI IPropertyStorage_fnAddRef( | 
 |     IPropertyStorage *iface) | 
 | { | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |     return InterlockedIncrement(&This->ref); | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnRelease (IPropertyStorage) | 
 |  */ | 
 | static ULONG WINAPI IPropertyStorage_fnRelease( | 
 |     IPropertyStorage *iface) | 
 | { | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |     ULONG ref; | 
 |  | 
 |     ref = InterlockedDecrement(&This->ref); | 
 |     if (ref == 0) | 
 |     { | 
 |         TRACE("Destroying %p\n", This); | 
 |         if (This->dirty) | 
 |             IPropertyStorage_Commit(iface, STGC_DEFAULT); | 
 |         IStream_Release(This->stm); | 
 |         This->cs.DebugInfo->Spare[0] = 0; | 
 |         DeleteCriticalSection(&This->cs); | 
 |         PropertyStorage_DestroyDictionaries(This); | 
 |         HeapFree(GetProcessHeap(), 0, This); | 
 |     } | 
 |     return ref; | 
 | } | 
 |  | 
 | static PROPVARIANT *PropertyStorage_FindProperty(PropertyStorage_impl *This, | 
 |  DWORD propid) | 
 | { | 
 |     PROPVARIANT *ret = NULL; | 
 |  | 
 |     dictionary_find(This->propid_to_prop, UlongToPtr(propid), (void **)&ret); | 
 |     TRACE("returning %p\n", ret); | 
 |     return ret; | 
 | } | 
 |  | 
 | /* Returns NULL if name is NULL. */ | 
 | static PROPVARIANT *PropertyStorage_FindPropertyByName( | 
 |  PropertyStorage_impl *This, LPCWSTR name) | 
 | { | 
 |     PROPVARIANT *ret = NULL; | 
 |     void *propid; | 
 |  | 
 |     if (!name) | 
 |         return NULL; | 
 |     if (This->codePage == CP_UNICODE) | 
 |     { | 
 |         if (dictionary_find(This->name_to_propid, name, &propid)) | 
 |             ret = PropertyStorage_FindProperty(This, PtrToUlong(propid)); | 
 |     } | 
 |     else | 
 |     { | 
 |         LPSTR ansiName; | 
 |         HRESULT hr = PropertyStorage_StringCopy((LPCSTR)name, CP_UNICODE, | 
 |          &ansiName, This->codePage); | 
 |  | 
 |         if (SUCCEEDED(hr)) | 
 |         { | 
 |             if (dictionary_find(This->name_to_propid, ansiName, &propid)) | 
 |                 ret = PropertyStorage_FindProperty(This, PtrToUlong(propid)); | 
 |             CoTaskMemFree(ansiName); | 
 |         } | 
 |     } | 
 |     TRACE("returning %p\n", ret); | 
 |     return ret; | 
 | } | 
 |  | 
 | static LPWSTR PropertyStorage_FindPropertyNameById(PropertyStorage_impl *This, | 
 |  DWORD propid) | 
 | { | 
 |     LPWSTR ret = NULL; | 
 |  | 
 |     dictionary_find(This->propid_to_name, UlongToPtr(propid), (void **)&ret); | 
 |     TRACE("returning %p\n", ret); | 
 |     return ret; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnReadMultiple (IPropertyStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertyStorage_fnReadMultiple( | 
 |     IPropertyStorage* iface, | 
 |     ULONG cpspec, | 
 |     const PROPSPEC rgpspec[], | 
 |     PROPVARIANT rgpropvar[]) | 
 | { | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |     HRESULT hr = S_OK; | 
 |     ULONG i; | 
 |  | 
 |     TRACE("(%p, %d, %p, %p)\n", iface, cpspec, rgpspec, rgpropvar); | 
 |  | 
 |     if (!cpspec) | 
 |         return S_FALSE; | 
 |     if (!rgpspec || !rgpropvar) | 
 |         return E_INVALIDARG; | 
 |     EnterCriticalSection(&This->cs); | 
 |     for (i = 0; i < cpspec; i++) | 
 |     { | 
 |         PropVariantInit(&rgpropvar[i]); | 
 |         if (rgpspec[i].ulKind == PRSPEC_LPWSTR) | 
 |         { | 
 |             PROPVARIANT *prop = PropertyStorage_FindPropertyByName(This, | 
 |              rgpspec[i].u.lpwstr); | 
 |  | 
 |             if (prop) | 
 |                 PropertyStorage_PropVariantCopy(&rgpropvar[i], prop, GetACP(), | 
 |                  This->codePage); | 
 |         } | 
 |         else | 
 |         { | 
 |             switch (rgpspec[i].u.propid) | 
 |             { | 
 |                 case PID_CODEPAGE: | 
 |                     rgpropvar[i].vt = VT_I2; | 
 |                     rgpropvar[i].u.iVal = This->codePage; | 
 |                     break; | 
 |                 case PID_LOCALE: | 
 |                     rgpropvar[i].vt = VT_I4; | 
 |                     rgpropvar[i].u.lVal = This->locale; | 
 |                     break; | 
 |                 default: | 
 |                 { | 
 |                     PROPVARIANT *prop = PropertyStorage_FindProperty(This, | 
 |                      rgpspec[i].u.propid); | 
 |  | 
 |                     if (prop) | 
 |                         PropertyStorage_PropVariantCopy(&rgpropvar[i], prop, | 
 |                          GetACP(), This->codePage); | 
 |                     else | 
 |                         hr = S_FALSE; | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 |     LeaveCriticalSection(&This->cs); | 
 |     return hr; | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_StringCopy(LPCSTR src, LCID srcCP, LPSTR *dst, | 
 |  LCID dstCP) | 
 | { | 
 |     HRESULT hr = S_OK; | 
 |     int len; | 
 |  | 
 |     TRACE("%s, %p, %d, %d\n", | 
 |      srcCP == CP_UNICODE ? debugstr_w((LPCWSTR)src) : debugstr_a(src), dst, | 
 |      dstCP, srcCP); | 
 |     assert(src); | 
 |     assert(dst); | 
 |     *dst = NULL; | 
 |     if (dstCP == srcCP) | 
 |     { | 
 |         size_t len; | 
 |  | 
 |         if (dstCP == CP_UNICODE) | 
 |             len = (strlenW((LPCWSTR)src) + 1) * sizeof(WCHAR); | 
 |         else | 
 |             len = strlen(src) + 1; | 
 |         *dst = CoTaskMemAlloc(len * sizeof(WCHAR)); | 
 |         if (!*dst) | 
 |             hr = STG_E_INSUFFICIENTMEMORY; | 
 |         else | 
 |             memcpy(*dst, src, len); | 
 |     } | 
 |     else | 
 |     { | 
 |         if (dstCP == CP_UNICODE) | 
 |         { | 
 |             len = MultiByteToWideChar(srcCP, 0, src, -1, NULL, 0); | 
 |             *dst = CoTaskMemAlloc(len * sizeof(WCHAR)); | 
 |             if (!*dst) | 
 |                 hr = STG_E_INSUFFICIENTMEMORY; | 
 |             else | 
 |                 MultiByteToWideChar(srcCP, 0, src, -1, (LPWSTR)*dst, len); | 
 |         } | 
 |         else | 
 |         { | 
 |             LPCWSTR wideStr = NULL; | 
 |             LPWSTR wideStr_tmp = NULL; | 
 |  | 
 |             if (srcCP == CP_UNICODE) | 
 |                 wideStr = (LPCWSTR)src; | 
 |             else | 
 |             { | 
 |                 len = MultiByteToWideChar(srcCP, 0, src, -1, NULL, 0); | 
 |                 wideStr_tmp = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); | 
 |                 if (wideStr_tmp) | 
 |                 { | 
 |                     MultiByteToWideChar(srcCP, 0, src, -1, wideStr_tmp, len); | 
 |                     wideStr = wideStr_tmp; | 
 |                 } | 
 |                 else | 
 |                     hr = STG_E_INSUFFICIENTMEMORY; | 
 |             } | 
 |             if (SUCCEEDED(hr)) | 
 |             { | 
 |                 len = WideCharToMultiByte(dstCP, 0, wideStr, -1, NULL, 0, | 
 |                  NULL, NULL); | 
 |                 *dst = CoTaskMemAlloc(len); | 
 |                 if (!*dst) | 
 |                     hr = STG_E_INSUFFICIENTMEMORY; | 
 |                 else | 
 |                 { | 
 |                     BOOL defCharUsed = FALSE; | 
 |  | 
 |                     if (WideCharToMultiByte(dstCP, 0, wideStr, -1, *dst, len, | 
 |                      NULL, &defCharUsed) == 0 || defCharUsed) | 
 |                     { | 
 |                         CoTaskMemFree(*dst); | 
 |                         *dst = NULL; | 
 |                         hr = HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION); | 
 |                     } | 
 |                 } | 
 |             } | 
 |             HeapFree(GetProcessHeap(), 0, wideStr_tmp); | 
 |         } | 
 |     } | 
 |     TRACE("returning 0x%08x (%s)\n", hr, | 
 |      dstCP == CP_UNICODE ? debugstr_w((LPCWSTR)*dst) : debugstr_a(*dst)); | 
 |     return hr; | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_PropVariantCopy(PROPVARIANT *prop, | 
 |  const PROPVARIANT *propvar, LCID targetCP, LCID srcCP) | 
 | { | 
 |     HRESULT hr = S_OK; | 
 |  | 
 |     assert(prop); | 
 |     assert(propvar); | 
 |     if (propvar->vt == VT_LPSTR) | 
 |     { | 
 |         hr = PropertyStorage_StringCopy(propvar->u.pszVal, srcCP, | 
 |          &prop->u.pszVal, targetCP); | 
 |         if (SUCCEEDED(hr)) | 
 |             prop->vt = VT_LPSTR; | 
 |     } | 
 |     else | 
 |         PropVariantCopy(prop, propvar); | 
 |     return hr; | 
 | } | 
 |  | 
 | /* Stores the property with id propid and value propvar into this property | 
 |  * storage.  lcid is ignored if propvar's type is not VT_LPSTR.  If propvar's | 
 |  * type is VT_LPSTR, converts the string using lcid as the source code page | 
 |  * and This->codePage as the target code page before storing. | 
 |  * As a side effect, may change This->format to 1 if the type of propvar is | 
 |  * a version 1-only property. | 
 |  */ | 
 | static HRESULT PropertyStorage_StorePropWithId(PropertyStorage_impl *This, | 
 |  PROPID propid, const PROPVARIANT *propvar, LCID lcid) | 
 | { | 
 |     HRESULT hr = S_OK; | 
 |     PROPVARIANT *prop = PropertyStorage_FindProperty(This, propid); | 
 |  | 
 |     assert(propvar); | 
 |     if (propvar->vt & VT_BYREF || propvar->vt & VT_ARRAY) | 
 |         This->format = 1; | 
 |     switch (propvar->vt) | 
 |     { | 
 |     case VT_DECIMAL: | 
 |     case VT_I1: | 
 |     case VT_INT: | 
 |     case VT_UINT: | 
 |     case VT_VECTOR|VT_I1: | 
 |         This->format = 1; | 
 |     } | 
 |     TRACE("Setting 0x%08x to type %d\n", propid, propvar->vt); | 
 |     if (prop) | 
 |     { | 
 |         PropVariantClear(prop); | 
 |         hr = PropertyStorage_PropVariantCopy(prop, propvar, This->codePage, | 
 |          lcid); | 
 |     } | 
 |     else | 
 |     { | 
 |         prop = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, | 
 |          sizeof(PROPVARIANT)); | 
 |         if (prop) | 
 |         { | 
 |             hr = PropertyStorage_PropVariantCopy(prop, propvar, This->codePage, | 
 |              lcid); | 
 |             if (SUCCEEDED(hr)) | 
 |             { | 
 |                 dictionary_insert(This->propid_to_prop, UlongToPtr(propid), prop); | 
 |                 if (propid > This->highestProp) | 
 |                     This->highestProp = propid; | 
 |             } | 
 |             else | 
 |                 HeapFree(GetProcessHeap(), 0, prop); | 
 |         } | 
 |         else | 
 |             hr = STG_E_INSUFFICIENTMEMORY; | 
 |     } | 
 |     return hr; | 
 | } | 
 |  | 
 | /* Adds the name srcName to the name dictionaries, mapped to property ID id. | 
 |  * srcName is encoded in code page cp, and is converted to This->codePage. | 
 |  * If cp is CP_UNICODE, srcName is actually a unicode string. | 
 |  * As a side effect, may change This->format to 1 if srcName is too long for | 
 |  * a version 0 property storage. | 
 |  * Doesn't validate id. | 
 |  */ | 
 | static HRESULT PropertyStorage_StoreNameWithId(PropertyStorage_impl *This, | 
 |  LPCSTR srcName, LCID cp, PROPID id) | 
 | { | 
 |     LPSTR name; | 
 |     HRESULT hr; | 
 |  | 
 |     assert(srcName); | 
 |  | 
 |     hr = PropertyStorage_StringCopy(srcName, cp, &name, This->codePage); | 
 |     if (SUCCEEDED(hr)) | 
 |     { | 
 |         if (This->codePage == CP_UNICODE) | 
 |         { | 
 |             if (lstrlenW((LPWSTR)name) >= MAX_VERSION_0_PROP_NAME_LENGTH) | 
 |                 This->format = 1; | 
 |         } | 
 |         else | 
 |         { | 
 |             if (strlen(name) >= MAX_VERSION_0_PROP_NAME_LENGTH) | 
 |                 This->format = 1; | 
 |         } | 
 |         TRACE("Adding prop name %s, propid %d\n", | 
 |          This->codePage == CP_UNICODE ? debugstr_w((LPCWSTR)name) : | 
 |          debugstr_a(name), id); | 
 |         dictionary_insert(This->name_to_propid, name, UlongToPtr(id)); | 
 |         dictionary_insert(This->propid_to_name, UlongToPtr(id), name); | 
 |     } | 
 |     return hr; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnWriteMultiple (IPropertyStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertyStorage_fnWriteMultiple( | 
 |     IPropertyStorage* iface, | 
 |     ULONG cpspec, | 
 |     const PROPSPEC rgpspec[], | 
 |     const PROPVARIANT rgpropvar[], | 
 |     PROPID propidNameFirst) | 
 | { | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |     HRESULT hr = S_OK; | 
 |     ULONG i; | 
 |  | 
 |     TRACE("(%p, %d, %p, %p)\n", iface, cpspec, rgpspec, rgpropvar); | 
 |  | 
 |     if (cpspec && (!rgpspec || !rgpropvar)) | 
 |         return E_INVALIDARG; | 
 |     if (!(This->grfMode & STGM_READWRITE)) | 
 |         return STG_E_ACCESSDENIED; | 
 |     EnterCriticalSection(&This->cs); | 
 |     This->dirty = TRUE; | 
 |     This->originatorOS = (DWORD)MAKELONG(LOWORD(GetVersion()), | 
 |      PROPSETHDR_OSVER_KIND_WIN32) ; | 
 |     for (i = 0; i < cpspec; i++) | 
 |     { | 
 |         if (rgpspec[i].ulKind == PRSPEC_LPWSTR) | 
 |         { | 
 |             PROPVARIANT *prop = PropertyStorage_FindPropertyByName(This, | 
 |              rgpspec[i].u.lpwstr); | 
 |  | 
 |             if (prop) | 
 |                 PropVariantCopy(prop, &rgpropvar[i]); | 
 |             else | 
 |             { | 
 |                 /* Note that I don't do the special cases here that I do below, | 
 |                  * because naming the special PIDs isn't supported. | 
 |                  */ | 
 |                 if (propidNameFirst < PID_FIRST_USABLE || | 
 |                  propidNameFirst >= PID_MIN_READONLY) | 
 |                     hr = STG_E_INVALIDPARAMETER; | 
 |                 else | 
 |                 { | 
 |                     PROPID nextId = max(propidNameFirst, This->highestProp + 1); | 
 |  | 
 |                     hr = PropertyStorage_StoreNameWithId(This, | 
 |                      (LPCSTR)rgpspec[i].u.lpwstr, CP_UNICODE, nextId); | 
 |                     if (SUCCEEDED(hr)) | 
 |                         hr = PropertyStorage_StorePropWithId(This, nextId, | 
 |                          &rgpropvar[i], GetACP()); | 
 |                 } | 
 |             } | 
 |         } | 
 |         else | 
 |         { | 
 |             switch (rgpspec[i].u.propid) | 
 |             { | 
 |             case PID_DICTIONARY: | 
 |                 /* Can't set the dictionary */ | 
 |                 hr = STG_E_INVALIDPARAMETER; | 
 |                 break; | 
 |             case PID_CODEPAGE: | 
 |                 /* Can only set the code page if nothing else has been set */ | 
 |                 if (dictionary_num_entries(This->propid_to_prop) == 0 && | 
 |                  rgpropvar[i].vt == VT_I2) | 
 |                 { | 
 |                     This->codePage = rgpropvar[i].u.iVal; | 
 |                     if (This->codePage == CP_UNICODE) | 
 |                         This->grfFlags &= ~PROPSETFLAG_ANSI; | 
 |                     else | 
 |                         This->grfFlags |= PROPSETFLAG_ANSI; | 
 |                 } | 
 |                 else | 
 |                     hr = STG_E_INVALIDPARAMETER; | 
 |                 break; | 
 |             case PID_LOCALE: | 
 |                 /* Can only set the locale if nothing else has been set */ | 
 |                 if (dictionary_num_entries(This->propid_to_prop) == 0 && | 
 |                  rgpropvar[i].vt == VT_I4) | 
 |                     This->locale = rgpropvar[i].u.lVal; | 
 |                 else | 
 |                     hr = STG_E_INVALIDPARAMETER; | 
 |                 break; | 
 |             case PID_ILLEGAL: | 
 |                 /* silently ignore like MSDN says */ | 
 |                 break; | 
 |             default: | 
 |                 if (rgpspec[i].u.propid >= PID_MIN_READONLY) | 
 |                     hr = STG_E_INVALIDPARAMETER; | 
 |                 else | 
 |                     hr = PropertyStorage_StorePropWithId(This, | 
 |                      rgpspec[i].u.propid, &rgpropvar[i], GetACP()); | 
 |             } | 
 |         } | 
 |     } | 
 |     if (This->grfFlags & PROPSETFLAG_UNBUFFERED) | 
 |         IPropertyStorage_Commit(iface, STGC_DEFAULT); | 
 |     LeaveCriticalSection(&This->cs); | 
 |     return hr; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnDeleteMultiple (IPropertyStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertyStorage_fnDeleteMultiple( | 
 |     IPropertyStorage* iface, | 
 |     ULONG cpspec, | 
 |     const PROPSPEC rgpspec[]) | 
 | { | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |     ULONG i; | 
 |     HRESULT hr; | 
 |  | 
 |     TRACE("(%p, %d, %p)\n", iface, cpspec, rgpspec); | 
 |  | 
 |     if (cpspec && !rgpspec) | 
 |         return E_INVALIDARG; | 
 |     if (!(This->grfMode & STGM_READWRITE)) | 
 |         return STG_E_ACCESSDENIED; | 
 |     hr = S_OK; | 
 |     EnterCriticalSection(&This->cs); | 
 |     This->dirty = TRUE; | 
 |     for (i = 0; i < cpspec; i++) | 
 |     { | 
 |         if (rgpspec[i].ulKind == PRSPEC_LPWSTR) | 
 |         { | 
 |             void *propid; | 
 |  | 
 |             if (dictionary_find(This->name_to_propid, rgpspec[i].u.lpwstr, &propid)) | 
 |                 dictionary_remove(This->propid_to_prop, propid); | 
 |         } | 
 |         else | 
 |         { | 
 |             if (rgpspec[i].u.propid >= PID_FIRST_USABLE && | 
 |              rgpspec[i].u.propid < PID_MIN_READONLY) | 
 |                 dictionary_remove(This->propid_to_prop, UlongToPtr(rgpspec[i].u.propid)); | 
 |             else | 
 |                 hr = STG_E_INVALIDPARAMETER; | 
 |         } | 
 |     } | 
 |     if (This->grfFlags & PROPSETFLAG_UNBUFFERED) | 
 |         IPropertyStorage_Commit(iface, STGC_DEFAULT); | 
 |     LeaveCriticalSection(&This->cs); | 
 |     return hr; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnReadPropertyNames (IPropertyStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertyStorage_fnReadPropertyNames( | 
 |     IPropertyStorage* iface, | 
 |     ULONG cpropid, | 
 |     const PROPID rgpropid[], | 
 |     LPOLESTR rglpwstrName[]) | 
 | { | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |     ULONG i; | 
 |     HRESULT hr = S_FALSE; | 
 |  | 
 |     TRACE("(%p, %d, %p, %p)\n", iface, cpropid, rgpropid, rglpwstrName); | 
 |  | 
 |     if (cpropid && (!rgpropid || !rglpwstrName)) | 
 |         return E_INVALIDARG; | 
 |     EnterCriticalSection(&This->cs); | 
 |     for (i = 0; i < cpropid && SUCCEEDED(hr); i++) | 
 |     { | 
 |         LPWSTR name = PropertyStorage_FindPropertyNameById(This, rgpropid[i]); | 
 |  | 
 |         if (name) | 
 |         { | 
 |             size_t len = lstrlenW(name); | 
 |  | 
 |             hr = S_OK; | 
 |             rglpwstrName[i] = CoTaskMemAlloc((len + 1) * sizeof(WCHAR)); | 
 |             if (rglpwstrName[i]) | 
 |                 memcpy(rglpwstrName[i], name, (len + 1) * sizeof(WCHAR)); | 
 |             else | 
 |                 hr = STG_E_INSUFFICIENTMEMORY; | 
 |         } | 
 |         else | 
 |             rglpwstrName[i] = NULL; | 
 |     } | 
 |     LeaveCriticalSection(&This->cs); | 
 |     return hr; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnWritePropertyNames (IPropertyStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertyStorage_fnWritePropertyNames( | 
 |     IPropertyStorage* iface, | 
 |     ULONG cpropid, | 
 |     const PROPID rgpropid[], | 
 |     const LPOLESTR rglpwstrName[]) | 
 | { | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |     ULONG i; | 
 |     HRESULT hr; | 
 |  | 
 |     TRACE("(%p, %d, %p, %p)\n", iface, cpropid, rgpropid, rglpwstrName); | 
 |  | 
 |     if (cpropid && (!rgpropid || !rglpwstrName)) | 
 |         return E_INVALIDARG; | 
 |     if (!(This->grfMode & STGM_READWRITE)) | 
 |         return STG_E_ACCESSDENIED; | 
 |     hr = S_OK; | 
 |     EnterCriticalSection(&This->cs); | 
 |     This->dirty = TRUE; | 
 |     for (i = 0; SUCCEEDED(hr) && i < cpropid; i++) | 
 |     { | 
 |         if (rgpropid[i] != PID_ILLEGAL) | 
 |             hr = PropertyStorage_StoreNameWithId(This, (LPCSTR)rglpwstrName[i], | 
 |              CP_UNICODE, rgpropid[i]); | 
 |     } | 
 |     if (This->grfFlags & PROPSETFLAG_UNBUFFERED) | 
 |         IPropertyStorage_Commit(iface, STGC_DEFAULT); | 
 |     LeaveCriticalSection(&This->cs); | 
 |     return hr; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnDeletePropertyNames (IPropertyStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertyStorage_fnDeletePropertyNames( | 
 |     IPropertyStorage* iface, | 
 |     ULONG cpropid, | 
 |     const PROPID rgpropid[]) | 
 | { | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |     ULONG i; | 
 |     HRESULT hr; | 
 |  | 
 |     TRACE("(%p, %d, %p)\n", iface, cpropid, rgpropid); | 
 |  | 
 |     if (cpropid && !rgpropid) | 
 |         return E_INVALIDARG; | 
 |     if (!(This->grfMode & STGM_READWRITE)) | 
 |         return STG_E_ACCESSDENIED; | 
 |     hr = S_OK; | 
 |     EnterCriticalSection(&This->cs); | 
 |     This->dirty = TRUE; | 
 |     for (i = 0; i < cpropid; i++) | 
 |     { | 
 |         LPWSTR name = NULL; | 
 |  | 
 |         if (dictionary_find(This->propid_to_name, UlongToPtr(rgpropid[i]), (void **)&name)) | 
 |         { | 
 |             dictionary_remove(This->propid_to_name, UlongToPtr(rgpropid[i])); | 
 |             dictionary_remove(This->name_to_propid, name); | 
 |         } | 
 |     } | 
 |     if (This->grfFlags & PROPSETFLAG_UNBUFFERED) | 
 |         IPropertyStorage_Commit(iface, STGC_DEFAULT); | 
 |     LeaveCriticalSection(&This->cs); | 
 |     return hr; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnCommit (IPropertyStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertyStorage_fnCommit( | 
 |     IPropertyStorage* iface, | 
 |     DWORD grfCommitFlags) | 
 | { | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |     HRESULT hr; | 
 |  | 
 |     TRACE("(%p, 0x%08x)\n", iface, grfCommitFlags); | 
 |  | 
 |     if (!(This->grfMode & STGM_READWRITE)) | 
 |         return STG_E_ACCESSDENIED; | 
 |     EnterCriticalSection(&This->cs); | 
 |     if (This->dirty) | 
 |         hr = PropertyStorage_WriteToStream(This); | 
 |     else | 
 |         hr = S_OK; | 
 |     LeaveCriticalSection(&This->cs); | 
 |     return hr; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnRevert (IPropertyStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertyStorage_fnRevert( | 
 |     IPropertyStorage* iface) | 
 | { | 
 |     HRESULT hr; | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |  | 
 |     TRACE("%p\n", iface); | 
 |  | 
 |     EnterCriticalSection(&This->cs); | 
 |     if (This->dirty) | 
 |     { | 
 |         PropertyStorage_DestroyDictionaries(This); | 
 |         hr = PropertyStorage_CreateDictionaries(This); | 
 |         if (SUCCEEDED(hr)) | 
 |             hr = PropertyStorage_ReadFromStream(This); | 
 |     } | 
 |     else | 
 |         hr = S_OK; | 
 |     LeaveCriticalSection(&This->cs); | 
 |     return hr; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnEnum (IPropertyStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertyStorage_fnEnum( | 
 |     IPropertyStorage* iface, | 
 |     IEnumSTATPROPSTG** ppenum) | 
 | { | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |     return create_EnumSTATPROPSTG(This, ppenum); | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnSetTimes (IPropertyStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertyStorage_fnSetTimes( | 
 |     IPropertyStorage* iface, | 
 |     const FILETIME* pctime, | 
 |     const FILETIME* patime, | 
 |     const FILETIME* pmtime) | 
 | { | 
 |     FIXME("\n"); | 
 |     return E_NOTIMPL; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnSetClass (IPropertyStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertyStorage_fnSetClass( | 
 |     IPropertyStorage* iface, | 
 |     REFCLSID clsid) | 
 | { | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |  | 
 |     TRACE("%p, %s\n", iface, debugstr_guid(clsid)); | 
 |  | 
 |     if (!clsid) | 
 |         return E_INVALIDARG; | 
 |     if (!(This->grfMode & STGM_READWRITE)) | 
 |         return STG_E_ACCESSDENIED; | 
 |     This->clsid = *clsid; | 
 |     This->dirty = TRUE; | 
 |     if (This->grfFlags & PROPSETFLAG_UNBUFFERED) | 
 |         IPropertyStorage_Commit(iface, STGC_DEFAULT); | 
 |     return S_OK; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertyStorage_fnStat (IPropertyStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertyStorage_fnStat( | 
 |     IPropertyStorage* iface, | 
 |     STATPROPSETSTG* statpsstg) | 
 | { | 
 |     PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); | 
 |     STATSTG stat; | 
 |     HRESULT hr; | 
 |  | 
 |     TRACE("%p, %p\n", iface, statpsstg); | 
 |  | 
 |     if (!statpsstg) | 
 |         return E_INVALIDARG; | 
 |  | 
 |     hr = IStream_Stat(This->stm, &stat, STATFLAG_NONAME); | 
 |     if (SUCCEEDED(hr)) | 
 |     { | 
 |         statpsstg->fmtid = This->fmtid; | 
 |         statpsstg->clsid = This->clsid; | 
 |         statpsstg->grfFlags = This->grfFlags; | 
 |         statpsstg->mtime = stat.mtime; | 
 |         statpsstg->ctime = stat.ctime; | 
 |         statpsstg->atime = stat.atime; | 
 |         statpsstg->dwOSVersion = This->originatorOS; | 
 |     } | 
 |     return hr; | 
 | } | 
 |  | 
 | static int PropertyStorage_PropNameCompare(const void *a, const void *b, | 
 |  void *extra) | 
 | { | 
 |     PropertyStorage_impl *This = extra; | 
 |  | 
 |     if (This->codePage == CP_UNICODE) | 
 |     { | 
 |         TRACE("(%s, %s)\n", debugstr_w(a), debugstr_w(b)); | 
 |         if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) | 
 |             return lstrcmpW(a, b); | 
 |         else | 
 |             return lstrcmpiW(a, b); | 
 |     } | 
 |     else | 
 |     { | 
 |         TRACE("(%s, %s)\n", debugstr_a(a), debugstr_a(b)); | 
 |         if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) | 
 |             return lstrcmpA(a, b); | 
 |         else | 
 |             return lstrcmpiA(a, b); | 
 |     } | 
 | } | 
 |  | 
 | static void PropertyStorage_PropNameDestroy(void *k, void *d, void *extra) | 
 | { | 
 |     CoTaskMemFree(k); | 
 | } | 
 |  | 
 | static int PropertyStorage_PropCompare(const void *a, const void *b, | 
 |  void *extra) | 
 | { | 
 |     TRACE("(%d, %d)\n", PtrToUlong(a), PtrToUlong(b)); | 
 |     return PtrToUlong(a) - PtrToUlong(b); | 
 | } | 
 |  | 
 | static void PropertyStorage_PropertyDestroy(void *k, void *d, void *extra) | 
 | { | 
 |     PropVariantClear(d); | 
 |     HeapFree(GetProcessHeap(), 0, d); | 
 | } | 
 |  | 
 | #ifdef WORDS_BIGENDIAN | 
 | /* Swaps each character in str to or from little endian; assumes the conversion | 
 |  * is symmetric, that is, that lendian16toh is equivalent to htole16. | 
 |  */ | 
 | static void PropertyStorage_ByteSwapString(LPWSTR str, size_t len) | 
 | { | 
 |     DWORD i; | 
 |  | 
 |     /* Swap characters to host order. | 
 |      * FIXME: alignment? | 
 |      */ | 
 |     for (i = 0; i < len; i++) | 
 |         str[i] = lendian16toh(str[i]); | 
 | } | 
 | #else | 
 | #define PropertyStorage_ByteSwapString(s, l) | 
 | #endif | 
 |  | 
 | /* Reads the dictionary from the memory buffer beginning at ptr.  Interprets | 
 |  * the entries according to the values of This->codePage and This->locale. | 
 |  * FIXME: there isn't any checking whether the read property extends past the | 
 |  * end of the buffer. | 
 |  */ | 
 | static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This, | 
 |  BYTE *ptr) | 
 | { | 
 |     DWORD numEntries, i; | 
 |     HRESULT hr = S_OK; | 
 |  | 
 |     assert(This->name_to_propid); | 
 |     assert(This->propid_to_name); | 
 |  | 
 |     StorageUtl_ReadDWord(ptr, 0, &numEntries); | 
 |     TRACE("Reading %d entries:\n", numEntries); | 
 |     ptr += sizeof(DWORD); | 
 |     for (i = 0; SUCCEEDED(hr) && i < numEntries; i++) | 
 |     { | 
 |         PROPID propid; | 
 |         DWORD cbEntry; | 
 |  | 
 |         StorageUtl_ReadDWord(ptr, 0, &propid); | 
 |         ptr += sizeof(PROPID); | 
 |         StorageUtl_ReadDWord(ptr, 0, &cbEntry); | 
 |         ptr += sizeof(DWORD); | 
 |         TRACE("Reading entry with ID 0x%08x, %d bytes\n", propid, cbEntry); | 
 |         /* Make sure the source string is NULL-terminated */ | 
 |         if (This->codePage != CP_UNICODE) | 
 |             ptr[cbEntry - 1] = '\0'; | 
 |         else | 
 |             *((LPWSTR)ptr + cbEntry / sizeof(WCHAR)) = '\0'; | 
 |         hr = PropertyStorage_StoreNameWithId(This, (char*)ptr, This->codePage, propid); | 
 |         if (This->codePage == CP_UNICODE) | 
 |         { | 
 |             /* Unicode entries are padded to DWORD boundaries */ | 
 |             if (cbEntry % sizeof(DWORD)) | 
 |                 ptr += sizeof(DWORD) - (cbEntry % sizeof(DWORD)); | 
 |         } | 
 |         ptr += sizeof(DWORD) + cbEntry; | 
 |     } | 
 |     return hr; | 
 | } | 
 |  | 
 | /* FIXME: there isn't any checking whether the read property extends past the | 
 |  * end of the buffer. | 
 |  */ | 
 | static HRESULT PropertyStorage_ReadProperty(PropertyStorage_impl *This, | 
 |  PROPVARIANT *prop, const BYTE *data) | 
 | { | 
 |     HRESULT hr = S_OK; | 
 |  | 
 |     assert(prop); | 
 |     assert(data); | 
 |     StorageUtl_ReadDWord(data, 0, (DWORD *)&prop->vt); | 
 |     data += sizeof(DWORD); | 
 |     switch (prop->vt) | 
 |     { | 
 |     case VT_EMPTY: | 
 |     case VT_NULL: | 
 |         break; | 
 |     case VT_I1: | 
 |         prop->u.cVal = *(const char *)data; | 
 |         TRACE("Read char 0x%x ('%c')\n", prop->u.cVal, prop->u.cVal); | 
 |         break; | 
 |     case VT_UI1: | 
 |         prop->u.bVal = *data; | 
 |         TRACE("Read byte 0x%x\n", prop->u.bVal); | 
 |         break; | 
 |     case VT_I2: | 
 |         StorageUtl_ReadWord(data, 0, (WORD*)&prop->u.iVal); | 
 |         TRACE("Read short %d\n", prop->u.iVal); | 
 |         break; | 
 |     case VT_UI2: | 
 |         StorageUtl_ReadWord(data, 0, &prop->u.uiVal); | 
 |         TRACE("Read ushort %d\n", prop->u.uiVal); | 
 |         break; | 
 |     case VT_INT: | 
 |     case VT_I4: | 
 |         StorageUtl_ReadDWord(data, 0, (DWORD*)&prop->u.lVal); | 
 |         TRACE("Read long %d\n", prop->u.lVal); | 
 |         break; | 
 |     case VT_UINT: | 
 |     case VT_UI4: | 
 |         StorageUtl_ReadDWord(data, 0, &prop->u.ulVal); | 
 |         TRACE("Read ulong %d\n", prop->u.ulVal); | 
 |         break; | 
 |     case VT_LPSTR: | 
 |     { | 
 |         DWORD count; | 
 |         | 
 |         StorageUtl_ReadDWord(data, 0, &count); | 
 |         if (This->codePage == CP_UNICODE && count / 2) | 
 |         { | 
 |             WARN("Unicode string has odd number of bytes\n"); | 
 |             hr = STG_E_INVALIDHEADER; | 
 |         } | 
 |         else | 
 |         { | 
 |             prop->u.pszVal = CoTaskMemAlloc(count); | 
 |             if (prop->u.pszVal) | 
 |             { | 
 |                 memcpy(prop->u.pszVal, data + sizeof(DWORD), count); | 
 |                 /* This is stored in the code page specified in This->codePage. | 
 |                  * Don't convert it, the caller will just store it as-is. | 
 |                  */ | 
 |                 if (This->codePage == CP_UNICODE) | 
 |                 { | 
 |                     /* Make sure it's NULL-terminated */ | 
 |                     prop->u.pszVal[count / sizeof(WCHAR) - 1] = '\0'; | 
 |                     TRACE("Read string value %s\n", | 
 |                      debugstr_w(prop->u.pwszVal)); | 
 |                 } | 
 |                 else | 
 |                 { | 
 |                     /* Make sure it's NULL-terminated */ | 
 |                     prop->u.pszVal[count - 1] = '\0'; | 
 |                     TRACE("Read string value %s\n", debugstr_a(prop->u.pszVal)); | 
 |                 } | 
 |             } | 
 |             else | 
 |                 hr = STG_E_INSUFFICIENTMEMORY; | 
 |         } | 
 |         break; | 
 |     } | 
 |     case VT_BLOB: | 
 |     { | 
 |         DWORD count; | 
 |  | 
 |         StorageUtl_ReadDWord(data, 0, &count); | 
 |         prop->u.blob.cbSize = count; | 
 |         prop->u.blob.pBlobData = CoTaskMemAlloc(count); | 
 |         if (prop->u.blob.pBlobData) | 
 |         { | 
 |             memcpy(prop->u.blob.pBlobData, data + sizeof(DWORD), count); | 
 |             TRACE("Read blob value of size %d\n", count); | 
 |         } | 
 |         else | 
 |             hr = STG_E_INSUFFICIENTMEMORY; | 
 |         break; | 
 |     } | 
 |     case VT_LPWSTR: | 
 |     { | 
 |         DWORD count; | 
 |  | 
 |         StorageUtl_ReadDWord(data, 0, &count); | 
 |         prop->u.pwszVal = CoTaskMemAlloc(count * sizeof(WCHAR)); | 
 |         if (prop->u.pwszVal) | 
 |         { | 
 |             memcpy(prop->u.pwszVal, data + sizeof(DWORD), | 
 |              count * sizeof(WCHAR)); | 
 |             /* make sure string is NULL-terminated */ | 
 |             prop->u.pwszVal[count - 1] = '\0'; | 
 |             PropertyStorage_ByteSwapString(prop->u.pwszVal, count); | 
 |             TRACE("Read string value %s\n", debugstr_w(prop->u.pwszVal)); | 
 |         } | 
 |         else | 
 |             hr = STG_E_INSUFFICIENTMEMORY; | 
 |         break; | 
 |     } | 
 |     case VT_FILETIME: | 
 |         StorageUtl_ReadULargeInteger(data, 0, | 
 |          (ULARGE_INTEGER *)&prop->u.filetime); | 
 |         break; | 
 |     case VT_CF: | 
 |         { | 
 |             DWORD len = 0, tag = 0; | 
 |  | 
 |             StorageUtl_ReadDWord(data, 0, &len); | 
 |             StorageUtl_ReadDWord(data, 4, &tag); | 
 |             if (len > 8) | 
 |             { | 
 |                 len -= 8; | 
 |                 prop->u.pclipdata = CoTaskMemAlloc(sizeof (CLIPDATA)); | 
 |                 prop->u.pclipdata->cbSize = len; | 
 |                 prop->u.pclipdata->ulClipFmt = tag; | 
 |                 prop->u.pclipdata->pClipData = CoTaskMemAlloc(len - sizeof(prop->u.pclipdata->ulClipFmt)); | 
 |                 memcpy(prop->u.pclipdata->pClipData, data+8, len - sizeof(prop->u.pclipdata->ulClipFmt)); | 
 |             } | 
 |             else | 
 |                 hr = STG_E_INVALIDPARAMETER; | 
 |         } | 
 |         break; | 
 |     default: | 
 |         FIXME("unsupported type %d\n", prop->vt); | 
 |         hr = STG_E_INVALIDPARAMETER; | 
 |     } | 
 |     return hr; | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_ReadHeaderFromStream(IStream *stm, | 
 |  PROPERTYSETHEADER *hdr) | 
 | { | 
 |     BYTE buf[sizeof(PROPERTYSETHEADER)]; | 
 |     ULONG count = 0; | 
 |     HRESULT hr; | 
 |  | 
 |     assert(stm); | 
 |     assert(hdr); | 
 |     hr = IStream_Read(stm, buf, sizeof(buf), &count); | 
 |     if (SUCCEEDED(hr)) | 
 |     { | 
 |         if (count != sizeof(buf)) | 
 |         { | 
 |             WARN("read only %d\n", count); | 
 |             hr = STG_E_INVALIDHEADER; | 
 |         } | 
 |         else | 
 |         { | 
 |             StorageUtl_ReadWord(buf, offsetof(PROPERTYSETHEADER, wByteOrder), | 
 |              &hdr->wByteOrder); | 
 |             StorageUtl_ReadWord(buf, offsetof(PROPERTYSETHEADER, wFormat), | 
 |              &hdr->wFormat); | 
 |             StorageUtl_ReadDWord(buf, offsetof(PROPERTYSETHEADER, dwOSVer), | 
 |              &hdr->dwOSVer); | 
 |             StorageUtl_ReadGUID(buf, offsetof(PROPERTYSETHEADER, clsid), | 
 |              &hdr->clsid); | 
 |             StorageUtl_ReadDWord(buf, offsetof(PROPERTYSETHEADER, reserved), | 
 |              &hdr->reserved); | 
 |         } | 
 |     } | 
 |     TRACE("returning 0x%08x\n", hr); | 
 |     return hr; | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_ReadFmtIdOffsetFromStream(IStream *stm, | 
 |  FORMATIDOFFSET *fmt) | 
 | { | 
 |     BYTE buf[sizeof(FORMATIDOFFSET)]; | 
 |     ULONG count = 0; | 
 |     HRESULT hr; | 
 |  | 
 |     assert(stm); | 
 |     assert(fmt); | 
 |     hr = IStream_Read(stm, buf, sizeof(buf), &count); | 
 |     if (SUCCEEDED(hr)) | 
 |     { | 
 |         if (count != sizeof(buf)) | 
 |         { | 
 |             WARN("read only %d\n", count); | 
 |             hr = STG_E_INVALIDHEADER; | 
 |         } | 
 |         else | 
 |         { | 
 |             StorageUtl_ReadGUID(buf, offsetof(FORMATIDOFFSET, fmtid), | 
 |              &fmt->fmtid); | 
 |             StorageUtl_ReadDWord(buf, offsetof(FORMATIDOFFSET, dwOffset), | 
 |              &fmt->dwOffset); | 
 |         } | 
 |     } | 
 |     TRACE("returning 0x%08x\n", hr); | 
 |     return hr; | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_ReadSectionHeaderFromStream(IStream *stm, | 
 |  PROPERTYSECTIONHEADER *hdr) | 
 | { | 
 |     BYTE buf[sizeof(PROPERTYSECTIONHEADER)]; | 
 |     ULONG count = 0; | 
 |     HRESULT hr; | 
 |  | 
 |     assert(stm); | 
 |     assert(hdr); | 
 |     hr = IStream_Read(stm, buf, sizeof(buf), &count); | 
 |     if (SUCCEEDED(hr)) | 
 |     { | 
 |         if (count != sizeof(buf)) | 
 |         { | 
 |             WARN("read only %d\n", count); | 
 |             hr = STG_E_INVALIDHEADER; | 
 |         } | 
 |         else | 
 |         { | 
 |             StorageUtl_ReadDWord(buf, offsetof(PROPERTYSECTIONHEADER, | 
 |              cbSection), &hdr->cbSection); | 
 |             StorageUtl_ReadDWord(buf, offsetof(PROPERTYSECTIONHEADER, | 
 |              cProperties), &hdr->cProperties); | 
 |         } | 
 |     } | 
 |     TRACE("returning 0x%08x\n", hr); | 
 |     return hr; | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_ReadFromStream(PropertyStorage_impl *This) | 
 | { | 
 |     PROPERTYSETHEADER hdr; | 
 |     FORMATIDOFFSET fmtOffset; | 
 |     PROPERTYSECTIONHEADER sectionHdr; | 
 |     LARGE_INTEGER seek; | 
 |     ULONG i; | 
 |     STATSTG stat; | 
 |     HRESULT hr; | 
 |     BYTE *buf = NULL; | 
 |     ULONG count = 0; | 
 |     DWORD dictOffset = 0; | 
 |  | 
 |     This->dirty = FALSE; | 
 |     This->highestProp = 0; | 
 |     hr = IStream_Stat(This->stm, &stat, STATFLAG_NONAME); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     if (stat.cbSize.u.HighPart) | 
 |     { | 
 |         WARN("stream too big\n"); | 
 |         /* maximum size varies, but it can't be this big */ | 
 |         hr = STG_E_INVALIDHEADER; | 
 |         goto end; | 
 |     } | 
 |     if (stat.cbSize.u.LowPart == 0) | 
 |     { | 
 |         /* empty stream is okay */ | 
 |         hr = S_OK; | 
 |         goto end; | 
 |     } | 
 |     else if (stat.cbSize.u.LowPart < sizeof(PROPERTYSETHEADER) + | 
 |      sizeof(FORMATIDOFFSET)) | 
 |     { | 
 |         WARN("stream too small\n"); | 
 |         hr = STG_E_INVALIDHEADER; | 
 |         goto end; | 
 |     } | 
 |     seek.QuadPart = 0; | 
 |     hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     hr = PropertyStorage_ReadHeaderFromStream(This->stm, &hdr); | 
 |     /* I've only seen reserved == 1, but the article says I shouldn't disallow | 
 |      * higher values. | 
 |      */ | 
 |     if (hdr.wByteOrder != PROPSETHDR_BYTEORDER_MAGIC || hdr.reserved < 1) | 
 |     { | 
 |         WARN("bad magic in prop set header\n"); | 
 |         hr = STG_E_INVALIDHEADER; | 
 |         goto end; | 
 |     } | 
 |     if (hdr.wFormat != 0 && hdr.wFormat != 1) | 
 |     { | 
 |         WARN("bad format version %d\n", hdr.wFormat); | 
 |         hr = STG_E_INVALIDHEADER; | 
 |         goto end; | 
 |     } | 
 |     This->format = hdr.wFormat; | 
 |     This->clsid = hdr.clsid; | 
 |     This->originatorOS = hdr.dwOSVer; | 
 |     if (PROPSETHDR_OSVER_KIND(hdr.dwOSVer) == PROPSETHDR_OSVER_KIND_MAC) | 
 |         WARN("File comes from a Mac, strings will probably be screwed up\n"); | 
 |     hr = PropertyStorage_ReadFmtIdOffsetFromStream(This->stm, &fmtOffset); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     if (fmtOffset.dwOffset > stat.cbSize.u.LowPart) | 
 |     { | 
 |         WARN("invalid offset %d (stream length is %d)\n", fmtOffset.dwOffset, | 
 |          stat.cbSize.u.LowPart); | 
 |         hr = STG_E_INVALIDHEADER; | 
 |         goto end; | 
 |     } | 
 |     /* wackiness alert: if the format ID is FMTID_DocSummaryInformation, there | 
 |      * follow not one, but two sections.  The first is the standard properties | 
 |      * for the document summary information, and the second is user-defined | 
 |      * properties.  This is the only case in which multiple sections are | 
 |      * allowed. | 
 |      * Reading the second stream isn't implemented yet. | 
 |      */ | 
 |     hr = PropertyStorage_ReadSectionHeaderFromStream(This->stm, §ionHdr); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     /* The section size includes the section header, so check it */ | 
 |     if (sectionHdr.cbSection < sizeof(PROPERTYSECTIONHEADER)) | 
 |     { | 
 |         WARN("section header too small, got %d\n", sectionHdr.cbSection); | 
 |         hr = STG_E_INVALIDHEADER; | 
 |         goto end; | 
 |     } | 
 |     buf = HeapAlloc(GetProcessHeap(), 0, sectionHdr.cbSection - | 
 |      sizeof(PROPERTYSECTIONHEADER)); | 
 |     if (!buf) | 
 |     { | 
 |         hr = STG_E_INSUFFICIENTMEMORY; | 
 |         goto end; | 
 |     } | 
 |     hr = IStream_Read(This->stm, buf, sectionHdr.cbSection - | 
 |      sizeof(PROPERTYSECTIONHEADER), &count); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     TRACE("Reading %d properties:\n", sectionHdr.cProperties); | 
 |     for (i = 0; SUCCEEDED(hr) && i < sectionHdr.cProperties; i++) | 
 |     { | 
 |         PROPERTYIDOFFSET *idOffset = (PROPERTYIDOFFSET *)(buf + | 
 |          i * sizeof(PROPERTYIDOFFSET)); | 
 |  | 
 |         if (idOffset->dwOffset < sizeof(PROPERTYSECTIONHEADER) || | 
 |          idOffset->dwOffset > sectionHdr.cbSection - sizeof(DWORD)) | 
 |             hr = STG_E_INVALIDPOINTER; | 
 |         else | 
 |         { | 
 |             if (idOffset->propid >= PID_FIRST_USABLE && | 
 |              idOffset->propid < PID_MIN_READONLY && idOffset->propid > | 
 |              This->highestProp) | 
 |                 This->highestProp = idOffset->propid; | 
 |             if (idOffset->propid == PID_DICTIONARY) | 
 |             { | 
 |                 /* Don't read the dictionary yet, its entries depend on the | 
 |                  * code page.  Just store the offset so we know to read it | 
 |                  * later. | 
 |                  */ | 
 |                 dictOffset = idOffset->dwOffset; | 
 |                 TRACE("Dictionary offset is %d\n", dictOffset); | 
 |             } | 
 |             else | 
 |             { | 
 |                 PROPVARIANT prop; | 
 |  | 
 |                 PropVariantInit(&prop); | 
 |                 if (SUCCEEDED(PropertyStorage_ReadProperty(This, &prop, | 
 |                  buf + idOffset->dwOffset - sizeof(PROPERTYSECTIONHEADER)))) | 
 |                 { | 
 |                     TRACE("Read property with ID 0x%08x, type %d\n", | 
 |                      idOffset->propid, prop.vt); | 
 |                     switch(idOffset->propid) | 
 |                     { | 
 |                     case PID_CODEPAGE: | 
 |                         if (prop.vt == VT_I2) | 
 |                             This->codePage = (UINT)prop.u.iVal; | 
 |                         break; | 
 |                     case PID_LOCALE: | 
 |                         if (prop.vt == VT_I4) | 
 |                             This->locale = (LCID)prop.u.lVal; | 
 |                         break; | 
 |                     case PID_BEHAVIOR: | 
 |                         if (prop.vt == VT_I4 && prop.u.lVal) | 
 |                             This->grfFlags |= PROPSETFLAG_CASE_SENSITIVE; | 
 |                         /* The format should already be 1, but just in case */ | 
 |                         This->format = 1; | 
 |                         break; | 
 |                     default: | 
 |                         hr = PropertyStorage_StorePropWithId(This, | 
 |                          idOffset->propid, &prop, This->codePage); | 
 |                     } | 
 |                 } | 
 |                 PropVariantClear(&prop); | 
 |             } | 
 |         } | 
 |     } | 
 |     if (!This->codePage) | 
 |     { | 
 |         /* default to Unicode unless told not to, as specified on msdn */ | 
 |         if (This->grfFlags & PROPSETFLAG_ANSI) | 
 |             This->codePage = GetACP(); | 
 |         else | 
 |             This->codePage = CP_UNICODE; | 
 |     } | 
 |     if (!This->locale) | 
 |         This->locale = LOCALE_SYSTEM_DEFAULT; | 
 |     TRACE("Code page is %d, locale is %d\n", This->codePage, This->locale); | 
 |     if (dictOffset) | 
 |         hr = PropertyStorage_ReadDictionary(This, | 
 |          buf + dictOffset - sizeof(PROPERTYSECTIONHEADER)); | 
 |  | 
 | end: | 
 |     HeapFree(GetProcessHeap(), 0, buf); | 
 |     if (FAILED(hr)) | 
 |     { | 
 |         dictionary_destroy(This->name_to_propid); | 
 |         This->name_to_propid = NULL; | 
 |         dictionary_destroy(This->propid_to_name); | 
 |         This->propid_to_name = NULL; | 
 |         dictionary_destroy(This->propid_to_prop); | 
 |         This->propid_to_prop = NULL; | 
 |     } | 
 |     return hr; | 
 | } | 
 |  | 
 | static void PropertyStorage_MakeHeader(PropertyStorage_impl *This, | 
 |  PROPERTYSETHEADER *hdr) | 
 | { | 
 |     assert(hdr); | 
 |     StorageUtl_WriteWord((BYTE *)&hdr->wByteOrder, 0, | 
 |      PROPSETHDR_BYTEORDER_MAGIC); | 
 |     StorageUtl_WriteWord((BYTE *)&hdr->wFormat, 0, This->format); | 
 |     StorageUtl_WriteDWord((BYTE *)&hdr->dwOSVer, 0, This->originatorOS); | 
 |     StorageUtl_WriteGUID((BYTE *)&hdr->clsid, 0, &This->clsid); | 
 |     StorageUtl_WriteDWord((BYTE *)&hdr->reserved, 0, 1); | 
 | } | 
 |  | 
 | static void PropertyStorage_MakeFmtIdOffset(PropertyStorage_impl *This, | 
 |  FORMATIDOFFSET *fmtOffset) | 
 | { | 
 |     assert(fmtOffset); | 
 |     StorageUtl_WriteGUID((BYTE *)fmtOffset, 0, &This->fmtid); | 
 |     StorageUtl_WriteDWord((BYTE *)fmtOffset, offsetof(FORMATIDOFFSET, dwOffset), | 
 |      sizeof(PROPERTYSETHEADER) + sizeof(FORMATIDOFFSET)); | 
 | } | 
 |  | 
 | static void PropertyStorage_MakeSectionHdr(DWORD cbSection, DWORD numProps, | 
 |  PROPERTYSECTIONHEADER *hdr) | 
 | { | 
 |     assert(hdr); | 
 |     StorageUtl_WriteDWord((BYTE *)hdr, 0, cbSection); | 
 |     StorageUtl_WriteDWord((BYTE *)hdr, | 
 |      offsetof(PROPERTYSECTIONHEADER, cProperties), numProps); | 
 | } | 
 |  | 
 | static void PropertyStorage_MakePropertyIdOffset(DWORD propid, DWORD dwOffset, | 
 |  PROPERTYIDOFFSET *propIdOffset) | 
 | { | 
 |     assert(propIdOffset); | 
 |     StorageUtl_WriteDWord((BYTE *)propIdOffset, 0, propid); | 
 |     StorageUtl_WriteDWord((BYTE *)propIdOffset, | 
 |      offsetof(PROPERTYIDOFFSET, dwOffset), dwOffset); | 
 | } | 
 |  | 
 | static inline HRESULT PropertStorage_WriteWStringToStream(IStream *stm, | 
 |  LPCWSTR str, DWORD len, DWORD *written) | 
 | { | 
 | #ifdef WORDS_BIGENDIAN | 
 |     WCHAR *leStr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); | 
 |     HRESULT hr; | 
 |  | 
 |     if (!leStr) | 
 |         return E_OUTOFMEMORY; | 
 |     memcpy(leStr, str, len * sizeof(WCHAR)); | 
 |     PropertyStorage_ByteSwapString(leStr, len); | 
 |     hr = IStream_Write(stm, leStr, len, written); | 
 |     HeapFree(GetProcessHeap(), 0, leStr); | 
 |     return hr; | 
 | #else | 
 |     return IStream_Write(stm, str, len, written); | 
 | #endif | 
 | } | 
 |  | 
 | struct DictionaryClosure | 
 | { | 
 |     HRESULT hr; | 
 |     DWORD bytesWritten; | 
 | }; | 
 |  | 
 | static BOOL PropertyStorage_DictionaryWriter(const void *key, | 
 |  const void *value, void *extra, void *closure) | 
 | { | 
 |     PropertyStorage_impl *This = extra; | 
 |     struct DictionaryClosure *c = closure; | 
 |     DWORD propid; | 
 |     ULONG count; | 
 |  | 
 |     assert(key); | 
 |     assert(closure); | 
 |     StorageUtl_WriteDWord((LPBYTE)&propid, 0, PtrToUlong(value)); | 
 |     c->hr = IStream_Write(This->stm, &propid, sizeof(propid), &count); | 
 |     if (FAILED(c->hr)) | 
 |         goto end; | 
 |     c->bytesWritten += sizeof(DWORD); | 
 |     if (This->codePage == CP_UNICODE) | 
 |     { | 
 |         DWORD keyLen, pad = 0; | 
 |  | 
 |         StorageUtl_WriteDWord((LPBYTE)&keyLen, 0, | 
 |          (lstrlenW((LPCWSTR)key) + 1) * sizeof(WCHAR)); | 
 |         c->hr = IStream_Write(This->stm, &keyLen, sizeof(keyLen), &count); | 
 |         if (FAILED(c->hr)) | 
 |             goto end; | 
 |         c->bytesWritten += sizeof(DWORD); | 
 |         c->hr = PropertStorage_WriteWStringToStream(This->stm, key, keyLen, | 
 |          &count); | 
 |         if (FAILED(c->hr)) | 
 |             goto end; | 
 |         c->bytesWritten += keyLen * sizeof(WCHAR); | 
 |         if (keyLen % sizeof(DWORD)) | 
 |         { | 
 |             c->hr = IStream_Write(This->stm, &pad, | 
 |              sizeof(DWORD) - keyLen % sizeof(DWORD), &count); | 
 |             if (FAILED(c->hr)) | 
 |                 goto end; | 
 |             c->bytesWritten += sizeof(DWORD) - keyLen % sizeof(DWORD); | 
 |         } | 
 |     } | 
 |     else | 
 |     { | 
 |         DWORD keyLen; | 
 |  | 
 |         StorageUtl_WriteDWord((LPBYTE)&keyLen, 0, strlen((LPCSTR)key) + 1); | 
 |         c->hr = IStream_Write(This->stm, &keyLen, sizeof(keyLen), &count); | 
 |         if (FAILED(c->hr)) | 
 |             goto end; | 
 |         c->bytesWritten += sizeof(DWORD); | 
 |         c->hr = IStream_Write(This->stm, key, keyLen, &count); | 
 |         if (FAILED(c->hr)) | 
 |             goto end; | 
 |         c->bytesWritten += keyLen; | 
 |     } | 
 | end: | 
 |     return SUCCEEDED(c->hr); | 
 | } | 
 |  | 
 | #define SECTIONHEADER_OFFSET sizeof(PROPERTYSETHEADER) + sizeof(FORMATIDOFFSET) | 
 |  | 
 | /* Writes the dictionary to the stream.  Assumes without checking that the | 
 |  * dictionary isn't empty. | 
 |  */ | 
 | static HRESULT PropertyStorage_WriteDictionaryToStream( | 
 |  PropertyStorage_impl *This, DWORD *sectionOffset) | 
 | { | 
 |     HRESULT hr; | 
 |     LARGE_INTEGER seek; | 
 |     PROPERTYIDOFFSET propIdOffset; | 
 |     ULONG count; | 
 |     DWORD dwTemp; | 
 |     struct DictionaryClosure closure; | 
 |  | 
 |     assert(sectionOffset); | 
 |  | 
 |     /* The dictionary's always the first property written, so seek to its | 
 |      * spot. | 
 |      */ | 
 |     seek.QuadPart = SECTIONHEADER_OFFSET + sizeof(PROPERTYSECTIONHEADER); | 
 |     hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     PropertyStorage_MakePropertyIdOffset(PID_DICTIONARY, *sectionOffset, | 
 |      &propIdOffset); | 
 |     hr = IStream_Write(This->stm, &propIdOffset, sizeof(propIdOffset), &count); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |  | 
 |     seek.QuadPart = SECTIONHEADER_OFFSET + *sectionOffset; | 
 |     hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, | 
 |      dictionary_num_entries(This->name_to_propid)); | 
 |     hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     *sectionOffset += sizeof(dwTemp); | 
 |  | 
 |     closure.hr = S_OK; | 
 |     closure.bytesWritten = 0; | 
 |     dictionary_enumerate(This->name_to_propid, PropertyStorage_DictionaryWriter, | 
 |      &closure); | 
 |     hr = closure.hr; | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     *sectionOffset += closure.bytesWritten; | 
 |     if (closure.bytesWritten % sizeof(DWORD)) | 
 |     { | 
 |         DWORD padding = sizeof(DWORD) - closure.bytesWritten % sizeof(DWORD); | 
 |         TRACE("adding %d bytes of padding\n", padding); | 
 |         *sectionOffset += padding; | 
 |     } | 
 |  | 
 | end: | 
 |     return hr; | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_WritePropertyToStream(PropertyStorage_impl *This, | 
 |  DWORD propNum, DWORD propid, const PROPVARIANT *var, DWORD *sectionOffset) | 
 | { | 
 |     HRESULT hr; | 
 |     LARGE_INTEGER seek; | 
 |     PROPERTYIDOFFSET propIdOffset; | 
 |     ULONG count; | 
 |     DWORD dwType, bytesWritten; | 
 |  | 
 |     assert(var); | 
 |     assert(sectionOffset); | 
 |  | 
 |     TRACE("%p, %d, 0x%08x, (%d), (%d)\n", This, propNum, propid, var->vt, | 
 |      *sectionOffset); | 
 |  | 
 |     seek.QuadPart = SECTIONHEADER_OFFSET + sizeof(PROPERTYSECTIONHEADER) + | 
 |      propNum * sizeof(PROPERTYIDOFFSET); | 
 |     hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     PropertyStorage_MakePropertyIdOffset(propid, *sectionOffset, &propIdOffset); | 
 |     hr = IStream_Write(This->stm, &propIdOffset, sizeof(propIdOffset), &count); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |  | 
 |     seek.QuadPart = SECTIONHEADER_OFFSET + *sectionOffset; | 
 |     hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     StorageUtl_WriteDWord((LPBYTE)&dwType, 0, var->vt); | 
 |     hr = IStream_Write(This->stm, &dwType, sizeof(dwType), &count); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     *sectionOffset += sizeof(dwType); | 
 |  | 
 |     switch (var->vt) | 
 |     { | 
 |     case VT_EMPTY: | 
 |     case VT_NULL: | 
 |         bytesWritten = 0; | 
 |         break; | 
 |     case VT_I1: | 
 |     case VT_UI1: | 
 |         hr = IStream_Write(This->stm, &var->u.cVal, sizeof(var->u.cVal), | 
 |          &count); | 
 |         bytesWritten = count; | 
 |         break; | 
 |     case VT_I2: | 
 |     case VT_UI2: | 
 |     { | 
 |         WORD wTemp; | 
 |  | 
 |         StorageUtl_WriteWord((LPBYTE)&wTemp, 0, var->u.iVal); | 
 |         hr = IStream_Write(This->stm, &wTemp, sizeof(wTemp), &count); | 
 |         bytesWritten = count; | 
 |         break; | 
 |     } | 
 |     case VT_I4: | 
 |     case VT_UI4: | 
 |     { | 
 |         DWORD dwTemp; | 
 |  | 
 |         StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, var->u.lVal); | 
 |         hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); | 
 |         bytesWritten = count; | 
 |         break; | 
 |     } | 
 |     case VT_LPSTR: | 
 |     { | 
 |         DWORD len, dwTemp; | 
 |  | 
 |         if (This->codePage == CP_UNICODE) | 
 |             len = (lstrlenW(var->u.pwszVal) + 1) * sizeof(WCHAR); | 
 |         else | 
 |             len = lstrlenA(var->u.pszVal) + 1; | 
 |         StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, len); | 
 |         hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); | 
 |         if (FAILED(hr)) | 
 |             goto end; | 
 |         hr = IStream_Write(This->stm, var->u.pszVal, len, &count); | 
 |         bytesWritten = count + sizeof(DWORD); | 
 |         break; | 
 |     } | 
 |     case VT_LPWSTR: | 
 |     { | 
 |         DWORD len = lstrlenW(var->u.pwszVal) + 1, dwTemp; | 
 |  | 
 |         StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, len); | 
 |         hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); | 
 |         if (FAILED(hr)) | 
 |             goto end; | 
 |         hr = IStream_Write(This->stm, var->u.pwszVal, len * sizeof(WCHAR), | 
 |          &count); | 
 |         bytesWritten = count + sizeof(DWORD); | 
 |         break; | 
 |     } | 
 |     case VT_FILETIME: | 
 |     { | 
 |         FILETIME temp; | 
 |  | 
 |         StorageUtl_WriteULargeInteger((BYTE *)&temp, 0, | 
 |          (const ULARGE_INTEGER *)&var->u.filetime); | 
 |         hr = IStream_Write(This->stm, &temp, sizeof(FILETIME), &count); | 
 |         bytesWritten = count; | 
 |         break; | 
 |     } | 
 |     case VT_CF: | 
 |     { | 
 |         DWORD cf_hdr[2], len; | 
 |  | 
 |         len = var->u.pclipdata->cbSize; | 
 |         StorageUtl_WriteDWord((LPBYTE)&cf_hdr[0], 0, len + 8); | 
 |         StorageUtl_WriteDWord((LPBYTE)&cf_hdr[1], 0, var->u.pclipdata->ulClipFmt); | 
 |         hr = IStream_Write(This->stm, cf_hdr, sizeof(cf_hdr), &count); | 
 |         if (FAILED(hr)) | 
 |             goto end; | 
 |         hr = IStream_Write(This->stm, var->u.pclipdata->pClipData, | 
 |                            len - sizeof(var->u.pclipdata->ulClipFmt), &count); | 
 |         if (FAILED(hr)) | 
 |             goto end; | 
 |         bytesWritten = count + sizeof cf_hdr; | 
 |         break; | 
 |     } | 
 |     case VT_CLSID: | 
 |     { | 
 |         CLSID temp; | 
 |  | 
 |         StorageUtl_WriteGUID((BYTE *)&temp, 0, var->u.puuid); | 
 |         hr = IStream_Write(This->stm, &temp, sizeof(temp), &count); | 
 |         bytesWritten = count; | 
 |         break; | 
 |     } | 
 |     default: | 
 |         FIXME("unsupported type: %d\n", var->vt); | 
 |         return STG_E_INVALIDPARAMETER; | 
 |     } | 
 |  | 
 |     if (SUCCEEDED(hr)) | 
 |     { | 
 |         *sectionOffset += bytesWritten; | 
 |         if (bytesWritten % sizeof(DWORD)) | 
 |         { | 
 |             DWORD padding = sizeof(DWORD) - bytesWritten % sizeof(DWORD); | 
 |             TRACE("adding %d bytes of padding\n", padding); | 
 |             *sectionOffset += padding; | 
 |         } | 
 |     } | 
 |  | 
 | end: | 
 |     return hr; | 
 | } | 
 |  | 
 | struct PropertyClosure | 
 | { | 
 |     HRESULT hr; | 
 |     DWORD   propNum; | 
 |     DWORD  *sectionOffset; | 
 | }; | 
 |  | 
 | static BOOL PropertyStorage_PropertiesWriter(const void *key, const void *value, | 
 |  void *extra, void *closure) | 
 | { | 
 |     PropertyStorage_impl *This = extra; | 
 |     struct PropertyClosure *c = closure; | 
 |  | 
 |     assert(key); | 
 |     assert(value); | 
 |     assert(extra); | 
 |     assert(closure); | 
 |     c->hr = PropertyStorage_WritePropertyToStream(This, c->propNum++, | 
 |                                                   PtrToUlong(key), value, c->sectionOffset); | 
 |     return SUCCEEDED(c->hr); | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_WritePropertiesToStream( | 
 |  PropertyStorage_impl *This, DWORD startingPropNum, DWORD *sectionOffset) | 
 | { | 
 |     struct PropertyClosure closure; | 
 |  | 
 |     assert(sectionOffset); | 
 |     closure.hr = S_OK; | 
 |     closure.propNum = startingPropNum; | 
 |     closure.sectionOffset = sectionOffset; | 
 |     dictionary_enumerate(This->propid_to_prop, PropertyStorage_PropertiesWriter, | 
 |      &closure); | 
 |     return closure.hr; | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_WriteHeadersToStream(PropertyStorage_impl *This) | 
 | { | 
 |     HRESULT hr; | 
 |     ULONG count = 0; | 
 |     LARGE_INTEGER seek = { {0} }; | 
 |     PROPERTYSETHEADER hdr; | 
 |     FORMATIDOFFSET fmtOffset; | 
 |  | 
 |     hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     PropertyStorage_MakeHeader(This, &hdr); | 
 |     hr = IStream_Write(This->stm, &hdr, sizeof(hdr), &count); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     if (count != sizeof(hdr)) | 
 |     { | 
 |         hr = STG_E_WRITEFAULT; | 
 |         goto end; | 
 |     } | 
 |  | 
 |     PropertyStorage_MakeFmtIdOffset(This, &fmtOffset); | 
 |     hr = IStream_Write(This->stm, &fmtOffset, sizeof(fmtOffset), &count); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     if (count != sizeof(fmtOffset)) | 
 |     { | 
 |         hr = STG_E_WRITEFAULT; | 
 |         goto end; | 
 |     } | 
 |     hr = S_OK; | 
 |  | 
 | end: | 
 |     return hr; | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_WriteToStream(PropertyStorage_impl *This) | 
 | { | 
 |     PROPERTYSECTIONHEADER sectionHdr; | 
 |     HRESULT hr; | 
 |     ULONG count; | 
 |     LARGE_INTEGER seek; | 
 |     DWORD numProps, prop, sectionOffset, dwTemp; | 
 |     PROPVARIANT var; | 
 |  | 
 |     PropertyStorage_WriteHeadersToStream(This); | 
 |  | 
 |     /* Count properties.  Always at least one property, the code page */ | 
 |     numProps = 1; | 
 |     if (dictionary_num_entries(This->name_to_propid)) | 
 |         numProps++; | 
 |     if (This->locale != LOCALE_SYSTEM_DEFAULT) | 
 |         numProps++; | 
 |     if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) | 
 |         numProps++; | 
 |     numProps += dictionary_num_entries(This->propid_to_prop); | 
 |  | 
 |     /* Write section header with 0 bytes right now, I'll adjust it after | 
 |      * writing properties. | 
 |      */ | 
 |     PropertyStorage_MakeSectionHdr(0, numProps, §ionHdr); | 
 |     seek.QuadPart = SECTIONHEADER_OFFSET; | 
 |     hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     hr = IStream_Write(This->stm, §ionHdr, sizeof(sectionHdr), &count); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |  | 
 |     prop = 0; | 
 |     sectionOffset = sizeof(PROPERTYSECTIONHEADER) + | 
 |      numProps * sizeof(PROPERTYIDOFFSET); | 
 |  | 
 |     if (dictionary_num_entries(This->name_to_propid)) | 
 |     { | 
 |         prop++; | 
 |         hr = PropertyStorage_WriteDictionaryToStream(This, §ionOffset); | 
 |         if (FAILED(hr)) | 
 |             goto end; | 
 |     } | 
 |  | 
 |     PropVariantInit(&var); | 
 |  | 
 |     var.vt = VT_I2; | 
 |     var.u.iVal = This->codePage; | 
 |     hr = PropertyStorage_WritePropertyToStream(This, prop++, PID_CODEPAGE, | 
 |      &var, §ionOffset); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |  | 
 |     if (This->locale != LOCALE_SYSTEM_DEFAULT) | 
 |     { | 
 |         var.vt = VT_I4; | 
 |         var.u.lVal = This->locale; | 
 |         hr = PropertyStorage_WritePropertyToStream(This, prop++, PID_LOCALE, | 
 |          &var, §ionOffset); | 
 |         if (FAILED(hr)) | 
 |             goto end; | 
 |     } | 
 |  | 
 |     if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) | 
 |     { | 
 |         var.vt = VT_I4; | 
 |         var.u.lVal = 1; | 
 |         hr = PropertyStorage_WritePropertyToStream(This, prop++, PID_BEHAVIOR, | 
 |          &var, §ionOffset); | 
 |         if (FAILED(hr)) | 
 |             goto end; | 
 |     } | 
 |  | 
 |     hr = PropertyStorage_WritePropertiesToStream(This, prop, §ionOffset); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |  | 
 |     /* Now write the byte count of the section */ | 
 |     seek.QuadPart = SECTIONHEADER_OFFSET; | 
 |     hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); | 
 |     if (FAILED(hr)) | 
 |         goto end; | 
 |     StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, sectionOffset); | 
 |     hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); | 
 |  | 
 | end: | 
 |     return hr; | 
 | } | 
 |  | 
 | /*********************************************************************** | 
 |  * PropertyStorage_Construct | 
 |  */ | 
 | static void PropertyStorage_DestroyDictionaries(PropertyStorage_impl *This) | 
 | { | 
 |     dictionary_destroy(This->name_to_propid); | 
 |     This->name_to_propid = NULL; | 
 |     dictionary_destroy(This->propid_to_name); | 
 |     This->propid_to_name = NULL; | 
 |     dictionary_destroy(This->propid_to_prop); | 
 |     This->propid_to_prop = NULL; | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_CreateDictionaries(PropertyStorage_impl *This) | 
 | { | 
 |     HRESULT hr = S_OK; | 
 |  | 
 |     This->name_to_propid = dictionary_create( | 
 |      PropertyStorage_PropNameCompare, PropertyStorage_PropNameDestroy, | 
 |      This); | 
 |     if (!This->name_to_propid) | 
 |     { | 
 |         hr = STG_E_INSUFFICIENTMEMORY; | 
 |         goto end; | 
 |     } | 
 |     This->propid_to_name = dictionary_create(PropertyStorage_PropCompare, | 
 |      NULL, This); | 
 |     if (!This->propid_to_name) | 
 |     { | 
 |         hr = STG_E_INSUFFICIENTMEMORY; | 
 |         goto end; | 
 |     } | 
 |     This->propid_to_prop = dictionary_create(PropertyStorage_PropCompare, | 
 |      PropertyStorage_PropertyDestroy, This); | 
 |     if (!This->propid_to_prop) | 
 |     { | 
 |         hr = STG_E_INSUFFICIENTMEMORY; | 
 |         goto end; | 
 |     } | 
 | end: | 
 |     if (FAILED(hr)) | 
 |         PropertyStorage_DestroyDictionaries(This); | 
 |     return hr; | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_BaseConstruct(IStream *stm, | 
 |  REFFMTID rfmtid, DWORD grfMode, PropertyStorage_impl **pps) | 
 | { | 
 |     HRESULT hr = S_OK; | 
 |  | 
 |     assert(pps); | 
 |     assert(rfmtid); | 
 |     *pps = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof **pps); | 
 |     if (!*pps) | 
 |         return E_OUTOFMEMORY; | 
 |  | 
 |     (*pps)->IPropertyStorage_iface.lpVtbl = &IPropertyStorage_Vtbl; | 
 |     (*pps)->ref = 1; | 
 |     InitializeCriticalSection(&(*pps)->cs); | 
 |     (*pps)->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": PropertyStorage_impl.cs"); | 
 |     (*pps)->stm = stm; | 
 |     (*pps)->fmtid = *rfmtid; | 
 |     (*pps)->grfMode = grfMode; | 
 |  | 
 |     hr = PropertyStorage_CreateDictionaries(*pps); | 
 |     if (FAILED(hr)) | 
 |     { | 
 |         IStream_Release(stm); | 
 |         (*pps)->cs.DebugInfo->Spare[0] = 0; | 
 |         DeleteCriticalSection(&(*pps)->cs); | 
 |         HeapFree(GetProcessHeap(), 0, *pps); | 
 |         *pps = NULL; | 
 |     } | 
 |  | 
 |     return hr; | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_ConstructFromStream(IStream *stm, | 
 |  REFFMTID rfmtid, DWORD grfMode, IPropertyStorage** pps) | 
 | { | 
 |     PropertyStorage_impl *ps; | 
 |     HRESULT hr; | 
 |  | 
 |     assert(pps); | 
 |     hr = PropertyStorage_BaseConstruct(stm, rfmtid, grfMode, &ps); | 
 |     if (SUCCEEDED(hr)) | 
 |     { | 
 |         hr = PropertyStorage_ReadFromStream(ps); | 
 |         if (SUCCEEDED(hr)) | 
 |         { | 
 |             *pps = &ps->IPropertyStorage_iface; | 
 |             TRACE("PropertyStorage %p constructed\n", ps); | 
 |             hr = S_OK; | 
 |         } | 
 |         else | 
 |         { | 
 |             PropertyStorage_DestroyDictionaries(ps); | 
 |             HeapFree(GetProcessHeap(), 0, ps); | 
 |         } | 
 |     } | 
 |     return hr; | 
 | } | 
 |  | 
 | static HRESULT PropertyStorage_ConstructEmpty(IStream *stm, | 
 |  REFFMTID rfmtid, DWORD grfFlags, DWORD grfMode, IPropertyStorage** pps) | 
 | { | 
 |     PropertyStorage_impl *ps; | 
 |     HRESULT hr; | 
 |  | 
 |     assert(pps); | 
 |     hr = PropertyStorage_BaseConstruct(stm, rfmtid, grfMode, &ps); | 
 |     if (SUCCEEDED(hr)) | 
 |     { | 
 |         ps->format = 0; | 
 |         ps->grfFlags = grfFlags; | 
 |         if (ps->grfFlags & PROPSETFLAG_CASE_SENSITIVE) | 
 |             ps->format = 1; | 
 |         /* default to Unicode unless told not to, as specified on msdn */ | 
 |         if (ps->grfFlags & PROPSETFLAG_ANSI) | 
 |             ps->codePage = GetACP(); | 
 |         else | 
 |             ps->codePage = CP_UNICODE; | 
 |         ps->locale = LOCALE_SYSTEM_DEFAULT; | 
 |         TRACE("Code page is %d, locale is %d\n", ps->codePage, ps->locale); | 
 |         *pps = &ps->IPropertyStorage_iface; | 
 |         TRACE("PropertyStorage %p constructed\n", ps); | 
 |         hr = S_OK; | 
 |     } | 
 |     return hr; | 
 | } | 
 |  | 
 |  | 
 | /*********************************************************************** | 
 |  * Implementation of IPropertySetStorage | 
 |  */ | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertySetStorage_fnQueryInterface (IUnknown) | 
 |  * | 
 |  *  This method forwards to the common QueryInterface implementation | 
 |  */ | 
 | static HRESULT WINAPI IPropertySetStorage_fnQueryInterface( | 
 |     IPropertySetStorage *ppstg, | 
 |     REFIID riid, | 
 |     void** ppvObject) | 
 | { | 
 |     StorageImpl *This = impl_from_IPropertySetStorage(ppstg); | 
 |     return IStorage_QueryInterface( (IStorage*)This, riid, ppvObject ); | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertySetStorage_fnAddRef (IUnknown) | 
 |  * | 
 |  *  This method forwards to the common AddRef implementation | 
 |  */ | 
 | static ULONG WINAPI IPropertySetStorage_fnAddRef( | 
 |     IPropertySetStorage *ppstg) | 
 | { | 
 |     StorageImpl *This = impl_from_IPropertySetStorage(ppstg); | 
 |     return IStorage_AddRef( (IStorage*)This ); | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertySetStorage_fnRelease (IUnknown) | 
 |  * | 
 |  *  This method forwards to the common Release implementation | 
 |  */ | 
 | static ULONG WINAPI IPropertySetStorage_fnRelease( | 
 |     IPropertySetStorage *ppstg) | 
 | { | 
 |     StorageImpl *This = impl_from_IPropertySetStorage(ppstg); | 
 |     return IStorage_Release( (IStorage*)This ); | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertySetStorage_fnCreate (IPropertySetStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertySetStorage_fnCreate( | 
 |     IPropertySetStorage *ppstg, | 
 |     REFFMTID rfmtid, | 
 |     const CLSID* pclsid, | 
 |     DWORD grfFlags, | 
 |     DWORD grfMode, | 
 |     IPropertyStorage** ppprstg) | 
 | { | 
 |     StorageImpl *This = impl_from_IPropertySetStorage(ppstg); | 
 |     WCHAR name[CCH_MAX_PROPSTG_NAME]; | 
 |     IStream *stm = NULL; | 
 |     HRESULT r; | 
 |  | 
 |     TRACE("%p %s %08x %08x %p\n", This, debugstr_guid(rfmtid), grfFlags, | 
 |      grfMode, ppprstg); | 
 |  | 
 |     /* be picky */ | 
 |     if (grfMode != (STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE)) | 
 |     { | 
 |         r = STG_E_INVALIDFLAG; | 
 |         goto end; | 
 |     } | 
 |  | 
 |     if (!rfmtid) | 
 |     { | 
 |         r = E_INVALIDARG; | 
 |         goto end; | 
 |     } | 
 |  | 
 |     /* FIXME: if (grfFlags & PROPSETFLAG_NONSIMPLE), we need to create a | 
 |      * storage, not a stream.  For now, disallow it. | 
 |      */ | 
 |     if (grfFlags & PROPSETFLAG_NONSIMPLE) | 
 |     { | 
 |         FIXME("PROPSETFLAG_NONSIMPLE not supported\n"); | 
 |         r = STG_E_INVALIDFLAG; | 
 |         goto end; | 
 |     } | 
 |  | 
 |     r = FmtIdToPropStgName(rfmtid, name); | 
 |     if (FAILED(r)) | 
 |         goto end; | 
 |  | 
 |     r = IStorage_CreateStream( (IStorage*)This, name, grfMode, 0, 0, &stm ); | 
 |     if (FAILED(r)) | 
 |         goto end; | 
 |  | 
 |     r = PropertyStorage_ConstructEmpty(stm, rfmtid, grfFlags, grfMode, ppprstg); | 
 |  | 
 | end: | 
 |     TRACE("returning 0x%08x\n", r); | 
 |     return r; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertySetStorage_fnOpen (IPropertySetStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertySetStorage_fnOpen( | 
 |     IPropertySetStorage *ppstg, | 
 |     REFFMTID rfmtid, | 
 |     DWORD grfMode, | 
 |     IPropertyStorage** ppprstg) | 
 | { | 
 |     StorageImpl *This = impl_from_IPropertySetStorage(ppstg); | 
 |     IStream *stm = NULL; | 
 |     WCHAR name[CCH_MAX_PROPSTG_NAME]; | 
 |     HRESULT r; | 
 |  | 
 |     TRACE("%p %s %08x %p\n", This, debugstr_guid(rfmtid), grfMode, ppprstg); | 
 |  | 
 |     /* be picky */ | 
 |     if (grfMode != (STGM_READWRITE|STGM_SHARE_EXCLUSIVE) && | 
 |         grfMode != (STGM_READ|STGM_SHARE_EXCLUSIVE)) | 
 |     { | 
 |         r = STG_E_INVALIDFLAG; | 
 |         goto end; | 
 |     } | 
 |  | 
 |     if (!rfmtid) | 
 |     { | 
 |         r = E_INVALIDARG; | 
 |         goto end; | 
 |     } | 
 |  | 
 |     r = FmtIdToPropStgName(rfmtid, name); | 
 |     if (FAILED(r)) | 
 |         goto end; | 
 |  | 
 |     r = IStorage_OpenStream((IStorage*) This, name, 0, grfMode, 0, &stm ); | 
 |     if (FAILED(r)) | 
 |         goto end; | 
 |  | 
 |     r = PropertyStorage_ConstructFromStream(stm, rfmtid, grfMode, ppprstg); | 
 |  | 
 | end: | 
 |     TRACE("returning 0x%08x\n", r); | 
 |     return r; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertySetStorage_fnDelete (IPropertySetStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertySetStorage_fnDelete( | 
 |     IPropertySetStorage *ppstg, | 
 |     REFFMTID rfmtid) | 
 | { | 
 |     StorageImpl *This = impl_from_IPropertySetStorage(ppstg); | 
 |     IStorage *stg = NULL; | 
 |     WCHAR name[CCH_MAX_PROPSTG_NAME]; | 
 |     HRESULT r; | 
 |  | 
 |     TRACE("%p %s\n", This, debugstr_guid(rfmtid)); | 
 |  | 
 |     if (!rfmtid) | 
 |         return E_INVALIDARG; | 
 |  | 
 |     r = FmtIdToPropStgName(rfmtid, name); | 
 |     if (FAILED(r)) | 
 |         return r; | 
 |  | 
 |     stg = (IStorage*) This; | 
 |     return IStorage_DestroyElement(stg, name); | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * IPropertySetStorage_fnEnum (IPropertySetStorage) | 
 |  */ | 
 | static HRESULT WINAPI IPropertySetStorage_fnEnum( | 
 |     IPropertySetStorage *ppstg, | 
 |     IEnumSTATPROPSETSTG** ppenum) | 
 | { | 
 |     StorageImpl *This = impl_from_IPropertySetStorage(ppstg); | 
 |     return create_EnumSTATPROPSETSTG(This, ppenum); | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * Implement IEnumSTATPROPSETSTG using enumx | 
 |  */ | 
 | static HRESULT WINAPI IEnumSTATPROPSETSTG_fnQueryInterface( | 
 |     IEnumSTATPROPSETSTG *iface, | 
 |     REFIID riid, | 
 |     void** ppvObject) | 
 | { | 
 |     return enumx_QueryInterface((enumx_impl*)iface, riid, ppvObject); | 
 | } | 
 |  | 
 | static ULONG WINAPI IEnumSTATPROPSETSTG_fnAddRef( | 
 |     IEnumSTATPROPSETSTG *iface) | 
 | { | 
 |     return enumx_AddRef((enumx_impl*)iface); | 
 | } | 
 |  | 
 | static ULONG WINAPI IEnumSTATPROPSETSTG_fnRelease( | 
 |     IEnumSTATPROPSETSTG *iface) | 
 | { | 
 |     return enumx_Release((enumx_impl*)iface); | 
 | } | 
 |  | 
 | static HRESULT WINAPI IEnumSTATPROPSETSTG_fnNext( | 
 |     IEnumSTATPROPSETSTG *iface, | 
 |     ULONG celt, | 
 |     STATPROPSETSTG *rgelt, | 
 |     ULONG *pceltFetched) | 
 | { | 
 |     return enumx_Next((enumx_impl*)iface, celt, rgelt, pceltFetched); | 
 | } | 
 |  | 
 | static HRESULT WINAPI IEnumSTATPROPSETSTG_fnSkip( | 
 |     IEnumSTATPROPSETSTG *iface, | 
 |     ULONG celt) | 
 | { | 
 |     return enumx_Skip((enumx_impl*)iface, celt); | 
 | } | 
 |  | 
 | static HRESULT WINAPI IEnumSTATPROPSETSTG_fnReset( | 
 |     IEnumSTATPROPSETSTG *iface) | 
 | { | 
 |     return enumx_Reset((enumx_impl*)iface); | 
 | } | 
 |  | 
 | static HRESULT WINAPI IEnumSTATPROPSETSTG_fnClone( | 
 |     IEnumSTATPROPSETSTG *iface, | 
 |     IEnumSTATPROPSETSTG **ppenum) | 
 | { | 
 |     return enumx_Clone((enumx_impl*)iface, (enumx_impl**)ppenum); | 
 | } | 
 |  | 
 | static HRESULT create_EnumSTATPROPSETSTG( | 
 |     StorageImpl *This, | 
 |     IEnumSTATPROPSETSTG** ppenum) | 
 | { | 
 |     IStorage *stg = (IStorage*) &This->base.lpVtbl; | 
 |     IEnumSTATSTG *penum = NULL; | 
 |     STATSTG stat; | 
 |     ULONG count; | 
 |     HRESULT r; | 
 |     STATPROPSETSTG statpss; | 
 |     enumx_impl *enumx; | 
 |  | 
 |     TRACE("%p %p\n", This, ppenum); | 
 |  | 
 |     enumx = enumx_allocate(&IID_IEnumSTATPROPSETSTG, | 
 |                            &IEnumSTATPROPSETSTG_Vtbl, | 
 |                            sizeof (STATPROPSETSTG)); | 
 |  | 
 |     /* add all the property set elements into a list */ | 
 |     r = IStorage_EnumElements(stg, 0, NULL, 0, &penum); | 
 |     if (FAILED(r)) | 
 |         return E_OUTOFMEMORY; | 
 |  | 
 |     while (1) | 
 |     { | 
 |         count = 0; | 
 |         r = IEnumSTATSTG_Next(penum, 1, &stat, &count); | 
 |         if (FAILED(r)) | 
 |             break; | 
 |         if (!count) | 
 |             break; | 
 |         if (!stat.pwcsName) | 
 |             continue; | 
 |         if (stat.pwcsName[0] == 5 && stat.type == STGTY_STREAM) | 
 |         { | 
 |             PropStgNameToFmtId(stat.pwcsName, &statpss.fmtid); | 
 |             TRACE("adding %s (%s)\n", debugstr_w(stat.pwcsName), | 
 |                   debugstr_guid(&statpss.fmtid)); | 
 |             statpss.mtime = stat.mtime; | 
 |             statpss.atime = stat.atime; | 
 |             statpss.ctime = stat.ctime; | 
 |             statpss.grfFlags = stat.grfMode; | 
 |             statpss.clsid = stat.clsid; | 
 |             enumx_add_element(enumx, &statpss); | 
 |         } | 
 |         CoTaskMemFree(stat.pwcsName); | 
 |     } | 
 |     IEnumSTATSTG_Release(penum); | 
 |  | 
 |     *ppenum = (IEnumSTATPROPSETSTG*) enumx; | 
 |  | 
 |     return S_OK; | 
 | } | 
 |  | 
 | /************************************************************************ | 
 |  * Implement IEnumSTATPROPSTG using enumx | 
 |  */ | 
 | static HRESULT WINAPI IEnumSTATPROPSTG_fnQueryInterface( | 
 |     IEnumSTATPROPSTG *iface, | 
 |     REFIID riid, | 
 |     void** ppvObject) | 
 | { | 
 |     return enumx_QueryInterface((enumx_impl*)iface, riid, ppvObject); | 
 | } | 
 |  | 
 | static ULONG WINAPI IEnumSTATPROPSTG_fnAddRef( | 
 |     IEnumSTATPROPSTG *iface) | 
 | { | 
 |     return enumx_AddRef((enumx_impl*)iface); | 
 | } | 
 |  | 
 | static ULONG WINAPI IEnumSTATPROPSTG_fnRelease( | 
 |     IEnumSTATPROPSTG *iface) | 
 | { | 
 |     return enumx_Release((enumx_impl*)iface); | 
 | } | 
 |  | 
 | static HRESULT WINAPI IEnumSTATPROPSTG_fnNext( | 
 |     IEnumSTATPROPSTG *iface, | 
 |     ULONG celt, | 
 |     STATPROPSTG *rgelt, | 
 |     ULONG *pceltFetched) | 
 | { | 
 |     return enumx_Next((enumx_impl*)iface, celt, rgelt, pceltFetched); | 
 | } | 
 |  | 
 | static HRESULT WINAPI IEnumSTATPROPSTG_fnSkip( | 
 |     IEnumSTATPROPSTG *iface, | 
 |     ULONG celt) | 
 | { | 
 |     return enumx_Skip((enumx_impl*)iface, celt); | 
 | } | 
 |  | 
 | static HRESULT WINAPI IEnumSTATPROPSTG_fnReset( | 
 |     IEnumSTATPROPSTG *iface) | 
 | { | 
 |     return enumx_Reset((enumx_impl*)iface); | 
 | } | 
 |  | 
 | static HRESULT WINAPI IEnumSTATPROPSTG_fnClone( | 
 |     IEnumSTATPROPSTG *iface, | 
 |     IEnumSTATPROPSTG **ppenum) | 
 | { | 
 |     return enumx_Clone((enumx_impl*)iface, (enumx_impl**)ppenum); | 
 | } | 
 |  | 
 | static BOOL prop_enum_stat(const void *k, const void *v, void *extra, void *arg) | 
 | { | 
 |     enumx_impl *enumx = arg; | 
 |     PROPID propid = PtrToUlong(k); | 
 |     const PROPVARIANT *prop = v; | 
 |     STATPROPSTG stat; | 
 |  | 
 |     stat.lpwstrName = NULL; | 
 |     stat.propid = propid; | 
 |     stat.vt = prop->vt; | 
 |  | 
 |     enumx_add_element(enumx, &stat); | 
 |  | 
 |     return TRUE; | 
 | } | 
 |  | 
 | static HRESULT create_EnumSTATPROPSTG( | 
 |     PropertyStorage_impl *This, | 
 |     IEnumSTATPROPSTG** ppenum) | 
 | { | 
 |     enumx_impl *enumx; | 
 |  | 
 |     TRACE("%p %p\n", This, ppenum); | 
 |  | 
 |     enumx = enumx_allocate(&IID_IEnumSTATPROPSTG, | 
 |                            &IEnumSTATPROPSTG_Vtbl, | 
 |                            sizeof (STATPROPSTG)); | 
 |  | 
 |     dictionary_enumerate(This->propid_to_prop, prop_enum_stat, enumx); | 
 |  | 
 |     *ppenum = (IEnumSTATPROPSTG*) enumx; | 
 |  | 
 |     return S_OK; | 
 | } | 
 |  | 
 | /*********************************************************************** | 
 |  * vtables | 
 |  */ | 
 | const IPropertySetStorageVtbl IPropertySetStorage_Vtbl = | 
 | { | 
 |     IPropertySetStorage_fnQueryInterface, | 
 |     IPropertySetStorage_fnAddRef, | 
 |     IPropertySetStorage_fnRelease, | 
 |     IPropertySetStorage_fnCreate, | 
 |     IPropertySetStorage_fnOpen, | 
 |     IPropertySetStorage_fnDelete, | 
 |     IPropertySetStorage_fnEnum | 
 | }; | 
 |  | 
 | static const IPropertyStorageVtbl IPropertyStorage_Vtbl = | 
 | { | 
 |     IPropertyStorage_fnQueryInterface, | 
 |     IPropertyStorage_fnAddRef, | 
 |     IPropertyStorage_fnRelease, | 
 |     IPropertyStorage_fnReadMultiple, | 
 |     IPropertyStorage_fnWriteMultiple, | 
 |     IPropertyStorage_fnDeleteMultiple, | 
 |     IPropertyStorage_fnReadPropertyNames, | 
 |     IPropertyStorage_fnWritePropertyNames, | 
 |     IPropertyStorage_fnDeletePropertyNames, | 
 |     IPropertyStorage_fnCommit, | 
 |     IPropertyStorage_fnRevert, | 
 |     IPropertyStorage_fnEnum, | 
 |     IPropertyStorage_fnSetTimes, | 
 |     IPropertyStorage_fnSetClass, | 
 |     IPropertyStorage_fnStat, | 
 | }; | 
 |  | 
 | static const IEnumSTATPROPSETSTGVtbl IEnumSTATPROPSETSTG_Vtbl = | 
 | { | 
 |     IEnumSTATPROPSETSTG_fnQueryInterface, | 
 |     IEnumSTATPROPSETSTG_fnAddRef, | 
 |     IEnumSTATPROPSETSTG_fnRelease, | 
 |     IEnumSTATPROPSETSTG_fnNext, | 
 |     IEnumSTATPROPSETSTG_fnSkip, | 
 |     IEnumSTATPROPSETSTG_fnReset, | 
 |     IEnumSTATPROPSETSTG_fnClone, | 
 | }; | 
 |  | 
 | static const IEnumSTATPROPSTGVtbl IEnumSTATPROPSTG_Vtbl = | 
 | { | 
 |     IEnumSTATPROPSTG_fnQueryInterface, | 
 |     IEnumSTATPROPSTG_fnAddRef, | 
 |     IEnumSTATPROPSTG_fnRelease, | 
 |     IEnumSTATPROPSTG_fnNext, | 
 |     IEnumSTATPROPSTG_fnSkip, | 
 |     IEnumSTATPROPSTG_fnReset, | 
 |     IEnumSTATPROPSTG_fnClone, | 
 | }; | 
 |  | 
 | /*********************************************************************** | 
 |  * Format ID <-> name conversion | 
 |  */ | 
 | static const WCHAR szSummaryInfo[] = { 5,'S','u','m','m','a','r','y', | 
 |  'I','n','f','o','r','m','a','t','i','o','n',0 }; | 
 | static const WCHAR szDocSummaryInfo[] = { 5,'D','o','c','u','m','e','n','t', | 
 |  'S','u','m','m','a','r','y','I','n','f','o','r','m','a','t','i','o','n',0 }; | 
 |  | 
 | #define BITS_PER_BYTE    8 | 
 | #define CHARMASK         0x1f | 
 | #define BITS_IN_CHARMASK 5 | 
 | #define NUM_ALPHA_CHARS  26 | 
 |  | 
 | /*********************************************************************** | 
 |  * FmtIdToPropStgName					[ole32.@] | 
 |  * Returns the storage name of the format ID rfmtid. | 
 |  * PARAMS | 
 |  *  rfmtid [I] Format ID for which to return a storage name | 
 |  *  str    [O] Storage name associated with rfmtid. | 
 |  * | 
 |  * RETURNS | 
 |  *  E_INVALIDARG if rfmtid or str i NULL, S_OK otherwise. | 
 |  * | 
 |  * NOTES | 
 |  * str must be at least CCH_MAX_PROPSTG_NAME characters in length. | 
 |  */ | 
 | HRESULT WINAPI FmtIdToPropStgName(const FMTID *rfmtid, LPOLESTR str) | 
 | { | 
 |     static const char fmtMap[] = "abcdefghijklmnopqrstuvwxyz012345"; | 
 |  | 
 |     TRACE("%s, %p\n", debugstr_guid(rfmtid), str); | 
 |  | 
 |     if (!rfmtid) return E_INVALIDARG; | 
 |     if (!str) return E_INVALIDARG; | 
 |  | 
 |     if (IsEqualGUID(&FMTID_SummaryInformation, rfmtid)) | 
 |         lstrcpyW(str, szSummaryInfo); | 
 |     else if (IsEqualGUID(&FMTID_DocSummaryInformation, rfmtid)) | 
 |         lstrcpyW(str, szDocSummaryInfo); | 
 |     else if (IsEqualGUID(&FMTID_UserDefinedProperties, rfmtid)) | 
 |         lstrcpyW(str, szDocSummaryInfo); | 
 |     else | 
 |     { | 
 |         const BYTE *fmtptr; | 
 |         WCHAR *pstr = str; | 
 |         ULONG bitsRemaining = BITS_PER_BYTE; | 
 |  | 
 |         *pstr++ = 5; | 
 |         for (fmtptr = (const BYTE *)rfmtid; fmtptr < (const BYTE *)rfmtid + sizeof(FMTID); ) | 
 |         { | 
 |             ULONG i = *fmtptr >> (BITS_PER_BYTE - bitsRemaining); | 
 |  | 
 |             if (bitsRemaining >= BITS_IN_CHARMASK) | 
 |             { | 
 |                 *pstr = (WCHAR)(fmtMap[i & CHARMASK]); | 
 |                 if (bitsRemaining == BITS_PER_BYTE && *pstr >= 'a' && | 
 |                  *pstr <= 'z') | 
 |                     *pstr += 'A' - 'a'; | 
 |                 pstr++; | 
 |                 bitsRemaining -= BITS_IN_CHARMASK; | 
 |                 if (bitsRemaining == 0) | 
 |                 { | 
 |                     fmtptr++; | 
 |                     bitsRemaining = BITS_PER_BYTE; | 
 |                 } | 
 |             } | 
 |             else | 
 |             { | 
 |                 if (++fmtptr < (const BYTE *)rfmtid + sizeof(FMTID)) | 
 |                     i |= *fmtptr << bitsRemaining; | 
 |                 *pstr++ = (WCHAR)(fmtMap[i & CHARMASK]); | 
 |                 bitsRemaining += BITS_PER_BYTE - BITS_IN_CHARMASK; | 
 |             } | 
 |         } | 
 |         *pstr = 0; | 
 |     } | 
 |     TRACE("returning %s\n", debugstr_w(str)); | 
 |     return S_OK; | 
 | } | 
 |  | 
 | /*********************************************************************** | 
 |  * PropStgNameToFmtId					[ole32.@] | 
 |  * Returns the format ID corresponding to the given name. | 
 |  * PARAMS | 
 |  *  str    [I] Storage name to convert to a format ID. | 
 |  *  rfmtid [O] Format ID corresponding to str. | 
 |  * | 
 |  * RETURNS | 
 |  *  E_INVALIDARG if rfmtid or str is NULL or if str can't be converted to | 
 |  *  a format ID, S_OK otherwise. | 
 |  */ | 
 | HRESULT WINAPI PropStgNameToFmtId(const LPOLESTR str, FMTID *rfmtid) | 
 | { | 
 |     HRESULT hr = STG_E_INVALIDNAME; | 
 |  | 
 |     TRACE("%s, %p\n", debugstr_w(str), rfmtid); | 
 |  | 
 |     if (!rfmtid) return E_INVALIDARG; | 
 |     if (!str) return STG_E_INVALIDNAME; | 
 |  | 
 |     if (!lstrcmpiW(str, szDocSummaryInfo)) | 
 |     { | 
 |         *rfmtid = FMTID_DocSummaryInformation; | 
 |         hr = S_OK; | 
 |     } | 
 |     else if (!lstrcmpiW(str, szSummaryInfo)) | 
 |     { | 
 |         *rfmtid = FMTID_SummaryInformation; | 
 |         hr = S_OK; | 
 |     } | 
 |     else | 
 |     { | 
 |         ULONG bits; | 
 |         BYTE *fmtptr = (BYTE *)rfmtid - 1; | 
 |         const WCHAR *pstr = str; | 
 |  | 
 |         memset(rfmtid, 0, sizeof(*rfmtid)); | 
 |         for (bits = 0; bits < sizeof(FMTID) * BITS_PER_BYTE; | 
 |          bits += BITS_IN_CHARMASK) | 
 |         { | 
 |             ULONG bitsUsed = bits % BITS_PER_BYTE, bitsStored; | 
 |             WCHAR wc; | 
 |  | 
 |             if (bitsUsed == 0) | 
 |                 fmtptr++; | 
 |             wc = *++pstr - 'A'; | 
 |             if (wc > NUM_ALPHA_CHARS) | 
 |             { | 
 |                 wc += 'A' - 'a'; | 
 |                 if (wc > NUM_ALPHA_CHARS) | 
 |                 { | 
 |                     wc += 'a' - '0' + NUM_ALPHA_CHARS; | 
 |                     if (wc > CHARMASK) | 
 |                     { | 
 |                         WARN("invalid character (%d)\n", *pstr); | 
 |                         goto end; | 
 |                     } | 
 |                 } | 
 |             } | 
 |             *fmtptr |= wc << bitsUsed; | 
 |             bitsStored = min(BITS_PER_BYTE - bitsUsed, BITS_IN_CHARMASK); | 
 |             if (bitsStored < BITS_IN_CHARMASK) | 
 |             { | 
 |                 wc >>= BITS_PER_BYTE - bitsUsed; | 
 |                 if (bits + bitsStored == sizeof(FMTID) * BITS_PER_BYTE) | 
 |                 { | 
 |                     if (wc != 0) | 
 |                     { | 
 |                         WARN("extra bits\n"); | 
 |                         goto end; | 
 |                     } | 
 |                     break; | 
 |                 } | 
 |                 fmtptr++; | 
 |                 *fmtptr |= (BYTE)wc; | 
 |             } | 
 |         } | 
 |         hr = S_OK; | 
 |     } | 
 | end: | 
 |     return hr; | 
 | } |