| /* |
| * winemsibuilder - tool to build MSI packages |
| * |
| * Copyright 2010 Hans Leidekker 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 |
| */ |
| |
| #define WIN32_LEAN_AND_MEAN |
| #define COBJMACROS |
| |
| #include <stdio.h> |
| #include <windows.h> |
| #include <msi.h> |
| #include <msiquery.h> |
| #include <objbase.h> |
| |
| #include "wine/debug.h" |
| #include "wine/unicode.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(winemsibuilder); |
| |
| static UINT open_database( const WCHAR *msifile, MSIHANDLE *handle ) |
| { |
| UINT r; |
| MSIHANDLE hdb; |
| |
| if (GetFileAttributesW( msifile ) == INVALID_FILE_ATTRIBUTES) |
| { |
| r = MsiOpenDatabaseW( msifile, MSIDBOPEN_CREATE, &hdb ); |
| if (r != ERROR_SUCCESS) |
| { |
| WINE_ERR( "failed to create package database %s (%u)\n", wine_dbgstr_w(msifile), r ); |
| return r; |
| } |
| r = MsiDatabaseCommit( hdb ); |
| if (r != ERROR_SUCCESS) |
| { |
| WINE_ERR( "failed to commit database (%u)\n", r ); |
| MsiCloseHandle( hdb ); |
| return r; |
| } |
| } |
| else |
| { |
| r = MsiOpenDatabaseW( msifile, MSIDBOPEN_TRANSACT, &hdb ); |
| if (r != ERROR_SUCCESS) |
| { |
| WINE_ERR( "failed to open package database %s (%u)\n", wine_dbgstr_w(msifile), r ); |
| return r; |
| } |
| } |
| |
| *handle = hdb; |
| return ERROR_SUCCESS; |
| } |
| |
| static int import_tables( const WCHAR *msifile, WCHAR **tables ) |
| { |
| UINT r; |
| MSIHANDLE hdb; |
| WCHAR *dir; |
| DWORD len; |
| |
| r = open_database( msifile, &hdb ); |
| if (r != ERROR_SUCCESS) return 1; |
| |
| len = GetCurrentDirectoryW( 0, NULL ); |
| if (!(dir = HeapAlloc( GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) ))) |
| { |
| MsiCloseHandle( hdb ); |
| return 1; |
| } |
| GetCurrentDirectoryW( len + 1, dir ); |
| |
| while (*tables) |
| { |
| r = MsiDatabaseImportW( hdb, dir, *tables ); |
| if (r != ERROR_SUCCESS) |
| { |
| WINE_ERR( "failed to import table %s (%u)\n", wine_dbgstr_w(*tables), r ); |
| break; |
| } |
| tables++; |
| } |
| |
| if (r == ERROR_SUCCESS) |
| { |
| r = MsiDatabaseCommit( hdb ); |
| if (r != ERROR_SUCCESS) |
| WINE_ERR( "failed to commit changes (%u)\n", r ); |
| } |
| |
| HeapFree( GetProcessHeap(), 0, dir ); |
| MsiCloseHandle( hdb ); |
| return (r != ERROR_SUCCESS); |
| } |
| |
| /* taken from dlls/msi/table.c */ |
| static int utf2mime( int x ) |
| { |
| if (x >= '0' && x <= '9') |
| return x - '0'; |
| if (x >= 'A' && x <= 'Z') |
| return x - 'A' + 10; |
| if (x >= 'a' && x <= 'z') |
| return x - 'a' + 10 + 26; |
| if (x == '.') |
| return 10 + 26 + 26; |
| if (x == '_') |
| return 10 + 26 + 26 + 1; |
| return -1; |
| } |
| |
| #define MAX_STREAM_NAME 0x1f |
| |
| static WCHAR *encode_stream( const WCHAR *in ) |
| { |
| DWORD c, next, count; |
| WCHAR *out, *p; |
| |
| count = strlenW( in ); |
| if (count > MAX_STREAM_NAME) |
| return NULL; |
| |
| count += 2; |
| if (!(out = HeapAlloc( GetProcessHeap(), 0, count * sizeof(WCHAR) ))) return NULL; |
| p = out; |
| while (count--) |
| { |
| c = *in++; |
| if (!c) |
| { |
| *p = c; |
| return out; |
| } |
| if (c < 0x80 && utf2mime( c ) >= 0) |
| { |
| c = utf2mime( c ) + 0x4800; |
| next = *in; |
| if (next && next < 0x80) |
| { |
| next = utf2mime( next ); |
| if (next != -1) |
| { |
| next += 0x3ffffc0; |
| c += next << 6; |
| in++; |
| } |
| } |
| } |
| *p++ = c; |
| } |
| HeapFree( GetProcessHeap(), 0, out ); |
| return NULL; |
| } |
| |
| static int add_stream( const WCHAR *msifile, const WCHAR *stream, const WCHAR *file ) |
| { |
| UINT r; |
| HRESULT hr; |
| MSIHANDLE hdb; |
| IStorage *stg; |
| IStream *stm = NULL; |
| HANDLE handle; |
| char buffer[4096]; |
| ULARGE_INTEGER size; |
| DWORD low, high, read; |
| WCHAR *encname; |
| int ret = 1; |
| |
| /* make sure we have the right type of file */ |
| r = open_database( msifile, &hdb ); |
| if (r != ERROR_SUCCESS) return 1; |
| MsiCloseHandle( hdb ); |
| |
| hr = StgOpenStorage( msifile, NULL, STGM_TRANSACTED|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, 0, &stg ); |
| if (hr != S_OK) |
| { |
| WINE_WARN( "failed to open storage %s (0x%08x)\n", wine_dbgstr_w(msifile), hr ); |
| return 1; |
| } |
| encname = encode_stream( stream ); |
| if (!encname) |
| { |
| WINE_WARN( "failed to encode stream name %s\n", wine_dbgstr_w(stream) ); |
| goto done; |
| } |
| hr = IStorage_CreateStream( stg, encname, STGM_CREATE|STGM_WRITE|STGM_SHARE_EXCLUSIVE, 0, 0, &stm ); |
| if (hr != S_OK) |
| { |
| WINE_WARN( "failed to create stream %s (0x%08x)\n", wine_dbgstr_w(encname), hr ); |
| goto done; |
| } |
| handle = CreateFileW( file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL ); |
| if (handle == INVALID_HANDLE_VALUE) |
| { |
| WINE_WARN( "failed to open file %s (%u)\n", wine_dbgstr_w(file), GetLastError() ); |
| goto done; |
| } |
| low = GetFileSize( handle, &high ); |
| if (low == INVALID_FILE_SIZE || high) |
| { |
| WINE_WARN( "file %s too big\n", wine_dbgstr_w(file) ); |
| CloseHandle( handle ); |
| goto done; |
| } |
| size.QuadPart = low; |
| hr = IStream_SetSize( stm, size ); |
| if (hr != S_OK) goto done; |
| |
| while (ReadFile( handle, buffer, sizeof(buffer), &read, NULL ) && read) |
| { |
| hr = IStream_Write( stm, buffer, read, NULL ); |
| if (hr != S_OK) break; |
| size.QuadPart -= read; |
| } |
| CloseHandle( handle ); |
| if (size.QuadPart) |
| { |
| WINE_WARN( "failed to write stream contents\n" ); |
| goto done; |
| } |
| IStorage_Commit( stg, 0 ); |
| ret = 0; |
| |
| done: |
| HeapFree( GetProcessHeap(), 0, encname ); |
| if (stm) IStream_Release( stm ); |
| IStorage_Release( stg ); |
| return ret; |
| } |
| |
| static void show_usage( void ) |
| { |
| WINE_MESSAGE( |
| "Usage: winemsibuilder [OPTION] [MSIFILE] ...\n" |
| "Options:\n" |
| " -i package.msi table1.idt [table2.idt ...] Import one or more tables into the database.\n" |
| " -a package.msi stream file Add 'stream' to storage with contents of 'file'.\n" |
| "\nExisting tables or streams will be overwritten. If package.msi does not exist a new file\n" |
| "will be created with an empty database.\n" |
| ); |
| } |
| |
| int wmain( int argc, WCHAR *argv[] ) |
| { |
| if (argc < 3 || argv[1][0] != '-') |
| { |
| show_usage(); |
| return 1; |
| } |
| |
| switch (argv[1][1]) |
| { |
| case 'i': |
| if (argc < 4) break; |
| return import_tables( argv[2], argv + 3 ); |
| case 'a': |
| if (argc < 5) break; |
| return add_stream( argv[2], argv[3], argv[4] ); |
| default: |
| WINE_WARN( "unknown option\n" ); |
| break; |
| } |
| |
| show_usage(); |
| return 1; |
| } |