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