| 212 | } |
| 213 | |
| 214 | static bool ExtractFile(HANDLE hStorage, const CASC_FIND_DATA& findData, const char* targetDirPath, const char* targetFilePath) { |
| 215 | namespace fs = std::filesystem; |
| 216 | |
| 217 | PCASC_FILE_SPAN_INFO pSpans = nullptr; |
| 218 | HANDLE hFile = nullptr; |
| 219 | CASC_FILE_FULL_INFO fileInfo{}; |
| 220 | LPCSTR szOpenName = findData.szFileName; |
| 221 | DWORD dwErrCode = ERROR_SUCCESS; |
| 222 | |
| 223 | if (CascOpenFile(hStorage, szOpenName, NULL /*dwLocaleFlags*/, CASC_STRICT_DATA_CHECK, &hFile)) |
| 224 | { |
| 225 | // Retrieve the information about file spans. |
| 226 | if ((pSpans = GetFileInfo(hFile, fileInfo)) != nullptr && fileInfo.ContentSize > 0) |
| 227 | { |
| 228 | DWORD dwBytesRead = 0; |
| 229 | ULONGLONG totalWritten = 0; |
| 230 | FILE* pOutFile = nullptr; |
| 231 | |
| 232 | // Load the entire file, one read request per span. |
| 233 | // Using span-aligned reads will cause CascReadFile not to do any caching, |
| 234 | // and the amount of memcpys will be almost zero |
| 235 | for (DWORD i = 0; i < fileInfo.SpanCount && dwErrCode == ERROR_SUCCESS; ++i) |
| 236 | { |
| 237 | const CASC_FILE_SPAN_INFO* const pFileSpan = pSpans + i; |
| 238 | LPBYTE pbFileSpan = nullptr; |
| 239 | const auto cbFileSpan = (DWORD) (pFileSpan->EndOffset - pFileSpan->StartOffset); |
| 240 | bool bReadOk = true; |
| 241 | |
| 242 | // Do not read empty spans. |
| 243 | if (cbFileSpan == 0) |
| 244 | { |
| 245 | continue; |
| 246 | } |
| 247 | |
| 248 | // Allocate span buffer |
| 249 | pbFileSpan = CASC_ALLOC<BYTE>(cbFileSpan); |
| 250 | if (pbFileSpan == nullptr) |
| 251 | { |
| 252 | std::cerr << " ERROR: Not enough memory to allocate " << cbFileSpan << " bytes" << std::endl; |
| 253 | dwErrCode = ERROR_NOT_ENOUGH_MEMORY; |
| 254 | break; |
| 255 | } |
| 256 | |
| 257 | // CascReadFile will read as much as possible. If there is a frame error |
| 258 | // (e.g. MD5 mismatch, missing encryption key or disc error), |
| 259 | // CascReadFile only returns frames that are loaded entirely. |
| 260 | bReadOk = CascReadFile(hFile, pbFileSpan, cbFileSpan, &dwBytesRead); |
| 261 | if (bReadOk) { |
| 262 | // prepare file stream |
| 263 | if (!pOutFile) { |
| 264 | |
| 265 | if (fs::exists(targetDirPath) || fs::create_directories(targetDirPath)) { |
| 266 | |
| 267 | // create filestream and empty file |
| 268 | if ((pOutFile = OpenExtractedFile(targetFilePath)) == nullptr) { |
| 269 | std::cerr << " ERROR: Could not create file: " << targetFilePath << std::endl; |
| 270 | } |
| 271 | } |
no test coverage detected