msi: Implement special handling for the _Streams table.
diff --git a/dlls/msi/Makefile.in b/dlls/msi/Makefile.in
index 5fb0941..e7ba773 100644
--- a/dlls/msi/Makefile.in
+++ b/dlls/msi/Makefile.in
@@ -39,6 +39,7 @@
 	regsvr.c \
 	select.c \
 	source.c \
+	streams.c \
 	string.c \
 	suminfo.c \
 	table.c \
diff --git a/dlls/msi/msipriv.h b/dlls/msi/msipriv.h
index e7c0c71..a12a264 100644
--- a/dlls/msi/msipriv.h
+++ b/dlls/msi/msipriv.h
@@ -568,7 +568,7 @@
 extern UINT read_stream_data( IStorage *stg, LPCWSTR stname,
                               USHORT **pdata, UINT *psz );
 extern UINT write_stream_data( IStorage *stg, LPCWSTR stname,
-                               LPVOID data, UINT sz );
+                               LPVOID data, UINT sz, BOOL bTable );
 
 /* transform functions */
 extern UINT msi_table_apply_transform( MSIDATABASE *db, IStorage *stg );
@@ -606,6 +606,8 @@
 extern UINT get_raw_stream( MSIHANDLE hdb, LPCWSTR stname, IStream **stm );
 extern UINT db_get_raw_stream( MSIDATABASE *db, LPCWSTR stname, IStream **stm );
 extern void enum_stream_names( IStorage *stg );
+extern BOOL decode_streamname(LPWSTR in, LPWSTR out);
+extern LPWSTR encode_streamname(BOOL bTable, LPCWSTR in);
 
 /* database internals */
 extern UINT MSI_OpenDatabaseW( LPCWSTR, LPCWSTR, MSIDATABASE ** );
diff --git a/dlls/msi/query.h b/dlls/msi/query.h
index 69d511c..2bf8955 100644
--- a/dlls/msi/query.h
+++ b/dlls/msi/query.h
@@ -124,6 +124,8 @@
 
 UINT ALTER_CreateView( MSIDATABASE *db, MSIVIEW **view, LPCWSTR name, int hold );
 
+UINT STREAMS_CreateView( MSIDATABASE *db, MSIVIEW **view );
+
 int sqliteGetToken(const WCHAR *z, int *tokenType);
 
 MSIRECORD *msi_query_merge_record( UINT fields, column_info *vl, MSIRECORD *rec );
