LZCopy: modified timestamp of a expanded file has to be the same of
[wine] / dlls / lzexpand / lzexpand_main.c
1 /*
2  * LZ Decompression functions
3  *
4  * Copyright 1996 Marcus Meissner
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20 /*
21  * FIXME: return values might be wrong
22  */
23
24 #include "config.h"
25
26 #include <string.h>
27 #include <ctype.h>
28 #include <sys/types.h>
29 #include <stdio.h>
30 #ifdef HAVE_UNISTD_H
31 # include <unistd.h>
32 #endif
33
34 #include "winbase.h"
35 #include "lzexpand.h"
36
37 #include "wine/unicode.h"
38
39 #include "wine/debug.h"
40
41 WINE_DEFAULT_DEBUG_CHANNEL(file);
42
43 /* The readahead length of the decompressor. Reading single bytes
44  * using _lread() would be SLOW.
45  */
46 #define GETLEN  2048
47
48 /* Format of first 14 byte of LZ compressed file */
49 struct lzfileheader {
50         BYTE    magic[8];
51         BYTE    compressiontype;
52         CHAR    lastchar;
53         DWORD   reallength;
54 };
55 static BYTE LZMagic[8]={'S','Z','D','D',0x88,0xf0,0x27,0x33};
56
57 struct lzstate {
58         HFILE   realfd;         /* the real filedescriptor */
59         CHAR    lastchar;       /* the last char of the filename */
60
61         DWORD   reallength;     /* the decompressed length of the file */
62         DWORD   realcurrent;    /* the position the decompressor currently is */
63         DWORD   realwanted;     /* the position the user wants to read from */
64
65         BYTE    table[0x1000];  /* the rotating LZ table */
66         UINT    curtabent;      /* CURrent TABle ENTry */
67
68         BYTE    stringlen;      /* length and position of current string */
69         DWORD   stringpos;      /* from stringtable */
70
71
72         WORD    bytetype;       /* bitmask within blocks */
73
74         BYTE    *get;           /* GETLEN bytes */
75         DWORD   getcur;         /* current read */
76         DWORD   getlen;         /* length last got */
77 };
78
79 #define MAX_LZSTATES 16
80 static struct lzstate *lzstates[MAX_LZSTATES];
81
82 #define IS_LZ_HANDLE(h) (((h) >= 0x400) && ((h) < 0x400+MAX_LZSTATES))
83 #define GET_LZ_STATE(h) (IS_LZ_HANDLE(h) ? lzstates[(h)-0x400] : NULL)
84
85 /* reads one compressed byte, including buffering */
86 #define GET(lzs,b)      _lzget(lzs,&b)
87 #define GET_FLUSH(lzs)  lzs->getcur=lzs->getlen;
88
89 static int
90 _lzget(struct lzstate *lzs,BYTE *b) {
91         if (lzs->getcur<lzs->getlen) {
92                 *b              = lzs->get[lzs->getcur++];
93                 return          1;
94         } else {
95                 int ret = _lread(lzs->realfd,lzs->get,GETLEN);
96                 if (ret==HFILE_ERROR)
97                         return HFILE_ERROR;
98                 if (ret==0)
99                         return 0;
100                 lzs->getlen     = ret;
101                 lzs->getcur     = 1;
102                 *b              = *(lzs->get);
103                 return 1;
104         }
105 }
106 /* internal function, reads lzheader
107  * returns BADINHANDLE for non filedescriptors
108  * return 0 for file not compressed using LZ
109  * return UNKNOWNALG for unknown algorithm
110  * returns lzfileheader in *head
111  */
112 static INT read_header(HFILE fd,struct lzfileheader *head)
113 {
114         BYTE    buf[14];
115
116         if (_llseek(fd,0,SEEK_SET)==-1)
117                 return LZERROR_BADINHANDLE;
118
119         /* We can't directly read the lzfileheader struct due to
120          * structure element alignment
121          */
122         if (_lread(fd,buf,14)<14)
123                 return 0;
124         memcpy(head->magic,buf,8);
125         memcpy(&(head->compressiontype),buf+8,1);
126         memcpy(&(head->lastchar),buf+9,1);
127
128         /* FIXME: consider endianess on non-intel architectures */
129         memcpy(&(head->reallength),buf+10,4);
130
131         if (memcmp(head->magic,LZMagic,8))
132                 return 0;
133         if (head->compressiontype!='A')
134                 return LZERROR_UNKNOWNALG;
135         return 1;
136 }
137
138
139 /***********************************************************************
140  *           LZStart   (LZ32.@)
141  */
142 INT WINAPI LZStart(void)
143 {
144     TRACE("(void)\n");
145     return 1;
146 }
147
148
149 /***********************************************************************
150  *           LZInit   (LZ32.@)
151  *
152  * initializes internal decompression buffers, returns lzfiledescriptor.
153  * (return value the same as hfSrc, if hfSrc is not compressed)
154  * on failure, returns error code <0
155  * lzfiledescriptors range from 0x400 to 0x410 (only 16 open files per process)
156  *
157  * since _llseek uses the same types as libc.lseek, we just use the macros of
158  *  libc
159  */
160 HFILE WINAPI LZInit( HFILE hfSrc )
161 {
162
163         struct  lzfileheader    head;
164         struct  lzstate         *lzs;
165         DWORD   ret;
166         int i;
167
168         TRACE("(%d)\n",hfSrc);
169         ret=read_header(hfSrc,&head);
170         if (ret<=0) {
171                 _llseek(hfSrc,0,SEEK_SET);
172                 return ret?ret:hfSrc;
173         }
174         for (i = 0; i < MAX_LZSTATES; i++) if (!lzstates[i]) break;
175         if (i == MAX_LZSTATES) return LZERROR_GLOBALLOC;
176         lzstates[i] = lzs = HeapAlloc( GetProcessHeap(), 0, sizeof(struct lzstate) );
177         if(lzs == NULL) return LZERROR_GLOBALLOC;
178
179         memset(lzs,'\0',sizeof(*lzs));
180         lzs->realfd     = hfSrc;
181         lzs->lastchar   = head.lastchar;
182         lzs->reallength = head.reallength;
183
184         lzs->get        = HeapAlloc( GetProcessHeap(), 0, GETLEN );
185         lzs->getlen     = 0;
186         lzs->getcur     = 0;
187
188         if(lzs->get == NULL) {
189                 HeapFree(GetProcessHeap(), 0, lzs);
190                 lzstates[i] = NULL;
191                 return LZERROR_GLOBALLOC;
192         }
193
194         /* Yes, preinitialize with spaces */
195         memset(lzs->table,' ',0x1000);
196         /* Yes, start 16 byte from the END of the table */
197         lzs->curtabent  = 0xff0;
198         return 0x400 + i;
199 }
200
201
202 /***********************************************************************
203  *           LZDone   (LZEXPAND.9)
204  *           LZDone   (LZ32.@)
205  */
206 void WINAPI LZDone(void)
207 {
208     TRACE("(void)\n");
209 }
210
211
212 /***********************************************************************
213  *           GetExpandedNameA   (LZ32.@)
214  *
215  * gets the full filename of the compressed file 'in' by opening it
216  * and reading the header
217  *
218  * "file." is being translated to "file"
219  * "file.bl_" (with lastchar 'a') is being translated to "file.bla"
220  * "FILE.BL_" (with lastchar 'a') is being translated to "FILE.BLA"
221  */
222
223 INT WINAPI GetExpandedNameA( LPSTR in, LPSTR out )
224 {
225         struct lzfileheader     head;
226         HFILE           fd;
227         OFSTRUCT        ofs;
228         INT             fnislowercased,ret,len;
229         LPSTR           s,t;
230
231         TRACE("(%s)\n",in);
232         fd=OpenFile(in,&ofs,OF_READ);
233         if (fd==HFILE_ERROR)
234                 return (INT)(INT16)LZERROR_BADINHANDLE;
235         strcpy(out,in);
236         ret=read_header(fd,&head);
237         if (ret<=0) {
238                 /* not a LZ compressed file, so the expanded name is the same
239                  * as the input name */
240                 _lclose(fd);
241                 return 1;
242         }
243
244
245         /* look for directory prefix and skip it. */
246         s=out;
247         while (NULL!=(t=strpbrk(s,"/\\:")))
248                 s=t+1;
249
250         /* now mangle the basename */
251         if (!*s) {
252                 /* FIXME: hmm. shouldn't happen? */
253                 WARN("Specified a directory or what? (%s)\n",in);
254                 _lclose(fd);
255                 return 1;
256         }
257         /* see if we should use lowercase or uppercase on the last char */
258         fnislowercased=1;
259         t=s+strlen(s)-1;
260         while (t>=out) {
261                 if (!isalpha(*t)) {
262                         t--;
263                         continue;
264                 }
265                 fnislowercased=islower(*t);
266                 break;
267         }
268         if (isalpha(head.lastchar)) {
269                 if (fnislowercased)
270                         head.lastchar=tolower(head.lastchar);
271                 else
272                         head.lastchar=toupper(head.lastchar);
273         }
274
275         /* now look where to replace the last character */
276         if (NULL!=(t=strchr(s,'.'))) {
277                 if (t[1]=='\0') {
278                         t[0]='\0';
279                 } else {
280                         len=strlen(t)-1;
281                         if (t[len]=='_')
282                                 t[len]=head.lastchar;
283                 }
284         } /* else no modification necessary */
285         _lclose(fd);
286         return 1;
287 }
288
289
290 /***********************************************************************
291  *           GetExpandedNameW   (LZ32.@)
292  */
293 INT WINAPI GetExpandedNameW( LPWSTR in, LPWSTR out )
294 {
295     INT ret;
296     DWORD len = WideCharToMultiByte( CP_ACP, 0, in, -1, NULL, 0, NULL, NULL );
297     char *xin = HeapAlloc( GetProcessHeap(), 0, len );
298     char *xout = HeapAlloc( GetProcessHeap(), 0, len+3 );
299     WideCharToMultiByte( CP_ACP, 0, in, -1, xin, len, NULL, NULL );
300     if ((ret = GetExpandedNameA( xin, xout )) > 0)
301         MultiByteToWideChar( CP_ACP, 0, xout, -1, out, strlenW(in)+4 );
302     HeapFree( GetProcessHeap(), 0, xin );
303     HeapFree( GetProcessHeap(), 0, xout );
304     return ret;
305 }
306
307
308 /***********************************************************************
309  *           LZRead   (LZ32.@)
310  */
311 INT WINAPI LZRead( HFILE fd, LPSTR vbuf, INT toread )
312 {
313         int     howmuch;
314         BYTE    b,*buf;
315         struct  lzstate *lzs;
316
317         buf=(LPBYTE)vbuf;
318         TRACE("(%d,%p,%d)\n",fd,buf,toread);
319         howmuch=toread;
320         if (!(lzs = GET_LZ_STATE(fd))) return _lread(fd,buf,toread);
321
322 /* The decompressor itself is in a define, cause we need it twice
323  * in this function. (the decompressed byte will be in b)
324  */
325 #define DECOMPRESS_ONE_BYTE                                             \
326                 if (lzs->stringlen) {                                   \
327                         b               = lzs->table[lzs->stringpos];   \
328                         lzs->stringpos  = (lzs->stringpos+1)&0xFFF;     \
329                         lzs->stringlen--;                               \
330                 } else {                                                \
331                         if (!(lzs->bytetype&0x100)) {                   \
332                                 if (1!=GET(lzs,b))                      \
333                                         return toread-howmuch;          \
334                                 lzs->bytetype = b|0xFF00;               \
335                         }                                               \
336                         if (lzs->bytetype & 1) {                        \
337                                 if (1!=GET(lzs,b))                      \
338                                         return toread-howmuch;          \
339                         } else {                                        \
340                                 BYTE    b1,b2;                          \
341                                                                         \
342                                 if (1!=GET(lzs,b1))                     \
343                                         return toread-howmuch;          \
344                                 if (1!=GET(lzs,b2))                     \
345                                         return toread-howmuch;          \
346                                 /* Format:                              \
347                                  * b1 b2                                \
348                                  * AB CD                                \
349                                  * where CAB is the stringoffset in the table\
350                                  * and D+3 is the len of the string     \
351                                  */                                     \
352                                 lzs->stringpos  = b1|((b2&0xf0)<<4);    \
353                                 lzs->stringlen  = (b2&0xf)+2;           \
354                                 /* 3, but we use a  byte already below ... */\
355                                 b               = lzs->table[lzs->stringpos];\
356                                 lzs->stringpos  = (lzs->stringpos+1)&0xFFF;\
357                         }                                               \
358                         lzs->bytetype>>=1;                              \
359                 }                                                       \
360                 /* store b in table */                                  \
361                 lzs->table[lzs->curtabent++]= b;                        \
362                 lzs->curtabent  &= 0xFFF;                               \
363                 lzs->realcurrent++;
364
365         /* if someone has seeked, we have to bring the decompressor
366          * to that position
367          */
368         if (lzs->realcurrent!=lzs->realwanted) {
369                 /* if the wanted position is before the current position
370                  * I see no easy way to unroll ... We have to restart at
371                  * the beginning. *sigh*
372                  */
373                 if (lzs->realcurrent>lzs->realwanted) {
374                         /* flush decompressor state */
375                         _llseek(lzs->realfd,14,SEEK_SET);
376                         GET_FLUSH(lzs);
377                         lzs->realcurrent= 0;
378                         lzs->bytetype   = 0;
379                         lzs->stringlen  = 0;
380                         memset(lzs->table,' ',0x1000);
381                         lzs->curtabent  = 0xFF0;
382                 }
383                 while (lzs->realcurrent<lzs->realwanted) {
384                         DECOMPRESS_ONE_BYTE;
385                 }
386         }
387
388         while (howmuch) {
389                 DECOMPRESS_ONE_BYTE;
390                 lzs->realwanted++;
391                 *buf++          = b;
392                 howmuch--;
393         }
394         return  toread;
395 #undef DECOMPRESS_ONE_BYTE
396 }
397
398
399 /***********************************************************************
400  *           LZSeek   (LZ32.@)
401  */
402 LONG WINAPI LZSeek( HFILE fd, LONG off, INT type )
403 {
404         struct  lzstate *lzs;
405         LONG    newwanted;
406
407         TRACE("(%d,%ld,%d)\n",fd,off,type);
408         /* not compressed? just use normal _llseek() */
409         if (!(lzs = GET_LZ_STATE(fd))) return _llseek(fd,off,type);
410         newwanted = lzs->realwanted;
411         switch (type) {
412         case 1: /* SEEK_CUR */
413                 newwanted      += off;
414                 break;
415         case 2: /* SEEK_END */
416                 newwanted       = lzs->reallength-off;
417                 break;
418         default:/* SEEK_SET */
419                 newwanted       = off;
420                 break;
421         }
422         if (newwanted>lzs->reallength)
423                 return LZERROR_BADVALUE;
424         if (newwanted<0)
425                 return LZERROR_BADVALUE;
426         lzs->realwanted = newwanted;
427         return newwanted;
428 }
429
430
431 /***********************************************************************
432  *           LZCopy   (LZ32.@)
433  *
434  * Copies everything from src to dest
435  * if src is a LZ compressed file, it will be uncompressed.
436  * will return the number of bytes written to dest or errors.
437  */
438 LONG WINAPI LZCopy( HFILE src, HFILE dest )
439 {
440         int     usedlzinit = 0, ret, wret;
441         LONG    len;
442         HFILE   oldsrc = src, srcfd;
443         FILETIME filetime;
444         struct  lzstate *lzs;
445 #define BUFLEN  1000
446         BYTE    buf[BUFLEN];
447         /* we need that weird typedef, for i can't seem to get function pointer
448          * casts right. (Or they probably just do not like WINAPI in general)
449          */
450         typedef UINT    (WINAPI *_readfun)(HFILE,LPVOID,UINT);
451
452         _readfun        xread;
453
454         TRACE("(%d,%d)\n",src,dest);
455         if (!IS_LZ_HANDLE(src)) {
456                 src = LZInit(src);
457                 if ((INT)src <= 0) return 0;
458                 if (src != oldsrc) usedlzinit=1;
459         }
460
461         /* not compressed? just copy */
462         if (!IS_LZ_HANDLE(src))
463                 xread=_lread;
464         else
465                 xread=(_readfun)LZRead;
466         len=0;
467         while (1) {
468                 ret=xread(src,buf,BUFLEN);
469                 if (ret<=0) {
470                         if (ret==0)
471                                 break;
472                         if (ret==-1)
473                                 return LZERROR_READ;
474                         return ret;
475                 }
476                 len    += ret;
477                 wret    = _lwrite(dest,buf,ret);
478                 if (wret!=ret)
479                         return LZERROR_WRITE;
480         }
481
482         /* Maintain the timestamp of source file to destination file */
483         srcfd = (!(lzs = GET_LZ_STATE(src))) ? src : lzs->realfd;
484         GetFileTime((HANDLE)srcfd, NULL, NULL, &filetime);
485         SetFileTime((HANDLE)dest, NULL, NULL, &filetime);
486
487         /* close handle */
488         if (usedlzinit)
489                 LZClose(src);
490         return len;
491 #undef BUFLEN
492 }
493
494 /* reverses GetExpandedPathname */
495 static LPSTR LZEXPAND_MangleName( LPCSTR fn )
496 {
497     char *p;
498     char *mfn = (char *)HeapAlloc( GetProcessHeap(), 0,
499                                    strlen(fn) + 3 ); /* "._" and \0 */
500     if(mfn == NULL) return NULL;
501     strcpy( mfn, fn );
502     if (!(p = strrchr( mfn, '\\' ))) p = mfn;
503     if ((p = strchr( p, '.' )))
504     {
505         p++;
506         if (strlen(p) < 3) strcat( p, "_" );  /* append '_' */
507         else p[strlen(p)-1] = '_';  /* replace last character */
508     }
509     else strcat( mfn, "._" );   /* append "._" */
510     return mfn;
511 }
512
513
514 /***********************************************************************
515  *           LZOpenFileA   (LZ32.@)
516  *
517  * Opens a file. If not compressed, open it as a normal file.
518  */
519 HFILE WINAPI LZOpenFileA( LPSTR fn, LPOFSTRUCT ofs, WORD mode )
520 {
521         HFILE   fd,cfd;
522
523         TRACE("(%s,%p,%d)\n",fn,ofs,mode);
524         /* 0x70 represents all OF_SHARE_* flags, ignore them for the check */
525         fd=OpenFile(fn,ofs,mode);
526         if (fd==HFILE_ERROR)
527         {
528             LPSTR mfn = LZEXPAND_MangleName(fn);
529             fd = OpenFile(mfn,ofs,mode);
530             HeapFree( GetProcessHeap(), 0, mfn );
531         }
532         if ((mode&~0x70)!=OF_READ)
533                 return fd;
534         if (fd==HFILE_ERROR)
535                 return HFILE_ERROR;
536         cfd=LZInit(fd);
537         if ((INT)cfd <= 0) return fd;
538         return cfd;
539 }
540
541
542 /***********************************************************************
543  *           LZOpenFileW   (LZ32.@)
544  */
545 HFILE WINAPI LZOpenFileW( LPWSTR fn, LPOFSTRUCT ofs, WORD mode )
546 {
547     HFILE ret;
548     DWORD len = WideCharToMultiByte( CP_ACP, 0, fn, -1, NULL, 0, NULL, NULL );
549     LPSTR xfn = HeapAlloc( GetProcessHeap(), 0, len );
550     WideCharToMultiByte( CP_ACP, 0, fn, -1, xfn, len, NULL, NULL );
551     ret = LZOpenFileA(xfn,ofs,mode);
552     HeapFree( GetProcessHeap(), 0, xfn );
553     return ret;
554 }
555
556
557 /***********************************************************************
558  *           LZClose   (LZ32.@)
559  */
560 void WINAPI LZClose( HFILE fd )
561 {
562         struct lzstate *lzs;
563
564         TRACE("(%d)\n",fd);
565         if (!(lzs = GET_LZ_STATE(fd))) _lclose(fd);
566         else
567         {
568             if (lzs->get) HeapFree( GetProcessHeap(), 0, lzs->get );
569             CloseHandle((HANDLE)lzs->realfd);
570             lzstates[fd - 0x400] = NULL;
571             HeapFree( GetProcessHeap(), 0, lzs );
572         }
573 }
574
575
576 /***********************************************************************
577  *           CopyLZFile  (LZ32.@)
578  *
579  * Copy src to dest (including uncompressing src).
580  * NOTE: Yes. This is exactly the same function as LZCopy.
581  */
582 LONG WINAPI CopyLZFile( HFILE src, HFILE dest )
583 {
584     TRACE("(%d,%d)\n",src,dest);
585     return LZCopy(src,dest);
586 }