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