You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
698 lines
17 KiB
698 lines
17 KiB
#include "nt.h"
|
|
#include "ntdef.h"
|
|
#include "ntrtl.h"
|
|
#include "nturtl.h"
|
|
#include "ntrtl.h"
|
|
#include "ntosp.h"
|
|
#include "stdio.h"
|
|
|
|
#include "sxs-rtl.h"
|
|
#include "fasterxml.h"
|
|
#include "skiplist.h"
|
|
#include "namespacemanager.h"
|
|
#include "xmlstructure.h"
|
|
#include "sxsid.h"
|
|
#include "xmlassert.h"
|
|
#include "manifestinspection.h"
|
|
|
|
void
|
|
RtlTraceNtSuccessFailure(
|
|
PCSTR pcszStatement,
|
|
NTSTATUS FailureCode,
|
|
PCSTR pcszFileName,
|
|
LONG LineNumber
|
|
)
|
|
{
|
|
CHAR SmallBuffer[512];
|
|
STRING s;
|
|
s.Buffer = SmallBuffer;
|
|
s.Length = s.MaximumLength = (USHORT)_snprintf(
|
|
"%s(%d): NTSTATUS 0x%08lx from '%s'\n",
|
|
NUMBER_OF(SmallBuffer),
|
|
pcszFileName,
|
|
LineNumber,
|
|
FailureCode,
|
|
pcszStatement);
|
|
|
|
#if 0 // When we move to kernel mode, we should turn this on - for now, let's just use OutputDebugStringA
|
|
DebugPrint(&s, 0, 0);
|
|
#else
|
|
printf(SmallBuffer);
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
#undef NT_SUCCESS
|
|
#define NT_SUCCESS(q) (((status = (q)) < 0) ? (RtlTraceNtSuccessFailure(#q, status, __FILE__, __LINE__), FALSE) : TRUE)
|
|
|
|
|
|
|
|
|
|
NTSTATUS
|
|
RtlpGenerateIdentityFromAttributes(
|
|
IN PXML_TOKENIZATION_STATE pState,
|
|
IN PXMLDOC_ATTRIBUTE pAttributeList,
|
|
IN ULONG ulAttributes,
|
|
IN OUT PUNICODE_STRING pusDiskName,
|
|
IN OUT PUNICODE_STRING pusTextualIdentity
|
|
)
|
|
/*++
|
|
|
|
Parameters:
|
|
|
|
pState - State of xml tokenization/parsing that can be used to extract strings
|
|
and other stuff from the attributes in pAttributeList
|
|
|
|
pAttributeList - Array of pointers to PXMLDOC_ATTRIBUTE structures that
|
|
represent the identity attributes
|
|
|
|
ulAttributes - Number of attributes in pAttributeList
|
|
|
|
pusDiskName - Pointer to a UNICODE_STRING whose MaxLength is enough to contain
|
|
104 wchars. On exit, pusDiskName->Buffer will contain the on-disk identity
|
|
of this set of attributes, and pusDiskName->Length will be the length of
|
|
said data. (Not null terminated!)
|
|
|
|
pusTextualIdentity - Pointer to a UNICODE_STRING which will be filled out on
|
|
exit with the 'textual identity' of this set of attributes.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
ULONG ulHash = 0;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
RtlGetSxsAssemblyRoot(
|
|
ULONG ulFlags,
|
|
PUNICODE_STRING pusTempPathname,
|
|
PUSHORT pulRequiredChars
|
|
)
|
|
{
|
|
static const UNICODE_STRING s_us_WinSxsRoot = RTL_CONSTANT_STRING(L"\\WinSxS\\");
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
UNICODE_STRING NtSystemRoot;
|
|
USHORT usLength;
|
|
|
|
//
|
|
// If there was a buffer, zero out the length so a naive caller won't
|
|
// accidentally use it.
|
|
//
|
|
if (pusTempPathname) {
|
|
pusTempPathname->Length = 0;
|
|
}
|
|
|
|
RtlInitUnicodeString(&NtSystemRoot, USER_SHARED_DATA->NtSystemRoot);
|
|
usLength = NtSystemRoot.Length + s_us_WinSxsRoot.Length;
|
|
|
|
if (pulRequiredChars)
|
|
*pulRequiredChars = usLength;
|
|
|
|
// No buffer, or it's too small completely, then oops
|
|
if (!pusTempPathname || (pusTempPathname->MaximumLength < usLength)) {
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
// Otherwise, start copying
|
|
else {
|
|
PWCHAR pwszCursor = pusTempPathname->Buffer;
|
|
|
|
RtlCopyMemory(pwszCursor, NtSystemRoot.Buffer, NtSystemRoot.Length);
|
|
RtlCopyMemory((PCHAR)pwszCursor + NtSystemRoot.Length, s_us_WinSxsRoot.Buffer, s_us_WinSxsRoot.Length);
|
|
pusTempPathname->Length = usLength;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
// Installtemp identifiers are a combination of the current system time in
|
|
// a "nicely formatted" format, plus some 16-bit hex uniqueness value
|
|
#define CHARS_IN_INSTALLTEMP_IDENT (NUMBER_OF("yyyymmddhhmmssllll.xxxx") - 1)
|
|
|
|
|
|
|
|
|
|
|
|
NTSTATUS
|
|
RtlpCreateWinSxsTempPath(
|
|
ULONG ulFlags,
|
|
PUNICODE_STRING pusTempPath,
|
|
WCHAR wchStatic,
|
|
USHORT uscchStatus
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
USHORT ulLength = 0;
|
|
|
|
if ((pusTempPath == NULL) || (ulFlags != 0)) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Get the length of the root path
|
|
//
|
|
status = RtlGetSxsAssemblyRoot(0, NULL, &ulLength);
|
|
if (!NT_SUCCESS(status) && (status != STATUS_BUFFER_TOO_SMALL)) {
|
|
return status;
|
|
}
|
|
|
|
ulLength += 1 + CHARS_IN_INSTALLTEMP_IDENT;
|
|
|
|
//
|
|
// Ensure there's space
|
|
//
|
|
if (ulLength >= pusTempPath->MaximumLength) {
|
|
pusTempPath->MaximumLength = ulLength;
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
//
|
|
// Get it again.
|
|
//
|
|
status = RtlGetSxsAssemblyRoot(0, pusTempPath, &ulLength);
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NTSTATUS
|
|
RtlpPrepareForAssemblyInstall(
|
|
ULONG ulFlags,
|
|
PUNICODE_STRING pusTempPathname
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
USHORT ulRequired = 0;
|
|
|
|
// Find out how long the 'root' path is.
|
|
status = RtlGetSxsAssemblyRoot(0, NULL, &ulRequired);
|
|
|
|
// Now let's find out how long our id is going to be
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
const static WCHAR s_rgchBase64Encoding[] = {
|
|
L'A', L'B', L'C', L'D', L'E', L'F', L'G', L'H', L'I', L'J', L'K', // 11
|
|
L'L', L'M', L'N', L'O', L'P', L'Q', L'R', L'S', L'T', L'U', L'V', // 22
|
|
L'W', L'X', L'Y', L'Z', L'a', L'b', L'c', L'd', L'e', L'f', L'g', // 33
|
|
L'h', L'i', L'j', L'k', L'l', L'm', L'n', L'o', L'p', L'q', L'r', // 44
|
|
L's', L't', L'u', L'v', L'w', L'x', L'y', L'z', L'0', L'1', L'2', // 55
|
|
L'3', L'4', L'5', L'6', L'7', L'8', L'9', L'+', L'/' // 64
|
|
};
|
|
|
|
|
|
NTSTATUS
|
|
RtlBase64Encode(
|
|
PVOID pvBuffer,
|
|
SIZE_T cbBuffer,
|
|
PWSTR pwszEncoded,
|
|
PSIZE_T pcchEncoded
|
|
)
|
|
{
|
|
SIZE_T cchRequiredEncodingSize;
|
|
SIZE_T iInput, iOutput;
|
|
|
|
//
|
|
// Null input buffer, null output size pointer, and a nonzero
|
|
// encoded size with a null output buffer are all invalid
|
|
// parameters
|
|
//
|
|
if (!pvBuffer || !pcchEncoded || ((*pcchEncoded > 0) && !pwszEncoded)) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Make sure the buffer is large enough
|
|
//
|
|
cchRequiredEncodingSize = ((cbBuffer + 2) / 3) * 4;
|
|
|
|
if (*pcchEncoded < cchRequiredEncodingSize) {
|
|
*pcchEncoded = cchRequiredEncodingSize;
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
//
|
|
// Convert the input buffer bytes through the encoding table and
|
|
// out into the output buffer.
|
|
//
|
|
iInput = iOutput = 0;
|
|
while (iInput < cbBuffer) {
|
|
const UCHAR uc0 = ((PUCHAR)pvBuffer)[iInput++];
|
|
const UCHAR uc1 = (iInput < cbBuffer) ? ((PUCHAR)pvBuffer)[iInput++] : 0;
|
|
const UCHAR uc2 = (iInput < cbBuffer) ? ((PUCHAR)pvBuffer)[iInput++] : 0;
|
|
|
|
pwszEncoded[iOutput++] = s_rgchBase64Encoding[uc0 >> 2];
|
|
pwszEncoded[iOutput++] = s_rgchBase64Encoding[((uc0 << 4) & 0x30) | ((uc1 >> 4) & 0xf)];
|
|
pwszEncoded[iOutput++] = s_rgchBase64Encoding[((uc1 << 2) & 0x3c) | ((uc2 >> 6) & 0x3)];
|
|
pwszEncoded[iOutput++] = s_rgchBase64Encoding[uc2 & 0x3f];
|
|
}
|
|
|
|
//
|
|
// Fill in leftover bytes at the end
|
|
//
|
|
switch(cbBuffer % 3) {
|
|
case 0:
|
|
break;
|
|
//
|
|
// One byte out of three, add padding and fall through
|
|
//
|
|
case 1:
|
|
pwszEncoded[iOutput - 2] = L'=';
|
|
//
|
|
// Two bytes out of three, add padding.
|
|
case 2:
|
|
pwszEncoded[iOutput - 1] = L'=';
|
|
break;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NTSTATUS
|
|
RtlInstallAssembly(
|
|
ULONG ulFlags,
|
|
PCWSTR pcwszManifestPath
|
|
)
|
|
{
|
|
SIZE_T cbFileSize;
|
|
PVOID pvFileBase = 0;
|
|
NTSTATUS status;
|
|
PRTL_MANIFEST_CONTENT_RAW pRawContent = NULL;
|
|
XML_TOKENIZATION_STATE TokenizationStateUsed;
|
|
UNICODE_STRING usFilePath;
|
|
|
|
status = RtlSxsInitializeManifestRawContent(RTLIMS_GATHER_FILES, &pRawContent, NULL, 0);
|
|
if (!NT_SUCCESS(status)) {
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Get ahold of the file
|
|
//
|
|
status = RtlOpenAndMapEntireFile(pcwszManifestPath, &pvFileBase, &cbFileSize);
|
|
if (!NT_SUCCESS(status)) {
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// We should have found some files
|
|
//
|
|
status = RtlInspectManifestStream(
|
|
RTLIMS_GATHER_FILES,
|
|
pvFileBase,
|
|
cbFileSize,
|
|
pRawContent,
|
|
&TokenizationStateUsed);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto Exit;
|
|
|
|
//
|
|
// Validate that the assembly
|
|
|
|
Exit:
|
|
if (pRawContent) {
|
|
RtlSxsDestroyManifestContent(pRawContent);
|
|
}
|
|
RtlUnmapViewOfFile(pvFileBase);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
RtlDosPathNameToNtPathName_Ustr(
|
|
IN PCUNICODE_STRING DosFileNameString,
|
|
OUT PUNICODE_STRING NtFileName,
|
|
OUT PWSTR *FilePart OPTIONAL,
|
|
OUT PRTL_RELATIVE_NAME_U RelativeName OPTIONAL
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
RtlOpenAndMapEntireFile(
|
|
PCWSTR pcwszFilePath,
|
|
PVOID *ppvMappedView,
|
|
PSIZE_T pcbFileSize
|
|
)
|
|
{
|
|
HANDLE SectionHandle = INVALID_HANDLE_VALUE;
|
|
HANDLE FileHandle = INVALID_HANDLE_VALUE;
|
|
UNICODE_STRING ObjectName;
|
|
OBJECT_ATTRIBUTES ObjA;
|
|
POBJECT_ATTRIBUTES pObjA;
|
|
ACCESS_MASK DesiredAccess;
|
|
ULONG ulAllocationAttributes;
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK IOStatusBlock;
|
|
BOOLEAN Translation;
|
|
SIZE_T FileSize;
|
|
FILE_STANDARD_INFORMATION Info;
|
|
|
|
if (pcbFileSize) {
|
|
*pcbFileSize = 0;
|
|
}
|
|
|
|
if (ppvMappedView) {
|
|
*ppvMappedView = NULL;
|
|
}
|
|
|
|
if (!ARGUMENT_PRESENT(pcwszFilePath)) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (!ARGUMENT_PRESENT(pcbFileSize)) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (!ARGUMENT_PRESENT(ppvMappedView)) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
Translation = RtlDosPathNameToNtPathName_U(
|
|
pcwszFilePath,
|
|
&ObjectName,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (!Translation) {
|
|
return STATUS_NOT_FOUND;
|
|
}
|
|
|
|
|
|
//
|
|
// Open the file requested
|
|
//
|
|
InitializeObjectAttributes(
|
|
&ObjA,
|
|
&ObjectName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
|
|
status = NtOpenFile(
|
|
&FileHandle,
|
|
FILE_GENERIC_READ,
|
|
&ObjA,
|
|
&IOStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_DELETE,
|
|
FILE_NON_DIRECTORY_FILE);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
goto ErrorExit;
|
|
}
|
|
|
|
status = NtQueryInformationFile(
|
|
FileHandle,
|
|
&IOStatusBlock,
|
|
&Info,
|
|
sizeof(Info),
|
|
FileStandardInformation);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
goto ErrorExit;
|
|
}
|
|
|
|
*pcbFileSize = (SIZE_T)Info.EndOfFile.QuadPart;
|
|
|
|
status = NtCreateSection(
|
|
&SectionHandle,
|
|
SECTION_MAP_READ | SECTION_QUERY,
|
|
NULL,
|
|
NULL,
|
|
PAGE_READONLY,
|
|
SEC_COMMIT,
|
|
FileHandle);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Don't need the file object anymore, unmap it
|
|
//
|
|
status = NtClose(FileHandle);
|
|
FileHandle = INVALID_HANDLE_VALUE;;
|
|
|
|
*ppvMappedView = NULL;
|
|
|
|
//
|
|
// Map the whole file
|
|
//
|
|
status = NtMapViewOfSection(
|
|
SectionHandle,
|
|
NtCurrentProcess(),
|
|
ppvMappedView,
|
|
0, // Zero bits
|
|
0, // Committed size
|
|
NULL, // SectionOffset
|
|
pcbFileSize, // Size of this file, in bytes
|
|
ViewShare,
|
|
0,
|
|
PAGE_READONLY);
|
|
|
|
status = NtClose(SectionHandle);
|
|
SectionHandle = INVALID_HANDLE_VALUE;
|
|
|
|
//
|
|
// Reset this - the NtMapViewOfSection allocates on page granularity
|
|
//
|
|
*pcbFileSize = (SIZE_T)Info.EndOfFile.QuadPart;
|
|
|
|
Exit:
|
|
return status;
|
|
|
|
|
|
ErrorExit:
|
|
if (FileHandle != INVALID_HANDLE_VALUE) {
|
|
NtClose(FileHandle);
|
|
FileHandle = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if (SectionHandle != INVALID_HANDLE_VALUE) {
|
|
NtClose(SectionHandle);
|
|
SectionHandle = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if (ppvMappedView && (*ppvMappedView != NULL)) {
|
|
NTSTATUS newstatus = NtUnmapViewOfSection(NtCurrentProcess(), *ppvMappedView);
|
|
|
|
//
|
|
// Failed while failing
|
|
//
|
|
if (!NT_SUCCESS(newstatus)) {
|
|
}
|
|
|
|
*pcbFileSize = 0;
|
|
}
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
RtlUnmapViewOfFile(
|
|
PVOID pvBase
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
status = NtUnmapViewOfSection(
|
|
NtCurrentProcess(),
|
|
pvBase);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS FASTCALL
|
|
RtlMiniHeapAlloc(
|
|
SIZE_T cb,
|
|
PVOID *ppvAllocated,
|
|
PVOID pvContext
|
|
)
|
|
{
|
|
PRTL_MINI_HEAP pContent = (PRTL_MINI_HEAP)pvContext;
|
|
|
|
if ((pContent == NULL) || (ppvAllocated == NULL)) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (pContent->cbAvailableBytes < cb) {
|
|
return g_DefaultAllocator.pfnAlloc(cb, ppvAllocated, NULL);
|
|
}
|
|
else {
|
|
*ppvAllocated = pContent->pvNextAvailableByte;
|
|
pContent->cbAvailableBytes -= cb;
|
|
pContent->pvNextAvailableByte = (PUCHAR)pContent->pvNextAvailableByte + cb;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
NTSTATUS FASTCALL
|
|
RtlMiniHeapFree(
|
|
PVOID pvAllocation,
|
|
PVOID pvContext
|
|
)
|
|
{
|
|
PRTL_MINI_HEAP pContent = (PRTL_MINI_HEAP)pvContext;
|
|
|
|
if ((pvAllocation < pContent->pvAllocationBase) ||
|
|
(pvAllocation >= (PVOID)((PUCHAR)pContent->pvAllocationBase + pContent->cbOriginalSize)))
|
|
{
|
|
return g_DefaultAllocator.pfnFree(pvAllocation, NULL);
|
|
}
|
|
else {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
|
|
NTSTATUS FASTCALL
|
|
RtlInitializeMiniHeap(
|
|
PRTL_MINI_HEAP MiniHeap,
|
|
PVOID pvTargetRegion,
|
|
SIZE_T cbRegionSize
|
|
)
|
|
{
|
|
if (!MiniHeap || !(pvTargetRegion || (cbRegionSize == 0))) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
MiniHeap->pvNextAvailableByte = pvTargetRegion;
|
|
MiniHeap->pvAllocationBase = pvTargetRegion;
|
|
MiniHeap->cbAvailableBytes = cbRegionSize;
|
|
MiniHeap->cbOriginalSize = cbRegionSize;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS FASTCALL
|
|
RtlInitializeMiniHeapInPlace(
|
|
PVOID pvRegion,
|
|
SIZE_T cbOriginalSize,
|
|
PRTL_MINI_HEAP *ppMiniHeap
|
|
)
|
|
{
|
|
PRTL_MINI_HEAP pMiniHeapTemp = NULL;
|
|
|
|
if (!ppMiniHeap)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
*ppMiniHeap = NULL;
|
|
|
|
if (!(pvRegion || (cbOriginalSize == 0))) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (cbOriginalSize < sizeof(RTL_MINI_HEAP)) {
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
pMiniHeapTemp = (PRTL_MINI_HEAP)pvRegion;
|
|
pMiniHeapTemp->cbAvailableBytes = cbOriginalSize - sizeof(RTL_MINI_HEAP);
|
|
pMiniHeapTemp->cbOriginalSize = pMiniHeapTemp->cbAvailableBytes;
|
|
pMiniHeapTemp->pvAllocationBase = pMiniHeapTemp + 1;
|
|
pMiniHeapTemp->pvNextAvailableByte = pMiniHeapTemp->pvAllocationBase;
|
|
|
|
*ppMiniHeap = pMiniHeapTemp;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
RtlpConvertHexStringToBytes(
|
|
PUNICODE_STRING pSourceString,
|
|
PBYTE pbTarget,
|
|
SIZE_T cbTarget
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PCWSTR pcSource = pSourceString->Buffer;
|
|
ULONG ul = 0;
|
|
|
|
if (cbTarget < (pSourceString->Length / (2 * sizeof(WCHAR)))) {
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
else if ((pSourceString->Length % sizeof(WCHAR)) != 0) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
for (ul = 0; ul < (pSourceString->Length / sizeof(pSourceString->Buffer[0])); ul += 2) {
|
|
|
|
BYTE bvLow, bvHigh;
|
|
const WCHAR wchFirst = *pcSource++;
|
|
const WCHAR wchSecond = *pcSource++;
|
|
|
|
//
|
|
// Set the high nibble
|
|
//
|
|
switch (wchFirst) {
|
|
case L'0': case L'1': case L'2': case L'3':
|
|
case L'4': case L'5': case L'6': case L'7':
|
|
case L'8': case L'9':
|
|
bvHigh = wchFirst - L'0';
|
|
break;
|
|
|
|
case L'a': case L'b': case L'c':
|
|
case L'd': case L'e': case L'f':
|
|
bvHigh = (wchFirst - L'a') + 0x10;
|
|
break;
|
|
|
|
case L'A': case L'B': case L'C':
|
|
case L'D': case L'E': case L'F':
|
|
bvHigh = (wchFirst - L'A') + 0x10;
|
|
break;
|
|
|
|
default:
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Set the high nibble
|
|
//
|
|
switch (wchSecond) {
|
|
case L'0': case L'1': case L'2': case L'3':
|
|
case L'4': case L'5': case L'6': case L'7':
|
|
case L'8': case L'9':
|
|
bvLow = wchSecond - L'0';
|
|
break;
|
|
|
|
case L'a': case L'b': case L'c':
|
|
case L'd': case L'e': case L'f':
|
|
bvLow = (wchSecond - L'a') + 0x10;
|
|
break;
|
|
|
|
case L'A': case L'B': case L'C':
|
|
case L'D': case L'E': case L'F':
|
|
bvLow = (wchSecond - L'A') + 0x10;
|
|
break;
|
|
|
|
default:
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
pbTarget[ul / 2] = (bvHigh << 4) | bvLow;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|