/* Unit test suite for Rtl* Registry API functions
 *
 * Copyright 2003 Thomas Mertes
 * Copyright 2005 Brad DeMorrow
 *
 * 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
 *
 * NOTE: I don't test every RelativeTo value because it would be redundant, all calls go through
 * helper function RTL_GetKeyHandle().--Brad DeMorrow
 *
 */

#include "ntdll_test.h"
#include "winternl.h"
#include "stdio.h"
#include "winnt.h"
#include "winnls.h"
#include "stdlib.h"

/* A test string */
static const WCHAR stringW[] = {'s', 't', 'r', 'i', 'n', 'g', 'W', 0};
/* A size, in bytes, short enough to cause truncation of the above */
#define STR_TRUNC_SIZE (sizeof(stringW)-2*sizeof(*stringW))

#ifndef __WINE_WINTERNL_H

/* RtlQueryRegistryValues structs and defines */
#define RTL_REGISTRY_ABSOLUTE             0
#define RTL_REGISTRY_SERVICES             1
#define RTL_REGISTRY_CONTROL              2
#define RTL_REGISTRY_WINDOWS_NT           3
#define RTL_REGISTRY_DEVICEMAP            4
#define RTL_REGISTRY_USER                 5

#define RTL_REGISTRY_HANDLE       0x40000000
#define RTL_REGISTRY_OPTIONAL     0x80000000

#define RTL_QUERY_REGISTRY_SUBKEY         0x00000001
#define RTL_QUERY_REGISTRY_TOPKEY         0x00000002
#define RTL_QUERY_REGISTRY_REQUIRED       0x00000004
#define RTL_QUERY_REGISTRY_NOVALUE        0x00000008
#define RTL_QUERY_REGISTRY_NOEXPAND       0x00000010
#define RTL_QUERY_REGISTRY_DIRECT         0x00000020
#define RTL_QUERY_REGISTRY_DELETE         0x00000040

typedef NTSTATUS (WINAPI *PRTL_QUERY_REGISTRY_ROUTINE)( PCWSTR  ValueName,
                                                        ULONG  ValueType,
                                                        PVOID  ValueData,
                                                        ULONG  ValueLength,
                                                        PVOID  Context,
                                                        PVOID  EntryContext);

typedef struct _RTL_QUERY_REGISTRY_TABLE {
  PRTL_QUERY_REGISTRY_ROUTINE  QueryRoutine;
  ULONG  Flags;
  PWSTR  Name;
  PVOID  EntryContext;
  ULONG  DefaultType;
  PVOID  DefaultData;
  ULONG  DefaultLength;
} RTL_QUERY_REGISTRY_TABLE, *PRTL_QUERY_REGISTRY_TABLE;

typedef struct _KEY_VALUE_BASIC_INFORMATION {
    ULONG TitleIndex;
    ULONG Type;
    ULONG NameLength;
    WCHAR Name[1];
} KEY_VALUE_BASIC_INFORMATION, *PKEY_VALUE_BASIC_INFORMATION;

typedef struct _KEY_VALUE_PARTIAL_INFORMATION {
    ULONG TitleIndex;
    ULONG Type;
    ULONG DataLength;
    UCHAR Data[1];
} KEY_VALUE_PARTIAL_INFORMATION, *PKEY_VALUE_PARTIAL_INFORMATION;

typedef struct _KEY_VALUE_FULL_INFORMATION {
    ULONG TitleIndex;
    ULONG Type;
    ULONG DataOffset;
    ULONG DataLength;
    ULONG NameLength;
    WCHAR Name[1];
} KEY_VALUE_FULL_INFORMATION, *PKEY_VALUE_FULL_INFORMATION;

typedef enum _KEY_VALUE_INFORMATION_CLASS {
    KeyValueBasicInformation,
    KeyValueFullInformation,
    KeyValuePartialInformation,
    KeyValueFullInformationAlign64,
    KeyValuePartialInformationAlign64
} KEY_VALUE_INFORMATION_CLASS;

#define InitializeObjectAttributes(p,n,a,r,s) \
    do { \
        (p)->Length = sizeof(OBJECT_ATTRIBUTES); \
        (p)->RootDirectory = r; \
        (p)->Attributes = a; \
        (p)->ObjectName = n; \
        (p)->SecurityDescriptor = s; \
        (p)->SecurityQualityOfService = NULL; \
    } while (0)

#endif

static BOOLEAN  (WINAPI * pRtlCreateUnicodeStringFromAsciiz)(PUNICODE_STRING, LPCSTR);
static void     (WINAPI * pRtlInitUnicodeString)(PUNICODE_STRING,PCWSTR);
static NTSTATUS (WINAPI * pRtlFreeUnicodeString)(PUNICODE_STRING);
static NTSTATUS (WINAPI * pNtDeleteValueKey)(IN HANDLE, IN PUNICODE_STRING);
static NTSTATUS (WINAPI * pRtlQueryRegistryValues)(IN ULONG, IN PCWSTR,IN PRTL_QUERY_REGISTRY_TABLE, IN PVOID,IN PVOID);
static NTSTATUS (WINAPI * pRtlCheckRegistryKey)(IN ULONG,IN PWSTR);
static NTSTATUS (WINAPI * pRtlOpenCurrentUser)(IN ACCESS_MASK, PHANDLE);
static NTSTATUS (WINAPI * pNtOpenKey)(PHANDLE, IN ACCESS_MASK, IN POBJECT_ATTRIBUTES);
static NTSTATUS (WINAPI * pNtOpenKeyEx)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, ULONG);
static NTSTATUS (WINAPI * pNtClose)(IN HANDLE);
static NTSTATUS (WINAPI * pNtFlushKey)(HANDLE);
static NTSTATUS (WINAPI * pNtDeleteKey)(HANDLE);
static NTSTATUS (WINAPI * pNtCreateKey)( PHANDLE retkey, ACCESS_MASK access, const OBJECT_ATTRIBUTES *attr,
                             ULONG TitleIndex, const UNICODE_STRING *class, ULONG options,
                             PULONG dispos );
static NTSTATUS (WINAPI * pNtQueryKey)(HANDLE,KEY_INFORMATION_CLASS,PVOID,ULONG,PULONG);
static NTSTATUS (WINAPI * pNtQueryLicenseValue)(const UNICODE_STRING *,ULONG *,PVOID,ULONG,ULONG *);
static NTSTATUS (WINAPI * pNtQueryValueKey)(HANDLE,const UNICODE_STRING *,KEY_VALUE_INFORMATION_CLASS,void *,DWORD,DWORD *);
static NTSTATUS (WINAPI * pNtSetValueKey)(HANDLE, const PUNICODE_STRING, ULONG,
                               ULONG, const void*, ULONG  );
static NTSTATUS (WINAPI * pNtQueryInformationProcess)(HANDLE,PROCESSINFOCLASS,PVOID,ULONG,PULONG);
static NTSTATUS (WINAPI * pRtlFormatCurrentUserKeyPath)(PUNICODE_STRING);
static LONG     (WINAPI * pRtlCompareUnicodeString)(const PUNICODE_STRING,const PUNICODE_STRING,BOOLEAN);
static BOOLEAN  (WINAPI * pRtlCreateUnicodeString)(PUNICODE_STRING, LPCWSTR);
static LPVOID   (WINAPI * pRtlReAllocateHeap)(IN PVOID, IN ULONG, IN PVOID, IN ULONG);
static NTSTATUS (WINAPI * pRtlAppendUnicodeToString)(PUNICODE_STRING, PCWSTR);
static NTSTATUS (WINAPI * pRtlUnicodeStringToAnsiString)(PSTRING, PUNICODE_STRING, BOOL);
static NTSTATUS (WINAPI * pRtlFreeHeap)(PVOID, ULONG, PVOID);
static LPVOID   (WINAPI * pRtlAllocateHeap)(PVOID,ULONG,ULONG);
static NTSTATUS (WINAPI * pRtlZeroMemory)(PVOID, ULONG);
static NTSTATUS (WINAPI * pRtlCreateRegistryKey)(ULONG, PWSTR);
static NTSTATUS (WINAPI * pRtlpNtQueryValueKey)(HANDLE,ULONG*,PBYTE,DWORD*,void *);
static NTSTATUS (WINAPI * pNtNotifyChangeKey)(HANDLE,HANDLE,PIO_APC_ROUTINE,PVOID,PIO_STATUS_BLOCK,ULONG,BOOLEAN,PVOID,ULONG,BOOLEAN);
static NTSTATUS (WINAPI * pNtNotifyChangeMultipleKeys)(HANDLE,ULONG,OBJECT_ATTRIBUTES*,HANDLE,PIO_APC_ROUTINE,
                                                       void*,IO_STATUS_BLOCK*,ULONG,BOOLEAN,void*,ULONG,BOOLEAN);
static NTSTATUS (WINAPI * pNtWaitForSingleObject)(HANDLE,BOOLEAN,const LARGE_INTEGER*);

static HMODULE hntdll = 0;
static int CurrentTest = 0;
static UNICODE_STRING winetestpath;

