2011-02-02 22:44:47 +01:00
|
|
|
/*
|
|
|
|
* 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 <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2019-05-16 22:55:01 +02:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <io.h>
|
|
|
|
#include <share.h>
|
2011-02-02 22:44:47 +01:00
|
|
|
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
#include "windows.h"
|
|
|
|
#include "fci.h"
|
|
|
|
#include "fdi.h"
|
|
|
|
|
|
|
|
#include "wine/debug.h"
|
|
|
|
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(cabarc);
|
|
|
|
|
|
|
|
/* command-line options */
|
2011-02-14 17:24:53 +01:00
|
|
|
static int opt_cabinet_size = CB_MAX_DISK;
|
2011-02-02 22:44:47 +01:00
|
|
|
static int opt_cabinet_id;
|
|
|
|
static int opt_compression = tcompTYPE_MSZIP;
|
2014-02-19 22:13:29 +01:00
|
|
|
static BOOL opt_recurse;
|
|
|
|
static BOOL opt_preserve_paths;
|
2011-02-02 22:44:47 +01:00
|
|
|
static int opt_reserve_space;
|
|
|
|
static int opt_verbose;
|
2011-02-14 17:24:53 +01:00
|
|
|
static char *opt_cab_file;
|
2011-02-02 22:48:40 +01:00
|
|
|
static WCHAR *opt_dest_dir;
|
2011-02-02 22:44:47 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-02-14 17:24:53 +01:00
|
|
|
/* format a cabinet name by replacing the '*' wildcard by the cabinet id */
|
|
|
|
static BOOL format_cab_name( char *dest, int id, const char *name )
|
|
|
|
{
|
|
|
|
const char *num = strchr( name, '*' );
|
|
|
|
int len;
|
|
|
|
|
|
|
|
if (!num)
|
|
|
|
{
|
|
|
|
if (id == 1)
|
|
|
|
{
|
|
|
|
strcpy( dest, name );
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
WINE_MESSAGE( "cabarc: Cabinet name must contain a '*' character\n" );
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
len = num - name;
|
|
|
|
memcpy( dest, name, len );
|
|
|
|
len += sprintf( dest + len, "%u", id );
|
|
|
|
lstrcpynA( dest + len, num + 1, CB_MAX_CABINET_NAME - len );
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2011-02-02 22:44:47 +01:00
|
|
|
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 )
|
|
|
|
{
|
2011-02-19 13:06:20 +01:00
|
|
|
return format_cab_name( cab->szCab, cab->iCab + 1, opt_cab_file );
|
2011-02-02 22:44:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-02-02 22:48:40 +01:00
|
|
|
static INT_PTR CDECL fdi_open( char *file, int oflag, int pmode )
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
return fci_open( file, oflag, pmode, &err, NULL );
|
|
|
|
}
|
|
|
|
|
|
|
|
static UINT CDECL fdi_read( INT_PTR hf, void *pv, UINT cb )
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
return fci_read( hf, pv, cb, &err, NULL );
|
|
|
|
}
|
|
|
|
|
|
|
|
static UINT CDECL fdi_write( INT_PTR hf, void *pv, UINT cb )
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
return fci_write( hf, pv, cb, &err, NULL );
|
|
|
|
}
|
|
|
|
|
|
|
|
static int CDECL fdi_close( INT_PTR hf )
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
return fci_close( hf, &err, NULL );
|
|
|
|
}
|
|
|
|
|
|
|
|
static LONG CDECL fdi_lseek( INT_PTR hf, LONG dist, int whence )
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
return fci_lseek( hf, dist, whence, &err, NULL );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* create directories leading to a given file */
|
|
|
|
static void create_directories( const WCHAR *name )
|
|
|
|
{
|
|
|
|
WCHAR *path, *p;
|
|
|
|
|
|
|
|
/* create the directory/directories */
|
2019-04-29 10:45:59 +02:00
|
|
|
path = cab_alloc( (lstrlenW(name) + 1) * sizeof(WCHAR) );
|
|
|
|
lstrcpyW(path, name);
|
2011-02-02 22:48:40 +01:00
|
|
|
|
2019-04-29 10:45:59 +02:00
|
|
|
p = wcschr(path, '\\');
|
2011-02-02 22:48:40 +01:00
|
|
|
while (p != NULL)
|
|
|
|
{
|
|
|
|
*p = 0;
|
|
|
|
if (!CreateDirectoryW( path, NULL ))
|
2022-02-04 09:55:55 +01:00
|
|
|
WINE_TRACE("Couldn't create directory %s - error: %ld\n", wine_dbgstr_w(path), GetLastError());
|
2011-02-02 22:48:40 +01:00
|
|
|
*p = '\\';
|
2019-04-29 10:45:59 +02:00
|
|
|
p = wcschr(p+1, '\\');
|
2011-02-02 22:48:40 +01:00
|
|
|
}
|
|
|
|
cab_free( path );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check if file name matches against one of the files specification */
|
|
|
|
static BOOL match_files( const WCHAR *name )
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!*opt_files) return TRUE;
|
|
|
|
for (i = 0; opt_files[i]; i++)
|
|
|
|
{
|
2019-04-29 10:45:59 +02:00
|
|
|
unsigned int len = lstrlenW( opt_files[i] );
|
2011-02-02 22:48:40 +01:00
|
|
|
/* FIXME: do smarter matching, and wildcards */
|
|
|
|
if (!len) continue;
|
2019-04-29 10:45:59 +02:00
|
|
|
if (wcsnicmp( name, opt_files[i], len )) continue;
|
2011-02-02 22:48:40 +01:00
|
|
|
if (opt_files[i][len - 1] == '\\' || !name[len] || name[len] == '\\') return TRUE;
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static INT_PTR CDECL list_notify( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin )
|
|
|
|
{
|
|
|
|
WCHAR *nameW;
|
|
|
|
|
|
|
|
switch (fdint)
|
|
|
|
{
|
|
|
|
case fdintCABINET_INFO:
|
|
|
|
return 0;
|
|
|
|
case fdintCOPY_FILE:
|
|
|
|
nameW = strdupAtoW( (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP, pfdin->psz1 );
|
|
|
|
if (match_files( nameW ))
|
|
|
|
{
|
|
|
|
if (opt_verbose)
|
|
|
|
{
|
2019-11-28 08:50:49 +01:00
|
|
|
WCHAR attrs[] = L"rxash";
|
2011-02-02 22:48:40 +01:00
|
|
|
if (!(pfdin->attribs & _A_RDONLY)) attrs[0] = '-';
|
|
|
|
if (!(pfdin->attribs & _A_EXEC)) attrs[1] = '-';
|
|
|
|
if (!(pfdin->attribs & _A_ARCH)) attrs[2] = '-';
|
|
|
|
if (!(pfdin->attribs & _A_SYSTEM)) attrs[3] = '-';
|
|
|
|
if (!(pfdin->attribs & _A_HIDDEN)) attrs[4] = '-';
|
2019-11-28 08:50:49 +01:00
|
|
|
wprintf( L" %s %9u %04u/%02u/%02u %02u:%02u:%02u ", attrs, pfdin->cb,
|
2011-02-02 22:48:40 +01:00
|
|
|
(pfdin->date >> 9) + 1980, (pfdin->date >> 5) & 0x0f, pfdin->date & 0x1f,
|
|
|
|
pfdin->time >> 11, (pfdin->time >> 5) & 0x3f, (pfdin->time & 0x1f) * 2 );
|
|
|
|
}
|
2019-11-28 08:50:49 +01:00
|
|
|
wprintf( L"%s\n", nameW );
|
2011-02-02 22:48:40 +01:00
|
|
|
}
|
|
|
|
cab_free( nameW );
|
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
WINE_FIXME( "Unexpected notification type %d.\n", fdint );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-14 17:24:53 +01:00
|
|
|
static int list_cabinet( char *cab_dir )
|
2011-02-02 22:48:40 +01:00
|
|
|
{
|
|
|
|
ERF erf;
|
|
|
|
int ret = 0;
|
|
|
|
HFDI fdi = FDICreate( cab_alloc, cab_free, fdi_open, fdi_read,
|
|
|
|
fdi_write, fdi_close, fdi_lseek, cpuUNKNOWN, &erf );
|
|
|
|
|
2011-02-14 17:24:53 +01:00
|
|
|
if (!FDICopy( fdi, opt_cab_file, cab_dir, 0, list_notify, NULL, NULL )) ret = GetLastError();
|
2011-02-02 22:48:40 +01:00
|
|
|
FDIDestroy( fdi );
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static INT_PTR CDECL extract_notify( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin )
|
|
|
|
{
|
|
|
|
WCHAR *file, *nameW, *path = NULL;
|
|
|
|
INT_PTR ret;
|
|
|
|
|
|
|
|
switch (fdint)
|
|
|
|
{
|
|
|
|
case fdintCABINET_INFO:
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case fdintCOPY_FILE:
|
|
|
|
nameW = strdupAtoW( (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP, pfdin->psz1 );
|
|
|
|
if (opt_preserve_paths)
|
|
|
|
{
|
|
|
|
file = nameW;
|
|
|
|
while (*file == '\\') file++; /* remove leading backslashes */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-04-29 10:45:59 +02:00
|
|
|
if ((file = wcsrchr( nameW, '\\' ))) file++;
|
2011-02-02 22:48:40 +01:00
|
|
|
else file = nameW;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opt_dest_dir)
|
|
|
|
{
|
2019-04-29 10:45:59 +02:00
|
|
|
path = cab_alloc( (lstrlenW(opt_dest_dir) + lstrlenW(file) + 1) * sizeof(WCHAR) );
|
|
|
|
lstrcpyW( path, opt_dest_dir );
|
|
|
|
lstrcatW( path, file );
|
2011-02-02 22:48:40 +01:00
|
|
|
}
|
|
|
|
else path = file;
|
|
|
|
|
|
|
|
if (match_files( file ))
|
|
|
|
{
|
2019-11-28 08:50:49 +01:00
|
|
|
if (opt_verbose) wprintf( L"extracting %s\n", path );
|
2011-02-02 22:48:40 +01:00
|
|
|
create_directories( path );
|
|
|
|
/* FIXME: check for existing file and overwrite mode */
|
|
|
|
ret = (INT_PTR)CreateFileW( path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
|
|
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
|
|
|
|
}
|
|
|
|
else ret = 0;
|
|
|
|
|
|
|
|
cab_free( nameW );
|
|
|
|
if (path != file) cab_free( path );
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
case fdintCLOSE_FILE_INFO:
|
|
|
|
CloseHandle( (HANDLE)pfdin->hf );
|
|
|
|
return 0;
|
|
|
|
|
2012-12-03 09:18:40 +01:00
|
|
|
case fdintNEXT_CABINET:
|
|
|
|
WINE_TRACE("Next cab: status %u, path '%s', file '%s'\n", pfdin->fdie, pfdin->psz3, pfdin->psz1);
|
|
|
|
return pfdin->fdie == FDIERROR_NONE ? 0 : -1;
|
|
|
|
|
2012-12-03 09:18:47 +01:00
|
|
|
case fdintENUMERATE:
|
|
|
|
return 0;
|
|
|
|
|
2011-02-02 22:48:40 +01:00
|
|
|
default:
|
|
|
|
WINE_FIXME( "Unexpected notification type %d.\n", fdint );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-14 17:24:53 +01:00
|
|
|
static int extract_cabinet( char *cab_dir )
|
2011-02-02 22:48:40 +01:00
|
|
|
{
|
|
|
|
ERF erf;
|
|
|
|
int ret = 0;
|
|
|
|
HFDI fdi = FDICreate( cab_alloc, cab_free, fdi_open, fdi_read,
|
|
|
|
fdi_write, fdi_close, fdi_lseek, cpuUNKNOWN, &erf );
|
|
|
|
|
2012-12-03 09:18:24 +01:00
|
|
|
if (!FDICopy( fdi, opt_cab_file, cab_dir, 0, extract_notify, NULL, NULL ))
|
|
|
|
{
|
|
|
|
ret = GetLastError();
|
|
|
|
WINE_WARN("FDICopy() failed: code %u\n", ret);
|
|
|
|
}
|
2011-02-02 22:48:40 +01:00
|
|
|
FDIDestroy( fdi );
|
|
|
|
return ret;
|
|
|
|
}
|
2011-02-02 22:44:47 +01:00
|
|
|
|
|
|
|
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 )
|
|
|
|
{
|
|
|
|
WCHAR *p, *buffer;
|
|
|
|
HANDLE handle;
|
|
|
|
WIN32_FIND_DATAW data;
|
|
|
|
BOOL ret = TRUE;
|
|
|
|
|
2019-04-29 10:45:59 +02:00
|
|
|
if (!(buffer = cab_alloc( (lstrlenW(dir) + MAX_PATH + 2) * sizeof(WCHAR) ))) return FALSE;
|
|
|
|
lstrcpyW( buffer, dir );
|
|
|
|
p = buffer + lstrlenW( buffer );
|
2011-02-02 22:44:47 +01:00
|
|
|
if (p > buffer && p[-1] != '\\') *p++ = '\\';
|
2020-10-05 23:22:47 +02:00
|
|
|
lstrcpyW( p, L"*" );
|
2011-02-02 22:44:47 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2019-04-29 10:45:59 +02:00
|
|
|
lstrcpyW( p, data.cFileName );
|
2011-02-02 22:44:47 +01:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
2011-02-14 17:24:53 +01:00
|
|
|
static int new_cabinet( char *cab_dir )
|
2011-02-02 22:44:47 +01:00
|
|
|
{
|
|
|
|
WCHAR **file;
|
|
|
|
ERF erf;
|
|
|
|
BOOL ret = FALSE;
|
|
|
|
HFCI fci;
|
|
|
|
CCAB cab;
|
|
|
|
|
2011-02-14 17:24:53 +01:00
|
|
|
cab.cb = opt_cabinet_size;
|
2011-02-02 22:44:47 +01:00
|
|
|
cab.cbFolderThresh = CB_MAX_DISK;
|
|
|
|
cab.cbReserveCFHeader = opt_reserve_space;
|
|
|
|
cab.cbReserveCFFolder = 0;
|
|
|
|
cab.cbReserveCFData = 0;
|
2011-02-19 13:06:20 +01:00
|
|
|
cab.iCab = 0;
|
2011-02-02 22:44:47 +01:00
|
|
|
cab.iDisk = 0;
|
|
|
|
cab.setID = opt_cabinet_id;
|
|
|
|
cab.szDisk[0] = 0;
|
|
|
|
|
|
|
|
strcpy( cab.szCabPath, cab_dir );
|
|
|
|
strcat( cab.szCabPath, "\\" );
|
2011-02-14 17:24:53 +01:00
|
|
|
format_cab_name( cab.szCab, 1, opt_cab_file );
|
2011-02-02 22:44:47 +01:00
|
|
|
|
|
|
|
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++)
|
2011-02-14 17:27:29 +01:00
|
|
|
{
|
2020-10-05 23:22:47 +02:00
|
|
|
if (!lstrcmpW( *file, L"+" ))
|
2011-02-14 17:27:29 +01:00
|
|
|
FCIFlushFolder( fci, fci_get_next_cab, fci_status );
|
|
|
|
else
|
|
|
|
if (!(ret = add_file_or_directory( fci, *file ))) break;
|
|
|
|
}
|
2011-02-02 22:44:47 +01:00
|
|
|
|
|
|
|
if (ret)
|
|
|
|
{
|
|
|
|
if (!(ret = FCIFlushCabinet( fci, FALSE, fci_get_next_cab, fci_status )))
|
2011-02-14 17:24:53 +01:00
|
|
|
WINE_MESSAGE( "cabarc: Failed to create cabinet %s\n", wine_dbgstr_a(opt_cab_file) );
|
2011-02-02 22:44:47 +01:00
|
|
|
}
|
|
|
|
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"
|
2011-02-14 17:24:53 +01:00
|
|
|
" -d size Set maximum disk size\n"
|
2011-02-02 22:44:47 +01:00
|
|
|
" -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" );
|
|
|
|
}
|
|
|
|
|
2019-10-17 17:05:47 +02:00
|
|
|
int __cdecl wmain( int argc, WCHAR *argv[] )
|
2011-02-02 22:44:47 +01:00
|
|
|
{
|
|
|
|
WCHAR *p, *command;
|
|
|
|
char buffer[MAX_PATH];
|
2012-12-03 09:18:32 +01:00
|
|
|
char filename[MAX_PATH];
|
2011-02-02 22:44:47 +01:00
|
|
|
char *cab_file, *file_part;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
while (argv[1] && argv[1][0] == '-')
|
|
|
|
{
|
|
|
|
switch (argv[1][1])
|
|
|
|
{
|
2011-02-14 17:24:53 +01:00
|
|
|
case 'd':
|
|
|
|
argv++; argc--;
|
2019-04-29 10:45:59 +02:00
|
|
|
opt_cabinet_size = wcstol( argv[1], NULL, 10 );
|
2011-02-14 17:24:53 +01:00
|
|
|
if (opt_cabinet_size < 50000)
|
|
|
|
{
|
|
|
|
WINE_MESSAGE( "cabarc: Cabinet size must be at least 50000\n" );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
break;
|
2011-02-02 22:44:47 +01:00
|
|
|
case 'h':
|
|
|
|
usage();
|
|
|
|
return 0;
|
|
|
|
case 'i':
|
|
|
|
argv++; argc--;
|
2019-04-29 10:45:59 +02:00
|
|
|
opt_cabinet_id = wcstol( argv[1], NULL, 10 );
|
2011-02-02 22:44:47 +01:00
|
|
|
break;
|
|
|
|
case 'm':
|
|
|
|
argv++; argc--;
|
2020-10-05 23:22:47 +02:00
|
|
|
if (!wcscmp( argv[1], L"none" )) opt_compression = tcompTYPE_NONE;
|
|
|
|
else if (!wcscmp( argv[1], L"mszip" )) opt_compression = tcompTYPE_MSZIP;
|
2011-02-02 22:44:47 +01:00
|
|
|
else
|
|
|
|
{
|
2019-11-28 08:50:49 +01:00
|
|
|
WINE_MESSAGE( "cabarc: Unknown compression type %s\n", debugstr_w(argv[1]));
|
2011-02-02 22:44:47 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'p':
|
2014-02-19 22:13:29 +01:00
|
|
|
opt_preserve_paths = TRUE;
|
2011-02-02 22:44:47 +01:00
|
|
|
break;
|
|
|
|
case 'r':
|
2014-02-19 22:13:29 +01:00
|
|
|
opt_recurse = TRUE;
|
2011-02-02 22:44:47 +01:00
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
argv++; argc--;
|
2019-04-29 10:45:59 +02:00
|
|
|
opt_reserve_space = wcstol( argv[1], NULL, 10 );
|
2011-02-02 22:44:47 +01:00
|
|
|
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;
|
|
|
|
}
|
2012-12-03 09:18:32 +01:00
|
|
|
strcpy(filename, file_part);
|
|
|
|
file_part[0] = 0;
|
2011-02-02 22:44:47 +01:00
|
|
|
|
|
|
|
/* 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;
|
2012-12-03 09:18:32 +01:00
|
|
|
opt_cab_file = filename;
|
2011-02-02 22:44:47 +01:00
|
|
|
|
|
|
|
switch (*command)
|
|
|
|
{
|
|
|
|
case 'l':
|
|
|
|
case 'L':
|
2011-02-14 17:24:53 +01:00
|
|
|
return list_cabinet( buffer );
|
2011-02-02 22:44:47 +01:00
|
|
|
case 'n':
|
|
|
|
case 'N':
|
2011-02-14 17:24:53 +01:00
|
|
|
return new_cabinet( buffer );
|
2011-02-02 22:44:47 +01:00
|
|
|
case 'x':
|
|
|
|
case 'X':
|
2011-02-02 22:48:40 +01:00
|
|
|
if (argc > 1) /* check for destination dir as last argument */
|
|
|
|
{
|
|
|
|
WCHAR *last = argv[argc - 1];
|
2019-04-29 10:45:59 +02:00
|
|
|
if (last[0] && last[lstrlenW(last) - 1] == '\\')
|
2011-02-02 22:48:40 +01:00
|
|
|
{
|
|
|
|
opt_dest_dir = last;
|
|
|
|
argv[--argc] = NULL;
|
|
|
|
}
|
|
|
|
}
|
2012-12-03 09:18:24 +01:00
|
|
|
WINE_TRACE("Extracting file(s) from cabinet %s\n", wine_dbgstr_a(cab_file));
|
2011-02-14 17:24:53 +01:00
|
|
|
return extract_cabinet( buffer );
|
2011-02-02 22:44:47 +01:00
|
|
|
default:
|
|
|
|
usage();
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|