diff --git a/dlls/msi/streams.c b/dlls/msi/streams.c
new file mode 100644
index 0000000..611f997
--- /dev/null
+++ b/dlls/msi/streams.c
@@ -0,0 +1,404 @@
+/*
+ * Implementation of the Microsoft Installer (msi.dll)
+ *
+ * Copyright 2007 James Hawkins
+ *
+ * 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>
+
+#define COBJMACROS
+
+#include "windef.h"
+#include "winbase.h"
+#include "winerror.h"
+#include "msi.h"
+#include "msiquery.h"
+#include "objbase.h"
+#include "msipriv.h"
+
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(msidb);
+
+#define NUM_STREAMS_COLS    2
+#define MAX_STREAM_NAME_LEN 62
+
+typedef struct tabSTREAM
+{
+    int str_index;
+    LPWSTR name;
+    IStream *stream;
+} STREAM;
+
+typedef struct tagMSISTREAMSVIEW
+{
+    MSIVIEW view;
+    MSIDATABASE *db;
+    STREAM **streams;
+    UINT max_streams;
+    UINT num_rows;
+    UINT row_size;
+} MSISTREAMSVIEW;
+
+static BOOL add_stream_to_table(MSISTREAMSVIEW *sv, STREAM *stream, int index)
+{
+    if (index >= sv->max_streams)
+    {
+        sv->max_streams *= 2;
+        sv->streams = msi_realloc(sv->streams, sv->max_streams * sizeof(STREAM *));
+        if (!sv->streams)
+            return FALSE;
+    }
+
+    sv->streams[index] = stream;
+    return TRUE;
+}
+
+static STREAM *create_stream(MSISTREAMSVIEW *sv, LPWSTR name, BOOL encoded, IStream *stm)
+{
+    STREAM *stream;
+    WCHAR decoded[MAX_STREAM_NAME_LEN];
+    LPWSTR ptr = name;
+
+    stream = msi_alloc(sizeof(STREAM));
+    if (!stream)
+        return NULL;
+
+    if (encoded)
+    {
+        decode_streamname(name, decoded);
+        ptr = decoded;
+        TRACE("stream -> %s %s\n", debugstr_w(name), debugstr_w(decoded));
+    }
+
+    stream->name = strdupW(ptr);
+    if (!stream->name)
+    {
+        msi_free(stream);
+        return NULL;
+    }
+
+    stream->str_index = msi_addstringW(sv->db->strings, 0, stream->name, -1, 1, StringNonPersistent);
+    stream->stream = stm;
+    return stream;
+}
+
+static UINT STREAMS_fetch_int(struct tagMSIVIEW *view, UINT row, UINT col, UINT *val)
+{
+    MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
+
+    TRACE("(%p, %d, %d, %p)\n", view, row, col, val);
+
+    if (col != 1)
+        return ERROR_INVALID_PARAMETER;
+
+    if (row >= sv->num_rows)
+        return ERROR_NO_MORE_ITEMS;
+
+    *val = sv->streams[row]->str_index;
+
+    return ERROR_SUCCESS;
+}
+
+static UINT STREAMS_fetch_stream(struct tagMSIVIEW *view, UINT row, UINT col, IStream **stm)
+{
+    MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
+
+    TRACE("(%p, %d, %d, %p)\n", view, row, col, stm);
+
+    if (row >= sv->num_rows)
+        return ERROR_FUNCTION_FAILED;
+
+    IStream_AddRef(sv->streams[row]->stream);
+    *stm = sv->streams[row]->stream;
+
+    return ERROR_SUCCESS;
+}
+
+static UINT STREAMS_set_row(struct tagMSIVIEW *view, UINT row, MSIRECORD *rec, UINT mask)
+{
+    FIXME("(%p, %d, %p, %d): stub!\n", view, row, rec, mask);
+    return ERROR_SUCCESS;
+}
+
+static UINT STREAMS_insert_row(struct tagMSIVIEW *view, MSIRECORD *rec, BOOL temporary)
+{
+    MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
+    STREAM *stream;
+    IStream *stm;
+    STATSTG stat;
+    LPWSTR name = NULL;
+    USHORT *data = NULL;
+    HRESULT hr;
+    ULONG count;
+    UINT r = ERROR_FUNCTION_FAILED;
+
+    TRACE("(%p, %p, %d)\n", view, rec, temporary);
+
+    r = MSI_RecordGetIStream(rec, 2, &stm);
+    if (r != ERROR_SUCCESS)
+        return r;
+
+    hr = IStream_Stat(stm, &stat, STATFLAG_NONAME);
+    if (FAILED(hr))
+    {
+        WARN("failed to stat stream: %08x\n", hr);
+        goto done;
+    }
+
+    if (stat.cbSize.QuadPart >> 32)
+        goto done;
+
+    data = msi_alloc(stat.cbSize.QuadPart);
+    if (!data)
+        goto done;
+
+    hr = IStream_Read(stm, data, stat.cbSize.QuadPart, &count);
+    if (FAILED(hr) || count != stat.cbSize.QuadPart)
+    {
+        WARN("failed to read stream: %08x\n", hr);
+        goto done;
+    }
+
+    name = strdupW(MSI_RecordGetString(rec, 1));
+    if (!name)
+        goto done;
+
+    r = write_stream_data(sv->db->storage, name, data, count, FALSE);
+    if (r != ERROR_SUCCESS)
+    {
+        WARN("failed to write stream data: %d\n", r);
+        goto done;
+    }
+
+    stream = create_stream(sv, name, FALSE, NULL);
+    if (!stream)
+        goto done;
+
+    IStorage_OpenStream(sv->db->storage, name, 0,
+                        STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &stream->stream);
+
+    if (!add_stream_to_table(sv, stream, sv->num_rows++))
+        goto done;
+
+done:
+    msi_free(name);
+    msi_free(data);
+
+    IStream_Release(stm);
+
+    return r;
+}
+
+static UINT STREAMS_execute(struct tagMSIVIEW *view, MSIRECORD *record)
+{
+    TRACE("(%p, %p)\n", view, record);
+    return ERROR_SUCCESS;
+}
+
+static UINT STREAMS_close(struct tagMSIVIEW *view)
+{
+    TRACE("(%p)\n", view);
+    return ERROR_SUCCESS;
+}
+
+static UINT STREAMS_get_dimensions(struct tagMSIVIEW *view, UINT *rows, UINT *cols)
+{
+    MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
+
+    TRACE("(%p, %p, %p)\n", view, rows, cols);
+
+    if (cols) *cols = NUM_STREAMS_COLS;
+    if (rows) *rows = sv->num_rows;
+
+    return ERROR_SUCCESS;
+}
+
+static UINT STREAMS_get_column_info(struct tagMSIVIEW *view,
+                                    UINT n, LPWSTR *name, UINT *type)
+{
+    LPCWSTR name_ptr = NULL;
+
+    static const WCHAR Name[] = {'N','a','m','e',0};
+    static const WCHAR Data[] = {'D','a','t','a',0};
+
+    TRACE("(%p, %d, %p, %p)\n", view, n, name, type);
+
+    if (n == 0 || n > NUM_STREAMS_COLS)
+        return ERROR_INVALID_PARAMETER;
+
+    switch (n)
+    {
+    case 1:
+        name_ptr = Name;
+        if (type) *type = MSITYPE_STRING | MAX_STREAM_NAME_LEN;
+        break;
+
+    case 2:
+        name_ptr = Data;
+        if (type) *type = MSITYPE_STRING | MSITYPE_VALID | MSITYPE_NULLABLE;
+        break;
+    }
+
+    if (name)
+    {
+        *name = strdupW(name_ptr);
+        if (!*name) return ERROR_FUNCTION_FAILED;
+    }
+
+    return ERROR_SUCCESS;
+}
+
+static UINT STREAMS_modify(struct tagMSIVIEW *view, MSIMODIFY eModifyMode, MSIRECORD *rec)
+{
+    FIXME("(%p, %d, %p): stub!\n", view, eModifyMode, rec);
+    return ERROR_SUCCESS;
+}
+
+static UINT STREAMS_delete(struct tagMSIVIEW *view)
+{
+    MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
+    int i;
+
+    TRACE("(%p)\n", view);
+
+    for (i = 0; i < sv->num_rows; i++)
+    {
+        if (sv->streams[i]->stream)
+            IStream_Release(sv->streams[i]->stream);
+        msi_free(sv->streams[i]);
+    }
+
+    msi_free(sv->streams);
+
+    return ERROR_SUCCESS;
+}
+
+static UINT STREAMS_find_matching_rows(struct tagMSIVIEW *view, UINT col,
+                                       UINT val, UINT *row, MSIITERHANDLE *handle)
+{
+    MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
+    UINT index = (UINT)*handle;
+
+    TRACE("(%d, %d): %d\n", *row, col, val);
+
+    if (col == 0 || col > NUM_STREAMS_COLS)
+        return ERROR_INVALID_PARAMETER;
+
+    while (index < sv->num_rows)
+    {
+        if (sv->streams[index]->str_index == val)
+        {
+            *row = index;
+            break;
+        }
+
+        index++;
+    }
+
+    *handle = (MSIITERHANDLE)++index;
+    if (index >= sv->num_rows)
+        return ERROR_NO_MORE_ITEMS;
+
+    return ERROR_SUCCESS;
+}
+
+static const MSIVIEWOPS streams_ops =
+{
+    STREAMS_fetch_int,
+    STREAMS_fetch_stream,
+    STREAMS_set_row,
+    STREAMS_insert_row,
+    STREAMS_execute,
+    STREAMS_close,
+    STREAMS_get_dimensions,
+    STREAMS_get_column_info,
+    STREAMS_modify,
+    STREAMS_delete,
+    STREAMS_find_matching_rows
+};
+
+static UINT add_streams_to_table(MSISTREAMSVIEW *sv)
+{
+    IEnumSTATSTG *stgenum = NULL;
+    STATSTG stat;
+    STREAM *stream = NULL;
+    HRESULT hr;
+    UINT count = 0, size;
+
+    hr = IStorage_EnumElements(sv->db->storage, 0, NULL, 0, &stgenum);
+    if (FAILED(hr))
+        return -1;
+
+    sv->max_streams = 1;
+    sv->streams = msi_alloc(sizeof(STREAM *));
+    if (!sv->streams)
+        return -1;
+
+    while (TRUE)
+    {
+        size = 0;
+        hr = IEnumSTATSTG_Next(stgenum, 1, &stat, &size);
+        if (FAILED(hr) || !size)
+            break;
+
+        /* table streams are not in the _Streams table */
+        if (*stat.pwcsName == 0x4840)
+            continue;
+
+        stream = create_stream(sv, stat.pwcsName, TRUE, NULL);
+        if (!stream)
+        {
+            count = -1;
+            break;
+        }
+
+        IStorage_OpenStream(sv->db->storage, stat.pwcsName, 0,
+                            STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &stream->stream);
+
+        if (!add_stream_to_table(sv, stream, count++))
+        {
+            count = -1;
+            break;
+        }
+    }
+
+    IEnumSTATSTG_Release(stgenum);
+    return count;
+}
+
+UINT STREAMS_CreateView(MSIDATABASE *db, MSIVIEW **view)
+{
+    MSISTREAMSVIEW *sv;
+
+    TRACE("(%p, %p)\n", db, view);
+
+    sv = msi_alloc(sizeof(MSISTREAMSVIEW));
+    if (!sv)
+        return ERROR_FUNCTION_FAILED;
+
+    sv->view.ops = &streams_ops;
+    sv->db = db;
+    sv->num_rows = add_streams_to_table(sv);
+
+    if (sv->num_rows < 0)
+        return ERROR_FUNCTION_FAILED;
+
+    *view = (MSIVIEW *)sv;
+
+    return ERROR_SUCCESS;
+}
diff --git a/dlls/msi/string.c b/dlls/msi/string.c
index ef6d7c7..b773a35 100644
--- a/dlls/msi/string.c
+++ b/dlls/msi/string.c
@@ -495,12 +495,12 @@
     UINT ret;
 
     /* create the StringPool stream... add the zero string to it*/
