diff --git a/configure b/configure
index 64a7c50efdb244de96d4995acec89f734dd74115..c763328945db966c6fa6ed755e44353fd2081505 100755
--- a/configure
+++ b/configure
@@ -15370,6 +15370,7 @@ wine_fn_config_makefile libs/wpp enable_libs_wpp
 wine_fn_config_makefile loader enable_loader
 wine_fn_config_program aspnet_regiis enable_aspnet_regiis install
 wine_fn_config_program attrib enable_attrib install
+wine_fn_config_program cabarc enable_cabarc install
 wine_fn_config_program cacls enable_cacls install
 wine_fn_config_program clock enable_clock po,install
 wine_fn_config_program cmd enable_cmd po,install
diff --git a/configure.ac b/configure.ac
index 6d8b5740f21736977cd71d47bea461eaa6f7a4f6..fa2a6c009d2751b5375b7d0d8be5a991cf650dea 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2871,6 +2871,7 @@ WINE_CONFIG_MAKEFILE([libs/wpp])
 WINE_CONFIG_MAKEFILE([loader])
 WINE_CONFIG_PROGRAM(aspnet_regiis,,[install])
 WINE_CONFIG_PROGRAM(attrib,,[install])
+WINE_CONFIG_PROGRAM(cabarc,,[install])
 WINE_CONFIG_PROGRAM(cacls,,[install])
 WINE_CONFIG_PROGRAM(clock,,[po,install])
 WINE_CONFIG_PROGRAM(cmd,,[po,install])