#define NTDLL_GET_PROC(func) \
    p ## func = (void*)GetProcAddress(hntdll, #func); \
    if(!p ## func) { \
        trace("GetProcAddress(%s) failed\n", #func); \
        FreeLibrary(hntdll); \
        return FALSE; \
    }

static BOOL InitFunctionPtrs(void)
{
    hntdll = LoadLibraryA("ntdll.dll");
    if(!hntdll) {
        trace("Could not load ntdll.dll\n");
        return FALSE;
    }
    NTDLL_GET_PROC(RtlInitUnicodeString)
    NTDLL_GET_PROC(RtlCreateUnicodeStringFromAsciiz)
    NTDLL_GET_PROC(RtlCreateUnicodeString)
    NTDLL_GET_PROC(RtlFreeUnicodeString)
    NTDLL_GET_PROC(RtlQueryRegistryValues)
    NTDLL_GET_PROC(RtlCheckRegistryKey)
    NTDLL_GET_PROC(RtlOpenCurrentUser)
    NTDLL_GET_PROC(NtClose)
    NTDLL_GET_PROC(NtDeleteValueKey)
    NTDLL_GET_PROC(NtCreateKey)
    NTDLL_GET_PROC(NtFlushKey)
    NTDLL_GET_PROC(NtDeleteKey)
    NTDLL_GET_PROC(NtQueryKey)
    NTDLL_GET_PROC(NtQueryValueKey)
    NTDLL_GET_PROC(NtQueryInformationProcess)
    NTDLL_GET_PROC(NtSetValueKey)
    NTDLL_GET_PROC(NtOpenKey)
    NTDLL_GET_PROC(NtNotifyChangeKey)
    NTDLL_GET_PROC(RtlFormatCurrentUserKeyPath)
    NTDLL_GET_PROC(RtlCompareUnicodeString)
    NTDLL_GET_PROC(RtlReAllocateHeap)
    NTDLL_GET_PROC(RtlAppendUnicodeToString)
    NTDLL_GET_PROC(RtlUnicodeStringToAnsiString)
    NTDLL_GET_PROC(RtlFreeHeap)
    NTDLL_GET_PROC(RtlAllocateHeap)
    NTDLL_GET_PROC(RtlZeroMemory)
    NTDLL_GET_PROC(RtlCreateRegistryKey)
    NTDLL_GET_PROC(RtlpNtQueryValueKey)
    NTDLL_GET_PROC(RtlOpenCurrentUser)
    NTDLL_GET_PROC(NtWaitForSingleObject)

    /* optional functions */
    pNtQueryLicenseValue = (void *)GetProcAddress(hntdll, "NtQueryLicenseValue");
    pNtOpenKeyEx = (void *)GetProcAddress(hntdll, "NtOpenKeyEx");
    pNtNotifyChangeMultipleKeys = (void *)GetProcAddress(hntdll, "NtNotifyChangeMultipleKeys");

    return TRUE;
}
#undef NTDLL_GET_PROC

static NTSTATUS WINAPI QueryRoutine (IN PCWSTR ValueName, IN ULONG ValueType, IN PVOID ValueData,
                              IN ULONG ValueLength, IN PVOID Context, IN PVOID EntryContext)
{
    NTSTATUS ret = STATUS_SUCCESS;

    trace("**Test %d**\n", CurrentTest);
    trace("ValueName: %s\n", wine_dbgstr_w(ValueName));

    switch(ValueType)
    {
            case REG_NONE:
                trace("ValueType: REG_NONE\n");
                trace("ValueData: %p\n", ValueData);
                break;

            case REG_BINARY:
                trace("ValueType: REG_BINARY\n");
                trace("ValueData: %p\n", ValueData);
                break;

            case REG_SZ:
                trace("ValueType: REG_SZ\n");
                trace("ValueData: %s\n", (char*)ValueData);
                break;

            case REG_MULTI_SZ:
                trace("ValueType: REG_MULTI_SZ\n");
                trace("ValueData: %s\n", (char*)ValueData);
                break;

            case REG_EXPAND_SZ:
                trace("ValueType: REG_EXPAND_SZ\n");
                trace("ValueData: %s\n", (char*)ValueData);
                break;

            case REG_DWORD:
                trace("ValueType: REG_DWORD\n");
                trace("ValueData: %p\n", ValueData);
                break;
    };
    trace("ValueLength: %d\n", (int)ValueLength);

    if(CurrentTest == 0)
        ok(1, "\n"); /*checks that QueryRoutine is called*/
    if(CurrentTest > 7)
        ok(!1, "Invalid Test Specified!\n");

    CurrentTest++;

    return ret;
}

static void test_RtlQueryRegistryValues(void)
{

    /*
    ******************************
    *       QueryTable Flags     *
    ******************************
    *RTL_QUERY_REGISTRY_SUBKEY   * Name is the name of a subkey relative to Path
    *RTL_QUERY_REGISTRY_TOPKEY   * Resets location to original RelativeTo and Path
    *RTL_QUERY_REGISTRY_REQUIRED * Key required. returns STATUS_OBJECT_NAME_NOT_FOUND if not present
    *RTL_QUERY_REGISTRY_NOVALUE  * We just want a call-back
    *RTL_QUERY_REGISTRY_NOEXPAND * Don't expand the variables!
    *RTL_QUERY_REGISTRY_DIRECT   * Results of query will be stored in EntryContext(QueryRoutine ignored)
    *RTL_QUERY_REGISTRY_DELETE   * Delete value key after query
    ******************************


    **Test layout(numbered according to CurrentTest value)**
    0)NOVALUE           Just make sure call-back works
    1)Null Name         See if QueryRoutine is called for every value in current key
    2)SUBKEY            See if we can use SUBKEY to change the current path on the fly
    3)REQUIRED          Test for value that's not there
    4)NOEXPAND          See if it will return multiple strings(no expand should split strings up)
    5)DIRECT            Make it store data directly in EntryContext and not call QueryRoutine
    6)DefaultType       Test return values when key isn't present
    7)DefaultValue      Test Default Value returned with key isn't present(and no REQUIRED flag set)
    8)DefaultLength     Test Default Length with DefaultType = REG_SZ
   9)DefaultLength      Test Default Length with DefaultType = REG_MULTI_SZ
   10)DefaultLength     Test Default Length with DefaultType = REG_EXPAND_SZ
   11)DefaultData       Test whether DefaultData is used while DefaultType = REG_NONE(shouldn't be)
   12)Delete            Try to delete value key

    */
    NTSTATUS status;
    ULONG RelativeTo;

    PRTL_QUERY_REGISTRY_TABLE QueryTable = NULL;
    RelativeTo = RTL_REGISTRY_ABSOLUTE;/*Only using absolute - no need to test all relativeto variables*/

    QueryTable = pRtlAllocateHeap(GetProcessHeap(), 0, sizeof(RTL_QUERY_REGISTRY_TABLE)*26);

    pRtlZeroMemory( QueryTable, sizeof(RTL_QUERY_REGISTRY_TABLE) * 26);

    QueryTable[0].QueryRoutine = QueryRoutine;
    QueryTable[0].Flags = RTL_QUERY_REGISTRY_NOVALUE;
    QueryTable[0].Name = NULL;
    QueryTable[0].EntryContext = NULL;
    QueryTable[0].DefaultType = REG_BINARY;
    QueryTable[0].DefaultData = NULL;
    QueryTable[0].DefaultLength = 100;

    QueryTable[1].QueryRoutine = QueryRoutine;
    QueryTable[1].Flags = 0;
    QueryTable[1].Name = NULL;
    QueryTable[1].EntryContext = 0;
    QueryTable[1].DefaultType = REG_NONE;
    QueryTable[1].DefaultData = NULL;
    QueryTable[1].DefaultLength = 0;

    QueryTable[2].QueryRoutine = NULL;
    QueryTable[2].Flags = 0;
    QueryTable[2].Name = NULL;
    QueryTable[2].EntryContext = 0;
    QueryTable[2].DefaultType = REG_NONE;
    QueryTable[2].DefaultData = NULL;
    QueryTable[2].DefaultLength = 0;

    status = pRtlQueryRegistryValues(RelativeTo, winetestpath.Buffer, QueryTable, 0, 0);
    ok(status == STATUS_SUCCESS, "RtlQueryRegistryValues return: 0x%08x\n", status);

    pRtlFreeHeap(GetProcessHeap(), 0, QueryTable);
}

static void test_NtOpenKey(void)
{
    HANDLE key;
    NTSTATUS status;
    OBJECT_ATTRIBUTES attr;
    ACCESS_MASK am = KEY_READ;
    UNICODE_STRING str;

    /* All NULL */
    status = pNtOpenKey(NULL, 0, NULL);
    ok(status == STATUS_ACCESS_VIOLATION, "Expected STATUS_ACCESS_VIOLATION, got: 0x%08x\n", status);

    /* NULL attributes */
    status = pNtOpenKey(&key, 0, NULL);
    ok(status == STATUS_ACCESS_VIOLATION /* W2K3/XP/W2K */ || status == STATUS_INVALID_PARAMETER /* NT4 */,
        "Expected STATUS_ACCESS_VIOLATION or STATUS_INVALID_PARAMETER(NT4), got: 0x%08x\n", status);

    InitializeObjectAttributes(&attr, &winetestpath, 0, 0, 0);

    /* NULL key */
    status = pNtOpenKey(NULL, am, &attr);
    ok(status == STATUS_ACCESS_VIOLATION, "Expected STATUS_ACCESS_VIOLATION, got: 0x%08x\n", status);

    /* Length > sizeof(OBJECT_ATTRIBUTES) */
    attr.Length *= 2;
    status = pNtOpenKey(&key, am, &attr);
    ok(status == STATUS_INVALID_PARAMETER, "Expected STATUS_INVALID_PARAMETER, got: 0x%08x\n", status);

    /* Zero accessmask */
    attr.Length = sizeof(attr);
    key = (HANDLE)0xdeadbeef;
    status = pNtOpenKey(&key, 0, &attr);
todo_wine
    ok(status == STATUS_ACCESS_DENIED, "Expected STATUS_ACCESS_DENIED, got: 0x%08x\n", status);
todo_wine
    ok(!key, "key = %p\n", key);
    if (status == STATUS_SUCCESS) NtClose(key);

    /* Calling without parent key requires full registry path. */
    pRtlCreateUnicodeStringFromAsciiz( &str, "Machine" );
    InitializeObjectAttributes(&attr, &str, 0, 0, 0);
    key = (HANDLE)0xdeadbeef;
    status = pNtOpenKey(&key, KEY_READ, &attr);
    todo_wine ok(status == STATUS_OBJECT_PATH_SYNTAX_BAD, "NtOpenKey Failed: 0x%08x\n", status);
todo_wine
    ok(!key, "key = %p\n", key);
    pRtlFreeUnicodeString( &str );

    /* Open is case sensitive unless OBJ_CASE_INSENSITIVE is specified. */
    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Registry\\Machine" );
    status = pNtOpenKey(&key, KEY_READ, &attr);
    ok(status == STATUS_OBJECT_PATH_NOT_FOUND || status == STATUS_SUCCESS /* Win10 1607+ */,
            "NtOpenKey Failed: 0x%08x\n", status);
    if (!status) pNtClose( key );

    attr.Attributes = OBJ_CASE_INSENSITIVE;
    status = pNtOpenKey(&key, KEY_READ, &attr);
    ok(status == STATUS_SUCCESS, "NtOpenKey Failed: 0x%08x\n", status);
    pNtClose(key);
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "" );
    status = pNtOpenKey(&key, KEY_READ, &attr);
    todo_wine
    ok( status == STATUS_OBJECT_PATH_SYNTAX_BAD, "NtOpenKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\" );
    status = pNtOpenKey(&key, KEY_READ, &attr);
    todo_wine
    ok( status == STATUS_OBJECT_TYPE_MISMATCH, "NtOpenKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Registry" );
    status = pNtOpenKey(&key, KEY_READ, &attr);
    todo_wine
    ok( status == STATUS_SUCCESS, "NtOpenKey failed: 0x%08x\n", status );
    pNtClose( key );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Registry\\" );
    status = pNtOpenKey(&key, KEY_READ, &attr);
    ok( status == STATUS_SUCCESS, "NtOpenKey failed: 0x%08x\n", status );
    pNtClose( key );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Foobar" );
    status = pNtOpenKey(&key, KEY_READ, &attr);
    todo_wine
    ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtOpenKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Foobar\\Machine" );
    status = pNtOpenKey(&key, KEY_READ, &attr);
    todo_wine
    ok( status == STATUS_OBJECT_PATH_NOT_FOUND, "NtOpenKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Machine\\Software\\Classes" );
    status = pNtOpenKey(&key, KEY_READ, &attr);
    todo_wine
    ok( status == STATUS_OBJECT_PATH_NOT_FOUND, "NtOpenKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "Machine\\Software\\Classes" );
    status = pNtOpenKey(&key, KEY_READ, &attr);
    todo_wine
    ok( status == STATUS_OBJECT_PATH_SYNTAX_BAD, "NtOpenKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Device\\Null" );
    status = pNtOpenKey(&key, KEY_READ, &attr);
    todo_wine
    ok( status == STATUS_OBJECT_TYPE_MISMATCH, "NtOpenKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    if (!pNtOpenKeyEx)
    {
        win_skip("NtOpenKeyEx not available\n");
        return;
    }

    InitializeObjectAttributes(&attr, &winetestpath, 0, 0, 0);
    status = pNtOpenKeyEx(&key, KEY_WRITE|KEY_READ, &attr, 0);
    ok(status == STATUS_SUCCESS, "NtOpenKeyEx Failed: 0x%08x\n", status);

    pNtClose(key);
}

static void test_NtCreateKey(void)
{
    /*Create WineTest*/
    OBJECT_ATTRIBUTES attr;
    HANDLE key, subkey;
    ACCESS_MASK am = GENERIC_ALL;
    NTSTATUS status;
    UNICODE_STRING str;

    /* All NULL */
    status = pNtCreateKey(NULL, 0, NULL, 0, 0, 0, 0);
    ok(status == STATUS_ACCESS_VIOLATION || status == STATUS_INVALID_PARAMETER,
       "Expected STATUS_ACCESS_VIOLATION or STATUS_INVALID_PARAMETER, got: 0x%08x\n", status);

    /* Only the key */
    status = pNtCreateKey(&key, 0, NULL, 0, 0, 0, 0);
    ok(status == STATUS_ACCESS_VIOLATION /* W2K3/XP/W2K */ || status == STATUS_INVALID_PARAMETER /* NT4 */,
        "Expected STATUS_ACCESS_VIOLATION or STATUS_INVALID_PARAMETER(NT4), got: 0x%08x\n", status);

    /* Only accessmask */
    status = pNtCreateKey(NULL, am, NULL, 0, 0, 0, 0);
    ok(status == STATUS_ACCESS_VIOLATION || status == STATUS_INVALID_PARAMETER,
       "Expected STATUS_ACCESS_VIOLATION or STATUS_INVALID_PARAMETER, got: 0x%08x\n", status);

    /* Key and accessmask */
    status = pNtCreateKey(&key, am, NULL, 0, 0, 0, 0);
    ok(status == STATUS_ACCESS_VIOLATION /* W2K3/XP/W2K */ || status == STATUS_INVALID_PARAMETER /* NT4 */,
        "Expected STATUS_ACCESS_VIOLATION or STATUS_INVALID_PARAMETER(NT4), got: 0x%08x\n", status);

    InitializeObjectAttributes(&attr, &winetestpath, 0, 0, 0);

    /* Only attributes */
    status = pNtCreateKey(NULL, 0, &attr, 0, 0, 0, 0);
    ok(status == STATUS_ACCESS_VIOLATION || status == STATUS_ACCESS_DENIED /* Win7 */,
       "Expected STATUS_ACCESS_VIOLATION or STATUS_ACCESS_DENIED, got: 0x%08x\n", status);

    /* Length > sizeof(OBJECT_ATTRIBUTES) */
    attr.Length *= 2;
    status = pNtCreateKey(&key, am, &attr, 0, 0, 0, 0);
    ok(status == STATUS_INVALID_PARAMETER, "Expected STATUS_INVALID_PARAMETER, got: 0x%08x\n", status);

    attr.Length = sizeof(attr);
    status = pNtCreateKey(&key, am, &attr, 0, 0, 0, 0);
    ok(status == STATUS_SUCCESS, "NtCreateKey Failed: 0x%08x\n", status);

    attr.RootDirectory = key;
    attr.ObjectName = &str;

    pRtlCreateUnicodeStringFromAsciiz( &str, "test\\sub\\key" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtCreateKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "test\\subkey" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtCreateKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "test\\subkey\\" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtCreateKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "test_subkey\\" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS || broken(status == STATUS_OBJECT_NAME_NOT_FOUND), /* nt4 */
        "NtCreateKey failed: 0x%08x\n", status );
    if (status == STATUS_SUCCESS)
    {
        pNtDeleteKey( subkey );
        pNtClose( subkey );
    }
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "test_subkey" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );
    pNtDeleteKey( subkey );
    pNtClose( subkey );

    attr.RootDirectory = 0;
    attr.Attributes = OBJ_CASE_INSENSITIVE;

    pRtlCreateUnicodeStringFromAsciiz( &str, "" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    todo_wine
    ok( status == STATUS_OBJECT_PATH_SYNTAX_BAD, "NtCreateKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    todo_wine
    ok( status == STATUS_OBJECT_TYPE_MISMATCH, "NtCreateKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Registry" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    todo_wine
    ok( status == STATUS_SUCCESS || status == STATUS_ACCESS_DENIED,
        "NtCreateKey failed: 0x%08x\n", status );
    if (!status) pNtClose( subkey );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Registry\\" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS || status == STATUS_ACCESS_DENIED,
        "NtCreateKey failed: 0x%08x\n", status );
    if (!status) pNtClose( subkey );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Foobar" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    todo_wine
    ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtCreateKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Foobar\\Machine" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    todo_wine
    ok( status == STATUS_OBJECT_PATH_NOT_FOUND, "NtCreateKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Machine\\Software\\Classes" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    todo_wine
    ok( status == STATUS_OBJECT_PATH_NOT_FOUND, "NtCreateKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "Machine\\Software\\Classes" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    todo_wine
    ok( status == STATUS_OBJECT_PATH_SYNTAX_BAD, "NtCreateKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Device\\Null" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    todo_wine
    ok( status == STATUS_OBJECT_TYPE_MISMATCH, "NtCreateKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Registry\\Machine\\Software\\Classes" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS || status == STATUS_ACCESS_DENIED,
        "NtCreateKey failed: 0x%08x\n", status );
    if (!status) pNtClose( subkey );
    pRtlFreeUnicodeString( &str );

    /* the REGISTRY part is case-sensitive unless OBJ_CASE_INSENSITIVE is specified */
    attr.Attributes = 0;
    pRtlCreateUnicodeStringFromAsciiz( &str, "\\Registry\\Machine\\Software\\Classes" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    ok(status == STATUS_OBJECT_PATH_NOT_FOUND || status == STATUS_SUCCESS /* Win10 1607+ */,
            "NtCreateKey failed: 0x%08x\n", status );
    if (!status) pNtClose( subkey );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\REGISTRY\\Machine\\Software\\Classes" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS || status == STATUS_ACCESS_DENIED,
        "NtCreateKey failed: 0x%08x\n", status );
    if (!status) pNtClose( subkey );
    pRtlFreeUnicodeString( &str );

    pRtlCreateUnicodeStringFromAsciiz( &str, "\\REGISTRY\\MACHINE\\SOFTWARE\\CLASSES" );
    status = pNtCreateKey( &subkey, am, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS || status == STATUS_ACCESS_DENIED,
        "NtCreateKey failed: 0x%08x\n", status );
    if (!status) pNtClose( subkey );
    pRtlFreeUnicodeString( &str );

    pNtClose(key);
}

static void test_NtSetValueKey(void)
{
    HANDLE key;
    NTSTATUS status;
    OBJECT_ATTRIBUTES attr;
    ACCESS_MASK am = KEY_WRITE;
    UNICODE_STRING ValName;
    DWORD data = 711;

    InitializeObjectAttributes(&attr, &winetestpath, 0, 0, 0);
    status = pNtOpenKey(&key, am, &attr);
    ok(status == STATUS_SUCCESS, "NtOpenKey Failed: 0x%08x\n", status);

    pRtlCreateUnicodeStringFromAsciiz(&ValName, "deletetest");
    status = pNtSetValueKey(key, &ValName, 0, REG_DWORD, &data, sizeof(data));
    ok(status == STATUS_SUCCESS, "NtSetValueKey Failed: 0x%08x\n", status);
    pRtlFreeUnicodeString(&ValName);

    pRtlCreateUnicodeStringFromAsciiz(&ValName, "stringtest");
    status = pNtSetValueKey(key, &ValName, 0, REG_SZ, (VOID*)stringW, STR_TRUNC_SIZE);
    ok(status == STATUS_SUCCESS, "NtSetValueKey Failed: 0x%08x\n", status);
    pRtlFreeUnicodeString(&ValName);

    pNtClose(key);
}

static void test_RtlOpenCurrentUser(void)
{
    NTSTATUS status;
    HANDLE handle;
    status=pRtlOpenCurrentUser(KEY_READ, &handle);
    ok(status == STATUS_SUCCESS, "RtlOpenCurrentUser Failed: 0x%08x\n", status);
    pNtClose(handle);
}

static void test_RtlCheckRegistryKey(void)
{
    static WCHAR empty[] = {0};
    NTSTATUS status;

    status = pRtlCheckRegistryKey(RTL_REGISTRY_ABSOLUTE, winetestpath.Buffer);
    ok(status == STATUS_SUCCESS, "RtlCheckRegistryKey with RTL_REGISTRY_ABSOLUTE: 0x%08x\n", status);

    status = pRtlCheckRegistryKey((RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL), winetestpath.Buffer);
    ok(status == STATUS_SUCCESS, "RtlCheckRegistryKey with RTL_REGISTRY_ABSOLUTE and RTL_REGISTRY_OPTIONAL: 0x%08x\n", status);

    status = pRtlCheckRegistryKey(RTL_REGISTRY_ABSOLUTE, NULL);
    ok(status == STATUS_OBJECT_PATH_SYNTAX_BAD, "RtlCheckRegistryKey with RTL_REGISTRY_ABSOLUTE and Path being NULL: 0x%08x\n", status);

    status = pRtlCheckRegistryKey(RTL_REGISTRY_ABSOLUTE, empty);
    ok(status == STATUS_OBJECT_PATH_SYNTAX_BAD, "RtlCheckRegistryKey with RTL_REGISTRY_ABSOLUTE and Path being empty: 0x%08x\n", status);

    status = pRtlCheckRegistryKey(RTL_REGISTRY_USER, NULL);
    ok(status == STATUS_SUCCESS, "RtlCheckRegistryKey with RTL_REGISTRY_USER and Path being NULL: 0x%08x\n", status);

    status = pRtlCheckRegistryKey(RTL_REGISTRY_USER, empty);
    ok(status == STATUS_SUCCESS, "RtlCheckRegistryKey with RTL_REGISTRY_USER and Path being empty: 0x%08x\n", status);
}

static void test_NtFlushKey(void)
{
    NTSTATUS status;
    HANDLE hkey;
    OBJECT_ATTRIBUTES attr;
    ACCESS_MASK am = KEY_ALL_ACCESS;

    status = pNtFlushKey(NULL);
    ok(status == STATUS_INVALID_HANDLE, "Expected STATUS_INVALID_HANDLE, got: 0x%08x\n", status);

    InitializeObjectAttributes(&attr, &winetestpath, 0, 0, 0);
    pNtOpenKey(&hkey, am, &attr);

    status = pNtFlushKey(hkey);
    ok(status == STATUS_SUCCESS, "NtDeleteKey Failed: 0x%08x\n", status);

    pNtClose(hkey);
}

static void test_NtQueryValueKey(void)
{
    HANDLE key;
    NTSTATUS status;
    OBJECT_ATTRIBUTES attr;
    UNICODE_STRING ValName;
    KEY_VALUE_BASIC_INFORMATION *basic_info;
    KEY_VALUE_PARTIAL_INFORMATION *partial_info, pi;
    KEY_VALUE_FULL_INFORMATION *full_info;
    DWORD len, expected;

    pRtlCreateUnicodeStringFromAsciiz(&ValName, "deletetest");

    InitializeObjectAttributes(&attr, &winetestpath, 0, 0, 0);
    status = pNtOpenKey(&key, KEY_READ|KEY_SET_VALUE, &attr);
    ok(status == STATUS_SUCCESS, "NtOpenKey Failed: 0x%08x\n", status);

    len = FIELD_OFFSET(KEY_VALUE_BASIC_INFORMATION, Name[0]);
    basic_info = HeapAlloc(GetProcessHeap(), 0, len);
    status = pNtQueryValueKey(key, &ValName, KeyValueBasicInformation, basic_info, len, &len);
    ok(status == STATUS_BUFFER_OVERFLOW, "NtQueryValueKey should have returned STATUS_BUFFER_OVERFLOW instead of 0x%08x\n", status);
    ok(basic_info->TitleIndex == 0, "NtQueryValueKey returned wrong TitleIndex %d\n", basic_info->TitleIndex);
    ok(basic_info->Type == REG_DWORD, "NtQueryValueKey returned wrong Type %d\n", basic_info->Type);
    ok(basic_info->NameLength == 20, "NtQueryValueKey returned wrong NameLength %d\n", basic_info->NameLength);
    ok(len == FIELD_OFFSET(KEY_VALUE_BASIC_INFORMATION, Name[basic_info->NameLength/sizeof(WCHAR)]), "NtQueryValueKey returned wrong len %d\n", len);

    basic_info = HeapReAlloc(GetProcessHeap(), 0, basic_info, len);
    status = pNtQueryValueKey(key, &ValName, KeyValueBasicInformation, basic_info, len, &len);
    ok(status == STATUS_SUCCESS, "NtQueryValueKey should have returned STATUS_SUCCESS instead of 0x%08x\n", status);
    ok(basic_info->TitleIndex == 0, "NtQueryValueKey returned wrong TitleIndex %d\n", basic_info->TitleIndex);
    ok(basic_info->Type == REG_DWORD, "NtQueryValueKey returned wrong Type %d\n", basic_info->Type);
    ok(basic_info->NameLength == 20, "NtQueryValueKey returned wrong NameLength %d\n", basic_info->NameLength);
    ok(len == FIELD_OFFSET(KEY_VALUE_BASIC_INFORMATION, Name[basic_info->NameLength/sizeof(WCHAR)]), "NtQueryValueKey returned wrong len %d\n", len);
    ok(!memcmp(basic_info->Name, ValName.Buffer, ValName.Length), "incorrect Name returned\n");
    HeapFree(GetProcessHeap(), 0, basic_info);

    len = FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data[0]);
    partial_info = HeapAlloc(GetProcessHeap(), 0, len);
    status = pNtQueryValueKey(key, &ValName, KeyValuePartialInformation, partial_info, len, &len);
    ok(status == STATUS_BUFFER_OVERFLOW, "NtQueryValueKey should have returned STATUS_BUFFER_OVERFLOW instead of 0x%08x\n", status);
    ok(partial_info->TitleIndex == 0, "NtQueryValueKey returned wrong TitleIndex %d\n", partial_info->TitleIndex);
    ok(partial_info->Type == REG_DWORD, "NtQueryValueKey returned wrong Type %d\n", partial_info->Type);
    ok(partial_info->DataLength == 4, "NtQueryValueKey returned wrong DataLength %d\n", partial_info->DataLength);
    ok(len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data[partial_info->DataLength]), "NtQueryValueKey returned wrong len %d\n", len);

    partial_info = HeapReAlloc(GetProcessHeap(), 0, partial_info, len);
    status = pNtQueryValueKey(key, &ValName, KeyValuePartialInformation, partial_info, len, &len);
    ok(status == STATUS_SUCCESS, "NtQueryValueKey should have returned STATUS_SUCCESS instead of 0x%08x\n", status);
    ok(partial_info->TitleIndex == 0, "NtQueryValueKey returned wrong TitleIndex %d\n", partial_info->TitleIndex);
    ok(partial_info->Type == REG_DWORD, "NtQueryValueKey returned wrong Type %d\n", partial_info->Type);
    ok(partial_info->DataLength == 4, "NtQueryValueKey returned wrong DataLength %d\n", partial_info->DataLength);
    ok(len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data[partial_info->DataLength]), "NtQueryValueKey returned wrong len %d\n", len);
    ok(*(DWORD *)partial_info->Data == 711, "incorrect Data returned: 0x%x\n", *(DWORD *)partial_info->Data);
    HeapFree(GetProcessHeap(), 0, partial_info);

    len = FIELD_OFFSET(KEY_VALUE_FULL_INFORMATION, Name[0]);
    full_info = HeapAlloc(GetProcessHeap(), 0, len);
    status = pNtQueryValueKey(key, &ValName, KeyValueFullInformation, full_info, len, &len);
    ok(status == STATUS_BUFFER_OVERFLOW, "NtQueryValueKey should have returned STATUS_BUFFER_OVERFLOW instead of 0x%08x\n", status);
    ok(full_info->TitleIndex == 0, "NtQueryValueKey returned wrong TitleIndex %d\n", full_info->TitleIndex);
    ok(full_info->Type == REG_DWORD, "NtQueryValueKey returned wrong Type %d\n", full_info->Type);
    ok(full_info->DataLength == 4, "NtQueryValueKey returned wrong DataLength %d\n", full_info->DataLength);
    ok(full_info->NameLength == 20, "NtQueryValueKey returned wrong NameLength %d\n", full_info->NameLength);
    ok(len == FIELD_OFFSET(KEY_VALUE_FULL_INFORMATION, Name[0]) + full_info->DataLength + full_info->NameLength,
        "NtQueryValueKey returned wrong len %d\n", len);
    len = FIELD_OFFSET(KEY_VALUE_FULL_INFORMATION, Name[0]) + full_info->DataLength + full_info->NameLength;

    full_info = HeapReAlloc(GetProcessHeap(), 0, full_info, len);
    status = pNtQueryValueKey(key, &ValName, KeyValueFullInformation, full_info, len, &len);
    ok(status == STATUS_SUCCESS, "NtQueryValueKey should have returned STATUS_SUCCESS instead of 0x%08x\n", status);
    ok(full_info->TitleIndex == 0, "NtQueryValueKey returned wrong TitleIndex %d\n", full_info->TitleIndex);
    ok(full_info->Type == REG_DWORD, "NtQueryValueKey returned wrong Type %d\n", full_info->Type);
    ok(full_info->DataLength == 4, "NtQueryValueKey returned wrong DataLength %d\n", full_info->DataLength);
    ok(full_info->NameLength == 20, "NtQueryValueKey returned wrong NameLength %d\n", full_info->NameLength);
    ok(!memcmp(full_info->Name, ValName.Buffer, ValName.Length), "incorrect Name returned\n");
    ok(*(DWORD *)((char *)full_info + full_info->DataOffset) == 711, "incorrect Data returned: 0x%x\n",
        *(DWORD *)((char *)full_info + full_info->DataOffset));
    HeapFree(GetProcessHeap(), 0, full_info);

    pRtlFreeUnicodeString(&ValName);
    pRtlCreateUnicodeStringFromAsciiz(&ValName, "stringtest");

    status = pNtQueryValueKey(key, &ValName, KeyValuePartialInformation, NULL, 0, &len);
    ok(status == STATUS_BUFFER_TOO_SMALL, "NtQueryValueKey should have returned STATUS_BUFFER_TOO_SMALL instead of 0x%08x\n", status);
    partial_info = HeapAlloc(GetProcessHeap(), 0, len+1);
    memset(partial_info, 0xbd, len+1);
    status = pNtQueryValueKey(key, &ValName, KeyValuePartialInformation, partial_info, len, &len);
    ok(status == STATUS_SUCCESS, "NtQueryValueKey should have returned STATUS_SUCCESS instead of 0x%08x\n", status);
    ok(partial_info->TitleIndex == 0, "NtQueryValueKey returned wrong TitleIndex %d\n", partial_info->TitleIndex);
    ok(partial_info->Type == REG_SZ, "NtQueryValueKey returned wrong Type %d\n", partial_info->Type);
    ok(partial_info->DataLength == STR_TRUNC_SIZE, "NtQueryValueKey returned wrong DataLength %d\n", partial_info->DataLength);
    ok(!memcmp(partial_info->Data, stringW, STR_TRUNC_SIZE), "incorrect Data returned\n");
    ok(*(partial_info->Data+STR_TRUNC_SIZE) == 0xbd, "string overflowed %02x\n", *(partial_info->Data+STR_TRUNC_SIZE));

    expected = len;
    status = pNtQueryValueKey(key, &ValName, KeyValuePartialInformation, partial_info, 0, &len);
    ok(status == STATUS_BUFFER_TOO_SMALL, "NtQueryValueKey wrong status 0x%08x\n", status);
    ok(len == expected, "NtQueryValueKey wrong len %u\n", len);
    status = pNtQueryValueKey(key, &ValName, KeyValuePartialInformation, partial_info, 1, &len);
    ok(status == STATUS_BUFFER_TOO_SMALL, "NtQueryValueKey wrong status 0x%08x\n", status);
    ok(len == expected, "NtQueryValueKey wrong len %u\n", len);
    status = pNtQueryValueKey(key, &ValName, KeyValuePartialInformation, partial_info, FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data) - 1, &len);
    ok(status == STATUS_BUFFER_TOO_SMALL, "NtQueryValueKey wrong status 0x%08x\n", status);
    ok(len == expected, "NtQueryValueKey wrong len %u\n", len);
    status = pNtQueryValueKey(key, &ValName, KeyValuePartialInformation, partial_info, FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data), &len);
    ok(status == STATUS_BUFFER_OVERFLOW, "NtQueryValueKey wrong status 0x%08x\n", status);
    ok(len == expected, "NtQueryValueKey wrong len %u\n", len);

    HeapFree(GetProcessHeap(), 0, partial_info);
    pRtlFreeUnicodeString(&ValName);

    pRtlCreateUnicodeStringFromAsciiz(&ValName, "custtest");
    status = pNtSetValueKey(key, &ValName, 0, 0xff00ff00, NULL, 0);
    ok(status == STATUS_SUCCESS, "NtSetValueKey Failed: 0x%08x\n", status);

    status = pNtQueryValueKey(key, &ValName, KeyValuePartialInformation, &pi, sizeof(pi), &len);
    ok(status == STATUS_SUCCESS, "NtQueryValueKey should have returned STATUS_SUCCESS instead of 0x%08x\n", status);
    ok(pi.Type == 0xff00ff00, "Type=%x\n", pi.Type);
    ok(pi.DataLength == 0, "DataLength=%u\n", pi.DataLength);
    pRtlFreeUnicodeString(&ValName);

    pNtClose(key);
}

static void test_NtDeleteKey(void)
{
    NTSTATUS status;
    HANDLE hkey;
    OBJECT_ATTRIBUTES attr;
    ACCESS_MASK am = KEY_ALL_ACCESS;

    status = pNtDeleteKey(NULL);
    ok(status == STATUS_INVALID_HANDLE, "Expected STATUS_INVALID_HANDLE, got: 0x%08x\n", status);

    InitializeObjectAttributes(&attr, &winetestpath, 0, 0, 0);
    status = pNtOpenKey(&hkey, am, &attr);
    ok(status == STATUS_SUCCESS, "NtOpenKey Failed: 0x%08x\n", status);

    status = pNtDeleteKey(hkey);
    ok(status == STATUS_SUCCESS, "NtDeleteKey Failed: 0x%08x\n", status);
}

static void test_NtQueryLicenseKey(void)
{
    static const WCHAR emptyW[] = {'E','M','P','T','Y',0};
    UNICODE_STRING name;
    WORD buffer[32];
    NTSTATUS status;
    ULONG type, len;
    DWORD value;

    if (!pNtQueryLicenseValue)
    {
        win_skip("NtQueryLicenseValue not found, skipping tests\n");
        return;
    }

    type = 0xdead;
    len = 0xbeef;
    memset(&name, 0, sizeof(name));
    status = pNtQueryLicenseValue(&name, &type, buffer, sizeof(buffer), &len);
    ok(status == STATUS_INVALID_PARAMETER, "NtQueryLicenseValue returned %08x, expected STATUS_INVALID_PARAMETER\n", status);
    ok(type == 0xdead, "expected unmodified value for type, got %u\n", type);
    ok(len == 0xbeef, "expected unmodified value for len, got %u\n", len);

    /* test with empty key */
    pRtlCreateUnicodeStringFromAsciiz(&name, "");

    type = 0xdead;
    len = 0xbeef;
    status = pNtQueryLicenseValue(NULL, &type, buffer, sizeof(buffer), &len);
    ok(status == STATUS_INVALID_PARAMETER, "NtQueryLicenseValue returned %08x, expected STATUS_INVALID_PARAMETER\n", status);
    ok(type == 0xdead, "expected unmodified value for type, got %u\n", type);
    ok(len == 0xbeef, "expected unmodified value for len, got %u\n", len);

    type = 0xdead;
    status = pNtQueryLicenseValue(&name, &type, buffer, sizeof(buffer), NULL);
    ok(status == STATUS_INVALID_PARAMETER, "NtQueryLicenseValue returned %08x, expected STATUS_INVALID_PARAMETER\n", status);
    ok(type == 0xdead, "expected unmodified value for type, got %u\n", type);

    len = 0xbeef;
    status = pNtQueryLicenseValue(&name, NULL, buffer, sizeof(buffer), &len);
    ok(status == STATUS_INVALID_PARAMETER, "NtQueryLicenseValue returned %08x, expected STATUS_INVALID_PARAMETER\n", status);
    ok(len == 0xbeef, "expected unmodified value for len, got %u\n", len);

    type = 0xdead;
    len = 0xbeef;
    status = pNtQueryLicenseValue(&name, &type, buffer, sizeof(buffer), &len);
    ok(status == STATUS_INVALID_PARAMETER, "NtQueryLicenseValue returned %08x, expected STATUS_INVALID_PARAMETER\n", status);
    ok(type == 0xdead, "expected unmodified value for type, got %u\n", type);
    ok(len == 0xbeef, "expected unmodified value for len, got %u\n", len);

    pRtlFreeUnicodeString(&name);

    /* test with nonexistent licence key */
    pRtlCreateUnicodeStringFromAsciiz(&name, "Nonexistent-License-Value");

    type = 0xdead;
    len = 0xbeef;
    status = pNtQueryLicenseValue(NULL, &type, buffer, sizeof(buffer), &len);
    ok(status == STATUS_INVALID_PARAMETER, "NtQueryLicenseValue returned %08x, expected STATUS_INVALID_PARAMETER\n", status);
    ok(type == 0xdead, "expected unmodified value for type, got %u\n", type);
    ok(len == 0xbeef, "expected unmodified value for len, got %u\n", len);

    type = 0xdead;
    status = pNtQueryLicenseValue(&name, &type, buffer, sizeof(buffer), NULL);
    ok(status == STATUS_INVALID_PARAMETER, "NtQueryLicenseValue returned %08x, expected STATUS_INVALID_PARAMETER\n", status);
    ok(type == 0xdead, "expected unmodified value for type, got %u\n", type);

    len = 0xbeef;
    status = pNtQueryLicenseValue(&name, NULL, buffer, sizeof(buffer), &len);
    ok(status == STATUS_OBJECT_NAME_NOT_FOUND, "NtQueryLicenseValue returned %08x, expected STATUS_OBJECT_NAME_NOT_FOUND\n", status);
    ok(len == 0xbeef || broken(!len) /* Win10 1607 */, "expected unmodified value for len, got %u\n", len);

    type = 0xdead;
    len = 0xbeef;
    status = pNtQueryLicenseValue(&name, &type, buffer, sizeof(buffer), &len);
    ok(status == STATUS_OBJECT_NAME_NOT_FOUND, "NtQueryLicenseValue unexpected succeeded\n");
    ok(type == 0xdead, "expected unmodified value for type, got %u\n", type);
    ok(len == 0xbeef || broken(!len) /* Win10 1607 */, "expected unmodified value for len, got %u\n", len);

    pRtlFreeUnicodeString(&name);

    /* test with REG_SZ license key */
    pRtlCreateUnicodeStringFromAsciiz(&name, "Kernel-MUI-Language-Allowed");

    type = 0xdead;
    len = 0xbeef;
    status = pNtQueryLicenseValue(NULL, &type, buffer, sizeof(buffer), &len);
    ok(status == STATUS_INVALID_PARAMETER, "NtQueryLicenseValue returned %08x, expected STATUS_INVALID_PARAMETER\n", status);
    ok(type == 0xdead, "expected unmodified value for type, got %u\n", type);
    ok(len == 0xbeef, "expected unmodified value for len, got %u\n", len);

    type = 0xdead;
    status = pNtQueryLicenseValue(&name, &type, buffer, sizeof(buffer), NULL);
    ok(status == STATUS_INVALID_PARAMETER, "NtQueryLicenseValue returned %08x, expected STATUS_INVALID_PARAMETER\n", status);
    ok(type == 0xdead, "expected unmodified value for type, got %u\n", type);

    type = 0xdead;
    len = 0;
    status = pNtQueryLicenseValue(&name, &type, buffer, 0, &len);
    ok(status == STATUS_BUFFER_TOO_SMALL, "NtQueryLicenseValue returned %08x, expected STATUS_BUFFER_TOO_SMALL\n", status);
    ok(type == REG_SZ, "expected type = REG_SZ, got %u\n", type);
    ok(len == sizeof(emptyW), "expected len = %u, got %u\n", (DWORD)sizeof(emptyW), len);

    len = 0;
    status = pNtQueryLicenseValue(&name, NULL, buffer, 0, &len);
    ok(status == STATUS_BUFFER_TOO_SMALL, "NtQueryLicenseValue returned %08x, expected STATUS_BUFFER_TOO_SMALL\n", status);
    ok(len == sizeof(emptyW), "expected len = %u, got %u\n", (DWORD)sizeof(emptyW), len);

    type = 0xdead;
    len = 0;
    memset(buffer, 0x11, sizeof(buffer));
    status = pNtQueryLicenseValue(&name, &type, buffer, sizeof(buffer), &len);
    ok(status == STATUS_SUCCESS, "NtQueryLicenseValue returned %08x, expected STATUS_SUCCESS\n", status);
    ok(type == REG_SZ, "expected type = REG_SZ, got %u\n", type);
    ok(len == sizeof(emptyW), "expected len = %u, got %u\n", (DWORD)sizeof(emptyW), len);
    ok(!memcmp(buffer, emptyW, sizeof(emptyW)), "unexpected buffer content\n");

    type = 0xdead;
    len = 0;
    memset(buffer, 0x11, sizeof(buffer));
    status = pNtQueryLicenseValue(&name, &type, buffer, 2, &len);
    ok(status == STATUS_BUFFER_TOO_SMALL, "NtQueryLicenseValue returned %08x, expected STATUS_BUFFER_TOO_SMALL\n", status);
    ok(type == REG_SZ, "expected type REG_SZ, got %u\n", type);
    ok(len == sizeof(emptyW), "expected len = %u, got %u\n", (DWORD)sizeof(emptyW), len);
    ok(buffer[0] == 0x1111, "expected buffer[0] = 0x1111, got %u\n", buffer[0]);

    pRtlFreeUnicodeString(&name);

    /* test with REG_DWORD license key */
    pRtlCreateUnicodeStringFromAsciiz(&name, "Kernel-MUI-Number-Allowed");

    type = 0xdead;
    len = 0xbeef;
    status = pNtQueryLicenseValue(NULL, &type, &value, sizeof(value), &len);
    ok(status == STATUS_INVALID_PARAMETER, "NtQueryLicenseValue returned %08x, expected STATUS_INVALID_PARAMETER\n", status);
    ok(type == 0xdead, "expected unmodified value for type, got %u\n", type);
    ok(len == 0xbeef, "expected unmodified value for len, got %u\n", len);

    type = 0xdead;
    status = pNtQueryLicenseValue(&name, &type, &value, sizeof(value), NULL);
    ok(status == STATUS_INVALID_PARAMETER, "NtQueryLicenseValue returned %08x, expected STATUS_INVALID_PARAMETER\n", status);
    ok(type == 0xdead, "expected unmodified value for type, got %u\n", type);

    type = 0xdead;
    len = 0;
    status = pNtQueryLicenseValue(&name, &type, &value, 0, &len);
    ok(status == STATUS_BUFFER_TOO_SMALL, "NtQueryLicenseValue returned %08x, expected STATUS_BUFFER_TOO_SMALL\n", status);
    ok(type == REG_DWORD, "expected type = REG_DWORD, got %u\n", type);
    ok(len == sizeof(value), "expected len = %u, got %u\n", (DWORD)sizeof(value), len);

    len = 0;
    status = pNtQueryLicenseValue(&name, NULL, &value, 0, &len);
    ok(status == STATUS_BUFFER_TOO_SMALL, "NtQueryLicenseValue returned %08x, expected STATUS_BUFFER_TOO_SMALL\n", status);
    ok(len == sizeof(value), "expected len = %u, got %u\n", (DWORD)sizeof(value), len);

    type = 0xdead;
    len = 0;
    value = 0xdeadbeef;
    status = pNtQueryLicenseValue(&name, &type, &value, sizeof(value), &len);
    ok(status == STATUS_SUCCESS, "NtQueryLicenseValue returned %08x, expected STATUS_SUCCESS\n", status);
    ok(type == REG_DWORD, "expected type = REG_DWORD, got %u\n", type);
    ok(len == sizeof(value), "expected len = %u, got %u\n", (DWORD)sizeof(value), len);
    ok(value != 0xdeadbeef, "expected value != 0xdeadbeef\n");

    type = 0xdead;
    len = 0;
    status = pNtQueryLicenseValue(&name, &type, &value, 2, &len);
    ok(status == STATUS_BUFFER_TOO_SMALL, "NtQueryLicenseValue returned %08x, expected STATUS_BUFFER_TOO_SMALL\n", status);
    ok(type == REG_DWORD, "expected type REG_DWORD, got %u\n", type);
    ok(len == sizeof(value), "expected len = %u, got %u\n", (DWORD)sizeof(value), len);

    pRtlFreeUnicodeString(&name);
}

static void test_RtlpNtQueryValueKey(void)
{
    NTSTATUS status;

    status = pRtlpNtQueryValueKey(NULL, NULL, NULL, NULL, NULL);
    ok(status == STATUS_INVALID_HANDLE, "Expected STATUS_INVALID_HANDLE, got: 0x%08x\n", status);
}

static void test_symlinks(void)
{
    static const WCHAR linkW[] = {'l','i','n','k',0};
    static const WCHAR valueW[] = {'v','a','l','u','e',0};
    static const WCHAR symlinkW[] = {'S','y','m','b','o','l','i','c','L','i','n','k','V','a','l','u','e',0};
    static const WCHAR targetW[] = {'\\','t','a','r','g','e','t',0};
    static UNICODE_STRING null_str;
    char buffer[1024];
    KEY_VALUE_PARTIAL_INFORMATION *info = (KEY_VALUE_PARTIAL_INFORMATION *)buffer;
    WCHAR *target;
    UNICODE_STRING symlink_str, link_str, target_str, value_str;
    HANDLE root, key, link;
    OBJECT_ATTRIBUTES attr;
    NTSTATUS status;
    DWORD target_len, len, dw;

    pRtlInitUnicodeString( &link_str, linkW );
    pRtlInitUnicodeString( &symlink_str, symlinkW );
    pRtlInitUnicodeString( &target_str, targetW + 1 );
    pRtlInitUnicodeString( &value_str, valueW );

    target_len = winetestpath.Length + sizeof(targetW);
    target = pRtlAllocateHeap( GetProcessHeap(), 0, target_len + sizeof(targetW) /*for loop test*/ );
    memcpy( target, winetestpath.Buffer, winetestpath.Length );
    memcpy( target + winetestpath.Length/sizeof(WCHAR), targetW, sizeof(targetW) );

    attr.Length = sizeof(attr);
    attr.RootDirectory = 0;
    attr.Attributes = 0;
    attr.ObjectName = &winetestpath;
    attr.SecurityDescriptor = NULL;
    attr.SecurityQualityOfService = NULL;

    status = pNtCreateKey( &root, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );

    attr.RootDirectory = root;
    attr.ObjectName = &link_str;
    status = pNtCreateKey( &link, KEY_ALL_ACCESS, &attr, 0, 0, REG_OPTION_CREATE_LINK, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );

    /* REG_SZ is not allowed */
    status = pNtSetValueKey( link, &symlink_str, 0, REG_SZ, target, target_len );
    ok( status == STATUS_ACCESS_DENIED, "NtSetValueKey wrong status 0x%08x\n", status );
    status = pNtSetValueKey( link, &symlink_str, 0, REG_LINK, target, target_len - sizeof(WCHAR) );
    ok( status == STATUS_SUCCESS, "NtSetValueKey failed: 0x%08x\n", status );
    /* other values are not allowed */
    status = pNtSetValueKey( link, &link_str, 0, REG_LINK, target, target_len - sizeof(WCHAR) );
    ok( status == STATUS_ACCESS_DENIED, "NtSetValueKey wrong status 0x%08x\n", status );

    /* try opening the target through the link */

    attr.ObjectName = &link_str;
    key = (HANDLE)0xdeadbeef;
    status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
    ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtOpenKey wrong status 0x%08x\n", status );
    ok( !key, "key = %p\n", key );

    attr.ObjectName = &target_str;
    status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );

    dw = 0xbeef;
    status = pNtSetValueKey( key, &value_str, 0, REG_DWORD, &dw, sizeof(dw) );
    ok( status == STATUS_SUCCESS, "NtSetValueKey failed: 0x%08x\n", status );
    pNtClose( key );

    attr.ObjectName = &link_str;
    status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
    ok( status == STATUS_SUCCESS, "NtOpenKey failed: 0x%08x\n", status );

    len = sizeof(buffer);
    status = pNtQueryValueKey( key, &value_str, KeyValuePartialInformation, info, len, &len );
    ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
    ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + sizeof(DWORD), "wrong len %u\n", len );

    status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
    ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtQueryValueKey failed: 0x%08x\n", status );

    /* REG_LINK can be created in non-link keys */
    status = pNtSetValueKey( key, &symlink_str, 0, REG_LINK, target, target_len - sizeof(WCHAR) );
    ok( status == STATUS_SUCCESS, "NtSetValueKey failed: 0x%08x\n", status );
    len = sizeof(buffer);
    status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
    ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
    ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + target_len - sizeof(WCHAR),
        "wrong len %u\n", len );
    status = pNtDeleteValueKey( key, &symlink_str );
    ok( status == STATUS_SUCCESS, "NtDeleteValueKey failed: 0x%08x\n", status );

    pNtClose( key );

    attr.Attributes = 0;
    status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );

    len = sizeof(buffer);
    status = pNtQueryValueKey( key, &value_str, KeyValuePartialInformation, info, len, &len );
    ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
    ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + sizeof(DWORD), "wrong len %u\n", len );

    status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
    ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtQueryValueKey failed: 0x%08x\n", status );
    pNtClose( key );

    /* now open the symlink itself */

    attr.RootDirectory = root;
    attr.Attributes = OBJ_OPENLINK;
    attr.ObjectName = &link_str;
    status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
    ok( status == STATUS_SUCCESS, "NtOpenKey failed: 0x%08x\n", status );

    len = sizeof(buffer);
    status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
    ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
    ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + target_len - sizeof(WCHAR),
        "wrong len %u\n", len );
    pNtClose( key );

    if (pNtOpenKeyEx)
    {
        /* REG_OPTION_OPEN_LINK flag doesn't matter */
        status = pNtOpenKeyEx( &key, KEY_ALL_ACCESS, &attr, REG_OPTION_OPEN_LINK );
        ok( status == STATUS_SUCCESS, "NtOpenKey failed: 0x%08x\n", status );

        len = sizeof(buffer);
        status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
        ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
        ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + target_len - sizeof(WCHAR),
            "wrong len %u\n", len );
        pNtClose( key );

        status = pNtOpenKeyEx( &key, KEY_ALL_ACCESS, &attr, 0 );
        ok( status == STATUS_SUCCESS, "NtOpenKey failed: 0x%08x\n", status );

        len = sizeof(buffer);
        status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
        ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
        ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + target_len - sizeof(WCHAR),
            "wrong len %u\n", len );
        pNtClose( key );

        attr.Attributes = 0;
        status = pNtOpenKeyEx( &key, KEY_ALL_ACCESS, &attr, REG_OPTION_OPEN_LINK );
        ok( status == STATUS_SUCCESS, "NtOpenKey failed: 0x%08x\n", status );

        len = sizeof(buffer);
        status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
        ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtQueryValueKey failed: 0x%08x\n", status );
        pNtClose( key );
    }

    attr.Attributes = OBJ_OPENLINK;
    status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
    len = sizeof(buffer);
    status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
    ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
    ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + target_len - sizeof(WCHAR),
        "wrong len %u\n", len );
    pNtClose( key );

    /* delete target and create by NtCreateKey on link */
    attr.ObjectName = &target_str;
    status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
    ok( status == STATUS_SUCCESS, "NtOpenKey failed: 0x%08x\n", status );
    status = pNtDeleteKey( key );
    ok( status == STATUS_SUCCESS, "NtDeleteKey failed: 0x%08x\n", status );
    pNtClose( key );

    attr.ObjectName = &link_str;
    attr.Attributes = 0;
    status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
    ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtOpenKey wrong status 0x%08x\n", status );

    status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    todo_wine ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
    pNtClose( key );
    if (status) /* can be removed once todo_wine above is fixed */
    {
        attr.ObjectName = &target_str;
        attr.Attributes = OBJ_OPENLINK;
        status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
        ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
        pNtClose( key );
    }

    attr.ObjectName = &target_str;
    attr.Attributes = OBJ_OPENLINK;
    status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
    ok( status == STATUS_SUCCESS, "NtOpenKey wrong status 0x%08x\n", status );

    if (0)  /* crashes the Windows kernel on some Vista systems */
    {
        /* reopen the link from itself */

        attr.RootDirectory = link;
        attr.Attributes = OBJ_OPENLINK;
        attr.ObjectName = &null_str;
        status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
        ok( status == STATUS_SUCCESS, "NtOpenKey failed: 0x%08x\n", status );
        len = sizeof(buffer);
        status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
        ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
        ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + target_len - sizeof(WCHAR),
            "wrong len %u\n", len );
        pNtClose( key );

        status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
        ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
        len = sizeof(buffer);
        status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
        ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
        ok( len == FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data) + target_len - sizeof(WCHAR),
            "wrong len %u\n", len );
        pNtClose( key );
    }

    if (0)  /* crashes the Windows kernel in most versions */
    {
        attr.RootDirectory = link;
        attr.Attributes = 0;
        attr.ObjectName = &null_str;
        status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
        ok( status == STATUS_SUCCESS, "NtOpenKey failed: 0x%08x\n", status );
        len = sizeof(buffer);
        status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
        ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtQueryValueKey failed: 0x%08x\n", status );
        pNtClose( key );

        status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
        ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
        len = sizeof(buffer);
        status = pNtQueryValueKey( key, &symlink_str, KeyValuePartialInformation, info, len, &len );
        ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtQueryValueKey failed: 0x%08x\n", status );
        pNtClose( key );
    }

    /* target with terminating null doesn't work */
    status = pNtSetValueKey( link, &symlink_str, 0, REG_LINK, target, target_len );
    ok( status == STATUS_SUCCESS, "NtSetValueKey failed: 0x%08x\n", status );
    attr.RootDirectory = root;
    attr.Attributes = 0;
    attr.ObjectName = &link_str;
    status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
    ok( status == STATUS_OBJECT_NAME_NOT_FOUND, "NtOpenKey wrong status 0x%08x\n", status );

    /* relative symlink, works only on win2k */
    status = pNtSetValueKey( link, &symlink_str, 0, REG_LINK, targetW+1, sizeof(targetW)-2*sizeof(WCHAR) );
    ok( status == STATUS_SUCCESS, "NtSetValueKey failed: 0x%08x\n", status );
    attr.ObjectName = &link_str;
    status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
    ok( status == STATUS_OBJECT_NAME_NOT_FOUND || status == STATUS_OBJECT_NAME_INVALID /* Win10 1607+ */,
        "NtOpenKey wrong status 0x%08x\n", status );

    key = (HKEY)0xdeadbeef;
    status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, REG_OPTION_CREATE_LINK, NULL );
    ok( status == STATUS_OBJECT_NAME_COLLISION, "NtCreateKey failed: 0x%08x\n", status );
    ok( !key, "key = %p\n", key );

    status = pNtDeleteKey( link );
    ok( status == STATUS_SUCCESS, "NtDeleteKey failed: 0x%08x\n", status );
    pNtClose( link );

    attr.ObjectName = &target_str;
    status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
    ok( status == STATUS_SUCCESS, "NtOpenKey failed: 0x%08x\n", status );
    status = pNtDeleteKey( key );
    ok( status == STATUS_SUCCESS, "NtDeleteKey failed: 0x%08x\n", status );
    pNtClose( key );

    /* symlink loop */

    status = pNtCreateKey( &link, KEY_ALL_ACCESS, &attr, 0, 0, REG_OPTION_CREATE_LINK, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
    memcpy( target + target_len/sizeof(WCHAR) - 1, targetW, sizeof(targetW) );
    status = pNtSetValueKey( link, &symlink_str, 0, REG_LINK,
        target, target_len + sizeof(targetW) - sizeof(WCHAR) );
    ok( status == STATUS_SUCCESS, "NtSetValueKey failed: 0x%08x\n", status );

    status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
    ok( status == STATUS_OBJECT_NAME_NOT_FOUND /* XP */
            || status == STATUS_NAME_TOO_LONG
            || status == STATUS_INVALID_PARAMETER /* Win10 1607+ */,
            "NtOpenKey failed: 0x%08x\n", status );

    attr.Attributes = OBJ_OPENLINK;
    status = pNtOpenKey( &key, KEY_ALL_ACCESS, &attr );
    ok( status == STATUS_SUCCESS, "NtOpenKey failed: 0x%08x\n", status );
    pNtClose( key );

    status = pNtDeleteKey( link );
    ok( status == STATUS_SUCCESS, "NtDeleteKey failed: 0x%08x\n", status );
    pNtClose( link );

    status = pNtDeleteKey( root );
    ok( status == STATUS_SUCCESS, "NtDeleteKey failed: 0x%08x\n", status );
    pNtClose( root );

    pRtlFreeHeap(GetProcessHeap(), 0, target);
}

