cabarc: Add support for starting a new folder with a '+' file name.
[wine] / programs / cabarc / cabarc.c
1 /*
2  * Tool to manipulate cabinet files
3  *
4  * Copyright 2011 Alexandre Julliard
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include "config.h"
22 #include "wine/port.h"
23
24 #include <stdio.h>
25 #include <stdlib.h>
26
27 #define WIN32_LEAN_AND_MEAN
28 #include "windows.h"
29 #include "fci.h"
30 #include "fdi.h"
31
32 #include "wine/unicode.h"
33 #include "wine/debug.h"
34
35 WINE_DEFAULT_DEBUG_CHANNEL(cabarc);
36
37 /* from msvcrt */
38 #define _O_RDONLY      0
39 #define _O_WRONLY      1
40 #define _O_RDWR        2
41 #define _O_ACCMODE     (_O_RDONLY|_O_WRONLY|_O_RDWR)
42 #define _O_APPEND      0x0008
43 #define _O_RANDOM      0x0010
44 #define _O_SEQUENTIAL  0x0020
45 #define _O_TEMPORARY   0x0040
46 #define _O_NOINHERIT   0x0080
47 #define _O_CREAT       0x0100
48 #define _O_TRUNC       0x0200
49 #define _O_EXCL        0x0400
50 #define _O_SHORT_LIVED 0x1000
51 #define _O_TEXT        0x4000
52 #define _O_BINARY      0x8000
53
54 #define _SH_COMPAT     0x00
55 #define _SH_DENYRW     0x10
56 #define _SH_DENYWR     0x20
57 #define _SH_DENYRD     0x30
58 #define _SH_DENYNO     0x40
59
60 #define _A_RDONLY      0x01
61 #define _A_HIDDEN      0x02
62 #define _A_SYSTEM      0x04
63 #define _A_ARCH        0x20
64
65 /* command-line options */
66 static int opt_cabinet_size = CB_MAX_DISK;
67 static int opt_cabinet_id;
68 static int opt_compression = tcompTYPE_MSZIP;
69 static int opt_recurse;
70 static int opt_preserve_paths;
71 static int opt_reserve_space;
72 static int opt_verbose;
73 static char *opt_cab_file;
74 static WCHAR *opt_dest_dir;
75 static WCHAR **opt_files;
76
77 static void * CDECL cab_alloc( ULONG size )
78 {
79     return HeapAlloc( GetProcessHeap(), 0, size );
80 }
81
82 static void CDECL cab_free( void *ptr )
83 {
84     HeapFree( GetProcessHeap(), 0, ptr );
85 }
86
87 static WCHAR *strdupAtoW( UINT cp, const char *str )
88 {
89     WCHAR *ret = NULL;
90     if (str)
91     {
92         DWORD len = MultiByteToWideChar( cp, 0, str, -1, NULL, 0 );
93         if ((ret = cab_alloc( len * sizeof(WCHAR) )))
94             MultiByteToWideChar( cp, 0, str, -1, ret, len );
95     }
96     return ret;
97 }
98
99 static char *strdupWtoA( UINT cp, const WCHAR *str )
100 {
101     char *ret = NULL;
102     if (str)
103     {
104         DWORD len = WideCharToMultiByte( cp, 0, str, -1, NULL, 0, NULL, NULL );
105         if ((ret = cab_alloc( len )))
106             WideCharToMultiByte( cp, 0, str, -1, ret, len, NULL, NULL );
107     }
108     return ret;
109 }
110
111 /* format a cabinet name by replacing the '*' wildcard by the cabinet id */
112 static BOOL format_cab_name( char *dest, int id, const char *name )
113 {
114     const char *num = strchr( name, '*' );
115     int len;
116
117     if (!num)
118     {
119         if (id == 1)
120         {
121             strcpy( dest, name );
122             return TRUE;
123         }
124         WINE_MESSAGE( "cabarc: Cabinet name must contain a '*' character\n" );
125         return FALSE;
126     }
127     len = num - name;
128     memcpy( dest, name, len );
129     len += sprintf( dest + len, "%u", id );
130     lstrcpynA( dest + len, num + 1, CB_MAX_CABINET_NAME - len );
131     return TRUE;
132 }
133
134 static int CDECL fci_file_placed( CCAB *cab, char *file, LONG size, BOOL continuation, void *ptr )
135 {
136     if (!continuation && opt_verbose) printf( "adding %s\n", file );
137     return 0;
138 }
139
140 static INT_PTR CDECL fci_open( char *file, int oflag, int pmode, int *err, void *ptr )
141 {
142     DWORD creation = 0, sharing = 0;
143     int ioflag = 0;
144     HANDLE handle;
145
146     switch (oflag & _O_ACCMODE)
147     {
148     case _O_RDONLY: ioflag |= GENERIC_READ; break;
149     case _O_WRONLY: ioflag |= GENERIC_WRITE; break;
150     case _O_RDWR:   ioflag |= GENERIC_READ | GENERIC_WRITE; break;
151     }
152
153     if (oflag & _O_CREAT)
154     {
155         if (oflag & _O_EXCL) creation = CREATE_NEW;
156         else if (oflag & _O_TRUNC) creation = CREATE_ALWAYS;
157         else creation = OPEN_ALWAYS;
158     }
159     else
160     {
161         if (oflag & _O_TRUNC) creation = TRUNCATE_EXISTING;
162         else creation = OPEN_EXISTING;
163     }
164
165     switch (pmode & 0x70)
166     {
167     case _SH_DENYRW: sharing = 0; break;
168     case _SH_DENYWR: sharing = FILE_SHARE_READ; break;
169     case _SH_DENYRD: sharing = FILE_SHARE_WRITE; break;
170     default:         sharing = FILE_SHARE_READ | FILE_SHARE_WRITE; break;
171     }
172
173     handle = CreateFileA( file, ioflag, sharing, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL );
174     if (handle == INVALID_HANDLE_VALUE) *err = GetLastError();
175     return (INT_PTR)handle;
176 }
177
178 static UINT CDECL fci_read( INT_PTR hf, void *pv, UINT cb, int *err, void *ptr )
179 {
180     DWORD num_read;
181
182     if (!ReadFile( (HANDLE)hf, pv, cb, &num_read, NULL ))
183     {
184         *err = GetLastError();
185         return -1;
186     }
187     return num_read;
188 }
189
190 static UINT CDECL fci_write( INT_PTR hf, void *pv, UINT cb, int *err, void *ptr )
191 {
192     DWORD written;
193
194     if (!WriteFile( (HANDLE) hf, pv, cb, &written, NULL ))
195     {
196         *err = GetLastError();
197         return -1;
198     }
199     return written;
200 }
201
202 static int CDECL fci_close( INT_PTR hf, int *err, void *ptr )
203 {
204     if (!CloseHandle( (HANDLE)hf ))
205     {
206         *err = GetLastError();
207         return -1;
208     }
209     return 0;
210 }
211
212 static LONG CDECL fci_lseek( INT_PTR hf, LONG dist, int seektype, int *err, void *ptr )
213 {
214     DWORD ret;
215
216     ret = SetFilePointer( (HANDLE)hf, dist, NULL, seektype );
217     if (ret == INVALID_SET_FILE_POINTER && GetLastError())
218     {
219         *err = GetLastError();
220         return -1;
221     }
222     return ret;
223 }
224
225 static int CDECL fci_delete( char *file, int *err, void *ptr )
226 {
227     if (!DeleteFileA( file ))
228     {
229         *err = GetLastError();
230         return -1;
231     }
232     return 0;
233 }
234
235 static BOOL CDECL fci_get_temp( char *name, int size, void *ptr )
236 {
237     char path[MAX_PATH];
238
239     if (!GetTempPathA( MAX_PATH, path )) return FALSE;
240     if (!GetTempFileNameA( path, "cab", 0, name )) return FALSE;
241     DeleteFileA( name );
242     return TRUE;
243 }
244
245 static BOOL CDECL fci_get_next_cab( CCAB *cab, ULONG prev_size, void *ptr )
246 {
247     return format_cab_name( cab->szCab, cab->iCab, opt_cab_file );
248 }
249
250 static LONG CDECL fci_status( UINT type, ULONG cb1, ULONG cb2, void *ptr )
251 {
252     return 0;
253 }
254
255 static INT_PTR CDECL fci_get_open_info( char *name, USHORT *date, USHORT *time,
256                                         USHORT *attribs, int *err, void *ptr )
257 {
258     HANDLE handle;
259     BY_HANDLE_FILE_INFORMATION info;
260     WCHAR *p, *nameW = strdupAtoW( CP_UTF8, name );
261
262     handle = CreateFileW( nameW, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
263                           NULL, OPEN_EXISTING, 0, NULL );
264     if (handle == INVALID_HANDLE_VALUE)
265     {
266         *err = GetLastError();
267         WINE_ERR( "failed to open %s: error %u\n", wine_dbgstr_w(nameW), *err );
268         cab_free( nameW );
269         return -1;
270     }
271     if (!GetFileInformationByHandle( handle, &info ))
272     {
273         *err = GetLastError();
274         CloseHandle( handle );
275         cab_free( nameW );
276         return -1;
277     }
278     FileTimeToDosDateTime( &info.ftLastWriteTime, date, time );
279     *attribs = info.dwFileAttributes & (_A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_ARCH);
280     for (p = nameW; *p; p++) if (*p >= 0x80) break;
281     if (*p) *attribs |= _A_NAME_IS_UTF;
282     cab_free( nameW );
283     return (INT_PTR)handle;
284 }
285
286 static INT_PTR CDECL fdi_open( char *file, int oflag, int pmode )
287 {
288     int err;
289     return fci_open( file, oflag, pmode, &err, NULL );
290 }
291
292 static UINT CDECL fdi_read( INT_PTR hf, void *pv, UINT cb )
293 {
294     int err;
295     return fci_read( hf, pv, cb, &err, NULL );
296 }
297
298 static UINT CDECL fdi_write( INT_PTR hf, void *pv, UINT cb )
299 {
300     int err;
301     return fci_write( hf, pv, cb, &err, NULL );
302 }
303
304 static int CDECL fdi_close( INT_PTR hf )
305 {
306     int err;
307     return fci_close( hf, &err, NULL );
308 }
309
310 static LONG CDECL fdi_lseek( INT_PTR hf, LONG dist, int whence )
311 {
312     int err;
313     return fci_lseek( hf, dist, whence, &err, NULL );
314 }
315
316
317 /* create directories leading to a given file */
318 static void create_directories( const WCHAR *name )
319 {
320     WCHAR *path, *p;
321
322     /* create the directory/directories */
323     path = cab_alloc( (strlenW(name) + 1) * sizeof(WCHAR) );
324     strcpyW(path, name);
325
326     p = strchrW(path, '\\');
327     while (p != NULL)
328     {
329         *p = 0;
330         if (!CreateDirectoryW( path, NULL ))
331             WINE_TRACE("Couldn't create directory %s - error: %d\n", wine_dbgstr_w(path), GetLastError());
332         *p = '\\';
333         p = strchrW(p+1, '\\');
334     }
335     cab_free( path );
336 }
337
338 /* check if file name matches against one of the files specification */
339 static BOOL match_files( const WCHAR *name )
340 {
341     int i;
342
343     if (!*opt_files) return TRUE;
344     for (i = 0; opt_files[i]; i++)
345     {
346         unsigned int len = strlenW( opt_files[i] );
347         /* FIXME: do smarter matching, and wildcards */
348         if (!len) continue;
349         if (strncmpiW( name, opt_files[i], len )) continue;
350         if (opt_files[i][len - 1] == '\\' || !name[len] || name[len] == '\\') return TRUE;
351     }
352     return FALSE;
353 }
354
355 static INT_PTR CDECL list_notify( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin )
356 {
357     WCHAR *nameW;
358
359     switch (fdint)
360     {
361     case fdintCABINET_INFO:
362         return 0;
363     case fdintCOPY_FILE:
364         nameW = strdupAtoW( (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP, pfdin->psz1 );
365         if (match_files( nameW ))
366         {
367             char *nameU = strdupWtoA( CP_UNIXCP, nameW );
368             if (opt_verbose)
369             {
370                 char attrs[] = "rxash";
371                 if (!(pfdin->attribs & _A_RDONLY)) attrs[0] = '-';
372                 if (!(pfdin->attribs & _A_EXEC))   attrs[1] = '-';
373                 if (!(pfdin->attribs & _A_ARCH))   attrs[2] = '-';
374                 if (!(pfdin->attribs & _A_SYSTEM)) attrs[3] = '-';
375                 if (!(pfdin->attribs & _A_HIDDEN)) attrs[4] = '-';
376                 printf( " %s %9u  %04u/%02u/%02u %02u:%02u:%02u  ", attrs, pfdin->cb,
377                         (pfdin->date >> 9) + 1980, (pfdin->date >> 5) & 0x0f, pfdin->date & 0x1f,
378                         pfdin->time >> 11, (pfdin->time >> 5) & 0x3f, (pfdin->time & 0x1f) * 2 );
379             }
380             printf( "%s\n", nameU );
381             cab_free( nameU );
382         }
383         cab_free( nameW );
384         return 0;
385     default:
386         WINE_FIXME( "Unexpected notification type %d.\n", fdint );
387         return 0;
388     }
389 }
390
391 static int list_cabinet( char *cab_dir )
392 {
393     ERF erf;
394     int ret = 0;
395     HFDI fdi = FDICreate( cab_alloc, cab_free, fdi_open, fdi_read,
396                           fdi_write, fdi_close, fdi_lseek, cpuUNKNOWN, &erf );
397
398     if (!FDICopy( fdi, opt_cab_file, cab_dir, 0, list_notify, NULL, NULL )) ret = GetLastError();
399     FDIDestroy( fdi );
400     return ret;
401 }
402
403 static INT_PTR CDECL extract_notify( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin )
404 {
405     WCHAR *file, *nameW, *path = NULL;
406     INT_PTR ret;
407
408     switch (fdint)
409     {
410     case fdintCABINET_INFO:
411         return 0;
412
413     case fdintCOPY_FILE:
414         nameW = strdupAtoW( (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP, pfdin->psz1 );
415         if (opt_preserve_paths)
416         {
417             file = nameW;
418             while (*file == '\\') file++;  /* remove leading backslashes */
419         }
420         else
421         {
422             if ((file = strrchrW( nameW, '\\' ))) file++;
423             else file = nameW;
424         }
425
426         if (opt_dest_dir)
427         {
428             path = cab_alloc( (strlenW(opt_dest_dir) + strlenW(file) + 1) * sizeof(WCHAR) );
429             strcpyW( path, opt_dest_dir );
430             strcatW( path, file );
431         }
432         else path = file;
433
434         if (match_files( file ))
435         {
436             if (opt_verbose)
437             {
438                 char *nameU = strdupWtoA( CP_UNIXCP, path );
439                 printf( "extracting %s\n", nameU );
440                 cab_free( nameU );
441             }
442             create_directories( path );
443             /* FIXME: check for existing file and overwrite mode */
444             ret = (INT_PTR)CreateFileW( path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
445                                         NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
446         }
447         else ret = 0;
448
449         cab_free( nameW );
450         if (path != file) cab_free( path );
451         return ret;
452
453     case fdintCLOSE_FILE_INFO:
454         CloseHandle( (HANDLE)pfdin->hf );
455         return 0;
456
457     default:
458         WINE_FIXME( "Unexpected notification type %d.\n", fdint );
459         return 0;
460     }
461 }
462
463 static int extract_cabinet( char *cab_dir )
464 {
465     ERF erf;
466     int ret = 0;
467     HFDI fdi = FDICreate( cab_alloc, cab_free, fdi_open, fdi_read,
468                           fdi_write, fdi_close, fdi_lseek, cpuUNKNOWN, &erf );
469
470     if (!FDICopy( fdi, opt_cab_file, cab_dir, 0, extract_notify, NULL, NULL )) ret = GetLastError();
471     FDIDestroy( fdi );
472     return ret;
473 }
474
475 static BOOL add_file( HFCI fci, WCHAR *name )
476 {
477     BOOL ret;
478     char *filename, *path = strdupWtoA( CP_UTF8, name );
479
480     if (!opt_preserve_paths)
481     {
482         if ((filename = strrchr( path, '\\' ))) filename++;
483         else filename = path;
484     }
485     else
486     {
487         filename = path;
488         while (*filename == '\\') filename++;  /* remove leading backslashes */
489     }
490     ret = FCIAddFile( fci, path, filename, FALSE,
491                       fci_get_next_cab, fci_status, fci_get_open_info, opt_compression );
492     cab_free( path );
493     return ret;
494 }
495
496 static BOOL add_directory( HFCI fci, WCHAR *dir )
497 {
498     static const WCHAR wildcardW[] = {'*',0};
499     WCHAR *p, *buffer;
500     HANDLE handle;
501     WIN32_FIND_DATAW data;
502     BOOL ret = TRUE;
503
504     if (!(buffer = cab_alloc( (strlenW(dir) + MAX_PATH + 2) * sizeof(WCHAR) ))) return FALSE;
505     strcpyW( buffer, dir );
506     p = buffer + strlenW( buffer );
507     if (p > buffer && p[-1] != '\\') *p++ = '\\';
508     strcpyW( p, wildcardW );
509
510     if ((handle = FindFirstFileW( buffer, &data )) != INVALID_HANDLE_VALUE)
511     {
512         do
513         {
514             if (data.cFileName[0] == '.' && !data.cFileName[1]) continue;
515             if (data.cFileName[0] == '.' && data.cFileName[1] == '.' && !data.cFileName[2]) continue;
516             if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) continue;
517
518             strcpyW( p, data.cFileName );
519             if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
520                 ret = add_directory( fci, buffer );
521             else
522                 ret = add_file( fci, buffer );
523             if (!ret) break;
524         } while (FindNextFileW( handle, &data ));
525         FindClose( handle );
526     }
527     cab_free( buffer );
528     return TRUE;
529 }
530
531 static BOOL add_file_or_directory( HFCI fci, WCHAR *name )
532 {
533     DWORD attr = GetFileAttributesW( name );
534
535     if (attr == INVALID_FILE_ATTRIBUTES)
536     {
537         WINE_MESSAGE( "cannot open %s\n", wine_dbgstr_w(name) );
538         return FALSE;
539     }
540     if (attr & FILE_ATTRIBUTE_DIRECTORY)
541     {
542         if (opt_recurse) return add_directory( fci, name );
543         WINE_MESSAGE( "cabarc: Cannot add %s, it's a directory (use -r for recursive add)\n",
544                       wine_dbgstr_w(name) );
545         return FALSE;
546     }
547     return add_file( fci, name );
548 }
549
550 static int new_cabinet( char *cab_dir )
551 {
552     static const WCHAR plusW[] = {'+',0};
553     WCHAR **file;
554     ERF erf;
555     BOOL ret = FALSE;
556     HFCI fci;
557     CCAB cab;
558
559     cab.cb                = opt_cabinet_size;
560     cab.cbFolderThresh    = CB_MAX_DISK;
561     cab.cbReserveCFHeader = opt_reserve_space;
562     cab.cbReserveCFFolder = 0;
563     cab.cbReserveCFData   = 0;
564     cab.iCab              = 1;
565     cab.iDisk             = 0;
566     cab.setID             = opt_cabinet_id;
567     cab.szDisk[0]         = 0;
568
569     strcpy( cab.szCabPath, cab_dir );
570     strcat( cab.szCabPath, "\\" );
571     format_cab_name( cab.szCab, 1, opt_cab_file );
572
573     fci = FCICreate( &erf, fci_file_placed, cab_alloc, cab_free,fci_open, fci_read,
574                      fci_write, fci_close, fci_lseek, fci_delete, fci_get_temp, &cab, NULL );
575
576     for (file = opt_files; *file; file++)
577     {
578         if (!strcmpW( *file, plusW ))
579             FCIFlushFolder( fci, fci_get_next_cab, fci_status );
580         else
581             if (!(ret = add_file_or_directory( fci, *file ))) break;
582     }
583
584     if (ret)
585     {
586         if (!(ret = FCIFlushCabinet( fci, FALSE, fci_get_next_cab, fci_status )))
587             WINE_MESSAGE( "cabarc: Failed to create cabinet %s\n", wine_dbgstr_a(opt_cab_file) );
588     }
589     FCIDestroy( fci );
590     return !ret;
591 }
592
593 static void usage( void )
594 {
595     WINE_MESSAGE(
596         "Usage: cabarc [options] command file.cab [files...] [dest_dir\\]\n"
597         "\nCommands:\n"
598         "   L   List the contents of the cabinet\n"
599         "   N   Create a new cabinet\n"
600         "   X   Extract files from the cabinet into dest_dir\n"
601         "\nOptions:\n"
602         "  -d size  Set maximum disk size\n"
603         "  -h       Display this help\n"
604         "  -i id    Set cabinet id\n"
605         "  -m type  Set compression type (mszip|none)\n"
606         "  -p       Preserve directory names\n"
607         "  -r       Recurse into directories\n"
608         "  -s size  Reserve space in the cabinet header\n"
609         "  -v       More verbose output\n" );
610 }
611
612 int wmain( int argc, WCHAR *argv[] )
613 {
614     static const WCHAR noneW[] = {'n','o','n','e',0};
615     static const WCHAR mszipW[] = {'m','s','z','i','p',0};
616
617     WCHAR *p, *command;
618     char buffer[MAX_PATH];
619     char *cab_file, *file_part;
620     int i;
621
622     while (argv[1] && argv[1][0] == '-')
623     {
624         switch (argv[1][1])
625         {
626         case 'd':
627             argv++; argc--;
628             opt_cabinet_size = atoiW( argv[1] );
629             if (opt_cabinet_size < 50000)
630             {
631                 WINE_MESSAGE( "cabarc: Cabinet size must be at least 50000\n" );
632                 return 1;
633             }
634             break;
635         case 'h':
636             usage();
637             return 0;
638         case 'i':
639             argv++; argc--;
640             opt_cabinet_id = atoiW( argv[1] );
641             break;
642         case 'm':
643             argv++; argc--;
644             if (!strcmpiW( argv[1], noneW )) opt_compression = tcompTYPE_NONE;
645             else if (!strcmpiW( argv[1], mszipW )) opt_compression = tcompTYPE_MSZIP;
646             else
647             {
648                 WINE_MESSAGE( "cabarc: Unknown compression type '%s'\n", optarg );
649                 return 1;
650             }
651             break;
652         case 'p':
653             opt_preserve_paths = 1;
654             break;
655         case 'r':
656             opt_recurse = 1;
657             break;
658         case 's':
659             argv++; argc--;
660             opt_reserve_space = atoiW( argv[1] );
661             break;
662         case 'v':
663             opt_verbose++;
664             break;
665         default:
666             usage();
667             return 1;
668         }
669         argv++; argc--;
670     }
671
672     command = argv[1];
673     if (argc < 3 || !command[0] || command[1])
674     {
675         usage();
676         return 1;
677     }
678     cab_file = strdupWtoA( CP_ACP, argv[2] );
679     argv += 2;
680     argc -= 2;
681
682     if (!GetFullPathNameA( cab_file, MAX_PATH, buffer, &file_part ) || !file_part)
683     {
684         WINE_ERR( "cannot get full name for %s\n", wine_dbgstr_a( cab_file ));
685         return 1;
686     }
687     file_part[-1] = 0;
688     cab_free( cab_file );
689
690     /* map slash to backslash in all file arguments */
691     for (i = 1; i < argc; i++)
692         for (p = argv[i]; *p; p++)
693             if (*p == '/') *p = '\\';
694     opt_files = argv + 1;
695     opt_cab_file = file_part;
696
697     switch (*command)
698     {
699     case 'l':
700     case 'L':
701         return list_cabinet( buffer );
702     case 'n':
703     case 'N':
704         return new_cabinet( buffer );
705     case 'x':
706     case 'X':
707         if (argc > 1)  /* check for destination dir as last argument */
708         {
709             WCHAR *last = argv[argc - 1];
710             if (last[0] && last[strlenW(last) - 1] == '\\')
711             {
712                 opt_dest_dir = last;
713                 argv[--argc] = NULL;
714             }
715         }
716         return extract_cabinet( buffer );
717     default:
718         usage();
719         return 1;
720     }
721 }