diff --git a/programs/cabarc/Makefile.in b/programs/cabarc/Makefile.in
new file mode 100644
index 0000000000000000000000000000000000000000..47d48235b0460c3a62e73f3aac856a5a12721bba
--- /dev/null
+++ b/programs/cabarc/Makefile.in
@@ -0,0 +1,8 @@
+MODULE    = cabarc.exe
+APPMODE   = -mconsole -municode
+IMPORTS   = cabinet
+EXTRADEFS = -DWINE_NO_UNICODE_MACROS
+
+C_SRCS = cabarc.c
+
+@MAKE_PROG_RULES@
diff --git a/programs/cabarc/cabarc.c b/programs/cabarc/cabarc.c
new file mode 100644
index 0000000000000000000000000000000000000000..5a10d2145fb540b7a585d9cffe42d5a79a5215e7
--- /dev/null
+++ b/programs/cabarc/cabarc.c
@@ -0,0 +1,484 @@
+/*
+ * Tool to manipulate cabinet files
+ *
+ * Copyright 2011 Alexandre Julliard
+ *
+ * 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 "config.h"
+#include "wine/port.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include "windows.h"
+#include "fci.h"
+#include "fdi.h"
+
+#include "wine/unicode.h"
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(cabarc);
+
+/* from msvcrt */
+#define _O_RDONLY      0
+#define _O_WRONLY      1
+#define _O_RDWR        2
+#define _O_ACCMODE     (_O_RDONLY|_O_WRONLY|_O_RDWR)
+#define _O_APPEND      0x0008
+#define _O_RANDOM      0x0010
+#define _O_SEQUENTIAL  0x0020
+#define _O_TEMPORARY   0x0040
+#define _O_NOINHERIT   0x0080
+#define _O_CREAT       0x0100
+#define _O_TRUNC       0x0200
+#define _O_EXCL        0x0400
+#define _O_SHORT_LIVED 0x1000
+#define _O_TEXT        0x4000
+#define _O_BINARY      0x8000
+
+#define _SH_COMPAT     0x00
+#define _SH_DENYRW     0x10
+#define _SH_DENYWR     0x20
+#define _SH_DENYRD     0x30
+#define _SH_DENYNO     0x40
+
+#define _A_RDONLY      0x01
+#define _A_HIDDEN      0x02
+#define _A_SYSTEM      0x04
+#define _A_ARCH        0x20
+
+/* command-line options */
+static int opt_cabinet_id;
+static int opt_compression = tcompTYPE_MSZIP;
+static int opt_recurse;
+static int opt_preserve_paths;
+static int opt_reserve_space;
+static int opt_verbose;
+static WCHAR **opt_files;
+
+static void * CDECL cab_alloc( ULONG size )
+{
+    return HeapAlloc( GetProcessHeap(), 0, size );
+}
+
+static void CDECL cab_free( void *ptr )
+{
+    HeapFree( GetProcessHeap(), 0, ptr );
+}
+
+static WCHAR *strdupAtoW( UINT cp, const char *str )
+{
+    WCHAR *ret = NULL;
+    if (str)
+    {
+        DWORD len = MultiByteToWideChar( cp, 0, str, -1, NULL, 0 );
+        if ((ret = cab_alloc( len * sizeof(WCHAR) )))
+            MultiByteToWideChar( cp, 0, str, -1, ret, len );
+    }
+    return ret;
+}
+
+static char *strdupWtoA( UINT cp, const WCHAR *str )
+{
+    char *ret = NULL;
+    if (str)
+    {
+        DWORD len = WideCharToMultiByte( cp, 0, str, -1, NULL, 0, NULL, NULL );
+        if ((ret = cab_alloc( len )))
+            WideCharToMultiByte( cp, 0, str, -1, ret, len, NULL, NULL );
+    }
+    return ret;
+}
+
+static int CDECL fci_file_placed( CCAB *cab, char *file, LONG size, BOOL continuation, void *ptr )
+{
+    if (!continuation && opt_verbose) printf( "adding %s\n", file );
+    return 0;
+}
+
+static INT_PTR CDECL fci_open( char *file, int oflag, int pmode, int *err, void *ptr )
+{
+    DWORD creation = 0, sharing = 0;
+    int ioflag = 0;
+    HANDLE handle;
+
+    switch (oflag & _O_ACCMODE)
+    {
+    case _O_RDONLY: ioflag |= GENERIC_READ; break;
+    case _O_WRONLY: ioflag |= GENERIC_WRITE; break;
+    case _O_RDWR:   ioflag |= GENERIC_READ | GENERIC_WRITE; break;
+    }
+
+    if (oflag & _O_CREAT)
+    {
+        if (oflag & _O_EXCL) creation = CREATE_NEW;
+        else if (oflag & _O_TRUNC) creation = CREATE_ALWAYS;
+        else creation = OPEN_ALWAYS;
+    }
+    else
+    {
+        if (oflag & _O_TRUNC) creation = TRUNCATE_EXISTING;
+        else creation = OPEN_EXISTING;
+    }
+
+    switch (pmode & 0x70)
+    {
+    case _SH_DENYRW: sharing = 0; break;
+    case _SH_DENYWR: sharing = FILE_SHARE_READ; break;
+    case _SH_DENYRD: sharing = FILE_SHARE_WRITE; break;
+    default:         sharing = FILE_SHARE_READ | FILE_SHARE_WRITE; break;
+    }
+
+    handle = CreateFileA( file, ioflag, sharing, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL );
+    if (handle == INVALID_HANDLE_VALUE) *err = GetLastError();
+    return (INT_PTR)handle;
+}
+
+static UINT CDECL fci_read( INT_PTR hf, void *pv, UINT cb, int *err, void *ptr )
+{
+    DWORD num_read;
+
+    if (!ReadFile( (HANDLE)hf, pv, cb, &num_read, NULL ))
+    {
+        *err = GetLastError();
+        return -1;
+    }
+    return num_read;
+}
+
+static UINT CDECL fci_write( INT_PTR hf, void *pv, UINT cb, int *err, void *ptr )
+{
+    DWORD written;
+
+    if (!WriteFile( (HANDLE) hf, pv, cb, &written, NULL ))
+    {
+        *err = GetLastError();
+        return -1;
+    }
+    return written;
+}
+
+static int CDECL fci_close( INT_PTR hf, int *err, void *ptr )
+{
+    if (!CloseHandle( (HANDLE)hf ))
+    {
+        *err = GetLastError();
+        return -1;
+    }
+    return 0;
+}
+
+static LONG CDECL fci_lseek( INT_PTR hf, LONG dist, int seektype, int *err, void *ptr )
+{
+    DWORD ret;
+
+    ret = SetFilePointer( (HANDLE)hf, dist, NULL, seektype );
+    if (ret == INVALID_SET_FILE_POINTER && GetLastError())
+    {
+        *err = GetLastError();
+        return -1;
+    }
+    return ret;
+}
+
+static int CDECL fci_delete( char *file, int *err, void *ptr )
+{
+    if (!DeleteFileA( file ))
+    {
+        *err = GetLastError();
+        return -1;
+    }
+    return 0;
+}
+
+static BOOL CDECL fci_get_temp( char *name, int size, void *ptr )
+{
+    char path[MAX_PATH];
+
+    if (!GetTempPathA( MAX_PATH, path )) return FALSE;
+    if (!GetTempFileNameA( path, "cab", 0, name )) return FALSE;
+    DeleteFileA( name );
+    return TRUE;
+}
+
+static BOOL CDECL fci_get_next_cab( CCAB *cab, ULONG prev_size, void *ptr )
+{
+    WINE_ERR( "shouldn't happen\n" );
+    return FALSE;
+}
+
+static LONG CDECL fci_status( UINT type, ULONG cb1, ULONG cb2, void *ptr )
+{
+    return 0;
+}
+
+static INT_PTR CDECL fci_get_open_info( char *name, USHORT *date, USHORT *time,
+                                        USHORT *attribs, int *err, void *ptr )
+{
+    HANDLE handle;
+    BY_HANDLE_FILE_INFORMATION info;
+    WCHAR *p, *nameW = strdupAtoW( CP_UTF8, name );
+
+    handle = CreateFileW( nameW, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
+                          NULL, OPEN_EXISTING, 0, NULL );
+    if (handle == INVALID_HANDLE_VALUE)
+    {
+        *err = GetLastError();
+        WINE_ERR( "failed to open %s: error %u\n", wine_dbgstr_w(nameW), *err );
+        cab_free( nameW );
+        return -1;
+    }
+    if (!GetFileInformationByHandle( handle, &info ))
+    {
+        *err = GetLastError();
+        CloseHandle( handle );
+        cab_free( nameW );
+        return -1;
+    }
+    FileTimeToDosDateTime( &info.ftLastWriteTime, date, time );
+    *attribs = info.dwFileAttributes & (_A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_ARCH);
+    for (p = nameW; *p; p++) if (*p >= 0x80) break;
+    if (*p) *attribs |= _A_NAME_IS_UTF;
+    cab_free( nameW );
+    return (INT_PTR)handle;
+}
+
+
+static BOOL add_file( HFCI fci, WCHAR *name )
+{
+    BOOL ret;
+    char *filename, *path = strdupWtoA( CP_UTF8, name );
+
+    if (!opt_preserve_paths)
+    {
+        if ((filename = strrchr( path, '\\' ))) filename++;
+        else filename = path;
+    }
+    else
+    {
+        filename = path;
+        while (*filename == '\\') filename++;  /* remove leading backslashes */
+    }
+    ret = FCIAddFile( fci, path, filename, FALSE,
+                      fci_get_next_cab, fci_status, fci_get_open_info, opt_compression );
+    cab_free( path );
+    return ret;
+}
+
+static BOOL add_directory( HFCI fci, WCHAR *dir )
+{
+    static const WCHAR wildcardW[] = {'*',0};
+    WCHAR *p, *buffer;
+    HANDLE handle;
+    WIN32_FIND_DATAW data;
+    BOOL ret = TRUE;
+
+    if (!(buffer = cab_alloc( (strlenW(dir) + MAX_PATH + 2) * sizeof(WCHAR) ))) return FALSE;
+    strcpyW( buffer, dir );
+    p = buffer + strlenW( buffer );
+    if (p > buffer && p[-1] != '\\') *p++ = '\\';
+    strcpyW( p, wildcardW );
+
+    if ((handle = FindFirstFileW( buffer, &data )) != INVALID_HANDLE_VALUE)
+    {
+        do
+        {
+            if (data.cFileName[0] == '.' && !data.cFileName[1]) continue;
+            if (data.cFileName[0] == '.' && data.cFileName[1] == '.' && !data.cFileName[2]) continue;
+            if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) continue;
+
+            strcpyW( p, data.cFileName );
+            if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+                ret = add_directory( fci, buffer );
+            else
+                ret = add_file( fci, buffer );
+            if (!ret) break;
+        } while (FindNextFileW( handle, &data ));
+        FindClose( handle );
+    }
+    cab_free( buffer );
+    return TRUE;
+}
+
+static BOOL add_file_or_directory( HFCI fci, WCHAR *name )
+{
+    DWORD attr = GetFileAttributesW( name );
+
+    if (attr == INVALID_FILE_ATTRIBUTES)
+    {
+        WINE_MESSAGE( "cannot open %s\n", wine_dbgstr_w(name) );
+        return FALSE;
+    }
+    if (attr & FILE_ATTRIBUTE_DIRECTORY)
+    {
+        if (opt_recurse) return add_directory( fci, name );
+        WINE_MESSAGE( "cabarc: Cannot add %s, it's a directory (use -r for recursive add)\n",
+                      wine_dbgstr_w(name) );
+        return FALSE;
+    }
+    return add_file( fci, name );
+}
+
+static int new_cabinet( char *cab_dir, char *cab_file )
+{
+    WCHAR **file;
+    ERF erf;
+    BOOL ret = FALSE;
+    HFCI fci;
+    CCAB cab;
+
+    cab.cb                = CB_MAX_DISK;
+    cab.cbFolderThresh    = CB_MAX_DISK;
+    cab.cbReserveCFHeader = opt_reserve_space;
+    cab.cbReserveCFFolder = 0;
+    cab.cbReserveCFData   = 0;
+    cab.iCab              = 1;
+    cab.iDisk             = 0;
+    cab.setID             = opt_cabinet_id;
+    cab.szDisk[0]         = 0;
+
+    strcpy( cab.szCabPath, cab_dir );
+    strcat( cab.szCabPath, "\\" );
+    strcpy( cab.szCab, cab_file );
+
+    fci = FCICreate( &erf, fci_file_placed, cab_alloc, cab_free,fci_open, fci_read,
+                     fci_write, fci_close, fci_lseek, fci_delete, fci_get_temp, &cab, NULL );
+
+    for (file = opt_files; *file; file++)
+        if (!(ret = add_file_or_directory( fci, *file ))) break;
+
+    if (ret)
+    {
+        if (!(ret = FCIFlushCabinet( fci, FALSE, fci_get_next_cab, fci_status )))
+            WINE_MESSAGE( "cabarc: Failed to create cabinet %s\n", wine_dbgstr_a(cab_file) );
+    }
+    FCIDestroy( fci );
+    return !ret;
+}
+
+static void usage( void )
+{
+    WINE_MESSAGE(
+        "Usage: cabarc [options] command file.cab [files...] [dest_dir\\]\n"
+        "\nCommands:\n"
+        "   L   List the contents of the cabinet\n"
+        "   N   Create a new cabinet\n"
+        "   X   Extract files from the cabinet into dest_dir\n"
+        "\nOptions:\n"
+        "  -h       Display this help\n"
+        "  -i id    Set cabinet id\n"
+        "  -m type  Set compression type (mszip|none)\n"
+        "  -p       Preserve directory names\n"
+        "  -r       Recurse into directories\n"
+        "  -s size  Reserve space in the cabinet header\n"
+        "  -v       More verbose output\n" );
+}
+
+int wmain( int argc, WCHAR *argv[] )
+{
+    static const WCHAR noneW[] = {'n','o','n','e',0};
+    static const WCHAR mszipW[] = {'m','s','z','i','p',0};
+
+    WCHAR *p, *command;
+    char buffer[MAX_PATH];
+    char *cab_file, *file_part;
+    int i;
+
+    while (argv[1] && argv[1][0] == '-')
+    {
+        switch (argv[1][1])
+        {
+        case 'h':
+            usage();
+            return 0;
+        case 'i':
+            argv++; argc--;
+            opt_cabinet_id = atoiW( argv[1] );
+            break;
+        case 'm':
+            argv++; argc--;
+            if (!strcmpiW( argv[1], noneW )) opt_compression = tcompTYPE_NONE;
+            else if (!strcmpiW( argv[1], mszipW )) opt_compression = tcompTYPE_MSZIP;
+            else
+            {
+                WINE_MESSAGE( "cabarc: Unknown compression type '%s'\n", optarg );
+                return 1;
+            }
+            break;
+        case 'p':
+            opt_preserve_paths = 1;
+            break;
+        case 'r':
+            opt_recurse = 1;
+            break;
+        case 's':
+            argv++; argc--;
+            opt_reserve_space = atoiW( argv[1] );
+            break;
+        case 'v':
+            opt_verbose++;
+            break;
+        default:
+            usage();
+            return 1;
+        }
+        argv++; argc--;
+    }
+
+    command = argv[1];
+    if (argc < 3 || !command[0] || command[1])
+    {
+        usage();
+        return 1;
+    }
+    cab_file = strdupWtoA( CP_ACP, argv[2] );
+    argv += 2;
+    argc -= 2;
+
+    if (!GetFullPathNameA( cab_file, MAX_PATH, buffer, &file_part ) || !file_part)
+    {
+        WINE_ERR( "cannot get full name for %s\n", wine_dbgstr_a( cab_file ));
+        return 1;
+    }
+    file_part[-1] = 0;
+    cab_free( cab_file );
+
+    /* map slash to backslash in all file arguments */
+    for (i = 1; i < argc; i++)
+        for (p = argv[i]; *p; p++)
+            if (*p == '/') *p = '\\';
+    opt_files = argv + 1;
+
+    switch (*command)
+    {
+    case 'l':
+    case 'L':
+        WINE_FIXME( "list not implemented\n" );
+        return 1;
+    case 'n':
+    case 'N':
+        return new_cabinet( buffer, file_part );
+    case 'x':
+    case 'X':
+        WINE_FIXME( "extraction not implemented\n" );
+        return 1;
+    default:
+        usage();
+        return 1;
+    }
+}