-    ret = write_stream_data(stg, szStringPool, zero, sizeof zero);
+    ret = write_stream_data(stg, szStringPool, zero, sizeof zero, TRUE);
     if (ret != ERROR_SUCCESS)
         return E_FAIL;
 
     /* create the StringData stream... make it zero length */
-    ret = write_stream_data(stg, szStringData, NULL, 0);
+    ret = write_stream_data(stg, szStringData, NULL, 0, TRUE);
     if (ret != ERROR_SUCCESS)
         return E_FAIL;
 
@@ -663,11 +663,11 @@
     }
 
     /* write the streams */
-    r = write_stream_data( storage, szStringData, data, datasize );
+    r = write_stream_data( storage, szStringData, data, datasize, TRUE );
     TRACE("Wrote StringData r=%08x\n", r);
     if( r )
         goto err;
-    r = write_stream_data( storage, szStringPool, pool, poolsize );
+    r = write_stream_data( storage, szStringPool, pool, poolsize, TRUE );
     TRACE("Wrote StringPool r=%08x\n", r);
     if( r )
         goto err;
diff --git a/dlls/msi/table.c b/dlls/msi/table.c
index 974b3f1..0d96793 100644
--- a/dlls/msi/table.c
+++ b/dlls/msi/table.c
@@ -134,7 +134,7 @@
     return -1;
 }
 
