xref: /reactos/dll/win32/kernel32/wine/lzexpand.c (revision 2f1d080c)
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