static WCHAR valueW[] = {'v','a','l','u','e'};
static UNICODE_STRING value_str = { sizeof(valueW), sizeof(valueW), valueW };
static const DWORD ptr_size = 8 * sizeof(void*);

static DWORD get_key_value( HANDLE root, const char *name, DWORD flags )
{
    char tmp[32];
    NTSTATUS status;
    OBJECT_ATTRIBUTES attr;
    UNICODE_STRING str;
    HANDLE key;
    KEY_VALUE_PARTIAL_INFORMATION *info = (KEY_VALUE_PARTIAL_INFORMATION *)tmp;
    DWORD dw, len = sizeof(tmp);

    attr.Length = sizeof(attr);
    attr.RootDirectory = root;
    attr.Attributes = OBJ_CASE_INSENSITIVE;
    attr.ObjectName = &str;
    attr.SecurityDescriptor = NULL;
    attr.SecurityQualityOfService = NULL;
    pRtlCreateUnicodeStringFromAsciiz( &str, name );

    status = pNtCreateKey( &key, flags | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    if (status == STATUS_OBJECT_NAME_NOT_FOUND) return 0;
    ok( status == STATUS_SUCCESS, "%08x: NtCreateKey failed: 0x%08x\n", flags, status );

    status = pNtQueryValueKey( key, &value_str, KeyValuePartialInformation, info, len, &len );
    if (status == STATUS_OBJECT_NAME_NOT_FOUND)
        dw = 0;
    else
    {
        ok( status == STATUS_SUCCESS, "%08x: NtQueryValueKey failed: 0x%08x\n", flags, status );
        dw = *(DWORD *)info->Data;
    }
    pNtClose( key );
    pRtlFreeUnicodeString( &str );
    return dw;
}

static void _check_key_value( int line, HANDLE root, const char *name, DWORD flags, DWORD expect )
{
    DWORD dw = get_key_value( root, name, flags );
    ok_(__FILE__,line)( dw == expect, "%08x: wrong value %u/%u\n", flags, dw, expect );
}
#define check_key_value(root,name,flags,expect) _check_key_value( __LINE__, root, name, flags, expect )

static void test_redirection(void)
{
    static const WCHAR softwareW[] = {'\\','R','e','g','i','s','t','r','y','\\',
                                      'M','a','c','h','i','n','e','\\',
                                      'S','o','f','t','w','a','r','e',0};
    static const WCHAR wownodeW[] = {'\\','R','e','g','i','s','t','r','y','\\',
                                     'M','a','c','h','i','n','e','\\',
                                     'S','o','f','t','w','a','r','e','\\',
                                     'W','o','w','6','4','3','2','N','o','d','e',0};
    static const WCHAR wine64W[] = {'\\','R','e','g','i','s','t','r','y','\\',
                                    'M','a','c','h','i','n','e','\\',
                                    'S','o','f','t','w','a','r','e','\\',
                                    'W','i','n','e',0};
    static const WCHAR wine32W[] = {'\\','R','e','g','i','s','t','r','y','\\',
                                    'M','a','c','h','i','n','e','\\',
                                    'S','o','f','t','w','a','r','e','\\',
                                    'W','o','w','6','4','3','2','N','o','d','e','\\',
                                    'W','i','n','e',0};
    static const WCHAR key64W[] = {'\\','R','e','g','i','s','t','r','y','\\',
                                   'M','a','c','h','i','n','e','\\',
                                   'S','o','f','t','w','a','r','e','\\',
                                   'W','i','n','e','\\','W','i','n','e','t','e','s','t',0};
    static const WCHAR key32W[] = {'\\','R','e','g','i','s','t','r','y','\\',
                                   'M','a','c','h','i','n','e','\\',
                                   'S','o','f','t','w','a','r','e','\\',
                                   'W','o','w','6','4','3','2','N','o','d','e','\\',
                                   'W','i','n','e','\\', 'W','i','n','e','t','e','s','t',0};
    static const WCHAR classes64W[] = {'\\','R','e','g','i','s','t','r','y','\\',
                                       'M','a','c','h','i','n','e','\\',
                                       'S','o','f','t','w','a','r','e','\\',
                                       'C','l','a','s','s','e','s','\\',
                                       'W','i','n','e',0};
    static const WCHAR classes32W[] = {'\\','R','e','g','i','s','t','r','y','\\',
                                       'M','a','c','h','i','n','e','\\',
                                       'S','o','f','t','w','a','r','e','\\',
                                       'C','l','a','s','s','e','s','\\',
                                       'W','o','w','6','4','3','2','N','o','d','e','\\',
                                       'W','i','n','e',0};
    NTSTATUS status;
    OBJECT_ATTRIBUTES attr;
    UNICODE_STRING str;
    char buffer[1024];
    KEY_VALUE_PARTIAL_INFORMATION *info = (KEY_VALUE_PARTIAL_INFORMATION *)buffer;
    DWORD dw, len;
    HANDLE key, root32, root64, key32, key64;
    BOOL is_vista = FALSE;

    if (ptr_size != 64)
    {
        ULONG is_wow64, len;
        if (pNtQueryInformationProcess( GetCurrentProcess(), ProcessWow64Information,
                                        &is_wow64, sizeof(is_wow64), &len ) ||
            !is_wow64)
        {
            trace( "Not on Wow64, no redirection\n" );
            return;
        }
    }

    attr.Length = sizeof(attr);
    attr.RootDirectory = 0;
    attr.Attributes = OBJ_CASE_INSENSITIVE;
    attr.ObjectName = &str;
    attr.SecurityDescriptor = NULL;
    attr.SecurityQualityOfService = NULL;

    pRtlInitUnicodeString( &str, wine64W );
    status = pNtCreateKey( &root64, KEY_WOW64_64KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );

    pRtlInitUnicodeString( &str, wine32W );
    status = pNtCreateKey( &root32, KEY_WOW64_32KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );

    pRtlInitUnicodeString( &str, key64W );
    status = pNtCreateKey( &key64, KEY_WOW64_64KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );

    pRtlInitUnicodeString( &str, key32W );
    status = pNtCreateKey( &key32, KEY_WOW64_32KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );

    dw = 64;
    status = pNtSetValueKey( key64, &value_str, 0, REG_DWORD, &dw, sizeof(dw) );
    ok( status == STATUS_SUCCESS, "NtSetValueKey failed: 0x%08x\n", status );

    dw = 32;
    status = pNtSetValueKey( key32, &value_str, 0, REG_DWORD, &dw, sizeof(dw) );
    ok( status == STATUS_SUCCESS, "NtSetValueKey failed: 0x%08x\n", status );

    len = sizeof(buffer);
    status = pNtQueryValueKey( key32, &value_str, KeyValuePartialInformation, info, len, &len );
    ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
    dw = *(DWORD *)info->Data;
    ok( dw == 32, "wrong value %u\n", dw );

    len = sizeof(buffer);
    status = pNtQueryValueKey( key64, &value_str, KeyValuePartialInformation, info, len, &len );
    ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
    dw = *(DWORD *)info->Data;
    ok( dw == 64, "wrong value %u\n", dw );

    pRtlInitUnicodeString( &str, softwareW );
    status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );

    if (ptr_size == 32)
    {
        /* the Vista mechanism allows opening Wow6432Node from a 32-bit key too */
        /* the new (and simpler) Win7 mechanism doesn't */
        if (get_key_value( key, "Wow6432Node\\Wine\\Winetest", 0 ) == 32)
        {
            trace( "using Vista-style Wow6432Node handling\n" );
            is_vista = TRUE;
        }
        check_key_value( key, "Wine\\Winetest", 0, 32 );
        check_key_value( key, "Wine\\Winetest", KEY_WOW64_64KEY, is_vista ? 64 : 32 );
        check_key_value( key, "Wine\\Winetest", KEY_WOW64_32KEY, 32 );
        check_key_value( key, "Wow6432Node\\Wine\\Winetest", 0, is_vista ? 32 : 0 );
        check_key_value( key, "Wow6432Node\\Wine\\Winetest", KEY_WOW64_64KEY, is_vista ? 64 : 0 );
        check_key_value( key, "Wow6432Node\\Wine\\Winetest", KEY_WOW64_32KEY, is_vista ? 32 : 0 );
    }
    else
    {
        check_key_value( key, "Wine\\Winetest", 0, 64 );
        check_key_value( key, "Wow6432Node\\Wine\\Winetest", 0, 32 );
    }
    pNtClose( key );

    if (ptr_size == 32)
    {
        status = pNtCreateKey( &key, KEY_WOW64_64KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
        ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
        dw = get_key_value( key, "Wine\\Winetest", 0 );
        ok( dw == 64 || broken(dw == 32) /* xp64 */, "wrong value %u\n", dw );
        check_key_value( key, "Wine\\Winetest", KEY_WOW64_64KEY, 64 );
        check_key_value( key, "Wine\\Winetest", KEY_WOW64_32KEY, 32 );
        check_key_value( key, "Wow6432Node\\Wine\\Winetest", 0, 32 );
        dw = get_key_value( key, "Wow6432Node\\Wine\\Winetest", KEY_WOW64_64KEY );
        ok( dw == 32 || broken(dw == 64) /* xp64 */, "wrong value %u\n", dw );
        check_key_value( key, "Wow6432Node\\Wine\\Winetest", KEY_WOW64_32KEY, 32 );
        pNtClose( key );

        status = pNtCreateKey( &key, KEY_WOW64_32KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
        ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
        check_key_value( key, "Wine\\Winetest", 0, 32 );
        check_key_value( key, "Wine\\Winetest", KEY_WOW64_64KEY, is_vista ? 64 : 32 );
        check_key_value( key, "Wine\\Winetest", KEY_WOW64_32KEY, 32 );
        check_key_value( key, "Wow6432Node\\Wine\\Winetest", 0, is_vista ? 32 : 0 );
        check_key_value( key, "Wow6432Node\\Wine\\Winetest", KEY_WOW64_64KEY, is_vista ? 64 : 0 );
        check_key_value( key, "Wow6432Node\\Wine\\Winetest", KEY_WOW64_32KEY, is_vista ? 32 : 0 );
        pNtClose( key );
    }

    check_key_value( 0, "\\Registry\\Machine\\Software\\Wine\\Winetest", 0, ptr_size );
    check_key_value( 0, "\\Registry\\Machine\\Software\\Wow6432Node\\Wine\\Winetest", 0, 32 );
    if (ptr_size == 64)
    {
        /* KEY_WOW64 flags have no effect on 64-bit */
        check_key_value( 0, "\\Registry\\Machine\\Software\\Wine\\Winetest", KEY_WOW64_64KEY, 64 );
        check_key_value( 0, "\\Registry\\Machine\\Software\\Wine\\Winetest", KEY_WOW64_32KEY, 64 );
        check_key_value( 0, "\\Registry\\Machine\\Software\\Wow6432Node\\Wine\\Winetest", KEY_WOW64_64KEY, 32 );
        check_key_value( 0, "\\Registry\\Machine\\Software\\Wow6432Node\\Wine\\Winetest", KEY_WOW64_32KEY, 32 );
    }
    else
    {
        check_key_value( 0, "\\Registry\\Machine\\Software\\Wine\\Winetest", KEY_WOW64_64KEY, 64 );
        check_key_value( 0, "\\Registry\\Machine\\Software\\Wine\\Winetest", KEY_WOW64_32KEY, 32 );
        check_key_value( 0, "\\Registry\\Machine\\Software\\Wow6432Node\\Wine\\Winetest", KEY_WOW64_64KEY, is_vista ? 64 : 32 );
        check_key_value( 0, "\\Registry\\Machine\\Software\\Wow6432Node\\Wine\\Winetest", KEY_WOW64_32KEY, 32 );
    }

    pRtlInitUnicodeString( &str, wownodeW );
    status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
    check_key_value( key, "Wine\\Winetest", 0, 32 );
    check_key_value( key, "Wine\\Winetest", KEY_WOW64_64KEY, (ptr_size == 64) ? 32 : (is_vista ? 64 : 32) );
    check_key_value( key, "Wine\\Winetest", KEY_WOW64_32KEY, 32 );
    pNtClose( key );

    if (ptr_size == 32)
    {
        status = pNtCreateKey( &key, KEY_WOW64_64KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
        ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
        dw = get_key_value( key, "Wine\\Winetest", 0 );
        ok( dw == (is_vista ? 64 : 32) || broken(dw == 32) /* xp64 */, "wrong value %u\n", dw );
        check_key_value( key, "Wine\\Winetest", KEY_WOW64_64KEY, is_vista ? 64 : 32 );
        check_key_value( key, "Wine\\Winetest", KEY_WOW64_32KEY, 32 );
        pNtClose( key );

        status = pNtCreateKey( &key, KEY_WOW64_32KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
        ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
        check_key_value( key, "Wine\\Winetest", 0, 32 );
        check_key_value( key, "Wine\\Winetest", KEY_WOW64_64KEY, is_vista ? 64 : 32 );
        check_key_value( key, "Wine\\Winetest", KEY_WOW64_32KEY, 32 );
        pNtClose( key );
    }

    pRtlInitUnicodeString( &str, wine32W );
    status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
    check_key_value( key, "Winetest", 0, 32 );
    check_key_value( key, "Winetest", KEY_WOW64_64KEY, (ptr_size == 32 && is_vista) ? 64 : 32 );
    check_key_value( key, "Winetest", KEY_WOW64_32KEY, 32 );
    pNtClose( key );

    if (ptr_size == 32)
    {
        status = pNtCreateKey( &key, KEY_WOW64_64KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
        ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
        dw = get_key_value( key, "Winetest", 0 );
        ok( dw == 32 || (is_vista && dw == 64), "wrong value %u\n", dw );
        check_key_value( key, "Winetest", KEY_WOW64_64KEY, is_vista ? 64 : 32 );
        check_key_value( key, "Winetest", KEY_WOW64_32KEY, 32 );
        pNtClose( key );

        status = pNtCreateKey( &key, KEY_WOW64_32KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
        ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
        check_key_value( key, "Winetest", 0, 32 );
        check_key_value( key, "Winetest", KEY_WOW64_64KEY, is_vista ? 64 : 32 );
        check_key_value( key, "Winetest", KEY_WOW64_32KEY, 32 );
        pNtClose( key );
    }

    pRtlInitUnicodeString( &str, wine64W );
    status = pNtCreateKey( &key, KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
    check_key_value( key, "Winetest", 0, ptr_size );
    check_key_value( key, "Winetest", KEY_WOW64_64KEY, is_vista ? 64 : ptr_size );
    check_key_value( key, "Winetest", KEY_WOW64_32KEY, ptr_size );
    pNtClose( key );

    if (ptr_size == 32)
    {
        status = pNtCreateKey( &key, KEY_WOW64_64KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
        ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
        dw = get_key_value( key, "Winetest", 0 );
        ok( dw == 64 || broken(dw == 32) /* xp64 */, "wrong value %u\n", dw );
        check_key_value( key, "Winetest", KEY_WOW64_64KEY, 64 );
        dw = get_key_value( key, "Winetest", KEY_WOW64_32KEY );
        todo_wine ok( dw == 32, "wrong value %u\n", dw );
        pNtClose( key );

        status = pNtCreateKey( &key, KEY_WOW64_32KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
        ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
        check_key_value( key, "Winetest", 0, 32 );
        check_key_value( key, "Winetest", KEY_WOW64_64KEY, is_vista ? 64 : 32 );
        check_key_value( key, "Winetest", KEY_WOW64_32KEY, 32 );
        pNtClose( key );
    }

    status = pNtDeleteKey( key32 );
    ok( status == STATUS_SUCCESS, "NtDeleteKey failed: 0x%08x\n", status );
    pNtClose( key32 );

    status = pNtDeleteKey( key64 );
    ok( status == STATUS_SUCCESS, "NtDeleteKey failed: 0x%08x\n", status );
    pNtClose( key64 );

    pNtDeleteKey( root32 );
    pNtClose( root32 );
    pNtDeleteKey( root64 );
    pNtClose( root64 );

    /* Software\Classes is shared/reflected so behavior is different */

    pRtlInitUnicodeString( &str, classes64W );
    status = pNtCreateKey( &key64, KEY_WOW64_64KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    if (status == STATUS_ACCESS_DENIED)
    {
        skip("Not authorized to modify the Classes key\n");
        return;
    }
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );

    pRtlInitUnicodeString( &str, classes32W );
    status = pNtCreateKey( &key32, KEY_WOW64_32KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );

    dw = 64;
    status = pNtSetValueKey( key64, &value_str, 0, REG_DWORD, &dw, sizeof(dw) );
    ok( status == STATUS_SUCCESS, "NtSetValueKey failed: 0x%08x\n", status );
    pNtClose( key64 );

    dw = 32;
    status = pNtSetValueKey( key32, &value_str, 0, REG_DWORD, &dw, sizeof(dw) );
    ok( status == STATUS_SUCCESS, "NtSetValueKey failed: 0x%08x\n", status );
    pNtClose( key32 );

    pRtlInitUnicodeString( &str, classes64W );
    status = pNtCreateKey( &key64, KEY_WOW64_64KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
    len = sizeof(buffer);
    status = pNtQueryValueKey( key64, &value_str, KeyValuePartialInformation, info, len, &len );
    ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
    dw = *(DWORD *)info->Data;
    ok( dw == ptr_size, "wrong value %u\n", dw );

    pRtlInitUnicodeString( &str, classes32W );
    status = pNtCreateKey( &key32, KEY_WOW64_32KEY | KEY_ALL_ACCESS, &attr, 0, 0, 0, 0 );
    ok( status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status );
    len = sizeof(buffer);
    status = pNtQueryValueKey( key32, &value_str, KeyValuePartialInformation, info, len, &len );
    ok( status == STATUS_SUCCESS, "NtQueryValueKey failed: 0x%08x\n", status );
    dw = *(DWORD *)info->Data;
    ok( dw == 32, "wrong value %u\n", dw );

    pNtDeleteKey( key32 );
    pNtClose( key32 );
    pNtDeleteKey( key64 );
    pNtClose( key64 );
}

static void test_long_value_name(void)
{
    HANDLE key;
    NTSTATUS status, expected;
    OBJECT_ATTRIBUTES attr;
    UNICODE_STRING ValName;
    DWORD i;

    InitializeObjectAttributes(&attr, &winetestpath, 0, 0, 0);
    status = pNtOpenKey(&key, KEY_WRITE|KEY_READ, &attr);
    ok(status == STATUS_SUCCESS, "NtOpenKey Failed: 0x%08x\n", status);

    ValName.MaximumLength = 0xfffc;
    ValName.Length = ValName.MaximumLength - sizeof(WCHAR);
    ValName.Buffer = HeapAlloc(GetProcessHeap(), 0, ValName.MaximumLength);
    for (i = 0; i < ValName.Length / sizeof(WCHAR); i++)
        ValName.Buffer[i] = 'a';
    ValName.Buffer[i] = 0;

    status = pNtDeleteValueKey(key, &ValName);
    ok(status == STATUS_OBJECT_NAME_NOT_FOUND, "NtDeleteValueKey with nonexistent long value name returned 0x%08x\n", status);
    status = pNtSetValueKey(key, &ValName, 0, REG_DWORD, &i, sizeof(i));
    ok(status == STATUS_INVALID_PARAMETER || broken(status == STATUS_SUCCESS) /* nt4 */,
       "NtSetValueKey with long value name returned 0x%08x\n", status);
    expected = (status == STATUS_SUCCESS) ? STATUS_SUCCESS : STATUS_OBJECT_NAME_NOT_FOUND;
    status = pNtDeleteValueKey(key, &ValName);
    ok(status == expected, "NtDeleteValueKey with long value name returned 0x%08x\n", status);

    status = pNtQueryValueKey(key, &ValName, KeyValueBasicInformation, NULL, 0, &i);
    ok(status == STATUS_OBJECT_NAME_NOT_FOUND, "NtQueryValueKey with nonexistent long value name returned 0x%08x\n", status);

    pRtlFreeUnicodeString(&ValName);
    pNtClose(key);
}

static void test_NtQueryKey(void)
{
    HANDLE key, subkey, subkey2;
    NTSTATUS status;
    OBJECT_ATTRIBUTES attr;
    ULONG length, len;
    KEY_NAME_INFORMATION *info = NULL;
    KEY_CACHED_INFORMATION cached_info;
    UNICODE_STRING str;
    DWORD dw;

    InitializeObjectAttributes(&attr, &winetestpath, 0, 0, 0);
    status = pNtOpenKey(&key, KEY_READ, &attr);
    ok(status == STATUS_SUCCESS, "NtOpenKey Failed: 0x%08x\n", status);

    status = pNtQueryKey(key, KeyNameInformation, NULL, 0, &length);
    if (status == STATUS_INVALID_PARAMETER) {
        win_skip("KeyNameInformation is not supported\n");
        pNtClose(key);
        return;
    }
    todo_wine ok(status == STATUS_BUFFER_TOO_SMALL, "NtQueryKey Failed: 0x%08x\n", status);
    info = HeapAlloc(GetProcessHeap(), 0, length);

    /* non-zero buffer size, but insufficient */
    status = pNtQueryKey(key, KeyNameInformation, info, sizeof(*info), &len);
    ok(status == STATUS_BUFFER_OVERFLOW, "NtQueryKey Failed: 0x%08x\n", status);
    ok(length == len, "got %d, expected %d\n", len, length);
    ok(info->NameLength == winetestpath.Length, "got %d, expected %d\n",
       info->NameLength, winetestpath.Length);

    /* correct buffer size */
    status = pNtQueryKey(key, KeyNameInformation, info, length, &len);
    ok(status == STATUS_SUCCESS, "NtQueryKey Failed: 0x%08x\n", status);
    ok(length == len, "got %d, expected %d\n", len, length);

    str.Buffer = info->Name;
    str.Length = info->NameLength;
    ok(pRtlCompareUnicodeString(&winetestpath, &str, TRUE) == 0,
       "got %s, expected %s\n",
       wine_dbgstr_wn(str.Buffer, str.Length/sizeof(WCHAR)),
       wine_dbgstr_wn(winetestpath.Buffer, winetestpath.Length/sizeof(WCHAR)));

    HeapFree(GetProcessHeap(), 0, info);

    attr.RootDirectory = key;
    attr.ObjectName = &str;
    pRtlCreateUnicodeStringFromAsciiz(&str, "test_subkey");
    status = pNtCreateKey(&subkey, GENERIC_ALL, &attr, 0, 0, 0, 0);
    ok(status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status);
    pRtlFreeUnicodeString(&str);

    status = pNtQueryKey(subkey, KeyCachedInformation, &cached_info, sizeof(cached_info), &len);
    ok(status == STATUS_SUCCESS, "NtQueryKey Failed: 0x%08x\n", status);

    if (status == STATUS_SUCCESS)
    {
        ok(len == sizeof(cached_info), "got unexpected length %d\n", len);
        ok(cached_info.SubKeys == 0, "cached_info.SubKeys = %u\n", cached_info.SubKeys);
        ok(cached_info.MaxNameLen == 0, "cached_info.MaxNameLen = %u\n", cached_info.MaxNameLen);
        ok(cached_info.Values == 0, "cached_info.Values = %u\n", cached_info.Values);
        ok(cached_info.MaxValueNameLen == 0, "cached_info.MaxValueNameLen = %u\n", cached_info.MaxValueNameLen);
        ok(cached_info.MaxValueDataLen == 0, "cached_info.MaxValueDataLen = %u\n", cached_info.MaxValueDataLen);
        ok(cached_info.NameLength == 22, "cached_info.NameLength = %u\n", cached_info.NameLength);
    }

    attr.RootDirectory = subkey;
    attr.ObjectName = &str;
    pRtlCreateUnicodeStringFromAsciiz(&str, "test_subkey2");
    status = pNtCreateKey(&subkey2, GENERIC_ALL, &attr, 0, 0, 0, 0);
    ok(status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status);
    pRtlFreeUnicodeString(&str);

    pRtlCreateUnicodeStringFromAsciiz(&str, "val");
    dw = 64;
    status = pNtSetValueKey( subkey, &str, 0, REG_DWORD, &dw, sizeof(dw) );
    ok( status == STATUS_SUCCESS, "NtSetValueKey failed: 0x%08x\n", status );
    pRtlFreeUnicodeString(&str);

    status = pNtQueryKey(subkey, KeyCachedInformation, &cached_info, sizeof(cached_info), &len);
    ok(status == STATUS_SUCCESS, "NtQueryKey Failed: 0x%08x\n", status);

    if (status == STATUS_SUCCESS)
    {
        ok(len == sizeof(cached_info), "got unexpected length %d\n", len);
        ok(cached_info.SubKeys == 1, "cached_info.SubKeys = %u\n", cached_info.SubKeys);
        ok(cached_info.MaxNameLen == 24, "cached_info.MaxNameLen = %u\n", cached_info.MaxNameLen);
        ok(cached_info.Values == 1, "cached_info.Values = %u\n", cached_info.Values);
        ok(cached_info.MaxValueNameLen == 6, "cached_info.MaxValueNameLen = %u\n", cached_info.MaxValueNameLen);
        ok(cached_info.MaxValueDataLen == 4, "cached_info.MaxValueDataLen = %u\n", cached_info.MaxValueDataLen);
        ok(cached_info.NameLength == 22, "cached_info.NameLength = %u\n", cached_info.NameLength);
    }

    status = pNtDeleteKey(subkey2);
    ok(status == STATUS_SUCCESS, "NtDeleteSubkey failed: %x\n", status);
    status = pNtDeleteKey(subkey);
    ok(status == STATUS_SUCCESS, "NtDeleteSubkey failed: %x\n", status);

    pNtClose(subkey2);
    pNtClose(subkey);
    pNtClose(key);
}

static void test_notify(void)
{
    OBJECT_ATTRIBUTES attr;
    LARGE_INTEGER timeout;
    IO_STATUS_BLOCK iosb;
    UNICODE_STRING str;
    HANDLE key, events[2], subkey;
    NTSTATUS status;

    InitializeObjectAttributes(&attr, &winetestpath, 0, 0, 0);
    status = pNtOpenKey(&key, KEY_ALL_ACCESS, &attr);
    ok(status == STATUS_SUCCESS, "NtOpenKey Failed: 0x%08x\n", status);

    events[0] = CreateEventW(NULL, FALSE, TRUE, NULL);
    ok(events[0] != NULL, "CreateEvent failed: %u\n", GetLastError());
    events[1] = CreateEventW(NULL, FALSE, TRUE, NULL);
    ok(events[1] != NULL, "CreateEvent failed: %u\n", GetLastError());

    status = pNtNotifyChangeKey(key, events[0], NULL, NULL, &iosb, REG_NOTIFY_CHANGE_NAME, FALSE, NULL, 0, TRUE);
    ok(status == STATUS_PENDING, "NtNotifyChangeKey returned %x\n", status);
    status = pNtNotifyChangeKey(key, events[1], NULL, NULL, &iosb, REG_NOTIFY_CHANGE_NAME, FALSE, NULL, 0, TRUE);
    ok(status == STATUS_PENDING, "NtNotifyChangeKey returned %x\n", status);

    timeout.QuadPart = 0;
    status = pNtWaitForSingleObject(events[0], FALSE, &timeout);
    ok(status == STATUS_TIMEOUT, "NtWaitForSingleObject returned %x\n", status);
    status = pNtWaitForSingleObject(events[1], FALSE, &timeout);
    ok(status == STATUS_TIMEOUT, "NtWaitForSingleObject returned %x\n", status);

    attr.RootDirectory = key;
    attr.ObjectName = &str;

    pRtlCreateUnicodeStringFromAsciiz(&str, "test_subkey");
    status = pNtCreateKey(&subkey, GENERIC_ALL, &attr, 0, 0, 0, 0);
    ok(status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status);
    pRtlFreeUnicodeString(&str);

    status = pNtWaitForSingleObject(events[0], FALSE, &timeout);
    todo_wine ok(status == STATUS_SUCCESS, "NtWaitForSingleObject returned %x\n", status);
    status = pNtWaitForSingleObject(events[1], FALSE, &timeout);
    ok(status == STATUS_SUCCESS, "NtWaitForSingleObject returned %x\n", status);

    status = pNtNotifyChangeKey(key, events[0], NULL, NULL, &iosb, 0, FALSE, NULL, 0, TRUE);
    ok(status == STATUS_PENDING, "NtNotifyChangeKey returned %x\n", status);
    status = pNtNotifyChangeKey(key, events[1], NULL, NULL, &iosb, 0, FALSE, NULL, 0, TRUE);
    ok(status == STATUS_PENDING, "NtNotifyChangeKey returned %x\n", status);

    status = pNtDeleteKey(subkey);
    ok(status == STATUS_SUCCESS, "NtDeleteSubkey failed: %x\n", status);

    status = pNtWaitForSingleObject(events[0], FALSE, &timeout);
    todo_wine ok(status == STATUS_SUCCESS, "NtWaitForSingleObject returned %x\n", status);
    status = pNtWaitForSingleObject(events[1], FALSE, &timeout);
    ok(status == STATUS_SUCCESS, "NtWaitForSingleObject returned %x\n", status);

    pNtClose(subkey);

    status = pNtNotifyChangeKey(key, events[0], NULL, NULL, &iosb, 0, FALSE, NULL, 0, TRUE);
    ok(status == STATUS_PENDING, "NtNotifyChangeKey returned %x\n", status);
    status = pNtNotifyChangeKey(key, events[1], NULL, NULL, &iosb, 0, FALSE, NULL, 0, TRUE);
    ok(status == STATUS_PENDING, "NtNotifyChangeKey returned %x\n", status);

    pNtClose(key);

    status = pNtWaitForSingleObject(events[0], FALSE, &timeout);
    todo_wine ok(status == STATUS_SUCCESS, "NtWaitForSingleObject returned %x\n", status);
    status = pNtWaitForSingleObject(events[1], FALSE, &timeout);
    ok(status == STATUS_SUCCESS, "NtWaitForSingleObject returned %x\n", status);

    if (pNtNotifyChangeMultipleKeys)
    {
        InitializeObjectAttributes(&attr, &winetestpath, 0, 0, 0);
        status = pNtOpenKey(&key, KEY_ALL_ACCESS, &attr);
        ok(status == STATUS_SUCCESS, "NtOpenKey Failed: 0x%08x\n", status);

        status = pNtNotifyChangeMultipleKeys(key, 0, NULL, events[0], NULL, NULL, &iosb, REG_NOTIFY_CHANGE_NAME, FALSE, NULL, 0, TRUE);
        ok(status == STATUS_PENDING, "NtNotifyChangeKey returned %x\n", status);

        timeout.QuadPart = 0;
        status = pNtWaitForSingleObject(events[0], FALSE, &timeout);
        ok(status == STATUS_TIMEOUT, "NtWaitForSingleObject returned %x\n", status);

        attr.RootDirectory = key;
        attr.ObjectName = &str;
        pRtlCreateUnicodeStringFromAsciiz(&str, "test_subkey");
        status = pNtCreateKey(&subkey, GENERIC_ALL, &attr, 0, 0, 0, 0);
        ok(status == STATUS_SUCCESS, "NtCreateKey failed: 0x%08x\n", status);
        pRtlFreeUnicodeString(&str);

        status = pNtWaitForSingleObject(events[0], FALSE, &timeout);
        ok(status == STATUS_SUCCESS, "NtWaitForSingleObject returned %x\n", status);

        status = pNtDeleteKey(subkey);
        ok(status == STATUS_SUCCESS, "NtDeleteSubkey failed: %x\n", status);
        pNtClose(subkey);
        pNtClose(key);
    }
    else
    {
        win_skip("NtNotifyChangeMultipleKeys not available\n");
    }

    pNtClose(events[0]);
    pNtClose(events[1]);
}

static void test_RtlCreateRegistryKey(void)
{
    static WCHAR empty[] = {0};
    static const WCHAR key1[] = {'\\','R','t','l','C','r','e','a','t','e','R','e','g','i','s','t','r','y','K','e','y',0};
    UNICODE_STRING str;
    SIZE_T size;
    NTSTATUS status;

    RtlDuplicateUnicodeString(1, &winetestpath, &str);
    size = str.MaximumLength + sizeof(key1)* sizeof(WCHAR) * 2;
    str.Buffer = pRtlReAllocateHeap(GetProcessHeap(), HEAP_ZERO_MEMORY, str.Buffer, size);
    str.MaximumLength = size;
    pRtlAppendUnicodeToString(&str, key1);
    pRtlAppendUnicodeToString(&str, key1);

    /* should work */
    status = pRtlCreateRegistryKey(RTL_REGISTRY_ABSOLUTE, winetestpath.Buffer);
    ok(status == STATUS_SUCCESS, "RtlCreateRegistryKey failed: %08x\n", status);

    status = pRtlCreateRegistryKey(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, winetestpath.Buffer);
    ok(status == STATUS_SUCCESS, "RtlCreateRegistryKey failed: %08x\n", status);

    status = pRtlCreateRegistryKey(RTL_REGISTRY_USER, NULL);
    ok(status == STATUS_SUCCESS, "RtlCreateRegistryKey failed: %08x\n", status);

    status = pRtlCreateRegistryKey(RTL_REGISTRY_USER | RTL_REGISTRY_OPTIONAL, NULL);
    ok(status == STATUS_SUCCESS, "RtlCreateRegistryKey failed: %08x\n", status);

    status = pRtlCreateRegistryKey(RTL_REGISTRY_USER, empty);
    ok(status == STATUS_SUCCESS, "RtlCreateRegistryKey failed: %08x\n", status);

    status = pRtlCreateRegistryKey(RTL_REGISTRY_USER | RTL_REGISTRY_OPTIONAL, empty);
    ok(status == STATUS_SUCCESS, "RtlCreateRegistryKey failed: %08x\n", status);

    /* invalid first parameter */
    status = pRtlCreateRegistryKey(RTL_REGISTRY_USER+1, winetestpath.Buffer);
    ok(status == STATUS_INVALID_PARAMETER, "RtlCreateRegistryKey unexpected return value: %08x, expected %08x\n", status, STATUS_INVALID_PARAMETER);

    status = pRtlCreateRegistryKey((RTL_REGISTRY_USER+1) | RTL_REGISTRY_OPTIONAL, winetestpath.Buffer);
    ok(status == STATUS_INVALID_PARAMETER, "RtlCreateRegistryKey unexpected return value: %08x, expected %08x\n", status, STATUS_INVALID_PARAMETER);

    /* invalid second parameter */
    status = pRtlCreateRegistryKey(RTL_REGISTRY_ABSOLUTE, NULL);
    ok(status == STATUS_OBJECT_PATH_SYNTAX_BAD, "RtlCreateRegistryKey unexpected return value: %08x, expected %08x\n", status, STATUS_OBJECT_PATH_SYNTAX_BAD);

    status = pRtlCreateRegistryKey(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, NULL);
    ok(status == STATUS_OBJECT_PATH_SYNTAX_BAD, "RtlCreateRegistryKey unexpected return value: %08x, expected %08x\n", status, STATUS_OBJECT_PATH_SYNTAX_BAD);

    status = pRtlCreateRegistryKey(RTL_REGISTRY_ABSOLUTE, empty);
    ok(status == STATUS_OBJECT_PATH_SYNTAX_BAD, "RtlCreateRegistryKey unexpected return value: %08x, expected %08x\n", status, STATUS_OBJECT_PATH_SYNTAX_BAD);

    status = pRtlCreateRegistryKey(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, empty);
    ok(status == STATUS_OBJECT_PATH_SYNTAX_BAD, "RtlCreateRegistryKey unexpected return value: %08x, expected %08x\n", status, STATUS_OBJECT_PATH_SYNTAX_BAD);

    status = pRtlCreateRegistryKey(RTL_REGISTRY_ABSOLUTE, str.Buffer);
    ok(status == STATUS_OBJECT_NAME_NOT_FOUND, "RtlCreateRegistryKey unexpected return value: %08x, expected %08x\n", status, STATUS_OBJECT_NAME_NOT_FOUND);

    status = pRtlCreateRegistryKey(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, str.Buffer);
    ok(status == STATUS_OBJECT_NAME_NOT_FOUND, "RtlCreateRegistryKey unexpected return value: %08x, expected %08x\n", status, STATUS_OBJECT_NAME_NOT_FOUND);

    /* both parameters invalid */
    status = pRtlCreateRegistryKey(RTL_REGISTRY_USER+1, NULL);
    ok(status == STATUS_INVALID_PARAMETER, "RtlCreateRegistryKey unexpected return value: %08x, expected %08x\n", status, STATUS_INVALID_PARAMETER);

    status = pRtlCreateRegistryKey((RTL_REGISTRY_USER+1) | RTL_REGISTRY_OPTIONAL, NULL);
    ok(status == STATUS_INVALID_PARAMETER, "RtlCreateRegistryKey unexpected return value: %08x, expected %08x\n", status, STATUS_INVALID_PARAMETER);

    pRtlFreeUnicodeString(&str);
}

START_TEST(reg)
{
    static const WCHAR winetest[] = {'\\','W','i','n','e','T','e','s','t',0};
    if(!InitFunctionPtrs())
        return;
    pRtlFormatCurrentUserKeyPath(&winetestpath);
    winetestpath.Buffer = pRtlReAllocateHeap(GetProcessHeap(), HEAP_ZERO_MEMORY, winetestpath.Buffer,
                           winetestpath.MaximumLength + sizeof(winetest)*sizeof(WCHAR));
    winetestpath.MaximumLength = winetestpath.MaximumLength + sizeof(winetest)*sizeof(WCHAR);

    pRtlAppendUnicodeToString(&winetestpath, winetest);

    test_NtCreateKey();
    test_NtOpenKey();
    test_NtSetValueKey();
    test_RtlCheckRegistryKey();
    test_RtlOpenCurrentUser();
    test_RtlQueryRegistryValues();
    test_RtlpNtQueryValueKey();
    test_NtFlushKey();
    test_NtQueryKey();
    test_NtQueryLicenseKey();
    test_NtQueryValueKey();
    test_long_value_name();
    test_notify();
    test_RtlCreateRegistryKey();
    test_NtDeleteKey();
    test_symlinks();
    test_redirection();

    pRtlFreeUnicodeString(&winetestpath);

    FreeLibrary(hntdll);
}