-static LPWSTR encode_streamname(BOOL bTable, LPCWSTR in)
+LPWSTR encode_streamname(BOOL bTable, LPCWSTR in)
 {
     DWORD count = MAX_STREAM_NAME;
     DWORD ch, next;
@@ -193,7 +193,7 @@
     return '_';
 }
 
-static BOOL decode_streamname(LPWSTR in, LPWSTR out)
+BOOL decode_streamname(LPWSTR in, LPWSTR out)
 {
     WCHAR ch;
     DWORD count = 0;
@@ -395,7 +395,7 @@
 }
 
 UINT write_stream_data( IStorage *stg, LPCWSTR stname,
-                        LPVOID data, UINT sz )
+                        LPVOID data, UINT sz, BOOL bTable )
 {
     HRESULT r;
     UINT ret = ERROR_FUNCTION_FAILED;
@@ -405,7 +405,7 @@
     LARGE_INTEGER pos;
     LPWSTR encname;
 
-    encname = encode_streamname(TRUE, stname );
+    encname = encode_streamname(bTable, stname );
     r = IStorage_OpenStream( stg, encname, NULL, 
             STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &stm);
     if( FAILED(r) )
@@ -845,7 +845,7 @@
     }
 
     TRACE("writing %d bytes\n", rawsize);
