reactos/dll/win32/mspatcha/mspatcha_main.c

631 lines
17 KiB
C
Raw Normal View History

/*
* PatchAPI
*
* Copyright 2011 David Hedberg for CodeWeavers
* Copyright 2018 Mark Jansen (mark.jansen@reactos.org)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#define WIN32_NO_STATUS
#include "windef.h"
#include "winbase.h"
#include "winnls.h"
#include "ndk/rtlfuncs.h"
#include "patchapi.h"
#include "lzx.h"
#include "wine/debug.h"
static const char szHexString[] = "0123456789abcdef";
#define SIGNATURE_MIN_SIZE 9
#define UNKNOWN_FLAGS_COMBINATION 0x00c40001
WINE_DEFAULT_DEBUG_CHANNEL(mspatcha);
typedef struct _SAFE_READ
{
PBYTE Root;
DWORD Size;
PBYTE Ptr;
} SAFE_READ, *PSAFE_READ;
/**
* @name ReadByte
* Read the next byte available from @param pRead
*
* @param pRead
* The input buffer
*
* @return The byte, or 0
*/
BYTE ReadByte(PSAFE_READ pRead)
{
if (pRead->Ptr + sizeof(BYTE) <= (pRead->Root + pRead->Size))
{
BYTE Value = *(PBYTE)pRead->Ptr;
pRead->Ptr += sizeof(BYTE);
return Value;
}
pRead->Ptr = pRead->Root + pRead->Size;
return 0;
}
/**
* @name ReadUShort
* Read the next unsigned short available from @param pRead
*
* @param pRead
* The input buffer
*
* @return The unsigned short, or 0
*/
USHORT ReadUShort(PSAFE_READ pRead)
{
if (pRead->Ptr + sizeof(USHORT) <= (pRead->Root + pRead->Size))
{
USHORT Value = *(PUSHORT)pRead->Ptr;
pRead->Ptr += sizeof(USHORT);
return Value;
}
pRead->Ptr = pRead->Root + pRead->Size;
return 0;
}
/**
* @name ReadDWord
* Read the next dword available from @param pRead
*
* @param pRead
* The input buffer
*
* @return The dword, or 0
*/
DWORD ReadDWord(PSAFE_READ pRead)
{
if (pRead->Ptr + sizeof(DWORD) <= (pRead->Root + pRead->Size))
{
DWORD Value = *(PDWORD)pRead->Ptr;
pRead->Ptr += sizeof(DWORD);
return Value;
}
pRead->Ptr = pRead->Root + pRead->Size;
return 0;
}
/**
* @name DecodeDWord
* Read the next variable length-encoded dword from @param pRead
*
* @param pRead
* The input buffer
*
* @return The dword, or 0
*/
DWORD DecodeDWord(PSAFE_READ pRead)
{
UINT Result = 0, offset;
for (offset = 0; offset < 32; offset += 7)
{
DWORD val = ReadByte(pRead);
Result |= ((val & 0x7f) << offset);
if (val & 0x80)
break;
}
return Result;
}
/**
* @name DecodeInt
* Read the next variable length-encoded int from @param pRead
*
* @param pRead
* The input buffer
*
* @return The int, or 0
*/
INT DecodeInt(PSAFE_READ pRead)
{
INT Result = 0, offset;
for (offset = 0; offset < 32; offset += 6)
{
INT val = (INT)(DWORD)ReadByte(pRead);
Result |= ((val & 0x3f) << offset);
if (val & 0x80)
{
if (val & 0x40)
Result *= -1;
break;
}
}
return Result;
}
typedef struct _PATCH_HEADER
{
DWORD Flags;
DWORD ImageBase;
DWORD ImageTimeStamp;
DWORD OutputSize;
DWORD OutputCrc;
DWORD OldSize;
DWORD OldCrc;
DWORD DataSize; // Payload after the patch header
} PATCH_HEADER;
/**
* @name MapFile
* Map a view of a file into readonly memory
*
* @param hFile
* The input file handle, readable
*
* @param dwSize
* Mapped file size (out)
*
* @return A Pointer to the start of the memory
*/
static PBYTE MapFile(HANDLE hFile, DWORD* dwSize)
{
HANDLE hMap;
PVOID pView;
*dwSize = GetFileSize(hFile, NULL);
hMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hMap != INVALID_HANDLE_VALUE)
{
pView = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
CloseHandle(hMap);
return pView;
}
return NULL;
}
/*****************************************************
* DllMain (MSPATCHA.@)
*/
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hinstDLL);
break;
}
return TRUE;
}
/*****************************************************
* ApplyPatchToFileA (MSPATCHA.1)
*/
BOOL WINAPI ApplyPatchToFileA(LPCSTR patch_file, LPCSTR old_file, LPCSTR new_file, ULONG apply_flags)
{
BOOL ret = FALSE;
HANDLE hPatch, hOld, hNew;
hPatch = CreateFileA(patch_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
if (hPatch != INVALID_HANDLE_VALUE)
{
hOld = CreateFileA(old_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
if (hOld != INVALID_HANDLE_VALUE)
{
hNew = CreateFileA(new_file, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (hNew != INVALID_HANDLE_VALUE)
{
ret = ApplyPatchToFileByHandles(hPatch, hOld, hNew, apply_flags);
CloseHandle(hNew);
}
CloseHandle(hOld);
}
CloseHandle(hPatch);
}
return ret;
}
/**
* @name ParseHeader
* Parse a Patch file header
* @note The current implementation is far from complete!
*
* @param Patch
* Buffer pointing to the raw patch data
*
* @param Header
* The result of the parsed header
*
* @return STATUS_SUCCESS on success, an error code otherwise
*/
DWORD ParseHeader(SAFE_READ* Patch, PATCH_HEADER* Header)
{
DWORD Crc, Unknown;
int Delta;
ZeroMemory(Header, sizeof(*Header));
/* Validate the patch */
Crc = RtlComputeCrc32(0, Patch->Root, Patch->Size);
if (Crc != ~0)
return ERROR_PATCH_CORRUPT;
if (ReadDWord(Patch) != '91AP')
return ERROR_PATCH_DECODE_FAILURE;
/* Read the flags, warn about an unknown combination */
Header->Flags = ReadDWord(Patch);
if (Header->Flags ^ UNKNOWN_FLAGS_COMBINATION)
ERR("Unknown flags: 0x%x, patch will most likely fail\n", Header->Flags ^ UNKNOWN_FLAGS_COMBINATION);
/* 0x5bb3284e, 0x5bb33562, 0x5bb357b1 */
Unknown = ReadDWord(Patch);
TRACE("Unknown: 0x%x\n", Unknown);
Header->OutputSize = DecodeDWord(Patch);
Header->OutputCrc = ReadDWord(Patch);
Unknown = ReadByte(Patch);
if (Unknown != 1)
ERR("Field after CRC is not 1 but %u\n", Unknown);
Delta = DecodeInt(Patch);
Header->OldSize = Header->OutputSize + Delta;
Header->OldCrc = ReadDWord(Patch);
Unknown = ReadUShort(Patch);
if (Unknown != 0)
ERR("Field1 after OldCrc is not 0 but %u\n", Unknown);
Unknown = DecodeDWord(Patch);
if (Unknown != 0)
ERR("Field2 after OldCrc is not 0 but %u\n", Unknown);
Header->DataSize = DecodeDWord(Patch);
/* Remaining data, minus the CRC appended */
if (Header->DataSize != (Patch->Size - (Patch->Ptr - Patch->Root) - sizeof(DWORD)))
{
ERR("Unable to read header, check previous logging!\n");
return ERROR_PATCH_DECODE_FAILURE;
}
return STATUS_SUCCESS;
}
/**
* @name CreateNewFileFromPatch
* Using the input @param Header and @param Patch, create a new file on @param new_file
*
* @param Header
* Parsed / preprocessed patch header
*
* @param Patch
* Memory buffer pointing to the patch payload
*
* @param new_file
* A handle to the output file. This file will be resized
*
* @return STATUS_SUCCESS on success, an error code otherwise
*/
DWORD CreateNewFileFromPatch(PATCH_HEADER* Header, SAFE_READ* Patch, HANDLE new_file)
{
SAFE_READ NewFile;
HANDLE hMap;
USHORT BlockSize;
DWORD dwStatus;
struct LZXstate* state;
int lzxResult;
hMap = CreateFileMappingW(new_file, NULL, PAGE_READWRITE, 0, Header->OutputSize, NULL);
if (hMap == INVALID_HANDLE_VALUE)
return ERROR_PATCH_NOT_AVAILABLE;
NewFile.Root = NewFile.Ptr = MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, 0);
CloseHandle(hMap);
NewFile.Size = Header->OutputSize;
if (!NewFile.Root)
return ERROR_PATCH_NOT_AVAILABLE;
/* At this point Patch->Ptr should point to the payload */
BlockSize = ReadUShort(Patch);
/* This window size does not work on all files (for example, MS SQL Express 2008 setup) */
state = LZXinit(17);
if (state)
{
lzxResult = LZXdecompress(state, Patch->Ptr, NewFile.Ptr, BlockSize, NewFile.Size);
LZXteardown(state);
if (lzxResult == DECR_OK)
dwStatus = STATUS_SUCCESS;
else
dwStatus = ERROR_PATCH_DECODE_FAILURE;
}
else
{
dwStatus = ERROR_INSUFFICIENT_BUFFER;
}
UnmapViewOfFile(NewFile.Root);
return dwStatus;
}
/*****************************************************
* ApplyPatchToFileByHandles (MSPATCHA.2)
*/
BOOL WINAPI ApplyPatchToFileByHandles(HANDLE patch_file, HANDLE old_file, HANDLE new_file, ULONG apply_flags)
{
SAFE_READ Patch, OldFile;
DWORD dwStatus;
PATCH_HEADER Header;
Patch.Root = Patch.Ptr = MapFile(patch_file, &Patch.Size);
if (!Patch.Root)
{
SetLastError(ERROR_PATCH_CORRUPT);
return FALSE;
}
/* Let's decode the header */
dwStatus = ParseHeader(&Patch, &Header);
if (dwStatus != STATUS_SUCCESS)
{
UnmapViewOfFile(Patch.Root);
SetLastError(dwStatus);
return FALSE;
}
OldFile.Root = OldFile.Ptr = MapFile(old_file, &OldFile.Size);
if (OldFile.Root)
{
DWORD dwCrc;
/* Verify the input file */
dwCrc = RtlComputeCrc32(0, OldFile.Root, OldFile.Size);
if (OldFile.Size == Header.OldSize && dwCrc == Header.OldCrc)
{
if (apply_flags & APPLY_OPTION_TEST_ONLY)
dwStatus = STATUS_SUCCESS;
else
dwStatus = CreateNewFileFromPatch(&Header, &Patch, new_file);
}
else
{
dwStatus = ERROR_PATCH_WRONG_FILE;
}
UnmapViewOfFile(OldFile.Root);
}
else
{
dwStatus = GetLastError();
if (dwStatus == STATUS_SUCCESS)
dwStatus = ERROR_PATCH_NOT_AVAILABLE;
}
UnmapViewOfFile(Patch.Root);
SetLastError(dwStatus);
return dwStatus == STATUS_SUCCESS;
}
/*****************************************************
* ApplyPatchToFileW (MSPATCHA.6)
*/
BOOL WINAPI ApplyPatchToFileW(LPCWSTR patch_file, LPCWSTR old_file, LPCWSTR new_file, ULONG apply_flags)
{
BOOL ret = FALSE;
HANDLE hPatch, hOld, hNew;
hPatch = CreateFileW(patch_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
if (hPatch != INVALID_HANDLE_VALUE)
{
hOld = CreateFileW(old_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
if (hOld != INVALID_HANDLE_VALUE)
{
hNew = CreateFileW(new_file, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (hNew != INVALID_HANDLE_VALUE)
{
ret = ApplyPatchToFileByHandles(hPatch, hOld, hNew, apply_flags);
CloseHandle(hNew);
}
CloseHandle(hOld);
}
CloseHandle(hPatch);
}
return ret;
}
/*****************************************************
* GetFilePatchSignatureA (MSPATCHA.7)
*/
BOOL WINAPI GetFilePatchSignatureA(LPCSTR filename, ULONG flags, PVOID data, ULONG ignore_range_count,
PPATCH_IGNORE_RANGE ignore_range, ULONG retain_range_count,
PPATCH_RETAIN_RANGE retain_range, ULONG bufsize, PVOID buffer)
{
BOOL ret = FALSE;
HANDLE hFile;
hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
ret = GetFilePatchSignatureByHandle(hFile, flags, data, ignore_range_count, ignore_range,
retain_range_count, retain_range, bufsize, buffer);
CloseHandle(hFile);
}
return ret;
}
/*****************************************************
* GetFilePatchSignatureA (MSPATCHA.7)
*/
BOOL WINAPI GetFilePatchSignatureByHandle(HANDLE hFile, ULONG flags, PVOID data, ULONG ignore_range_count,
PPATCH_IGNORE_RANGE ignore_range, ULONG retain_range_count,
PPATCH_RETAIN_RANGE retain_range, ULONG bufsize, PVOID buffer)
{
BOOL ret = FALSE;
DWORD dwSize, ulCrc;
PBYTE pData;
if (flags)
FIXME("Unhandled flags 0x%x\n", flags);
if (ignore_range_count)
FIXME("Unhandled ignore_range_count %u\n", ignore_range_count);
if (retain_range_count)
FIXME("Unhandled ignore_range_count %u\n", retain_range_count);
if ((pData = MapFile(hFile, &dwSize)))
{
if (dwSize >= 2 && *(PWORD)pData == IMAGE_DOS_SIGNATURE)
{
FIXME("Potentially unimplemented case, normalized signature\n");
}
ulCrc = RtlComputeCrc32(0, pData, dwSize);
if (bufsize >= SIGNATURE_MIN_SIZE)
{
char *pBuffer = buffer;
pBuffer[8] = '\0';
for (dwSize = 0; dwSize < 8; ++dwSize)
{
pBuffer[7 - dwSize] = szHexString[ulCrc & 0xf];
ulCrc >>= 4;
}
ret = TRUE;
}
UnmapViewOfFile(pData);
if (bufsize < SIGNATURE_MIN_SIZE)
SetLastError(ERROR_INSUFFICIENT_BUFFER);
}
return ret;
}
/*****************************************************
* GetFilePatchSignatureW (MSPATCHA.9)
*/
BOOL WINAPI GetFilePatchSignatureW(LPCWSTR filename, ULONG flags, PVOID data, ULONG ignore_range_count,
PPATCH_IGNORE_RANGE ignore_range, ULONG retain_range_count,
PPATCH_RETAIN_RANGE retain_range, ULONG bufsize, PVOID buffer)
{
CHAR LocalBuf[SIGNATURE_MIN_SIZE];
BOOL ret = FALSE;
HANDLE hFile;
hFile = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
ret = GetFilePatchSignatureByHandle(hFile, flags, data, ignore_range_count, ignore_range,
retain_range_count, retain_range, sizeof(LocalBuf), LocalBuf);
CloseHandle(hFile);
if (bufsize < (SIGNATURE_MIN_SIZE * sizeof(WCHAR)))
{
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return FALSE;
}
if (ret)
{
MultiByteToWideChar(CP_ACP, 0, LocalBuf, -1, buffer, bufsize / sizeof(WCHAR));
}
}
return ret;
}
/*****************************************************
* TestApplyPatchToFileA (MSPATCHA.10)
*/
BOOL WINAPI TestApplyPatchToFileA(LPCSTR patch_file, LPCSTR old_file, ULONG apply_flags)
{
BOOL ret = FALSE;
HANDLE hPatch, hOld;
hPatch = CreateFileA(patch_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
if (hPatch != INVALID_HANDLE_VALUE)
{
hOld = CreateFileA(old_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
if (hOld != INVALID_HANDLE_VALUE)
{
ret = TestApplyPatchToFileByHandles(hPatch, hOld, apply_flags);
CloseHandle(hOld);
}
CloseHandle(hPatch);
}
return ret;
}
/*****************************************************
* TestApplyPatchToFileByHandles (MSPATCHA.11)
*/
BOOL WINAPI TestApplyPatchToFileByHandles(HANDLE patch_file, HANDLE old_file, ULONG apply_flags)
{
return ApplyPatchToFileByHandles(patch_file, old_file, INVALID_HANDLE_VALUE, apply_flags | APPLY_OPTION_TEST_ONLY);
}
/*****************************************************
* TestApplyPatchToFileW (MSPATCHA.12)
*/
BOOL WINAPI TestApplyPatchToFileW(LPCWSTR patch_file, LPCWSTR old_file, ULONG apply_flags)
{
BOOL ret = FALSE;
HANDLE hPatch, hOld;
hPatch = CreateFileW(patch_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
if (hPatch != INVALID_HANDLE_VALUE)
{
hOld = CreateFileW(old_file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
if (hOld != INVALID_HANDLE_VALUE)
{
ret = TestApplyPatchToFileByHandles(hPatch, hOld, apply_flags);
CloseHandle(hOld);
}
CloseHandle(hPatch);
}
return ret;
}