2 * PROJECT: ReactOS Applications Manager
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Cabinet extraction using FDI API
5 * COPYRIGHT: Copyright 2018 Alexander Shaposhnikov (sanchaez@reactos.org)
14 * HACK: treat any input strings as Unicode (UTF-8)
15 * cabinet.dll lacks any sort of a Unicode API, but FCI/FDI
16 * provide an ability to use user-defined callbacks for any file or memory
17 * operations. This flexibility and the magic power of C/C++ casting allows
18 * us to treat input as we please.
19 * This is by far the best way to extract .cab using Unicode paths.
22 /* String conversion helper functions */
24 // converts CStringW to CStringA using a given codepage
25 inline BOOL
WideToMultiByte(const CStringW
& szSource
,
29 // determine the needed size
30 INT sz
= WideCharToMultiByte(Codepage
,
41 // do the actual conversion
42 sz
= WideCharToMultiByte(Codepage
,
51 szDest
.ReleaseBuffer();
55 // converts CStringA to CStringW using a given codepage
56 inline BOOL
MultiByteToWide(const CStringA
& szSource
,
60 // determine the needed size
61 INT sz
= MultiByteToWideChar(Codepage
,
70 // do the actual conversion
71 sz
= MultiByteToWideChar(CP_UTF8
,
78 szDest
.ReleaseBuffer();
82 /* FDICreate callbacks */
86 return HeapAlloc(GetProcessHeap(), NULL
, cb
);
91 HeapFree(GetProcessHeap(), NULL
, pv
);
97 DWORD dwDesiredAccess
= 0;
98 DWORD dwCreationDisposition
= 0;
99 ATL::CStringW szFileName
;
101 UNREFERENCED_PARAMETER(pmode
);
105 dwDesiredAccess
= GENERIC_READ
| GENERIC_WRITE
;
107 else if (oflag
& _O_WRONLY
)
109 dwDesiredAccess
= GENERIC_WRITE
;
113 dwDesiredAccess
= GENERIC_READ
;
116 if (oflag
& _O_CREAT
)
118 dwCreationDisposition
= CREATE_ALWAYS
;
122 dwCreationDisposition
= OPEN_EXISTING
;
125 MultiByteToWide(pszFile
, szFileName
, CP_UTF8
);
127 hFile
= CreateFileW(szFileName
,
131 dwCreationDisposition
,
132 FILE_ATTRIBUTE_NORMAL
,
135 return (INT_PTR
) hFile
;
140 DWORD dwBytesRead
= 0;
142 if (ReadFile((HANDLE
) hf
, pv
, cb
, &dwBytesRead
, NULL
) == FALSE
)
144 dwBytesRead
= (DWORD
) -1L;
152 DWORD dwBytesWritten
= 0;
154 if (WriteFile((HANDLE
) hf
, pv
, cb
, &dwBytesWritten
, NULL
) == FALSE
)
156 dwBytesWritten
= (DWORD
) -1;
159 return dwBytesWritten
;
164 return (CloseHandle((HANDLE
) hf
) != FALSE
) ? 0 : -1;
169 return SetFilePointer((HANDLE
) hf
, dist
, NULL
, seektype
);
172 /* FDICopy callbacks */
174 FNFDINOTIFY(fnNotify
)
182 CStringW szExtractDir
, szCabFileName
;
184 // Append the destination directory to the file name.
185 MultiByteToWide((LPCSTR
) pfdin
->pv
, szExtractDir
, CP_UTF8
);
186 MultiByteToWide(pfdin
->psz1
, szCabFileName
, CP_ACP
);
188 if (szCabFileName
.Find('\\') >= 0)
190 CStringW szNewDirName
= szExtractDir
;
192 // We do not want to interpret the filename as directory,
193 // so bail out before the last token!
194 while (szCabFileName
.Find('\\', nTokenPos
) >= 0)
196 CStringW token
= szCabFileName
.Tokenize(L
"\\", nTokenPos
);
200 szNewDirName
+= L
"\\" + token
;
201 if (!CreateDirectoryW(szNewDirName
, NULL
))
203 DWORD dwErr
= GetLastError();
204 if (dwErr
!= ERROR_ALREADY_EXISTS
)
206 DPRINT1("ERROR: Unable to create directory %S (err %lu)\n", szNewDirName
.GetString(), dwErr
);
212 CStringW szNewFileName
= szExtractDir
+ L
"\\" + szCabFileName
;
214 CStringA szFilePathUTF8
;
215 WideToMultiByte(szNewFileName
, szFilePathUTF8
, CP_UTF8
);
218 iResult
= fnFileOpen((LPSTR
) szFilePathUTF8
.GetString(),
219 _O_WRONLY
| _O_CREAT
,
224 case fdintCLOSE_FILE_INFO
:
225 iResult
= !fnFileClose(pfdin
->hf
);
228 case fdintNEXT_CABINET
:
229 if (pfdin
->fdie
!= FDIERROR_NONE
)
235 case fdintPARTIAL_FILE
:
239 case fdintCABINET_INFO
:
255 /* cabinet.dll FDI function pointers */
257 typedef HFDI(*fnFDICreate
)(PFNALLOC
,
267 typedef BOOL(*fnFDICopy
)(HFDI
,
275 typedef BOOL(*fnFDIDestroy
)(HFDI
);
278 * Extraction function
279 * TODO: require only a full path to the cab as an argument
281 BOOL
ExtractFilesFromCab(const ATL::CStringW
& szCabName
,
282 const ATL::CStringW
& szCabDir
,
283 const ATL::CStringW
& szOutputDir
)
285 HINSTANCE hCabinetDll
;
288 ATL::CStringA szCabNameUTF8
, szCabDirUTF8
, szOutputDirUTF8
;
289 fnFDICreate pfnFDICreate
;
290 fnFDICopy pfnFDICopy
;
291 fnFDIDestroy pfnFDIDestroy
;
294 // Load cabinet.dll and extract needed functions
295 hCabinetDll
= LoadLibraryW(L
"cabinet.dll");
302 pfnFDICreate
= (fnFDICreate
) GetProcAddress(hCabinetDll
, "FDICreate");
303 pfnFDICopy
= (fnFDICopy
) GetProcAddress(hCabinetDll
, "FDICopy");
304 pfnFDIDestroy
= (fnFDIDestroy
) GetProcAddress(hCabinetDll
, "FDIDestroy");
306 if (!pfnFDICreate
|| !pfnFDICopy
|| !pfnFDIDestroy
)
308 FreeLibrary(hCabinetDll
);
312 // Create FDI context
313 ExtractHandler
= pfnFDICreate(fnMemAlloc
,
325 FreeLibrary(hCabinetDll
);
330 bResult
= CreateDirectoryW(szOutputDir
, NULL
);
332 if (bResult
|| GetLastError() == ERROR_ALREADY_EXISTS
)
334 // Convert wide strings to UTF-8
335 bResult
= WideToMultiByte(szCabName
, szCabNameUTF8
, CP_UTF8
);
336 bResult
&= WideToMultiByte(szCabDir
, szCabDirUTF8
, CP_UTF8
);
337 bResult
&= WideToMultiByte(szOutputDir
, szOutputDirUTF8
, CP_UTF8
);
340 // Perform extraction
343 // Add a slash to cab name as required by the api
344 szCabNameUTF8
= "\\" + szCabNameUTF8
;
346 bResult
= pfnFDICopy(ExtractHandler
,
347 (LPSTR
) szCabNameUTF8
.GetString(),
348 (LPSTR
) szCabDirUTF8
.GetString(),
352 (void FAR
*) szOutputDirUTF8
.GetString());
355 pfnFDIDestroy(ExtractHandler
);
356 FreeLibrary(hCabinetDll
);