| /* |
| * CHM Utility API |
| * |
| * Copyright 2005 James Hawkins |
| * Copyright 2007 Jacek Caban |
| * |
| * 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 |
| */ |
| |
| #include "hhctrl.h" |
| #include "stream.h" |
| |
| #include "winreg.h" |
| #include "shlwapi.h" |
| #include "wine/debug.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(htmlhelp); |
| |
| /* Reads a string from the #STRINGS section in the CHM file */ |
| static LPCSTR GetChmString(CHMInfo *chm, DWORD offset) |
| { |
| LPCSTR str; |
| |
| if(!chm->strings_stream) |
| return NULL; |
| |
| if(chm->strings_size <= (offset >> BLOCK_BITS)) { |
| chm->strings_size = (offset >> BLOCK_BITS)+1; |
| if(chm->strings) |
| chm->strings = heap_realloc_zero(chm->strings, |
| chm->strings_size*sizeof(char*)); |
| else |
| chm->strings = heap_alloc_zero( |
| chm->strings_size*sizeof(char*)); |
| |
| } |
| |
| if(!chm->strings[offset >> BLOCK_BITS]) { |
| LARGE_INTEGER pos; |
| DWORD read; |
| HRESULT hres; |
| |
| pos.QuadPart = offset & ~BLOCK_MASK; |
| hres = IStream_Seek(chm->strings_stream, pos, STREAM_SEEK_SET, NULL); |
| if(FAILED(hres)) { |
| WARN("Seek failed: %08x\n", hres); |
| return NULL; |
| } |
| |
| chm->strings[offset >> BLOCK_BITS] = heap_alloc(BLOCK_SIZE); |
| |
| hres = IStream_Read(chm->strings_stream, chm->strings[offset >> BLOCK_BITS], |
| BLOCK_SIZE, &read); |
| if(FAILED(hres)) { |
| WARN("Read failed: %08x\n", hres); |
| heap_free(chm->strings[offset >> BLOCK_BITS]); |
| chm->strings[offset >> BLOCK_BITS] = NULL; |
| return NULL; |
| } |
| } |
| |
| str = chm->strings[offset >> BLOCK_BITS] + (offset & BLOCK_MASK); |
| TRACE("offset %#x => %s\n", offset, debugstr_a(str)); |
| return str; |
| } |
| |
| static BOOL ReadChmSystem(CHMInfo *chm) |
| { |
| IStream *stream; |
| DWORD ver=0xdeadbeef, read, buf_size; |
| char *buf; |
| HRESULT hres; |
| |
| struct { |
| WORD code; |
| WORD len; |
| } entry; |
| |
| static const WCHAR wszSYSTEM[] = {'#','S','Y','S','T','E','M',0}; |
| |
| hres = IStorage_OpenStream(chm->pStorage, wszSYSTEM, NULL, STGM_READ, 0, &stream); |
| if(FAILED(hres)) { |
| WARN("Could not open #SYSTEM stream: %08x\n", hres); |
| return FALSE; |
| } |
| |
| IStream_Read(stream, &ver, sizeof(ver), &read); |
| TRACE("version is %x\n", ver); |
| |
| buf = heap_alloc(8*sizeof(DWORD)); |
| buf_size = 8*sizeof(DWORD); |
| |
| while(1) { |
| hres = IStream_Read(stream, &entry, sizeof(entry), &read); |
| if(hres != S_OK) |
| break; |
| |
| if(entry.len > buf_size) |
| buf = heap_realloc(buf, buf_size=entry.len); |
| |
| hres = IStream_Read(stream, buf, entry.len, &read); |
| if(hres != S_OK) |
| break; |
| |
| switch(entry.code) { |
| case 0x0: |
| TRACE("TOC is %s\n", debugstr_an(buf, entry.len)); |
| heap_free(chm->defToc); |
| chm->defToc = strdupnAtoW(buf, entry.len); |
| break; |
| case 0x2: |
| TRACE("Default topic is %s\n", debugstr_an(buf, entry.len)); |
| heap_free(chm->defTopic); |
| chm->defTopic = strdupnAtoW(buf, entry.len); |
| break; |
| case 0x3: |
| TRACE("Title is %s\n", debugstr_an(buf, entry.len)); |
| heap_free(chm->defTitle); |
| chm->defTitle = strdupnAtoW(buf, entry.len); |
| break; |
| case 0x4: |
| /* TODO: Currently only the Locale ID is loaded from this field */ |
| TRACE("Locale is: %d\n", *(LCID*)&buf[0]); |
| if(!GetLocaleInfoW(*(LCID*)&buf[0], LOCALE_IDEFAULTANSICODEPAGE|LOCALE_RETURN_NUMBER, |
| (WCHAR *)&chm->codePage, sizeof(chm->codePage)/sizeof(WCHAR))) |
| chm->codePage = CP_ACP; |
| break; |
| case 0x5: |
| TRACE("Window name is %s\n", debugstr_an(buf, entry.len)); |
| chm->defWindow = strdupnAtoW(buf, entry.len); |
| break; |
| case 0x6: |
| TRACE("Compiled file is %s\n", debugstr_an(buf, entry.len)); |
| heap_free(chm->compiledFile); |
| chm->compiledFile = strdupnAtoW(buf, entry.len); |
| break; |
| case 0x9: |
| TRACE("Version is %s\n", debugstr_an(buf, entry.len)); |
| break; |
| case 0xa: |
| TRACE("Time is %08x\n", *(DWORD*)buf); |
| break; |
| case 0xc: |
| TRACE("Number of info types: %d\n", *(DWORD*)buf); |
| break; |
| case 0xf: |
| TRACE("Check sum: %x\n", *(DWORD*)buf); |
| break; |
| default: |
| TRACE("unhandled code %x, size %x\n", entry.code, entry.len); |
| } |
| } |
| |
| heap_free(buf); |
| IStream_Release(stream); |
| |
| return SUCCEEDED(hres); |
| } |
| |
| LPWSTR FindContextAlias(CHMInfo *chm, DWORD index) |
| { |
| IStream *ivb_stream; |
| DWORD size, read, i; |
| DWORD *buf; |
| LPCSTR ret = NULL; |
| HRESULT hres; |
| |
| static const WCHAR wszIVB[] = {'#','I','V','B',0}; |
| |
| hres = IStorage_OpenStream(chm->pStorage, wszIVB, NULL, STGM_READ, 0, &ivb_stream); |
| if(FAILED(hres)) { |
| WARN("Could not open #IVB stream: %08x\n", hres); |
| return NULL; |
| } |
| |
| hres = IStream_Read(ivb_stream, &size, sizeof(size), &read); |
| if(FAILED(hres)) { |
| WARN("Read failed: %08x\n", hres); |
| IStream_Release(ivb_stream); |
| return NULL; |
| } |
| |
| buf = heap_alloc(size); |
| hres = IStream_Read(ivb_stream, buf, size, &read); |
| IStream_Release(ivb_stream); |
| if(FAILED(hres)) { |
| WARN("Read failed: %08x\n", hres); |
| heap_free(buf); |
| return NULL; |
| } |
| |
| size /= 2*sizeof(DWORD); |
| |
| for(i=0; i<size; i++) { |
| if(buf[2*i] == index) { |
| ret = GetChmString(chm, buf[2*i+1]); |
| break; |
| } |
| } |
| |
| heap_free(buf); |
| |
| TRACE("returning %s\n", debugstr_a(ret)); |
| return strdupAtoW(ret); |
| } |
| |
| /* |
| * Tests if the file <chmfile>.<ext> exists, used for loading Indices, Table of Contents, etc. |
| * when these files are not available from the HH_WINTYPE structure. |
| */ |
| static WCHAR *FindHTMLHelpSetting(HHInfo *info, const WCHAR *extW) |
| { |
| static const WCHAR periodW[] = {'.',0}; |
| IStorage *pStorage = info->pCHMInfo->pStorage; |
| IStream *pStream; |
| WCHAR *filename; |
| HRESULT hr; |
| |
| filename = heap_alloc( (strlenW(info->pCHMInfo->compiledFile) |
| + strlenW(periodW) + strlenW(extW) + 1) * sizeof(WCHAR) ); |
| strcpyW(filename, info->pCHMInfo->compiledFile); |
| strcatW(filename, periodW); |
| strcatW(filename, extW); |
| hr = IStorage_OpenStream(pStorage, filename, NULL, STGM_READ, 0, &pStream); |
| if (FAILED(hr)) |
| { |
| heap_free(filename); |
| return strdupAtoW(""); |
| } |
| IStream_Release(pStream); |
| return filename; |
| } |
| |
| static inline WCHAR *MergeChmString(LPCWSTR src, WCHAR **dst) |
| { |
| if(*dst == NULL) |
| *dst = strdupW(src); |
| return *dst; |
| } |
| |
| void MergeChmProperties(HH_WINTYPEW *src, HHInfo *info, BOOL override) |
| { |
| DWORD unhandled_params = src->fsValidMembers & ~(HHWIN_PARAM_PROPERTIES|HHWIN_PARAM_STYLES |
| |HHWIN_PARAM_EXSTYLES|HHWIN_PARAM_RECT|HHWIN_PARAM_NAV_WIDTH |
| |HHWIN_PARAM_SHOWSTATE|HHWIN_PARAM_INFOTYPES|HHWIN_PARAM_TB_FLAGS |
| |HHWIN_PARAM_EXPANSION|HHWIN_PARAM_TABPOS|HHWIN_PARAM_TABORDER |
| |HHWIN_PARAM_HISTORY_COUNT|HHWIN_PARAM_CUR_TAB); |
| HH_WINTYPEW *dst = &info->WinType; |
| DWORD merge = override ? src->fsValidMembers : src->fsValidMembers & ~dst->fsValidMembers; |
| |
| if (unhandled_params) |
| FIXME("Unsupported fsValidMembers fields: 0x%x\n", unhandled_params); |
| |
| dst->fsValidMembers |= merge; |
| if (dst->cbStruct == 0) |
| { |
| /* If the structure has not been filled in yet then use all of the values */ |
| dst->cbStruct = sizeof(HH_WINTYPEW); |
| merge = ~0; |
| } |
| if (merge & HHWIN_PARAM_PROPERTIES) dst->fsWinProperties = src->fsWinProperties; |
| if (merge & HHWIN_PARAM_STYLES) dst->dwStyles = src->dwStyles; |
| if (merge & HHWIN_PARAM_EXSTYLES) dst->dwExStyles = src->dwExStyles; |
| if (merge & HHWIN_PARAM_RECT) dst->rcWindowPos = src->rcWindowPos; |
| if (merge & HHWIN_PARAM_NAV_WIDTH) dst->iNavWidth = src->iNavWidth; |
| if (merge & HHWIN_PARAM_SHOWSTATE) dst->nShowState = src->nShowState; |
| if (merge & HHWIN_PARAM_INFOTYPES) dst->paInfoTypes = src->paInfoTypes; |
| if (merge & HHWIN_PARAM_TB_FLAGS) dst->fsToolBarFlags = src->fsToolBarFlags; |
| if (merge & HHWIN_PARAM_EXPANSION) dst->fNotExpanded = src->fNotExpanded; |
| if (merge & HHWIN_PARAM_TABPOS) dst->tabpos = src->tabpos; |
| if (merge & HHWIN_PARAM_TABORDER) memcpy(dst->tabOrder, src->tabOrder, sizeof(src->tabOrder)); |
| if (merge & HHWIN_PARAM_HISTORY_COUNT) dst->cHistory = src->cHistory; |
| if (merge & HHWIN_PARAM_CUR_TAB) dst->curNavType = src->curNavType; |
| |
| /* |
| * Note: We assume that hwndHelp, hwndCaller, hwndToolBar, hwndNavigation, and hwndHTML cannot be |
| * modified by the user. rcHTML and rcMinSize are not currently supported, so don't bother to copy them. |
| */ |
| |
| dst->pszType = MergeChmString(src->pszType, &info->stringsW.pszType); |
| dst->pszFile = MergeChmString(src->pszFile, &info->stringsW.pszFile); |
| dst->pszToc = MergeChmString(src->pszToc, &info->stringsW.pszToc); |
| dst->pszIndex = MergeChmString(src->pszIndex, &info->stringsW.pszIndex); |
| dst->pszCaption = MergeChmString(src->pszCaption, &info->stringsW.pszCaption); |
| dst->pszHome = MergeChmString(src->pszHome, &info->stringsW.pszHome); |
| dst->pszJump1 = MergeChmString(src->pszJump1, &info->stringsW.pszJump1); |
| dst->pszJump2 = MergeChmString(src->pszJump2, &info->stringsW.pszJump2); |
| dst->pszUrlJump1 = MergeChmString(src->pszUrlJump1, &info->stringsW.pszUrlJump1); |
| dst->pszUrlJump2 = MergeChmString(src->pszUrlJump2, &info->stringsW.pszUrlJump2); |
| |
| /* FIXME: pszCustomTabs is a list of multiple zero-terminated strings so ReadString won't |
| * work in this case |
| */ |
| #if 0 |
| dst->pszCustomTabs = MergeChmString(src->pszCustomTabs, &info->pszCustomTabs); |
| #endif |
| } |
| |
| static inline WCHAR *ConvertChmString(HHInfo *info, const WCHAR **str) |
| { |
| WCHAR *ret = NULL; |
| |
| if(*str) |
| *str = ret = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)*str)); |
| return ret; |
| } |
| |
| /* Loads the HH_WINTYPE data from the CHM file |
| * |
| * FIXME: There may be more than one window type in the file, so |
| * add the ability to choose a certain window type |
| */ |
| BOOL LoadWinTypeFromCHM(HHInfo *info) |
| { |
| LARGE_INTEGER liOffset; |
| WCHAR *pszType = NULL, *pszFile = NULL, *pszToc = NULL, *pszIndex = NULL, *pszCaption = NULL; |
| WCHAR *pszHome = NULL, *pszJump1 = NULL, *pszJump2 = NULL, *pszUrlJump1 = NULL, *pszUrlJump2 = NULL; |
| IStorage *pStorage = info->pCHMInfo->pStorage; |
| IStream *pStream = NULL; |
| HH_WINTYPEW wintype; |
| HRESULT hr; |
| DWORD cbRead; |
| BOOL ret = FALSE; |
| |
| static const WCHAR null[] = {0}; |
| static const WCHAR toc_extW[] = {'h','h','c',0}; |
| static const WCHAR index_extW[] = {'h','h','k',0}; |
| static const WCHAR windowsW[] = {'#','W','I','N','D','O','W','S',0}; |
| |
| hr = IStorage_OpenStream(pStorage, windowsW, NULL, STGM_READ, 0, &pStream); |
| if (SUCCEEDED(hr)) |
| { |
| /* jump past the #WINDOWS header */ |
| liOffset.QuadPart = sizeof(DWORD) * 2; |
| |
| hr = IStream_Seek(pStream, liOffset, STREAM_SEEK_SET, NULL); |
| if (FAILED(hr)) goto done; |
| |
| /* read the HH_WINTYPE struct data */ |
| hr = IStream_Read(pStream, &wintype, sizeof(wintype), &cbRead); |
| if (FAILED(hr)) goto done; |
| |
| /* convert the #STRINGS offsets to actual strings */ |
| pszType = ConvertChmString(info, &wintype.pszType); |
| pszFile = ConvertChmString(info, &wintype.pszFile); |
| pszToc = ConvertChmString(info, &wintype.pszToc); |
| pszIndex = ConvertChmString(info, &wintype.pszIndex); |
| pszCaption = ConvertChmString(info, &wintype.pszCaption); |
| pszHome = ConvertChmString(info, &wintype.pszHome); |
| pszJump1 = ConvertChmString(info, &wintype.pszJump1); |
| pszJump2 = ConvertChmString(info, &wintype.pszJump2); |
| pszUrlJump1 = ConvertChmString(info, &wintype.pszUrlJump1); |
| pszUrlJump2 = ConvertChmString(info, &wintype.pszUrlJump2); |
| } |
| else |
| { |
| /* no defined window types so use (hopefully) sane defaults */ |
| static const WCHAR defaultwinW[] = {'d','e','f','a','u','l','t','w','i','n','\0'}; |
| memset(&wintype, 0, sizeof(wintype)); |
| wintype.cbStruct = sizeof(wintype); |
| wintype.fUniCodeStrings = TRUE; |
| wintype.pszType = pszType = strdupW(info->pCHMInfo->defWindow ? info->pCHMInfo->defWindow : defaultwinW); |
| wintype.pszToc = pszToc = strdupW(info->pCHMInfo->defToc ? info->pCHMInfo->defToc : null); |
| wintype.pszIndex = pszIndex = strdupW(null); |
| wintype.fsValidMembers = 0; |
| wintype.fsWinProperties = HHWIN_PROP_TRI_PANE; |
| wintype.dwStyles = WS_POPUP; |
| wintype.dwExStyles = 0; |
| wintype.nShowState = SW_SHOW; |
| wintype.curNavType = HHWIN_NAVTYPE_TOC; |
| } |
| |
| /* merge the new data with any pre-existing HH_WINTYPE structure */ |
| MergeChmProperties(&wintype, info, FALSE); |
| if (!info->WinType.pszCaption) |
| info->WinType.pszCaption = info->stringsW.pszCaption = strdupW(info->pCHMInfo->defTitle ? info->pCHMInfo->defTitle : null); |
| if (!info->WinType.pszFile) |
| info->WinType.pszFile = info->stringsW.pszFile = strdupW(info->pCHMInfo->defTopic ? info->pCHMInfo->defTopic : null); |
| if (!info->WinType.pszToc) |
| info->WinType.pszToc = info->stringsW.pszToc = FindHTMLHelpSetting(info, toc_extW); |
| if (!info->WinType.pszIndex) |
| info->WinType.pszIndex = info->stringsW.pszIndex = FindHTMLHelpSetting(info, index_extW); |
| |
| heap_free(pszType); |
| heap_free(pszFile); |
| heap_free(pszToc); |
| heap_free(pszIndex); |
| heap_free(pszCaption); |
| heap_free(pszHome); |
| heap_free(pszJump1); |
| heap_free(pszJump2); |
| heap_free(pszUrlJump1); |
| heap_free(pszUrlJump2); |
| ret = TRUE; |
| |
| done: |
| if (pStream) |
| IStream_Release(pStream); |
| |
| return ret; |
| } |
| |
| LPCWSTR skip_schema(LPCWSTR url) |
| { |
| static const WCHAR its_schema[] = {'i','t','s',':'}; |
| static const WCHAR msits_schema[] = {'m','s','-','i','t','s',':'}; |
| static const WCHAR mk_schema[] = {'m','k',':','@','M','S','I','T','S','t','o','r','e',':'}; |
| |
| if(!strncmpiW(its_schema, url, sizeof(its_schema)/sizeof(WCHAR))) |
| return url+sizeof(its_schema)/sizeof(WCHAR); |
| if(!strncmpiW(msits_schema, url, sizeof(msits_schema)/sizeof(WCHAR))) |
| return url+sizeof(msits_schema)/sizeof(WCHAR); |
| if(!strncmpiW(mk_schema, url, sizeof(mk_schema)/sizeof(WCHAR))) |
| return url+sizeof(mk_schema)/sizeof(WCHAR); |
| |
| return url; |
| } |
| |
| void SetChmPath(ChmPath *file, LPCWSTR base_file, LPCWSTR path) |
| { |
| LPCWSTR ptr; |
| static const WCHAR separatorW[] = {':',':',0}; |
| |
| path = skip_schema(path); |
| |
| ptr = strstrW(path, separatorW); |
| if(ptr) { |
| WCHAR chm_file[MAX_PATH]; |
| WCHAR rel_path[MAX_PATH]; |
| WCHAR base_path[MAX_PATH]; |
| LPWSTR p; |
| |
| strcpyW(base_path, base_file); |
| p = strrchrW(base_path, '\\'); |
| if(p) |
| *p = 0; |
| |
| memcpy(rel_path, path, (ptr-path)*sizeof(WCHAR)); |
| rel_path[ptr-path] = 0; |
| |
| PathCombineW(chm_file, base_path, rel_path); |
| |
| file->chm_file = strdupW(chm_file); |
| ptr += 2; |
| }else { |
| file->chm_file = strdupW(base_file); |
| ptr = path; |
| } |
| |
| file->chm_index = strdupW(ptr); |
| |
| TRACE("ChmFile = {%s %s}\n", debugstr_w(file->chm_file), debugstr_w(file->chm_index)); |
| } |
| |
| IStream *GetChmStream(CHMInfo *info, LPCWSTR parent_chm, ChmPath *chm_file) |
| { |
| IStorage *storage; |
| IStream *stream = NULL; |
| HRESULT hres; |
| |
| TRACE("%s (%s :: %s)\n", debugstr_w(parent_chm), debugstr_w(chm_file->chm_file), |
| debugstr_w(chm_file->chm_index)); |
| |
| if(parent_chm || chm_file->chm_file) { |
| hres = IITStorage_StgOpenStorage(info->pITStorage, |
| chm_file->chm_file ? chm_file->chm_file : parent_chm, NULL, |
| STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0, &storage); |
| if(FAILED(hres)) { |
| WARN("Could not open storage: %08x\n", hres); |
| return NULL; |
| } |
| }else { |
| storage = info->pStorage; |
| IStorage_AddRef(info->pStorage); |
| } |
| |
| hres = IStorage_OpenStream(storage, chm_file->chm_index, NULL, STGM_READ, 0, &stream); |
| IStorage_Release(storage); |
| if(FAILED(hres)) |
| WARN("Could not open stream: %08x\n", hres); |
| |
| return stream; |
| } |
| |
| /* |
| * Retrieve a CHM document and parse the data from the <title> element to get the document's title. |
| */ |
| WCHAR *GetDocumentTitle(CHMInfo *info, LPCWSTR document) |
| { |
| strbuf_t node, node_name, content; |
| WCHAR *document_title = NULL; |
| IStream *str = NULL; |
| IStorage *storage; |
| stream_t stream; |
| HRESULT hres; |
| |
| TRACE("%s\n", debugstr_w(document)); |
| |
| storage = info->pStorage; |
| if(!storage) { |
| WARN("Could not open storage to obtain the title for a document.\n"); |
| return NULL; |
| } |
| IStorage_AddRef(storage); |
| |
| hres = IStorage_OpenStream(storage, document, NULL, STGM_READ, 0, &str); |
| IStorage_Release(storage); |
| if(FAILED(hres)) |
| WARN("Could not open stream: %08x\n", hres); |
| |
| stream_init(&stream, str); |
| strbuf_init(&node); |
| strbuf_init(&content); |
| strbuf_init(&node_name); |
| |
| while(next_node(&stream, &node)) { |
| get_node_name(&node, &node_name); |
| |
| TRACE("%s\n", node.buf); |
| |
| if(!strcasecmp(node_name.buf, "title")) { |
| if(next_content(&stream, &content) && content.len > 1) |
| { |
| document_title = strdupnAtoW(&content.buf[1], content.len-1); |
| FIXME("magic: %s\n", debugstr_w(document_title)); |
| break; |
| } |
| } |
| |
| strbuf_zero(&node); |
| } |
| |
| strbuf_free(&node); |
| strbuf_free(&content); |
| strbuf_free(&node_name); |
| IStream_Release(str); |
| |
| return document_title; |
| } |
| |
| /* Opens the CHM file for reading */ |
| CHMInfo *OpenCHM(LPCWSTR szFile) |
| { |
| HRESULT hres; |
| CHMInfo *ret; |
| |
| static const WCHAR wszSTRINGS[] = {'#','S','T','R','I','N','G','S',0}; |
| |
| if (!(ret = heap_alloc_zero(sizeof(CHMInfo)))) |
| return NULL; |
| ret->codePage = CP_ACP; |
| |
| if (!(ret->szFile = strdupW(szFile))) { |
| heap_free(ret); |
| return NULL; |
| } |
| |
| hres = CoCreateInstance(&CLSID_ITStorage, NULL, CLSCTX_INPROC_SERVER, |
| &IID_IITStorage, (void **) &ret->pITStorage) ; |
| if(FAILED(hres)) { |
| WARN("Could not create ITStorage: %08x\n", hres); |
| return CloseCHM(ret); |
| } |
| |
| hres = IITStorage_StgOpenStorage(ret->pITStorage, szFile, NULL, |
| STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0, &ret->pStorage); |
| if(FAILED(hres)) { |
| WARN("Could not open storage: %08x\n", hres); |
| return CloseCHM(ret); |
| } |
| hres = IStorage_OpenStream(ret->pStorage, wszSTRINGS, NULL, STGM_READ, 0, |
| &ret->strings_stream); |
| if(FAILED(hres)) { |
| WARN("Could not open #STRINGS stream: %08x\n", hres); |
| /* It's not critical, so we pass */ |
| } |
| |
| if(!ReadChmSystem(ret)) { |
| WARN("Could not read #SYSTEM\n"); |
| return CloseCHM(ret); |
| } |
| |
| return ret; |
| } |
| |
| CHMInfo *CloseCHM(CHMInfo *chm) |
| { |
| if(chm->pITStorage) |
| IITStorage_Release(chm->pITStorage); |
| |
| if(chm->pStorage) |
| IStorage_Release(chm->pStorage); |
| |
| if(chm->strings_stream) |
| IStream_Release(chm->strings_stream); |
| |
| if(chm->strings_size) { |
| DWORD i; |
| |
| for(i=0; i<chm->strings_size; i++) |
| heap_free(chm->strings[i]); |
| } |
| |
| heap_free(chm->strings); |
| heap_free(chm->defWindow); |
| heap_free(chm->defTitle); |
| heap_free(chm->defTopic); |
| heap_free(chm->defToc); |
| heap_free(chm->szFile); |
| heap_free(chm->compiledFile); |
| heap_free(chm); |
| |
| return NULL; |
| } |