| /* | |
| * 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; | |
| } |