1 /* 2 * PatchAPI 3 * 4 * Copyright 2011 David Hedberg for CodeWeavers 5 * Copyright 2018 Mark Jansen (mark.jansen@reactos.org) 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this library; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 20 */ 21 22 #define WIN32_NO_STATUS 23 #include "windef.h" 24 #include "winbase.h" 25 #include "winnls.h" 26 #include "ndk/rtlfuncs.h" 27 #include "patchapi.h" 28 #include "lzx.h" 29 #include "wine/debug.h" 30 31 static const char szHexString[] = "0123456789abcdef"; 32 #define SIGNATURE_MIN_SIZE 9 33 34 #define UNKNOWN_FLAGS_COMBINATION 0x00c40001 35 36 37 WINE_DEFAULT_DEBUG_CHANNEL(mspatcha); 38 39 40 typedef struct _SAFE_READ 41 { 42 PBYTE Root; 43 DWORD Size; 44 PBYTE Ptr; 45 } SAFE_READ, *PSAFE_READ; 46 47 48 /** 49 * @name ReadByte 50 * Read the next byte available from @param pRead 51 * 52 * @param pRead 53 * The input buffer 54 * 55 * @return The byte, or 0 56 */ 57 BYTE ReadByte(PSAFE_READ pRead) 58 { 59 if (pRead->Ptr + sizeof(BYTE) <= (pRead->Root + pRead->Size)) 60 { 61 BYTE Value = *(PBYTE)pRead->Ptr; 62 pRead->Ptr += sizeof(BYTE); 63 return Value; 64 } 65 pRead->Ptr = pRead->Root + pRead->Size; 66 return 0; 67 } 68 69 /** 70 * @name ReadUShort 71 * Read the next unsigned short available from @param pRead 72 * 73 * @param pRead 74 * The input buffer 75 * 76 * @return The unsigned short, or 0 77 */ 78 USHORT ReadUShort(PSAFE_READ pRead) 79 { 80 if (pRead->Ptr + sizeof(USHORT) <= (pRead->Root + pRead->Size)) 81 { 82 USHORT Value = *(PUSHORT)pRead->Ptr; 83 pRead->Ptr += sizeof(USHORT); 84 return Value; 85 } 86 pRead->Ptr = pRead->Root + pRead->Size; 87 return 0; 88 } 89 90 /** 91 * @name ReadDWord 92 * Read the next dword available from @param pRead 93 * 94 * @param pRead 95 * The input buffer 96 * 97 * @return The dword, or 0 98 */ 99 DWORD ReadDWord(PSAFE_READ pRead) 100 { 101 if (pRead->Ptr + sizeof(DWORD) <= (pRead->Root + pRead->Size)) 102 { 103 DWORD Value = *(PDWORD)pRead->Ptr; 104 pRead->Ptr += sizeof(DWORD); 105 return Value; 106 } 107 pRead->Ptr = pRead->Root + pRead->Size; 108 return 0; 109 } 110 111 /** 112 * @name DecodeDWord 113 * Read the next variable length-encoded dword from @param pRead 114 * 115 * @param pRead 116 * The input buffer 117 * 118 * @return The dword, or 0 119 */ 120 DWORD DecodeDWord(PSAFE_READ pRead) 121 { 122 UINT Result = 0, offset; 123 124 for (offset = 0; offset < 32; offset += 7) 125 { 126 DWORD val = ReadByte(pRead); 127 Result |= ((val & 0x7f) << offset); 128 if (val & 0x80) 129 break; 130 } 131 132 return Result; 133 } 134 135 136 /** 137 * @name DecodeInt 138 * Read the next variable length-encoded int from @param pRead 139 * 140 * @param pRead 141 * The input buffer 142 * 143 * @return The int, or 0 144 */ 145 INT DecodeInt(PSAFE_READ pRead) 146 { 147 INT Result = 0, offset; 148 149 for (offset = 0; offset < 32; offset += 6) 150 { 151 INT val = (INT)(DWORD)ReadByte(pRead); 152 Result |= ((val & 0x3f) << offset); 153 if (val & 0x80) 154 { 155 if (val & 0x40) 156 Result *= -1; 157 break; 158 } 159 } 160 161 return Result; 162 } 163 164 165 typedef struct _PATCH_HEADER 166 { 167 DWORD Flags; 168 169 DWORD ImageBase; 170 DWORD ImageTimeStamp; 171 172 DWORD OutputSize; 173 DWORD OutputCrc; 174 175 DWORD OldSize; 176 DWORD OldCrc; 177 DWORD DataSize; // Payload after the patch header 178 179 } PATCH_HEADER; 180 181 182 /** 183 * @name MapFile 184 * Map a view of a file into readonly memory 185 * 186 * @param hFile 187 * The input file handle, readable 188 * 189 * @param dwSize 190 * Mapped file size (out) 191 * 192 * @return A Pointer to the start of the memory 193 */ 194 static PBYTE MapFile(HANDLE hFile, DWORD* dwSize) 195 { 196 HANDLE hMap; 197 PVOID pView; 198 199 *dwSize = GetFileSize(hFile, NULL); 200 hMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL); 201 if (hMap != INVALID_HANDLE_VALUE) 202 { 203 pView = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); 204 CloseHandle(hMap); 205 return pView; 206 } 207 208 return NULL; 209 } 210 211 212 /***************************************************** 213 * DllMain (MSPATCHA.@) 214 */ 215 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) 216 { 217 switch (fdwReason) 218 { 219 case DLL_PROCESS_ATTACH: 220 DisableThreadLibraryCalls(hinstDLL); 221 break; 222 } 223 224 return TRUE; 225 } 226 227 /***************************************************** 228 * ApplyPatchToFileA (MSPATCHA.1) 229 */ 230 BOOL WINAPI ApplyPatchToFileA(LPCSTR patch_file, LPCSTR old_file, LPCSTR new_file, ULONG apply_flags) 231 { 232 BOOL ret = FALSE; 233 HANDLE hPatch, hOld, hNew; 234 235 hPatch = CreateFileA(patch_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 236 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); 237 if (hPatch != INVALID_HANDLE_VALUE) 238 { 239 hOld = CreateFileA(old_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 240 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); 241 if (hOld != INVALID_HANDLE_VALUE) 242 { 243 hNew = CreateFileA(new_file, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, 244 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); 245 if (hNew != INVALID_HANDLE_VALUE) 246 { 247 ret = ApplyPatchToFileByHandles(hPatch, hOld, hNew, apply_flags); 248 CloseHandle(hNew); 249 } 250 CloseHandle(hOld); 251 } 252 CloseHandle(hPatch); 253 } 254 255 return ret; 256 } 257 258 259 /** 260 * @name ParseHeader 261 * Parse a Patch file header 262 * @note The current implementation is far from complete! 263 * 264 * @param Patch 265 * Buffer pointing to the raw patch data 266 * 267 * @param Header 268 * The result of the parsed header 269 * 270 * @return STATUS_SUCCESS on success, an error code otherwise 271 */ 272 DWORD ParseHeader(SAFE_READ* Patch, PATCH_HEADER* Header) 273 { 274 DWORD Crc, Unknown; 275 int Delta; 276 277 ZeroMemory(Header, sizeof(*Header)); 278 279 /* Validate the patch */ 280 Crc = RtlComputeCrc32(0, Patch->Root, Patch->Size); 281 if (Crc != ~0) 282 return ERROR_PATCH_CORRUPT; 283 284 if (ReadDWord(Patch) != '91AP') 285 return ERROR_PATCH_DECODE_FAILURE; 286 287 /* Read the flags, warn about an unknown combination */ 288 Header->Flags = ReadDWord(Patch); 289 if (Header->Flags ^ UNKNOWN_FLAGS_COMBINATION) 290 ERR("Unknown flags: 0x%x, patch will most likely fail\n", Header->Flags ^ UNKNOWN_FLAGS_COMBINATION); 291 292 /* 0x5bb3284e, 0x5bb33562, 0x5bb357b1 */ 293 Unknown = ReadDWord(Patch); 294 TRACE("Unknown: 0x%x\n", Unknown); 295 296 Header->OutputSize = DecodeDWord(Patch); 297 Header->OutputCrc = ReadDWord(Patch); 298 299 Unknown = ReadByte(Patch); 300 if (Unknown != 1) 301 ERR("Field after CRC is not 1 but %u\n", Unknown); 302 303 Delta = DecodeInt(Patch); 304 Header->OldSize = Header->OutputSize + Delta; 305 Header->OldCrc = ReadDWord(Patch); 306 307 Unknown = ReadUShort(Patch); 308 if (Unknown != 0) 309 ERR("Field1 after OldCrc is not 0 but %u\n", Unknown); 310 311 Unknown = DecodeDWord(Patch); 312 if (Unknown != 0) 313 ERR("Field2 after OldCrc is not 0 but %u\n", Unknown); 314 315 Header->DataSize = DecodeDWord(Patch); 316 /* Remaining data, minus the CRC appended */ 317 if (Header->DataSize != (Patch->Size - (Patch->Ptr - Patch->Root) - sizeof(DWORD))) 318 { 319 ERR("Unable to read header, check previous logging!\n"); 320 return ERROR_PATCH_DECODE_FAILURE; 321 } 322 return STATUS_SUCCESS; 323 } 324 325 /** 326 * @name CreateNewFileFromPatch 327 * Using the input @param Header and @param Patch, create a new file on @param new_file 328 * 329 * @param Header 330 * Parsed / preprocessed patch header 331 * 332 * @param Patch 333 * Memory buffer pointing to the patch payload 334 * 335 * @param new_file 336 * A handle to the output file. This file will be resized 337 * 338 * @return STATUS_SUCCESS on success, an error code otherwise 339 */ 340 DWORD CreateNewFileFromPatch(PATCH_HEADER* Header, SAFE_READ* Patch, HANDLE new_file) 341 { 342 SAFE_READ NewFile; 343 HANDLE hMap; 344 USHORT BlockSize; 345 DWORD dwStatus; 346 struct LZXstate* state; 347 int lzxResult; 348 349 hMap = CreateFileMappingW(new_file, NULL, PAGE_READWRITE, 0, Header->OutputSize, NULL); 350 if (hMap == INVALID_HANDLE_VALUE) 351 return ERROR_PATCH_NOT_AVAILABLE; 352 353 NewFile.Root = NewFile.Ptr = MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, 0); 354 CloseHandle(hMap); 355 NewFile.Size = Header->OutputSize; 356 357 if (!NewFile.Root) 358 return ERROR_PATCH_NOT_AVAILABLE; 359 360 /* At this point Patch->Ptr should point to the payload */ 361 BlockSize = ReadUShort(Patch); 362 363 /* This window size does not work on all files (for example, MS SQL Express 2008 setup) */ 364 state = LZXinit(17); 365 if (state) 366 { 367 lzxResult = LZXdecompress(state, Patch->Ptr, NewFile.Ptr, BlockSize, NewFile.Size); 368 LZXteardown(state); 369 370 if (lzxResult == DECR_OK) 371 dwStatus = STATUS_SUCCESS; 372 else 373 dwStatus = ERROR_PATCH_DECODE_FAILURE; 374 } 375 else 376 { 377 dwStatus = ERROR_INSUFFICIENT_BUFFER; 378 } 379 380 UnmapViewOfFile(NewFile.Root); 381 return dwStatus; 382 } 383 384 385 /***************************************************** 386 * ApplyPatchToFileByHandles (MSPATCHA.2) 387 */ 388 BOOL WINAPI ApplyPatchToFileByHandles(HANDLE patch_file, HANDLE old_file, HANDLE new_file, ULONG apply_flags) 389 { 390 SAFE_READ Patch, OldFile; 391 DWORD dwStatus; 392 PATCH_HEADER Header; 393 394 Patch.Root = Patch.Ptr = MapFile(patch_file, &Patch.Size); 395 if (!Patch.Root) 396 { 397 SetLastError(ERROR_PATCH_CORRUPT); 398 return FALSE; 399 } 400 401 /* Let's decode the header */ 402 dwStatus = ParseHeader(&Patch, &Header); 403 if (dwStatus != STATUS_SUCCESS) 404 { 405 UnmapViewOfFile(Patch.Root); 406 SetLastError(dwStatus); 407 return FALSE; 408 } 409 410 OldFile.Root = OldFile.Ptr = MapFile(old_file, &OldFile.Size); 411 if (OldFile.Root) 412 { 413 DWORD dwCrc; 414 415 /* Verify the input file */ 416 dwCrc = RtlComputeCrc32(0, OldFile.Root, OldFile.Size); 417 if (OldFile.Size == Header.OldSize && dwCrc == Header.OldCrc) 418 { 419 if (apply_flags & APPLY_OPTION_TEST_ONLY) 420 dwStatus = STATUS_SUCCESS; 421 else 422 dwStatus = CreateNewFileFromPatch(&Header, &Patch, new_file); 423 } 424 else 425 { 426 dwStatus = ERROR_PATCH_WRONG_FILE; 427 } 428 UnmapViewOfFile(OldFile.Root); 429 } 430 else 431 { 432 dwStatus = GetLastError(); 433 if (dwStatus == STATUS_SUCCESS) 434 dwStatus = ERROR_PATCH_NOT_AVAILABLE; 435 } 436 437 UnmapViewOfFile(Patch.Root); 438 SetLastError(dwStatus); 439 return dwStatus == STATUS_SUCCESS; 440 } 441 442 /***************************************************** 443 * ApplyPatchToFileW (MSPATCHA.6) 444 */ 445 BOOL WINAPI ApplyPatchToFileW(LPCWSTR patch_file, LPCWSTR old_file, LPCWSTR new_file, ULONG apply_flags) 446 { 447 BOOL ret = FALSE; 448 HANDLE hPatch, hOld, hNew; 449 450 hPatch = CreateFileW(patch_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 451 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); 452 if (hPatch != INVALID_HANDLE_VALUE) 453 { 454 hOld = CreateFileW(old_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 455 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); 456 if (hOld != INVALID_HANDLE_VALUE) 457 { 458 hNew = CreateFileW(new_file, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, 459 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); 460 if (hNew != INVALID_HANDLE_VALUE) 461 { 462 ret = ApplyPatchToFileByHandles(hPatch, hOld, hNew, apply_flags); 463 CloseHandle(hNew); 464 } 465 CloseHandle(hOld); 466 } 467 CloseHandle(hPatch); 468 } 469 470 return ret; 471 } 472 473 /***************************************************** 474 * GetFilePatchSignatureA (MSPATCHA.7) 475 */ 476 BOOL WINAPI GetFilePatchSignatureA(LPCSTR filename, ULONG flags, PVOID data, ULONG ignore_range_count, 477 PPATCH_IGNORE_RANGE ignore_range, ULONG retain_range_count, 478 PPATCH_RETAIN_RANGE retain_range, ULONG bufsize, PVOID buffer) 479 { 480 BOOL ret = FALSE; 481 HANDLE hFile; 482 483 hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 484 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); 485 if (hFile != INVALID_HANDLE_VALUE) 486 { 487 ret = GetFilePatchSignatureByHandle(hFile, flags, data, ignore_range_count, ignore_range, 488 retain_range_count, retain_range, bufsize, buffer); 489 CloseHandle(hFile); 490 } 491 492 return ret; 493 } 494 495 /***************************************************** 496 * GetFilePatchSignatureA (MSPATCHA.7) 497 */ 498 BOOL WINAPI GetFilePatchSignatureByHandle(HANDLE hFile, ULONG flags, PVOID data, ULONG ignore_range_count, 499 PPATCH_IGNORE_RANGE ignore_range, ULONG retain_range_count, 500 PPATCH_RETAIN_RANGE retain_range, ULONG bufsize, PVOID buffer) 501 { 502 BOOL ret = FALSE; 503 DWORD dwSize, ulCrc; 504 PBYTE pData; 505 506 if (flags) 507 FIXME("Unhandled flags 0x%x\n", flags); 508 if (ignore_range_count) 509 FIXME("Unhandled ignore_range_count %u\n", ignore_range_count); 510 if (retain_range_count) 511 FIXME("Unhandled ignore_range_count %u\n", retain_range_count); 512 513 if ((pData = MapFile(hFile, &dwSize))) 514 { 515 if (dwSize >= 2 && *(PWORD)pData == IMAGE_DOS_SIGNATURE) 516 { 517 FIXME("Potentially unimplemented case, normalized signature\n"); 518 } 519 520 ulCrc = RtlComputeCrc32(0, pData, dwSize); 521 if (bufsize >= SIGNATURE_MIN_SIZE) 522 { 523 char *pBuffer = buffer; 524 pBuffer[8] = '\0'; 525 for (dwSize = 0; dwSize < 8; ++dwSize) 526 { 527 pBuffer[7 - dwSize] = szHexString[ulCrc & 0xf]; 528 ulCrc >>= 4; 529 } 530 ret = TRUE; 531 } 532 UnmapViewOfFile(pData); 533 534 if (bufsize < SIGNATURE_MIN_SIZE) 535 SetLastError(ERROR_INSUFFICIENT_BUFFER); 536 } 537 538 return ret; 539 } 540 541 /***************************************************** 542 * GetFilePatchSignatureW (MSPATCHA.9) 543 */ 544 BOOL WINAPI GetFilePatchSignatureW(LPCWSTR filename, ULONG flags, PVOID data, ULONG ignore_range_count, 545 PPATCH_IGNORE_RANGE ignore_range, ULONG retain_range_count, 546 PPATCH_RETAIN_RANGE retain_range, ULONG bufsize, PVOID buffer) 547 { 548 CHAR LocalBuf[SIGNATURE_MIN_SIZE]; 549 BOOL ret = FALSE; 550 HANDLE hFile; 551 552 hFile = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 553 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); 554 if (hFile != INVALID_HANDLE_VALUE) 555 { 556 ret = GetFilePatchSignatureByHandle(hFile, flags, data, ignore_range_count, ignore_range, 557 retain_range_count, retain_range, sizeof(LocalBuf), LocalBuf); 558 CloseHandle(hFile); 559 560 if (bufsize < (SIGNATURE_MIN_SIZE * sizeof(WCHAR))) 561 { 562 SetLastError(ERROR_INSUFFICIENT_BUFFER); 563 return FALSE; 564 } 565 if (ret) 566 { 567 MultiByteToWideChar(CP_ACP, 0, LocalBuf, -1, buffer, bufsize / sizeof(WCHAR)); 568 } 569 } 570 571 return ret; 572 } 573 574 /***************************************************** 575 * TestApplyPatchToFileA (MSPATCHA.10) 576 */ 577 BOOL WINAPI TestApplyPatchToFileA(LPCSTR patch_file, LPCSTR old_file, ULONG apply_flags) 578 { 579 BOOL ret = FALSE; 580 HANDLE hPatch, hOld; 581 582 hPatch = CreateFileA(patch_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 583 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); 584 if (hPatch != INVALID_HANDLE_VALUE) 585 { 586 hOld = CreateFileA(old_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 587 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); 588 if (hOld != INVALID_HANDLE_VALUE) 589 { 590 ret = TestApplyPatchToFileByHandles(hPatch, hOld, apply_flags); 591 CloseHandle(hOld); 592 } 593 CloseHandle(hPatch); 594 } 595 596 return ret; 597 } 598 599 /***************************************************** 600 * TestApplyPatchToFileByHandles (MSPATCHA.11) 601 */ 602 BOOL WINAPI TestApplyPatchToFileByHandles(HANDLE patch_file, HANDLE old_file, ULONG apply_flags) 603 { 604 return ApplyPatchToFileByHandles(patch_file, old_file, INVALID_HANDLE_VALUE, apply_flags | APPLY_OPTION_TEST_ONLY); 605 } 606 607 /***************************************************** 608 * TestApplyPatchToFileW (MSPATCHA.12) 609 */ 610 BOOL WINAPI TestApplyPatchToFileW(LPCWSTR patch_file, LPCWSTR old_file, ULONG apply_flags) 611 { 612 BOOL ret = FALSE; 613 HANDLE hPatch, hOld; 614 615 hPatch = CreateFileW(patch_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 616 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); 617 if (hPatch != INVALID_HANDLE_VALUE) 618 { 619 hOld = CreateFileW(old_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 620 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); 621 if (hOld != INVALID_HANDLE_VALUE) 622 { 623 ret = TestApplyPatchToFileByHandles(hPatch, hOld, apply_flags); 624 CloseHandle(hOld); 625 } 626 CloseHandle(hPatch); 627 } 628 629 return ret; 630 } 631