diff options
Diffstat (limited to 'libmsi/format.c')
-rw-r--r-- | libmsi/format.c | 1045 |
1 files changed, 1045 insertions, 0 deletions
diff --git a/libmsi/format.c b/libmsi/format.c new file mode 100644 index 0000000..8188f13 --- /dev/null +++ b/libmsi/format.c @@ -0,0 +1,1045 @@ +/* + * Implementation of the Microsoft Installer (msi.dll) + * + * Copyright 2005 Mike McCormack for CodeWeavers + * Copyright 2005 Aric Stewart for CodeWeavers + * + * 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 <stdarg.h> +#include <stdio.h> + +#define COBJMACROS + +#include "windef.h" +#include "winbase.h" +#include "winerror.h" +#include "wine/debug.h" +#include "msi.h" +#include "winnls.h" +#include "objbase.h" +#include "oleauto.h" + +#include "msipriv.h" +#include "msiserver.h" +#include "wine/unicode.h" + +WINE_DEFAULT_DEBUG_CHANNEL(msi); + +/* types arranged by precedence */ +#define FORMAT_NULL 0x0001 +#define FORMAT_LITERAL 0x0002 +#define FORMAT_NUMBER 0x0004 +#define FORMAT_LBRACK 0x0010 +#define FORMAT_LBRACE 0x0020 +#define FORMAT_RBRACK 0x0011 +#define FORMAT_RBRACE 0x0021 +#define FORMAT_ESCAPE 0x0040 +#define FORMAT_PROPNULL 0x0080 +#define FORMAT_ERROR 0x1000 +#define FORMAT_FAIL 0x2000 + +#define left_type(x) (x & 0xF0) + +typedef struct _tagFORMAT +{ + MSIPACKAGE *package; + MSIRECORD *record; + LPWSTR deformatted; + int len; + int n; + BOOL propfailed; + BOOL groupfailed; + int groups; +} FORMAT; + +typedef struct _tagFORMSTR +{ + struct list entry; + int n; + int len; + int type; + BOOL propfound; + BOOL nonprop; +} FORMSTR; + +typedef struct _tagSTACK +{ + struct list items; +} STACK; + +static STACK *create_stack(void) +{ + STACK *stack = msi_alloc(sizeof(STACK)); + list_init(&stack->items); + return stack; +} + +static void free_stack(STACK *stack) +{ + while (!list_empty(&stack->items)) + { + FORMSTR *str = LIST_ENTRY(list_head(&stack->items), FORMSTR, entry); + list_remove(&str->entry); + msi_free(str); + } + + msi_free(stack); +} + +static void stack_push(STACK *stack, FORMSTR *str) +{ + list_add_head(&stack->items, &str->entry); +} + +static FORMSTR *stack_pop(STACK *stack) +{ + FORMSTR *ret; + + if (list_empty(&stack->items)) + return NULL; + + ret = LIST_ENTRY(list_head(&stack->items), FORMSTR, entry); + list_remove(&ret->entry); + return ret; +} + +static FORMSTR *stack_find(STACK *stack, int type) +{ + FORMSTR *str; + + LIST_FOR_EACH_ENTRY(str, &stack->items, FORMSTR, entry) + { + if (str->type == type) + return str; + } + + return NULL; +} + +static FORMSTR *stack_peek(STACK *stack) +{ + return LIST_ENTRY(list_head(&stack->items), FORMSTR, entry); +} + +static LPCWSTR get_formstr_data(FORMAT *format, FORMSTR *str) +{ + return &format->deformatted[str->n]; +} + +static LPWSTR dup_formstr(FORMAT *format, FORMSTR *str) +{ + LPWSTR val; + LPCWSTR data; + + if (str->len == 0) + return NULL; + + val = msi_alloc((str->len + 1) * sizeof(WCHAR)); + data = get_formstr_data(format, str); + lstrcpynW(val, data, str->len + 1); + + return val; +} + +static LPWSTR deformat_index(FORMAT *format, FORMSTR *str) +{ + LPWSTR val, ret; + + val = msi_alloc((str->len + 1) * sizeof(WCHAR)); + lstrcpynW(val, get_formstr_data(format, str), str->len + 1); + + ret = msi_dup_record_field(format->record, atoiW(val)); + + msi_free(val); + return ret; +} + +static LPWSTR deformat_property(FORMAT *format, FORMSTR *str) +{ + LPWSTR val, ret; + + val = msi_alloc((str->len + 1) * sizeof(WCHAR)); + lstrcpynW(val, get_formstr_data(format, str), str->len + 1); + + ret = msi_dup_property(format->package->db, val); + + msi_free(val); + return ret; +} + +static LPWSTR deformat_component(FORMAT *format, FORMSTR *str) +{ + LPWSTR key, ret = NULL; + MSICOMPONENT *comp; + + key = msi_alloc((str->len + 1) * sizeof(WCHAR)); + lstrcpynW(key, get_formstr_data(format, str), str->len + 1); + + comp = msi_get_loaded_component(format->package, key); + if (!comp) + goto done; + + if (comp->Action == INSTALLSTATE_SOURCE) + ret = msi_resolve_source_folder( format->package, comp->Directory, NULL ); + else + ret = strdupW( msi_get_target_folder( format->package, comp->Directory ) ); + +done: + msi_free(key); + return ret; +} + +static LPWSTR deformat_file(FORMAT *format, FORMSTR *str, BOOL shortname) +{ + LPWSTR key, ret = NULL; + MSIFILE *file; + DWORD size; + + key = msi_alloc((str->len + 1) * sizeof(WCHAR)); + lstrcpynW(key, get_formstr_data(format, str), str->len + 1); + + file = msi_get_loaded_file(format->package, key); + if (!file) + goto done; + + if (!shortname) + { + ret = strdupW(file->TargetPath); + goto done; + } + + size = GetShortPathNameW(file->TargetPath, NULL, 0); + if (size <= 0) + { + ret = strdupW(file->TargetPath); + goto done; + } + + size++; + ret = msi_alloc(size * sizeof(WCHAR)); + GetShortPathNameW(file->TargetPath, ret, size); + +done: + msi_free(key); + return ret; +} + +static LPWSTR deformat_environment(FORMAT *format, FORMSTR *str) +{ + LPWSTR key, ret = NULL; + DWORD sz; + + key = msi_alloc((str->len + 1) * sizeof(WCHAR)); + lstrcpynW(key, get_formstr_data(format, str), str->len + 1); + + sz = GetEnvironmentVariableW(key, NULL ,0); + if (sz <= 0) + goto done; + + sz++; + ret = msi_alloc(sz * sizeof(WCHAR)); + GetEnvironmentVariableW(key, ret, sz); + +done: + msi_free(key); + return ret; +} + +static LPWSTR deformat_literal(FORMAT *format, FORMSTR *str, BOOL *propfound, + BOOL *nonprop, int *type) +{ + LPCWSTR data = get_formstr_data(format, str); + LPWSTR replaced = NULL; + char ch = data[0]; + + if (ch == '\\') + { + str->n++; + if (str->len == 1) + { + str->len = 0; + replaced = NULL; + } + else + { + str->len = 1; + replaced = dup_formstr(format, str); + } + } + else if (ch == '~') + { + if (str->len != 1) + replaced = NULL; + else + { + replaced = msi_alloc(sizeof(WCHAR)); + *replaced = '\0'; + } + } + else if (ch == '%' || ch == '#' || ch == '!' || ch == '$') + { + str->n++; + str->len--; + + switch (ch) + { + case '%': + replaced = deformat_environment(format, str); break; + case '#': + replaced = deformat_file(format, str, FALSE); break; + case '!': + replaced = deformat_file(format, str, TRUE); break; + case '$': + replaced = deformat_component(format, str); break; + } + + *type = FORMAT_LITERAL; + } + else + { + replaced = deformat_property(format, str); + *type = FORMAT_LITERAL; + + if (replaced) + *propfound = TRUE; + else + format->propfailed = TRUE; + } + + return replaced; +} + +static LPWSTR build_default_format(const MSIRECORD* record) +{ + int i; + int count; + LPWSTR rc, buf; + static const WCHAR fmt[] = {'%','i',':',' ','%','s',' ',0}; + static const WCHAR fmt_null[] = {'%','i',':',' ',' ',0}; + static const WCHAR fmt_index[] = {'%','i',0}; + LPCWSTR str; + WCHAR index[10]; + DWORD size, max_len, len; + + count = MSI_RecordGetFieldCount(record); + + max_len = MAX_PATH; + buf = msi_alloc((max_len + 1) * sizeof(WCHAR)); + + rc = NULL; + size = 1; + for (i = 1; i <= count; i++) + { + sprintfW(index, fmt_index, i); + str = MSI_RecordGetString(record, i); + len = (str) ? lstrlenW(str) : 0; + len += (sizeof(fmt_null)/sizeof(fmt_null[0]) - 3) + lstrlenW(index); + size += len; + + if (len > max_len) + { + max_len = len; + buf = msi_realloc(buf, (max_len + 1) * sizeof(WCHAR)); + if (!buf) return NULL; + } + + if (str) + sprintfW(buf, fmt, i, str); + else + sprintfW(buf, fmt_null, i); + + if (!rc) + { + rc = msi_alloc(size * sizeof(WCHAR)); + lstrcpyW(rc, buf); + } + else + { + rc = msi_realloc(rc, size * sizeof(WCHAR)); + lstrcatW(rc, buf); + } + } + + msi_free(buf); + return rc; +} + +static BOOL format_is_number(WCHAR x) +{ + return ((x >= '0') && (x <= '9')); +} + +static BOOL format_str_is_number(LPWSTR str) +{ + LPWSTR ptr; + + for (ptr = str; *ptr; ptr++) + if (!format_is_number(*ptr)) + return FALSE; + + return TRUE; +} + +static BOOL format_is_alpha(WCHAR x) +{ + return (!format_is_number(x) && x != '\0' && + x != '[' && x != ']' && x != '{' && x != '}'); +} + +static BOOL format_is_literal(WCHAR x) +{ + return (format_is_alpha(x) || format_is_number(x)); +} + +static int format_lex(FORMAT *format, FORMSTR **out) +{ + int type, len = 1; + FORMSTR *str; + LPCWSTR data; + WCHAR ch; + + *out = NULL; + + if (!format->deformatted) + return FORMAT_NULL; + + *out = msi_alloc_zero(sizeof(FORMSTR)); + if (!*out) + return FORMAT_FAIL; + + str = *out; + str->n = format->n; + str->len = 1; + data = get_formstr_data(format, str); + + ch = data[0]; + switch (ch) + { + case '{': type = FORMAT_LBRACE; break; + case '}': type = FORMAT_RBRACE; break; + case '[': type = FORMAT_LBRACK; break; + case ']': type = FORMAT_RBRACK; break; + case '~': type = FORMAT_PROPNULL; break; + case '\0': type = FORMAT_NULL; break; + + default: + type = 0; + } + + if (type) + { + str->type = type; + format->n++; + return type; + } + + if (ch == '\\') + { + while (data[len] && data[len] != ']') + len++; + + type = FORMAT_ESCAPE; + } + else if (format_is_alpha(ch)) + { + while (format_is_literal(data[len])) + len++; + + type = FORMAT_LITERAL; + } + else if (format_is_number(ch)) + { + while (format_is_number(data[len])) + len++; + + type = FORMAT_NUMBER; + + if (data[len] != ']') + { + while (format_is_literal(data[len])) + len++; + + type = FORMAT_LITERAL; + } + } + else + { + ERR("Got unknown character %c(%x)\n", ch, ch); + return FORMAT_ERROR; + } + + format->n += len; + str->len = len; + str->type = type; + + return type; +} + +static FORMSTR *format_replace(FORMAT *format, BOOL propfound, BOOL nonprop, + int oldsize, int type, LPWSTR replace) +{ + FORMSTR *ret; + LPWSTR str, ptr; + DWORD size = 0; + int n; + + if (replace) + { + if (!*replace) + size = 1; + else + size = lstrlenW(replace); + } + + size -= oldsize; + size = format->len + size + 1; + + if (size <= 1) + { + msi_free(format->deformatted); + format->deformatted = NULL; + format->len = 0; + return NULL; + } + + str = msi_alloc(size * sizeof(WCHAR)); + if (!str) + return NULL; + + str[0] = '\0'; + memcpy(str, format->deformatted, format->n * sizeof(WCHAR)); + n = format->n; + + if (replace) + { + if (!*replace) + { + str[n] = '\0'; + n++; + } + else + { + lstrcpyW(&str[n], replace); + n += lstrlenW(replace); + } + } + + ptr = &format->deformatted[format->n + oldsize]; + memcpy(&str[n], ptr, (lstrlenW(ptr) + 1) * sizeof(WCHAR)); + + msi_free(format->deformatted); + format->deformatted = str; + format->len = size - 1; + + /* don't reformat the NULL */ + if (replace && !*replace) + format->n++; + + if (!replace) + return NULL; + + ret = msi_alloc_zero(sizeof(FORMSTR)); + if (!ret) + return NULL; + + ret->len = lstrlenW(replace); + ret->type = type; + ret->n = format->n; + ret->propfound = propfound; + ret->nonprop = nonprop; + + return ret; +} + +static LPWSTR replace_stack_group(FORMAT *format, STACK *values, + BOOL *propfound, BOOL *nonprop, + int *oldsize, int *type) +{ + LPWSTR replaced = NULL; + FORMSTR *content; + FORMSTR *node; + int n; + + *nonprop = FALSE; + *propfound = FALSE; + + node = stack_pop(values); + n = node->n; + *oldsize = node->len; + msi_free(node); + + while ((node = stack_pop(values))) + { + *oldsize += node->len; + + if (node->nonprop) + *nonprop = TRUE; + + if (node->propfound) + *propfound = TRUE; + + msi_free(node); + } + + content = msi_alloc_zero(sizeof(FORMSTR)); + content->n = n; + content->len = *oldsize; + content->type = FORMAT_LITERAL; + + if (!format->groupfailed && (*oldsize == 2 || + (format->propfailed && !*nonprop))) + { + msi_free(content); + return NULL; + } + else if (format->deformatted[content->n + 1] == '{' && + format->deformatted[content->n + content->len - 2] == '}') + { + format->groupfailed = FALSE; + content->len = 0; + } + else if (*propfound && !*nonprop && + !format->groupfailed && format->groups == 0) + { + content->n++; + content->len -= 2; + } + else + { + if (format->groups != 0) + format->groupfailed = TRUE; + + *nonprop = TRUE; + } + + replaced = dup_formstr(format, content); + *type = content->type; + msi_free(content); + + if (format->groups == 0) + format->propfailed = FALSE; + + return replaced; +} + +static LPWSTR replace_stack_prop(FORMAT *format, STACK *values, + BOOL *propfound, BOOL *nonprop, + int *oldsize, int *type) +{ + LPWSTR replaced = NULL; + FORMSTR *content; + FORMSTR *node; + int n; + + *propfound = FALSE; + *nonprop = FALSE; + + node = stack_pop(values); + n = node->n; + *oldsize = node->len; + *type = stack_peek(values)->type; + msi_free(node); + + while ((node = stack_pop(values))) + { + *oldsize += node->len; + + if (*type != FORMAT_ESCAPE && + stack_peek(values) && node->type != *type) + *type = FORMAT_LITERAL; + + msi_free(node); + } + + content = msi_alloc_zero(sizeof(FORMSTR)); + content->n = n + 1; + content->len = *oldsize - 2; + content->type = *type; + + if (*type == FORMAT_NUMBER) + { + replaced = deformat_index(format, content); + if (replaced) + *propfound = TRUE; + else + format->propfailed = TRUE; + + if (replaced) + *type = format_str_is_number(replaced) ? + FORMAT_NUMBER : FORMAT_LITERAL; + } + else if (format->package) + { + replaced = deformat_literal(format, content, propfound, nonprop, type); + } + else + { + *nonprop = TRUE; + content->n--; + content->len += 2; + replaced = dup_formstr(format, content); + } + + msi_free(content); + return replaced; +} + +static UINT replace_stack(FORMAT *format, STACK *stack, STACK *values) +{ + LPWSTR replaced = NULL; + FORMSTR *beg; + FORMSTR *top; + FORMSTR *node; + BOOL propfound = FALSE; + BOOL nonprop = FALSE; + BOOL group = FALSE; + int oldsize = 0; + int type, n; + + node = stack_peek(values); + type = node->type; + n = node->n; + + if (type == FORMAT_LBRACK) + replaced = replace_stack_prop(format, values, &propfound, + &nonprop, &oldsize, &type); + else if (type == FORMAT_LBRACE) + { + replaced = replace_stack_group(format, values, &propfound, + &nonprop, &oldsize, &type); + group = TRUE; + } + + format->n = n; + beg = format_replace(format, propfound, nonprop, oldsize, type, replaced); + if (!beg) + return ERROR_SUCCESS; + + msi_free(replaced); + format->n = beg->n + beg->len; + + top = stack_peek(stack); + if (top) + { + type = top->type; + + if ((type == FORMAT_LITERAL || type == FORMAT_NUMBER) && + type == beg->type) + { + top->len += beg->len; + + if (group) + top->nonprop = FALSE; + + if (type == FORMAT_LITERAL) + top->nonprop = beg->nonprop; + + if (beg->propfound) + top->propfound = TRUE; + + msi_free(beg); + return ERROR_SUCCESS; + } + } + + stack_push(stack, beg); + return ERROR_SUCCESS; +} + +static BOOL verify_format(LPWSTR data) +{ + int count = 0; + + while (*data) + { + if (*data == '[' && *(data - 1) != '\\') + count++; + else if (*data == ']') + count--; + + data++; + } + + if (count > 0) + return FALSE; + + return TRUE; +} + +static DWORD deformat_string_internal(MSIPACKAGE *package, LPCWSTR ptr, + WCHAR** data, DWORD *len, + MSIRECORD* record, INT* failcount) +{ + FORMAT format; + FORMSTR *str = NULL; + STACK *stack, *temp; + FORMSTR *node; + int type; + + if (!ptr) + { + *data = NULL; + *len = 0; + return ERROR_SUCCESS; + } + + *data = strdupW(ptr); + *len = lstrlenW(ptr); + + ZeroMemory(&format, sizeof(FORMAT)); + format.package = package; + format.record = record; + format.deformatted = *data; + format.len = *len; + + if (!verify_format(*data)) + return ERROR_SUCCESS; + + stack = create_stack(); + temp = create_stack(); + + while ((type = format_lex(&format, &str)) != FORMAT_NULL) + { + if (type == FORMAT_LBRACK || type == FORMAT_LBRACE || + type == FORMAT_LITERAL || type == FORMAT_NUMBER || + type == FORMAT_ESCAPE || type == FORMAT_PROPNULL) + { + if (type == FORMAT_LBRACE) + { + format.propfailed = FALSE; + format.groups++; + } + else if (type == FORMAT_ESCAPE && + !stack_find(stack, FORMAT_LBRACK)) + { + format.n -= str->len - 1; + str->len = 1; + } + + stack_push(stack, str); + } + else if (type == FORMAT_RBRACK || type == FORMAT_RBRACE) + { + if (type == FORMAT_RBRACE) + format.groups--; + + stack_push(stack, str); + + if (stack_find(stack, left_type(type))) + { + do + { + node = stack_pop(stack); + stack_push(temp, node); + } while (node->type != left_type(type)); + + replace_stack(&format, stack, temp); + } + } + } + + *data = format.deformatted; + *len = format.len; + + msi_free(str); + free_stack(stack); + free_stack(temp); + + return ERROR_SUCCESS; +} + +UINT MSI_FormatRecordW( MSIPACKAGE* package, MSIRECORD* record, LPWSTR buffer, + LPDWORD size ) +{ + LPWSTR deformated; + LPWSTR rec; + DWORD len; + UINT rc = ERROR_INVALID_PARAMETER; + + TRACE("%p %p %p %p\n", package, record, buffer, size); + + rec = msi_dup_record_field(record,0); + if (!rec) + rec = build_default_format(record); + + TRACE("(%s)\n",debugstr_w(rec)); + + deformat_string_internal(package, rec, &deformated, &len, record, NULL); + if (buffer) + { + if (*size>len) + { + memcpy(buffer,deformated,len*sizeof(WCHAR)); + rc = ERROR_SUCCESS; + buffer[len] = 0; + } + else + { + if (*size > 0) + { + memcpy(buffer,deformated,(*size)*sizeof(WCHAR)); + buffer[(*size)-1] = 0; + } + rc = ERROR_MORE_DATA; + } + } + else + rc = ERROR_SUCCESS; + + *size = len; + + msi_free(rec); + msi_free(deformated); + return rc; +} + +UINT WINAPI MsiFormatRecordW( MSIHANDLE hInstall, MSIHANDLE hRecord, + LPWSTR szResult, LPDWORD sz ) +{ + UINT r = ERROR_INVALID_HANDLE; + MSIPACKAGE *package; + MSIRECORD *record; + + TRACE("%d %d %p %p\n", hInstall, hRecord, szResult, sz); + + package = msihandle2msiinfo( hInstall, MSIHANDLETYPE_PACKAGE ); + if (!package) + { + HRESULT hr; + IWineMsiRemotePackage *remote_package; + BSTR value = NULL; + awstring wstr; + + remote_package = (IWineMsiRemotePackage *)msi_get_remote( hInstall ); + if (remote_package) + { + hr = IWineMsiRemotePackage_FormatRecord( remote_package, hRecord, + &value ); + if (FAILED(hr)) + goto done; + + wstr.unicode = TRUE; + wstr.str.w = szResult; + r = msi_strcpy_to_awstring( value, &wstr, sz ); + +done: + IWineMsiRemotePackage_Release( remote_package ); + SysFreeString( value ); + + if (FAILED(hr)) + { + if (HRESULT_FACILITY(hr) == FACILITY_WIN32) + return HRESULT_CODE(hr); + + return ERROR_FUNCTION_FAILED; + } + + return r; + } + } + + record = msihandle2msiinfo( hRecord, MSIHANDLETYPE_RECORD ); + + if (!record) + return ERROR_INVALID_HANDLE; + if (!sz) + { + msiobj_release( &record->hdr ); + if (szResult) + return ERROR_INVALID_PARAMETER; + else + return ERROR_SUCCESS; + } + + r = MSI_FormatRecordW( package, record, szResult, sz ); + msiobj_release( &record->hdr ); + if (package) + msiobj_release( &package->hdr ); + return r; +} + +UINT WINAPI MsiFormatRecordA( MSIHANDLE hInstall, MSIHANDLE hRecord, + LPSTR szResult, LPDWORD sz ) +{ + UINT r; + DWORD len, save; + LPWSTR value; + + TRACE("%d %d %p %p\n", hInstall, hRecord, szResult, sz); + + if (!hRecord) + return ERROR_INVALID_HANDLE; + + if (!sz) + { + if (szResult) + return ERROR_INVALID_PARAMETER; + else + return ERROR_SUCCESS; + } + + r = MsiFormatRecordW( hInstall, hRecord, NULL, &len ); + if (r != ERROR_SUCCESS) + return r; + + value = msi_alloc(++len * sizeof(WCHAR)); + if (!value) + return ERROR_OUTOFMEMORY; + + r = MsiFormatRecordW( hInstall, hRecord, value, &len ); + if (r != ERROR_SUCCESS) + goto done; + + save = len + 1; + len = WideCharToMultiByte(CP_ACP, 0, value, len + 1, NULL, 0, NULL, NULL); + WideCharToMultiByte(CP_ACP, 0, value, len, szResult, *sz, NULL, NULL); + + if (szResult && len > *sz) + { + if (*sz) szResult[*sz - 1] = '\0'; + r = ERROR_MORE_DATA; + } + + *sz = save - 1; + +done: + msi_free(value); + return r; +} + +/* wrapper to resist a need for a full rewrite right now */ +DWORD deformat_string( MSIPACKAGE *package, const WCHAR *ptr, WCHAR **data ) +{ + if (ptr) + { + DWORD size = 0; + MSIRECORD *rec = MSI_CreateRecord( 1 ); + + MSI_RecordSetStringW( rec, 0, ptr ); + MSI_FormatRecordW( package, rec, NULL, &size ); + + size++; + *data = msi_alloc( size * sizeof(WCHAR) ); + if (size > 1) MSI_FormatRecordW( package, rec, *data, &size ); + else *data[0] = 0; + + msiobj_release( &rec->hdr ); + return size * sizeof(WCHAR); + } + *data = NULL; + return 0; +} |