-    r = write_stream_data( db->storage, t->name, rawdata, rawsize );
+    r = write_stream_data( db->storage, t->name, rawdata, rawsize, TRUE );
 
 err:
     msi_free( rawdata );
@@ -1642,8 +1642,13 @@
     MSITABLEVIEW *tv ;
     UINT r, sz;
 
+    static const WCHAR Streams[] = {'_','S','t','r','e','a','m','s',0};
+
     TRACE("%p %s %p\n", db, debugstr_w(name), view );
 
+    if ( !lstrcmpW( name, Streams ) )
+        return STREAMS_CreateView( db, view );
+
     sz = sizeof *tv + lstrlenW(name)*sizeof name[0] ;
     tv = msi_alloc_zero( sz );
     if( !tv )
diff --git a/dlls/msi/tests/db.c b/dlls/msi/tests/db.c
index 073cf01..1d24f5d 100644
--- a/dlls/msi/tests/db.c
+++ b/dlls/msi/tests/db.c
@@ -1194,14 +1194,25 @@
             "( `Property` CHAR(255), `Value` CHAR(1)  PRIMARY KEY `Property`)" );
     ok( r == ERROR_SUCCESS , "Failed to create table\n" );
 
+    r = run_query( hdb, 0,
+            "INSERT INTO `Properties` "
+            "( `Value`, `Property` ) VALUES ( 'Prop', 'value' )" );
+    ok( r == ERROR_SUCCESS, "Failed to add to table\n" );
+
+    r = MsiDatabaseCommit( hdb );
+    ok( r == ERROR_SUCCESS , "Failed to commit database\n" );
+
+    MsiCloseHandle( hdb );
+
+    r = MsiOpenDatabase(msifile, MSIDBOPEN_TRANSACT, &hdb );
+    ok( r == ERROR_SUCCESS , "Failed to open database\n" );
+
     /* check the column types */
     rec = get_column_info( hdb, "select * from `_Streams`", MSICOLINFO_TYPES );
     ok( rec, "failed to get column info record\n" );
 
