/*++ Copyright (c) 1993 Microsoft Corporation Module Name: diamond.c Abstract: Diamond MSZIP decompression support. Author: Ted Miller (tedm) 31-Jan-1995 Revision History: --*/ #include "setupntp.h" #pragma hdrstop // // Tls index used by Diamond. This is set up in // DiamondProcessAttach(). // DWORD DiamondTlsIndex = (DWORD)(-1); // // Per-thread globals. // typedef struct _DIAMOND_THREAD_DATA { // // Boolean value indicating whether the current thread // is inside diamond. Diamond doesn't really providee // a full context environment so we declare it non-reentrant. // BOOL InDiamond; // // Diamond context data // HFDI FdiContext; ERF FdiError; // // Last encountered error // DWORD LastError; // // Name of cabinet as passed to DiamondProcessCabinet, // and an ANSI version. // PCTSTR CabinetFile; PCSTR CabinetFileA; // // Notification callback and context parameter // PVOID MsgHandler; PVOID Context; BOOL IsMsgHandlerNativeCharWidth; // // Full path of the current target file being extracted. // PTSTR CurrentTargetFile; // // Flag indicating whether diamond asked us to switch cabinets. // If we do switch, then we stop copying when the current file // is done. This prevents diamond from happily doing each file // in the new cabinet, which would ruin the queue commit routine's // ability to allow some files to exist outside the cabinet, etc. // BOOL SwitchedCabinets; // // If the source path changes as the result of a prompt for a // new cabinet (when a file continues across multiple cabinets), // we remember the path the user gave us here. // TCHAR UserPath[MAX_PATH]; } DIAMOND_THREAD_DATA, *PDIAMOND_THREAD_DATA; BOOL DiamondInitialize( VOID ); int DIAMONDAPI SpdFdiOpen( IN PSTR FileName, IN int oflag, IN int pmode ); int DIAMONDAPI SpdFdiClose( IN int Handle ); UINT pDiamondNotifyFileDone( IN PDIAMOND_THREAD_DATA PerThread, IN DWORD Win32Error ) { UINT u; FILEPATHS FilePaths; MYASSERT(PerThread->CurrentTargetFile); FilePaths.Source = PerThread->CabinetFile; FilePaths.Target = PerThread->CurrentTargetFile; FilePaths.Win32Error = Win32Error; u = pSetupCallMsgHandler( PerThread->MsgHandler, PerThread->IsMsgHandlerNativeCharWidth, PerThread->Context, SPFILENOTIFY_FILEEXTRACTED, (UINT)&FilePaths, 0 ); return(u); } int DIAMONDAPI DiamondNotifyFunction( IN FDINOTIFICATIONTYPE Operation, IN PFDINOTIFICATION Parameters ) { PDIAMOND_THREAD_DATA PerThread; int h; PSTR FileNameA; CABINET_INFO CabInfo; FILE_IN_CABINET_INFO FileInCab; FILETIME FileTime; TCHAR NewPath[MAX_PATH]; PTSTR p; PerThread = TlsGetValue(DiamondTlsIndex); MYASSERT(PerThread); switch(Operation) { case fdintCABINET_INFO: // // Tell the callback function, in case it wants to do something // with this information. // h = ERROR_NOT_ENOUGH_MEMORY; CabInfo.CabinetFile = NewPortableString(Parameters->psz1); if(CabInfo.CabinetFile) { CabInfo.DiskName = NewPortableString(Parameters->psz2); if(CabInfo.DiskName) { CabInfo.CabinetPath = NewPortableString(Parameters->psz3); if(CabInfo.CabinetPath) { CabInfo.SetId = Parameters->setID; CabInfo.CabinetNumber = Parameters->iCabinet; h = pSetupCallMsgHandler( PerThread->MsgHandler, PerThread->IsMsgHandlerNativeCharWidth, PerThread->Context, SPFILENOTIFY_CABINETINFO, (UINT)&CabInfo, 0 ); MyFree(CabInfo.CabinetPath); } MyFree(CabInfo.DiskName); } MyFree(CabInfo.CabinetFile); } if(h != NO_ERROR) { PerThread->LastError = h; } return((h == NO_ERROR) ? 0 : -1); case fdintCOPY_FILE: // // Diamond is asking us whether we want to copy the file. // If we switched cabinets, then the answer is no. // if(PerThread->SwitchedCabinets) { PerThread->LastError = NO_ERROR; return(-1); } // Pass the information on to the callback function and // let it decide. // FileInCab.NameInCabinet = NewPortableString(Parameters->psz1); FileInCab.FileSize = Parameters->cb; FileInCab.DosDate = Parameters->date; FileInCab.DosTime = Parameters->time; FileInCab.DosAttribs = Parameters->attribs; FileInCab.Win32Error = NO_ERROR; if(!FileInCab.NameInCabinet) { PerThread->LastError = ERROR_NOT_ENOUGH_MEMORY; return(-1); } // // Call the callback function. // h = pSetupCallMsgHandler( PerThread->MsgHandler, PerThread->IsMsgHandlerNativeCharWidth, PerThread->Context, SPFILENOTIFY_FILEINCABINET, (UINT)&FileInCab, (UINT)PerThread->CabinetFile ); switch(h) { case FILEOP_SKIP: h = 0; break; case FILEOP_DOIT: // // The callback wants to copy the file. In this case it has // provided us the full target pathname to use. // MYASSERT(PerThread->CurrentTargetFile == NULL); if(p = DuplicateString(FileInCab.FullTargetName)) { if(FileNameA = NewAnsiString(FileInCab.FullTargetName)) { h = _lcreat(FileNameA,0); if(h == HFILE_ERROR) { PerThread->LastError = GetLastError(); h = -1; } MyFree(FileNameA); // // If we get here, a file is being processed. // if(h == -1) { MyFree(p); } else { PerThread->CurrentTargetFile = p; } } else { PerThread->LastError = ERROR_NOT_ENOUGH_MEMORY; MyFree(p); h = -1; } } else { PerThread->LastError = ERROR_NOT_ENOUGH_MEMORY; h = -1; } break; default: // // Abort. // h = -1; PerThread->LastError = FileInCab.Win32Error; break; } return(h); case fdintCLOSE_FILE_INFO: // // Diamond is done with the target file and wants us to close it. // (ie, this is the counterpart to fdintCOPY_FILE). // // We want the timestamp to be what is stored in the cabinet. // Note that we lose the create and last access times in this case. // if(DosDateTimeToFileTime(Parameters->date,Parameters->time,&FileTime)) { SetFileTime((HANDLE)Parameters->hf,NULL,NULL,&FileTime); } SpdFdiClose(Parameters->hf); // // Call the callback function to inform it that the file has been // successfully extracted from the cabinet. // MYASSERT(PerThread->CurrentTargetFile); h = pDiamondNotifyFileDone(PerThread,NO_ERROR); if(h != NO_ERROR) { PerThread->LastError = h; } MyFree(PerThread->CurrentTargetFile); PerThread->CurrentTargetFile = NULL; return((h == NO_ERROR) ? TRUE : -1); case fdintPARTIAL_FILE: // // We don't do anything with this. // return(0); case fdintNEXT_CABINET: if((Parameters->fdie == FDIERROR_NONE) || (Parameters->fdie == FDIERROR_WRONG_CABINET)) { // // A file continues into another cabinet. // Inform the callback function, who is responsible for // making sure the cabinet is accessible when it returns. // h = ERROR_NOT_ENOUGH_MEMORY; CabInfo.SetId = 0; CabInfo.CabinetNumber = 0; CabInfo.CabinetPath = NewPortableString(Parameters->psz3); if(CabInfo.CabinetPath) { CabInfo.CabinetFile = NewPortableString(Parameters->psz1); if(CabInfo.CabinetFile) { CabInfo.DiskName = NewPortableString(Parameters->psz2); if(CabInfo.DiskName) { h = pSetupCallMsgHandler( PerThread->MsgHandler, PerThread->IsMsgHandlerNativeCharWidth, PerThread->Context, SPFILENOTIFY_NEEDNEWCABINET, (UINT)&CabInfo, (UINT)NewPath ); if(h == NO_ERROR) { // // See if a new path was specified. // if(NewPath[0]) { lstrcpyn(PerThread->UserPath,NewPath,MAX_PATH); } // // Remember that we switched cabinets. // PerThread->SwitchedCabinets = TRUE; } MyFree(CabInfo.DiskName); } MyFree(CabInfo.CabinetFile); } MyFree(CabInfo.CabinetPath); } } else { // // Some other error we don't understand -- this indicates // a bad cabinet. // h = ERROR_INVALID_DATA; } if(h != NO_ERROR) { PerThread->LastError = h; } return((h == NO_ERROR) ? 0 : -1); default: // // Unknown notification type. Should never get here. // MYASSERT(0); return(0); } } PVOID DIAMONDAPI SpdFdiAlloc( IN ULONG NumberOfBytes ) /*++ Routine Description: Callback used by FDICopy to allocate memory. Arguments: NumberOfBytes - supplies desired size of block. Return Value: Returns pointer to a block of memory or NULL if memory cannot be allocated. --*/ { return(MyMalloc(NumberOfBytes)); } VOID DIAMONDAPI SpdFdiFree( IN PVOID Block ) /*++ Routine Description: Callback used by FDICopy to free a memory block. The block must have been allocated with SpdFdiAlloc(). Arguments: Block - supplies pointer to block of memory to be freed. Return Value: None. --*/ { MyFree(Block); } int DIAMONDAPI SpdFdiOpen( IN PSTR FileName, IN int oflag, IN int pmode ) /*++ Routine Description: Callback used by FDICopy to open files. This routine is capable only of opening existing files. Arguments: FileName - supplies name of file to be opened. oflag - supplies flags for open. pmode - supplies additional flags for open. Return Value: Handle to open file or -1 if error occurs. --*/ { HFILE h; PDIAMOND_THREAD_DATA PerThread; UNREFERENCED_PARAMETER(pmode); PerThread = TlsGetValue(DiamondTlsIndex); MYASSERT(PerThread); if(oflag & (_O_WRONLY | _O_RDWR | _O_APPEND | _O_CREAT | _O_TRUNC | _O_EXCL)) { PerThread->LastError = ERROR_INVALID_PARAMETER; return(-1); } h = _lopen(FileName,OF_READ | OF_SHARE_DENY_WRITE); if(h == HFILE_ERROR) { PerThread->LastError = GetLastError(); return(-1); } return((int)h); } UINT DIAMONDAPI SpdFdiRead( IN int Handle, OUT PVOID pv, IN UINT ByteCount ) /*++ Routine Description: Callback used by FDICopy to read from a file. Arguments: Handle - supplies handle to open file to be read from. pv - supplies pointer to buffer to receive bytes we read. ByteCount - supplies number of bytes to read. Return Value: Number of bytes read (ByteCount) or -1 if an error occurs. --*/ { UINT rc; PDIAMOND_THREAD_DATA PerThread; DWORD d; rc = _lread((HFILE)Handle,pv,ByteCount); if(rc == HFILE_ERROR) { rc = (UINT)(-1); d = GetLastError(); PerThread = TlsGetValue(DiamondTlsIndex); MYASSERT(PerThread); PerThread->LastError = d; } return(rc); } UINT DIAMONDAPI SpdFdiWrite( IN int Handle, IN PVOID pv, IN UINT ByteCount ) /*++ Routine Description: Callback used by FDICopy to write to a file. Arguments: Handle - supplies handle to open file to be written to. pv - supplies pointer to buffer containing bytes to write. ByteCount - supplies number of bytes to write. Return Value: Number of bytes written (ByteCount) or -1 if an error occurs. --*/ { UINT rc; PDIAMOND_THREAD_DATA PerThread; DWORD d; rc = _lwrite((HFILE)Handle,pv,ByteCount); if(rc == HFILE_ERROR) { rc = (UINT)(-1); d = GetLastError(); PerThread = TlsGetValue(DiamondTlsIndex); MYASSERT(PerThread); PerThread->LastError = d; } return(rc); } int DIAMONDAPI SpdFdiClose( IN int Handle ) /*++ Routine Description: Callback used by FDICopy to close files. Arguments: Handle - handle of file to close. Return Value: 0 (success). --*/ { HFILE h; h = _lclose((HFILE)Handle); MYASSERT(h == 0); return(0); } long DIAMONDAPI SpdFdiSeek( IN int Handle, IN long Distance, IN int SeekType ) /*++ Routine Description: Callback used by FDICopy to seek files. Arguments: Handle - handle of file to close. Distance - supplies distance to seek. Interpretation of this parameter depends on the value of SeekType. SeekType - supplies a value indicating how Distance is to be interpreted; one of SEEK_SET, SEEK_CUR, SEEK_END. Return Value: New file offset or -1 if an error occurs. --*/ { LONG rc; DWORD d; PDIAMOND_THREAD_DATA PerThread; rc = _llseek((HFILE)Handle,Distance,SeekType); if(rc == HFILE_ERROR) { d = GetLastError(); PerThread = TlsGetValue(DiamondTlsIndex); MYASSERT(PerThread); PerThread->LastError = d; rc = -1L; } return(rc); } DWORD DiamondProcessCabinet( IN PCTSTR CabinetFile, IN DWORD Flags, IN PVOID MsgHandler, IN PVOID Context, IN BOOL IsMsgHandlerNativeCharWidth ) /*++ Routine Description: Process a diamond cabinet file, iterating through all files contained within it and calling the callback function with information about each file. Arguments: SourceFileName - supplies name of cabinet file. Flags - supplies flags to control behavior of cabinet processing. MsgHandler - Supplies a callback routine to be notified of various significant events in cabinet processing. Context - Supplies a value that is passed to the MsgHandler callback function. Return Value: Win32 error code indicating result. If the cabinet was corrupt, ERROR_INVALID_DATA is returned. --*/ { BOOL b; DWORD rc; PDIAMOND_THREAD_DATA PerThread; PSTR CabinetFileA; PSTR p,FilePartA,PathPartA; CHAR c; int h; UNREFERENCED_PARAMETER(Flags); // // Initialize diamond for this thread if not // already initialized. // if(!DiamondInitialize()) { rc = ERROR_NOT_ENOUGH_MEMORY; goto c0; } // // Fetch pointer to per-thread data. // PerThread = TlsGetValue(DiamondTlsIndex); MYASSERT(PerThread); MYASSERT(PerThread->FdiContext); // // Because diamond does not really give us a truly comprehensive // context mechanism, our diamond support is NOT reentrant. // No synchronization is required to check this state because // it is stored in per-thread data. // if(PerThread->InDiamond) { rc = ERROR_INVALID_FUNCTION; goto c0; } PerThread->InDiamond = TRUE; // // Get an ANSI version of the name because diamond is not unicode. // CabinetFileA = NewAnsiString(CabinetFile); if(!CabinetFileA) { rc = ERROR_NOT_ENOUGH_MEMORY; goto c1; } // // Split the cabinet name into path and name. // Make separate copies because we want to remember the // full ansi version of the fill cabinet filename we were passed, // so we'll leave CabinetFileA alone. // if(FilePartA = strrchr(CabinetFileA,'\\')) { FilePartA++; } else { FilePartA = CabinetFileA; } c = *FilePartA; *FilePartA = 0; PathPartA = _strdup(CabinetFileA); *FilePartA = c; if(!PathPartA) { rc = ERROR_NOT_ENOUGH_MEMORY; goto c2; } FilePartA = _strdup(FilePartA); if(!FilePartA) { rc = ERROR_NOT_ENOUGH_MEMORY; goto c3; } // // Initialize thread globals. // PerThread->LastError = NO_ERROR; PerThread->CabinetFile = CabinetFile; PerThread->CabinetFileA = CabinetFileA; PerThread->MsgHandler = MsgHandler; PerThread->IsMsgHandlerNativeCharWidth = IsMsgHandlerNativeCharWidth; PerThread->Context = Context; PerThread->SwitchedCabinets = FALSE; PerThread->UserPath[0] = 0; PerThread->CurrentTargetFile = NULL; // // Perform the copy. // b = FDICopy( PerThread->FdiContext, FilePartA, PathPartA, 0, // flags DiamondNotifyFunction, NULL, // no decryption NULL // don't bother with user-specified data ); if(b) { // // Everything succeeded so we shouldn't have any partially // processed files. // MYASSERT(!PerThread->CurrentTargetFile); rc = NO_ERROR; } else { switch(PerThread->FdiError.erfOper) { case FDIERROR_NONE: // // We shouldn't see this -- if there was no error // then FDICopy should have returned TRUE. // MYASSERT(PerThread->FdiError.erfOper != FDIERROR_NONE); rc = ERROR_INVALID_DATA; break; case FDIERROR_CABINET_NOT_FOUND: rc = ERROR_FILE_NOT_FOUND; break; case FDIERROR_CORRUPT_CABINET: // // Read/open/seek error or corrupt cabinet // rc = PerThread->LastError; if(rc == NO_ERROR) { rc = ERROR_INVALID_DATA; } break; case FDIERROR_ALLOC_FAIL: rc = ERROR_NOT_ENOUGH_MEMORY; break; case FDIERROR_TARGET_FILE: case FDIERROR_USER_ABORT: rc = PerThread->LastError; break; case FDIERROR_NOT_A_CABINET: case FDIERROR_UNKNOWN_CABINET_VERSION: case FDIERROR_BAD_COMPR_TYPE: case FDIERROR_MDI_FAIL: case FDIERROR_RESERVE_MISMATCH: case FDIERROR_WRONG_CABINET: default: // // Cabinet is corrupt or not actually a cabinet, etc. // rc = ERROR_INVALID_DATA; break; } if(PerThread->CurrentTargetFile) { // // Call the callback function to inform it that the last file // was not successfully extracted from the cabinet. // Also remove the partially copied file. // DeleteFile(PerThread->CurrentTargetFile); pDiamondNotifyFileDone(PerThread,rc); MyFree(PerThread->CurrentTargetFile); PerThread->CurrentTargetFile = NULL; } } free(FilePartA); c3: free(PathPartA); c2: MyFree(CabinetFileA); c1: PerThread->InDiamond = FALSE; c0: return(rc); } BOOL DiamondIsCabinet( IN PCTSTR FileName ) /*++ Routine Description: Determine if a file is a diamond cabinet. Arguments: FileName - supplies name of file to be checked. Return Value: TRUE if file is diamond file. FALSE if not; --*/ { FDICABINETINFO CabinetInfo; BOOL b; int h; PDIAMOND_THREAD_DATA PerThread; PSTR FileNameA; b = FALSE; // // Initialize diamond for this thread if not // already initialized. // if(!DiamondInitialize()) { goto c0; } PerThread = TlsGetValue(DiamondTlsIndex); MYASSERT(PerThread); MYASSERT(PerThread->FdiContext); // // Because diamond does not really give us a truly comprehensive // context mechanism, our diamond support is NOT reentrant. // No synchronization is required to check this state because // it is stored in per-thread data. // if(PerThread->InDiamond) { goto c0; } PerThread->InDiamond = TRUE; // // Unfortunately the diamond engine is not Unicode. // Convert the filename to ANSI. // FileNameA = NewAnsiString(FileName); if(!FileNameA) { goto c1; } // // Open the file such that the handle is valid for use // in the diamond context (ie, seek, read routines above). // h = SpdFdiOpen(FileNameA,_O_RDONLY,0); if(h == -1) { goto c2; } b = FDIIsCabinet(PerThread->FdiContext,h,&CabinetInfo); SpdFdiClose(h); c2: MyFree(FileNameA); c1: PerThread->InDiamond = FALSE; c0: return(b); } BOOL DiamondInitialize( VOID ) /*++ Routine Description: Per-thread initialization routine for Diamond. Called internally. Arguments: None. Return Value: Boolean result indicating success or failure. Failure can be assumed to be out of memory. --*/ { HFDI FdiContext; PDIAMOND_THREAD_DATA PerThread; // // See if this thread is already initialized. // if(TlsGetValue(DiamondTlsIndex)) { return(TRUE); } // // Allocate a per-thread data structure // if(PerThread = MyMalloc(sizeof(DIAMOND_THREAD_DATA))) { ZeroMemory(PerThread,sizeof(DIAMOND_THREAD_DATA)); // // Initialize a diamond context. // FdiContext = FDICreate( SpdFdiAlloc, SpdFdiFree, SpdFdiOpen, SpdFdiRead, SpdFdiWrite, SpdFdiClose, SpdFdiSeek, cpuUNKNOWN, &PerThread->FdiError ); if(FdiContext) { if(TlsSetValue(DiamondTlsIndex,PerThread)) { PerThread->FdiContext = FdiContext; return(TRUE); } FDIDestroy(FdiContext); } MyFree(PerThread); } return(FALSE); } VOID DiamondTerminate( VOID ) /*++ Routine Description: Per-thread termination routine for Diamond. Called internally. Arguments: None. Return Value: Boolean result indicating success or failure. Failure can be assumed to be out of memory. --*/ { PDIAMOND_THREAD_DATA PerThread; if(PerThread = TlsGetValue(DiamondTlsIndex)) { FDIDestroy(PerThread->FdiContext); MyFree(PerThread); TlsSetValue(DiamondTlsIndex,NULL); } } BOOL DiamondProcessAttach( IN BOOL Attach ) /*++ Routine Description: Process attach routine. Must be called by the DLL entry point routine on DLL_PROCESS_ATTACH and DLL_PROCESS_DETACH notifications. This routine allocates a Tls index on process attach for thread-specific data used by Diamond. The Tls index is freed on process detach. Arguments: Attach - TRUE if process is attaching; FALSE if not. Return Value: Boolean result indicating success or failure. Meaningful only if Attach is TRUE. --*/ { if(Attach) { // // Allocate a Tls index. // DiamondTlsIndex = TlsAlloc(); return(DiamondTlsIndex != (DWORD)(-1)); } else { // // Free the Tls index // return(TlsFree(DiamondTlsIndex)); } } VOID DiamondThreadAttach( IN BOOL Attach ) /*++ Routine Description: Thread attach routine. Must be called by the DLL entry point routine on DLL_THREAD_ATTACH and DLL_THREAD_DETACH notifications. The routine initializes per-thread data used by diamond. Arguments: Attach - TRUE if thread is attaching; FALSE if not. Return Value: None. --*/ { PVOID p; if(Attach) { // // There really isn't any reason to do the initialization here // because it will get done on entry to externally exposed API. // (We have to do that, in case someone uses LoadLibrary and then // calls Diamond from a thread that existed before the LoadLibrary.) // //DiamondInitialize(); } else { DiamondTerminate(); } } /////////////////////////////////////////////////////////////////////////// #if 0 HFCI DiamondCreateCabinet( IN PCTSTR FileName ) { HFCI hfci; // // Initialize diamond for this thread if not // already initialized. // if(!DiamondInitialize()) { goto c0; } PerThread = TlsGetValue(DiamondTlsIndex); MYASSERT(PerThread); hfci = FCICreate( &PerThread->FdiError, SpdFciFilePlaced, SpdFdiAlloc, SpdFdiFree, SpdFciGetTempFile, &Cabinfo ); return(hfci); } #endif /////////////////////////////////////////////////////////////////////////// BOOL _SetupIterateCabinet( IN PCTSTR CabinetFile, IN DWORD Flags, IN PVOID MsgHandler, IN PVOID Context, IN BOOL IsMsgHandlerNativeCharWidth ) { PTSTR cabinetFile; DWORD rc; // // Flags param not used. Make sure it's zero. // if(Flags) { rc = ERROR_INVALID_PARAMETER; goto c0; } // // Get a copy of the cabinet file name to validate // the caller's buffer. // try { cabinetFile = DuplicateString(CabinetFile); } except(EXCEPTION_EXECUTE_HANDLER) { rc = ERROR_INVALID_PARAMETER; goto c0; } if(!cabinetFile) { rc = ERROR_NOT_ENOUGH_MEMORY; goto c0; } rc = DiamondProcessCabinet(cabinetFile,Flags,MsgHandler,Context,IsMsgHandlerNativeCharWidth); MyFree(cabinetFile); c0: SetLastError(rc); return(rc == NO_ERROR); } #ifdef UNICODE // // ANSI version // BOOL SetupIterateCabinetA( IN PCSTR CabinetFile, IN DWORD Flags, IN PSP_FILE_CALLBACK_A MsgHandler, IN PVOID Context ) { BOOL b; DWORD rc; PCWSTR cabinetFile; rc = CaptureAndConvertAnsiArg(CabinetFile,&cabinetFile); if(rc == NO_ERROR) { b = _SetupIterateCabinet(cabinetFile,Flags,MsgHandler,Context,FALSE); rc = GetLastError(); MyFree(cabinetFile); } else { b = FALSE; } SetLastError(rc); return(b); } #else // // Unicode stub // BOOL SetupIterateCabinetW( IN PCWSTR CabinetFile, IN DWORD Flags, IN PSP_FILE_CALLBACK_W MsgHandler, IN PVOID Context ) { UNREFERENCED_PARAMETER(CabinetFile); UNREFERENCED_PARAMETER(Flags); UNREFERENCED_PARAMETER(MsgHandler); UNREFERENCED_PARAMETER(Context); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return(FALSE); } #endif BOOL SetupIterateCabinet( IN PCTSTR CabinetFile, IN DWORD Flags, IN PSP_FILE_CALLBACK MsgHandler, IN PVOID Context ) { return(_SetupIterateCabinet(CabinetFile,Flags,MsgHandler,Context,TRUE)); }