-    todo_wine {
     ok( check_record( rec, 1, "s62"), "wrong record type\n");
     ok( check_record( rec, 2, "V0"), "wrong record type\n");
-    }
 
     MsiCloseHandle( rec );
 
@@ -1209,10 +1220,8 @@
     rec = get_column_info( hdb, "select * from `_Streams`", MSICOLINFO_NAMES );
     ok( rec, "failed to get column info record\n" );
 
-    todo_wine {
     ok( check_record( rec, 1, "Name"), "wrong record type\n");
     ok( check_record( rec, 2, "Data"), "wrong record type\n");
-    }
 
     MsiCloseHandle( rec );
 
@@ -1229,63 +1238,39 @@
 
     r = MsiDatabaseOpenView( hdb,
             "INSERT INTO `_Streams` ( `Name`, `Data` ) VALUES ( ?, ? )", &view );
-    todo_wine
-    {
-        ok( r == ERROR_SUCCESS, "Failed to open database view: %d\n", r);
-    }
+    ok( r == ERROR_SUCCESS, "Failed to open database view: %d\n", r);
 
     r = MsiViewExecute( view, rec );
-    todo_wine
-    {
-        ok( r == ERROR_SUCCESS, "Failed to execute view: %d\n", r);
-    }
+    ok( r == ERROR_SUCCESS, "Failed to execute view: %d\n", r);
 
     MsiCloseHandle( rec );
     MsiCloseHandle( view );
 
     r = MsiDatabaseOpenView( hdb,
             "SELECT `Name`, `Data` FROM `_Streams`", &view );
-    todo_wine
-    {
-        ok( r == ERROR_SUCCESS, "Failed to open database view: %d\n", r);
-    }
+    ok( r == ERROR_SUCCESS, "Failed to open database view: %d\n", r);
 
     r = MsiViewExecute( view, 0 );
-    todo_wine
-    {
-        ok( r == ERROR_SUCCESS, "Failed to execute view: %d\n", r);
-    }
+    ok( r == ERROR_SUCCESS, "Failed to execute view: %d\n", r);
 
     r = MsiViewFetch( view, &rec );
-    todo_wine
-    {
-        ok( r == ERROR_SUCCESS, "Failed to fetch record: %d\n", r);
-    }
+    ok( r == ERROR_SUCCESS, "Failed to fetch record: %d\n", r);
 
     size = MAX_PATH;
     r = MsiRecordGetString( rec, 1, file, &size );
-    todo_wine
-    {
-        ok( r == ERROR_SUCCESS, "Failed to get string: %d\n", r);
-        ok( !lstrcmp(file, "data"), "Expected 'data', got %s\n", file);
-    }
+    ok( r == ERROR_SUCCESS, "Failed to get string: %d\n", r);
+    ok( !lstrcmp(file, "data"), "Expected 'data', got %s\n", file);
 
     size = MAX_PATH;
     memset(buf, 0, MAX_PATH);
     r = MsiRecordReadStream( rec, 2, buf, &size );
-    todo_wine
-    {
-        ok( r == ERROR_SUCCESS, "Failed to get stream: %d\n", r);
-        ok( !lstrcmp(buf, "test.txt\n"), "Expected 'test.txt\\n', got %s\n", buf);
-    }
+    ok( r == ERROR_SUCCESS, "Failed to get stream: %d\n", r);
+    ok( !lstrcmp(buf, "test.txt\n"), "Expected 'test.txt\\n', got %s", buf);
 
     MsiCloseHandle( rec );
 
     r = MsiViewFetch( view, &rec );
-    todo_wine
-    {
-        ok( r == ERROR_NO_MORE_ITEMS, "Expected ERROR_NO_MORE_ITEMS, got %d\n", r);
-    }
+    ok( r == ERROR_NO_MORE_ITEMS, "Expected ERROR_NO_MORE_ITEMS, got %d\n", r);
 
     MsiCloseHandle( view );
     MsiCloseHandle( hdb );